[vendor/miniaudio/miniaudio.h]
trim_trailing_whitespace = unset
insert_final_newline = unset
+
+[tools/server/webui/**]
+indent_style = unset
+indent_size = unset
+end_of_line = unset
+charset = unset
+trim_trailing_whitespace = unset
+insert_final_newline = unset
run: |
pip install -r tools/server/tests/requirements.txt
- # Setup nodejs (to be used for verifying bundled index.html)
- - uses: actions/setup-node@v4
+ webui-setup:
+ name: WebUI Setup
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
with:
- node-version: '22.11.0'
+ fetch-depth: 0
+ ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }}
- - name: WebUI - Install dependencies
- id: webui_lint
- run: |
- cd tools/server/webui
- npm ci
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "22"
+ cache: "npm"
+ cache-dependency-path: "tools/server/webui/package-lock.json"
+
+ - name: Cache node_modules
+ uses: actions/cache@v4
+ id: cache-node-modules
+ with:
+ path: tools/server/webui/node_modules
+ key: ${{ runner.os }}-node-modules-${{ hashFiles('tools/server/webui/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-modules-
+
+ - name: Install dependencies
+ if: steps.cache-node-modules.outputs.cache-hit != 'true'
+ run: npm ci
+ working-directory: tools/server/webui
+
+ webui-check:
+ needs: webui-setup
+ name: WebUI Check
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }}
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "22"
+
+ - name: Restore node_modules cache
+ uses: actions/cache@v4
+ with:
+ path: tools/server/webui/node_modules
+ key: ${{ runner.os }}-node-modules-${{ hashFiles('tools/server/webui/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-modules-
+
+ - name: Run type checking
+ run: npm run check
+ working-directory: tools/server/webui
+
+ - name: Run linting
+ run: npm run lint
+ working-directory: tools/server/webui
+
+ webui-build:
+ needs: webui-check
+ name: WebUI Build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }}
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "22"
+
+ - name: Restore node_modules cache
+ uses: actions/cache@v4
+ with:
+ path: tools/server/webui/node_modules
+ key: ${{ runner.os }}-node-modules-${{ hashFiles('tools/server/webui/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-modules-
+
+ - name: Build application
+ run: npm run build
+ working-directory: tools/server/webui
+
+ webui-tests:
+ needs: webui-build
+ name: Run WebUI tests
+ permissions:
+ contents: read
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "22"
+
+ - name: Restore node_modules cache
+ uses: actions/cache@v4
+ with:
+ path: tools/server/webui/node_modules
+ key: ${{ runner.os }}-node-modules-${{ hashFiles('tools/server/webui/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-modules-
+
+ - name: Install Playwright browsers
+ run: npx playwright install --with-deps
+ working-directory: tools/server/webui
+
+ - name: Build Storybook
+ run: npm run build-storybook
+ working-directory: tools/server/webui
+
+ - name: Run Client tests
+ run: npm run test:client
+ working-directory: tools/server/webui
- - name: WebUI - Check code format
- id: webui_format
+ - name: Run Server tests
+ run: npm run test:server
+ working-directory: tools/server/webui
+
+ - name: Run UI tests
+ run: npm run test:ui
+ working-directory: tools/server/webui
+
+ - name: Run E2E tests
+ run: npm run test:e2e
+ working-directory: tools/server/webui
+
+ server-build:
+ needs: [webui-tests]
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ sanitizer: [ADDRESS, UNDEFINED] # THREAD is broken
+ build_type: [RelWithDebInfo]
+ include:
+ - build_type: Release
+ sanitizer: ""
+ fail-fast: false # While -DLLAMA_SANITIZE_THREAD=ON is broken
+
+ steps:
+ - name: Dependencies
+ id: depends
run: |
- git config --global --add safe.directory $(realpath .)
- cd tools/server/webui
- git status
-
- npm run format
- git status
- modified_files="$(git status -s)"
- echo "Modified files: ${modified_files}"
- if [ -n "${modified_files}" ]; then
- echo "Files do not follow coding style. To fix: npm run format"
- echo "${modified_files}"
- exit 1
- fi
-
- - name: Verify bundled index.html
- id: verify_server_index_html
+ sudo apt-get update
+ sudo apt-get -y install \
+ build-essential \
+ xxd \
+ git \
+ cmake \
+ curl \
+ wget \
+ language-pack-en \
+ libcurl4-openssl-dev
+
+ - name: Clone
+ id: checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }}
+
+ - name: Python setup
+ id: setup_python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.11'
+
+ - name: Tests dependencies
+ id: test_dependencies
run: |
- git config --global --add safe.directory $(realpath .)
- cd tools/server/webui
- git status
-
- npm run build
- git status
- modified_files="$(git status -s)"
- echo "Modified files: ${modified_files}"
- if [ -n "${modified_files}" ]; then
- echo "Repository is dirty or server/webui is not built as expected"
- echo "Hint: You may need to follow Web UI build guide in server/README.md"
- echo "${modified_files}"
- exit 1
- fi
+ pip install -r tools/server/tests/requirements.txt
+
+ - name: Setup Node.js for WebUI
+ uses: actions/setup-node@v4
+ with:
+ node-version: "22"
+ cache: "npm"
+ cache-dependency-path: "tools/server/webui/package-lock.json"
+
+ - name: Install WebUI dependencies
+ run: npm ci
+ working-directory: tools/server/webui
+
+ - name: Build WebUI
+ run: npm run build
+ working-directory: tools/server/webui
- name: Build (no OpenMP)
id: cmake_build_no_openmp
/run-vim.sh
/run-chat.sh
.ccache/
+
+# Code Workspace
+*.code-workspace
+
--- /dev/null
+---
+trigger: manual
+---
+
+#### Tailwind & CSS
+
+- We are using Tailwind v4 which uses oklch colors so we now want to refer to the CSS vars directly, without wrapping it with any color function like `hsla/hsl`, `rgba` etc.
--- /dev/null
+---
+trigger: manual
+---
+
+# Coding rules
+
+## Svelte & SvelteKit
+
+### Services vs Stores Separation Pattern
+
+#### `lib/services/` - Pure Business Logic
+
+- **Purpose**: Stateless business logic and external communication
+- **Contains**:
+ - API calls to external services (ApiService)
+ - Pure business logic functions (ChatService, etc.)
+- **Rules**:
+ - NO Svelte runes ($state, $derived, $effect)
+ - NO reactive state management
+ - Pure functions and classes only
+ - Can import types but not stores
+ - Focus on "how" - implementation details
+
+#### `lib/stores/` - Reactive State Management
+
+- **Purpose**: Svelte-specific reactive state with runes
+- **Contains**:
+ - Reactive state classes with $state, $derived, $effect
+ - Database operations (DatabaseStore)
+ - UI-focused state management
+ - Store orchestration logic
+- **Rules**:
+ - USE Svelte runes for reactivity
+ - Import and use services for business logic
+ - NO direct database operations
+ - NO direct API calls (use services)
+ - Focus on "what" - reactive state for UI
+
+#### Enforcement
+
+- Services should be testable without Svelte
+- Stores should leverage Svelte's reactivity system
+- Clear separation: services handle data, stores handle state
+- Services can be reused across multiple stores
+
+#### Misc
+
+- Always use `let` for $derived state variables
--- /dev/null
+---
+trigger: manual
+---
+
+# Automated Tests
+
+## General rules
+
+- NEVER include any test code in the production code - we should always have it in a separate dedicated files
--- /dev/null
+---
+trigger: manual
+---
+
+## TypeScript
+
+- Add JSDocs for functions
svr->Get (params.api_prefix + "/slots", handle_slots);
svr->Post(params.api_prefix + "/slots/:id_slot", handle_slots_action);
+ // SPA fallback route - serve index.html for any route that doesn't match API endpoints
+ // This enables client-side routing for dynamic routes like /chat/[id]
+ if (params.webui && params.public_path.empty()) {
+ // Only add fallback when using embedded static files
+ svr->Get(".*", [](const httplib::Request & req, httplib::Response & res) {
+ // Skip API routes - they should have been handled above
+ if (req.path.find("/v1/") != std::string::npos ||
+ req.path.find("/health") != std::string::npos ||
+ req.path.find("/metrics") != std::string::npos ||
+ req.path.find("/props") != std::string::npos ||
+ req.path.find("/models") != std::string::npos ||
+ req.path.find("/api/tags") != std::string::npos ||
+ req.path.find("/completions") != std::string::npos ||
+ req.path.find("/chat/completions") != std::string::npos ||
+ req.path.find("/embeddings") != std::string::npos ||
+ req.path.find("/tokenize") != std::string::npos ||
+ req.path.find("/detokenize") != std::string::npos ||
+ req.path.find("/lora-adapters") != std::string::npos ||
+ req.path.find("/slots") != std::string::npos) {
+ return false; // Let other handlers process API routes
+ }
+
+ // Serve index.html for all other routes (SPA fallback)
+ if (req.get_header_value("Accept-Encoding").find("gzip") == std::string::npos) {
+ res.set_content("Error: gzip is not supported by this browser", "text/plain");
+ } else {
+ res.set_header("Content-Encoding", "gzip");
+ // COEP and COOP headers, required by pyodide (python interpreter)
+ res.set_header("Cross-Origin-Embedder-Policy", "require-corp");
+ res.set_header("Cross-Origin-Opener-Policy", "same-origin");
+ res.set_content(reinterpret_cast<const char*>(index_html_gz), index_html_gz_len, "text/html; charset=utf-8");
+ }
+ return false;
+ });
+ }
+
//
// Start the server
//
url = f"http://{server.server_host}:{server.server_port}"
res = requests.get(url)
assert res.status_code == 200
- assert "<html>" in res.text
+ assert "<!doctype html>" in res.text
server.stop()
# with --no-webui
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-lerna-debug.log*
-
+test-results
node_modules
-dist
-dist-ssr
-*.local
-# Editor directories and files
-.vscode/*
-!.vscode/extensions.json
-.idea
+# Output
+.output
+.vercel
+.netlify
+.wrangler
+/.svelte-kit
+/build
+
+# OS
.DS_Store
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw?
+Thumbs.db
+
+# Env
+.env
+.env.*
+!.env.example
+!.env.test
+
+# Vite
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
+
+*storybook.log
+storybook-static
--- /dev/null
+engine-strict=true
-**/.vscode
-**/.github
-**/.git
-**/.svn
-**/.hg
-**/node_modules
-**/dist
-**/build
+# Package Managers
+package-lock.json
+pnpm-lock.yaml
+yarn.lock
+bun.lock
+bun.lockb
-*.config.js
+# Miscellaneous
+/static/
--- /dev/null
+{
+ "useTabs": true,
+ "singleQuote": true,
+ "trailingComma": "none",
+ "printWidth": 100,
+ "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
+ "overrides": [
+ {
+ "files": "*.svelte",
+ "options": {
+ "parser": "svelte"
+ }
+ }
+ ],
+ "tailwindStylesheet": "./src/app.css"
+}
--- /dev/null
+<script lang="ts">
+ import { ModeWatcher } from 'mode-watcher';
+ import { onMount } from 'svelte';
+
+ interface Props {
+ children?: any;
+ }
+
+ let { children }: Props = $props();
+
+ onMount(() => {
+ const root = document.documentElement;
+ const theme = localStorage.getItem('mode-watcher-mode') || 'system';
+
+ if (theme === 'dark') {
+ root.classList.add('dark');
+ } else if (theme === 'light') {
+ root.classList.remove('dark');
+ } else {
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
+ if (prefersDark) {
+ root.classList.add('dark');
+ } else {
+ root.classList.remove('dark');
+ }
+ }
+ });
+</script>
+
+<ModeWatcher />
+
+{#if children}
+ {@const Component = children}
+
+ <Component />
+{/if}
--- /dev/null
+<script lang="ts">
+ import * as Tooltip from '../src/lib/components/ui/tooltip';
+
+ interface Props {
+ children: any;
+ }
+
+ let { children }: Props = $props();
+</script>
+
+<Tooltip.Provider>
+ {@render children()}
+</Tooltip.Provider>
--- /dev/null
+import type { StorybookConfig } from '@storybook/sveltekit';
+
+const config: StorybookConfig = {
+ stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|ts|svelte)'],
+ addons: [
+ '@storybook/addon-svelte-csf',
+ '@chromatic-com/storybook',
+ '@storybook/addon-docs',
+ '@storybook/addon-a11y',
+ '@storybook/addon-vitest'
+ ],
+ framework: {
+ name: '@storybook/sveltekit',
+ options: {}
+ }
+};
+export default config;
--- /dev/null
+import type { Preview } from '@storybook/sveltekit';
+import '../src/app.css';
+import ModeWatcherDecorator from './ModeWatcherDecorator.svelte';
+import TooltipProviderDecorator from './TooltipProviderDecorator.svelte';
+
+const preview: Preview = {
+ parameters: {
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/i
+ }
+ },
+ backgrounds: {
+ disable: true
+ }
+ },
+ decorators: [
+ (story) => ({
+ Component: ModeWatcherDecorator,
+ props: {
+ children: story
+ }
+ }),
+ (story) => ({
+ Component: TooltipProviderDecorator,
+ props: {
+ children: story
+ }
+ })
+ ]
+};
+
+export default preview;
--- /dev/null
+import { setProjectAnnotations } from '@storybook/sveltekit';
+import * as previewAnnotations from './preview';
+import { beforeAll } from 'vitest';
+
+const project = setProjectAnnotations([previewAnnotations]);
+
+beforeAll(async () => {
+ if (project.beforeAll) {
+ await project.beforeAll();
+ }
+});
--- /dev/null
+# llama.cpp Web UI
+
+A modern, feature-rich web interface for llama.cpp built with SvelteKit. This UI provides an intuitive chat interface with advanced file handling, conversation management, and comprehensive model interaction capabilities.
+
+## Features
+
+- **Modern Chat Interface** - Clean, responsive design with dark/light mode
+- **File Attachments** - Support for images, text files, PDFs, and audio with rich previews and drag-and-drop support
+- **Conversation Management** - Create, edit, branch, and search conversations
+- **Advanced Markdown** - Code highlighting, math formulas (KaTeX), and content blocks
+- **Reasoning Content** - Support for models with thinking blocks
+- **Keyboard Shortcuts** - Keyboard navigation (Shift+Ctrl/Cmd+O for new chat, Shift+Ctrl/Cmdt+E for edit conversation, Shift+Ctrl/Cmdt+D for delete conversation, Ctrl/Cmd+K for search, Ctrl/Cmd+V for paste, Ctrl/Cmd+B for opening/collapsing sidebar)
+- **Request Tracking** - Monitor processing with slots endpoint integration
+- **UI Testing** - Storybook component library with automated tests
+
+## Development
+
+Install dependencies:
+
+```bash
+npm install
+```
+
+Start the development server + Storybook:
+
+```bash
+npm run dev
+```
+
+This will start both the SvelteKit dev server and Storybook on port 6006.
+
+## Building
+
+Create a production build:
+
+```bash
+npm run build
+```
+
+The build outputs static files to `../public` directory for deployment with llama.cpp server.
+
+## Testing
+
+Run the test suite:
+
+```bash
+# E2E tests
+npm run test:e2e
+
+# Unit tests
+npm run test:unit
+
+# UI tests
+npm run test:ui
+
+# All tests
+npm run test
+```
+
+## Architecture
+
+- **Framework**: SvelteKit with Svelte 5 runes
+- **Components**: ShadCN UI + bits-ui design system
+- **Database**: IndexedDB with Dexie for local storage
+- **Build**: Static adapter for deployment with llama.cpp server
+- **Testing**: Playwright (E2E) + Vitest (unit) + Storybook (components)
--- /dev/null
+{
+ "$schema": "https://shadcn-svelte.com/schema.json",
+ "tailwind": {
+ "css": "src/app.css",
+ "baseColor": "neutral"
+ },
+ "aliases": {
+ "components": "$lib/components",
+ "utils": "$lib/components/ui/utils",
+ "ui": "$lib/components/ui",
+ "hooks": "$lib/hooks",
+ "lib": "$lib"
+ },
+ "typescript": true,
+ "registry": "https://shadcn-svelte.com/registry"
+}
--- /dev/null
+import { expect, test } from '@playwright/test';
+
+test('home page has expected h1', async ({ page }) => {
+ await page.goto('/');
+ await expect(page.locator('h1')).toBeVisible();
+});
-import js from '@eslint/js'
-import globals from 'globals'
-import reactHooks from 'eslint-plugin-react-hooks'
-import reactRefresh from 'eslint-plugin-react-refresh'
-import tseslint from 'typescript-eslint'
+// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
+import storybook from 'eslint-plugin-storybook';
-export default tseslint.config(
- { ignores: ['dist'] },
- {
- extends: [js.configs.recommended, ...tseslint.configs.recommended],
- files: ['**/*.{ts,tsx}'],
- languageOptions: {
- ecmaVersion: 2020,
- globals: globals.browser,
- },
- plugins: {
- 'react-hooks': reactHooks,
- 'react-refresh': reactRefresh,
- },
- rules: {
- ...reactHooks.configs.recommended.rules,
- 'react-refresh/only-export-components': 'off',
- '@typescript-eslint/no-unused-vars': 'off',
- },
- },
-)
+import prettier from 'eslint-config-prettier';
+import { includeIgnoreFile } from '@eslint/compat';
+import js from '@eslint/js';
+import svelte from 'eslint-plugin-svelte';
+import globals from 'globals';
+import { fileURLToPath } from 'node:url';
+import ts from 'typescript-eslint';
+import svelteConfig from './svelte.config.js';
+
+const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
+
+export default ts.config(
+ includeIgnoreFile(gitignorePath),
+ js.configs.recommended,
+ ...ts.configs.recommended,
+ ...svelte.configs.recommended,
+ prettier,
+ ...svelte.configs.prettier,
+ {
+ languageOptions: {
+ globals: { ...globals.browser, ...globals.node }
+ },
+ rules: {
+ // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
+ // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
+ 'no-undef': 'off',
+ 'svelte/no-at-html-tags': 'off'
+ }
+ },
+ {
+ files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
+ languageOptions: {
+ parserOptions: {
+ projectService: true,
+ extraFileExtensions: ['.svelte'],
+ parser: ts.parser,
+ svelteConfig
+ }
+ }
+ },
+ {
+ // Exclude Storybook files from main ESLint rules
+ ignores: ['.storybook/**/*']
+ },
+ storybook.configs['flat/recommended']
+);
+++ /dev/null
-<!doctype html>
-<html>
- <head>
- <meta charset="UTF-8" />
- <meta
- name="viewport"
- content="width=device-width, initial-scale=1, maximum-scale=1"
- />
- <meta name="color-scheme" content="light dark" />
- <title>🦙 llama.cpp - chat</title>
- </head>
- <body>
- <div id="root"></div>
- <script type="module" src="/src/main.tsx"></script>
- </body>
-</html>
{
- "name": "webui",
- "version": "0.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "webui",
- "version": "0.0.0",
- "dependencies": {
- "@heroicons/react": "^2.2.0",
- "@sec-ant/readable-stream": "^0.6.0",
- "@tailwindcss/postcss": "^4.1.1",
- "@tailwindcss/vite": "^4.1.1",
- "@vscode/markdown-it-katex": "^1.1.1",
- "autoprefixer": "^10.4.20",
- "daisyui": "^5.0.12",
- "dexie": "^4.0.11",
- "highlight.js": "^11.10.0",
- "katex": "^0.16.15",
- "pdfjs-dist": "^5.2.133",
- "postcss": "^8.4.49",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
- "react-dropzone": "^14.3.8",
- "react-hot-toast": "^2.5.2",
- "react-markdown": "^9.0.3",
- "react-router": "^7.1.5",
- "rehype-highlight": "^7.0.2",
- "rehype-katex": "^7.0.1",
- "remark-breaks": "^4.0.0",
- "remark-gfm": "^4.0.0",
- "remark-math": "^6.0.0",
- "tailwindcss": "^4.1.1",
- "textlinestream": "^1.1.1",
- "vite-plugin-singlefile": "^2.0.3"
- },
- "devDependencies": {
- "@eslint/js": "^9.17.0",
- "@types/markdown-it": "^14.1.2",
- "@types/node": "^22.13.1",
- "@types/react": "^18.3.18",
- "@types/react-dom": "^18.3.5",
- "@vitejs/plugin-react": "^4.3.4",
- "eslint": "^9.17.0",
- "eslint-plugin-react-hooks": "^5.0.0",
- "eslint-plugin-react-refresh": "^0.4.16",
- "fflate": "^0.8.2",
- "globals": "^15.14.0",
- "prettier": "^3.4.2",
- "sass-embedded": "^1.83.4",
- "typescript": "~5.6.2",
- "typescript-eslint": "^8.18.2",
- "vite": "^6.0.5"
- }
- },
- "node_modules/@alloc/quick-lru": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
- "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@ampproject/remapping": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
- "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/code-frame": {
- "version": "7.26.2",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
- "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.25.9",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.0.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/compat-data": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz",
- "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/core": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz",
- "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.26.2",
- "@babel/generator": "^7.26.5",
- "@babel/helper-compilation-targets": "^7.26.5",
- "@babel/helper-module-transforms": "^7.26.0",
- "@babel/helpers": "^7.26.7",
- "@babel/parser": "^7.26.7",
- "@babel/template": "^7.25.9",
- "@babel/traverse": "^7.26.7",
- "@babel/types": "^7.26.7",
- "convert-source-map": "^2.0.0",
- "debug": "^4.1.0",
- "gensync": "^1.0.0-beta.2",
- "json5": "^2.2.3",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
- }
- },
- "node_modules/@babel/generator": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz",
- "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.26.5",
- "@babel/types": "^7.26.5",
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.25",
- "jsesc": "^3.0.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz",
- "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.26.5",
- "@babel/helper-validator-option": "^7.25.9",
- "browserslist": "^4.24.0",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-imports": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
- "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.25.9",
- "@babel/types": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-transforms": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
- "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9",
- "@babel/traverse": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-plugin-utils": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz",
- "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
- "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
- "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-option": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz",
- "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helpers": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz",
- "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.25.9",
- "@babel/types": "^7.26.7"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz",
- "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.26.7"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-self": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz",
- "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-react-jsx-source": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz",
- "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/template": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
- "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.25.9",
- "@babel/parser": "^7.25.9",
- "@babel/types": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/traverse": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz",
- "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.26.2",
- "@babel/generator": "^7.26.5",
- "@babel/parser": "^7.26.7",
- "@babel/template": "^7.25.9",
- "@babel/types": "^7.26.7",
- "debug": "^4.3.1",
- "globals": "^11.1.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/traverse/node_modules/globals": {
- "version": "11.12.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
- "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@babel/types": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz",
- "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@bufbuild/protobuf": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.3.tgz",
- "integrity": "sha512-tFQoXHJdkEOSwj5tRIZSPNUuXK3RaR7T1nUrPgbYX1pUbvqqaaZAsfo+NXBPsz5rZMSKVFrgK1WL8Q/MSLvprg==",
- "devOptional": true,
- "license": "(Apache-2.0 AND BSD-3-Clause)"
- },
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
- "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==",
- "cpu": [
- "ppc64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz",
- "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz",
- "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz",
- "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz",
- "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz",
- "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz",
- "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz",
- "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz",
- "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz",
- "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz",
- "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz",
- "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==",
- "cpu": [
- "loong64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz",
- "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==",
- "cpu": [
- "mips64el"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz",
- "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==",
- "cpu": [
- "ppc64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz",
- "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz",
- "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==",
- "cpu": [
- "s390x"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz",
- "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz",
- "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz",
- "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz",
- "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz",
- "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz",
- "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz",
- "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz",
- "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz",
- "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
- "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eslint-visitor-keys": "^3.4.3"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- },
- "peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
- }
- },
- "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint-community/regexpp": {
- "version": "4.12.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
- "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
- }
- },
- "node_modules/@eslint/config-array": {
- "version": "0.19.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz",
- "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/object-schema": "^2.1.6",
- "debug": "^4.3.1",
- "minimatch": "^3.1.2"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/core": {
- "version": "0.10.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz",
- "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@types/json-schema": "^7.0.15"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/eslintrc": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz",
- "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^10.0.1",
- "globals": "^14.0.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint/eslintrc/node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@eslint/js": {
- "version": "9.19.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
- "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/object-schema": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
- "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/plugin-kit": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz",
- "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.10.0",
- "levn": "^0.4.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@heroicons/react": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
- "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
- "license": "MIT",
- "peerDependencies": {
- "react": ">= 16 || ^19.0.0-rc"
- }
- },
- "node_modules/@humanfs/core": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
- "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanfs/node": {
- "version": "0.16.6",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
- "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@humanfs/core": "^0.19.1",
- "@humanwhocodes/retry": "^0.3.0"
- },
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
- "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@humanwhocodes/module-importer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
- "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=12.22"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@humanwhocodes/retry": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
- "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
- "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/source-map": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
- "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.25"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
- "devOptional": true,
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@napi-rs/canvas": {
- "version": "0.1.70",
- "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.70.tgz",
- "integrity": "sha512-nD6NGa4JbNYSZYsTnLGrqe9Kn/lCkA4ybXt8sx5ojDqZjr2i0TWAHxx/vhgfjX+i3hCdKWufxYwi7CfXqtITSA==",
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">= 10"
- },
- "optionalDependencies": {
- "@napi-rs/canvas-android-arm64": "0.1.70",
- "@napi-rs/canvas-darwin-arm64": "0.1.70",
- "@napi-rs/canvas-darwin-x64": "0.1.70",
- "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.70",
- "@napi-rs/canvas-linux-arm64-gnu": "0.1.70",
- "@napi-rs/canvas-linux-arm64-musl": "0.1.70",
- "@napi-rs/canvas-linux-riscv64-gnu": "0.1.70",
- "@napi-rs/canvas-linux-x64-gnu": "0.1.70",
- "@napi-rs/canvas-linux-x64-musl": "0.1.70",
- "@napi-rs/canvas-win32-x64-msvc": "0.1.70"
- }
- },
- "node_modules/@napi-rs/canvas-android-arm64": {
- "version": "0.1.70",
- "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.70.tgz",
- "integrity": "sha512-I/YOuQ0wbkVYxVaYtCgN42WKTYxNqFA0gTcTrHIGG1jfpDSyZWII/uHcjOo4nzd19io6Y4+/BqP8E5hJgf9OmQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/canvas-darwin-arm64": {
- "version": "0.1.70",
- "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.70.tgz",
- "integrity": "sha512-4pPGyXetHIHkw2TOJHujt3mkCP8LdDu8+CT15ld9Id39c752RcI0amDHSuMLMQfAjvusA9B5kKxazwjMGjEJpQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/canvas-darwin-x64": {
- "version": "0.1.70",
- "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.70.tgz",
- "integrity": "sha512-+2N6Os9LbkmDMHL+raknrUcLQhsXzc5CSXRbXws9C3pv/mjHRVszQ9dhFUUe9FjfPhCJznO6USVdwOtu7pOrzQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
- "version": "0.1.70",
- "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.70.tgz",
- "integrity": "sha512-QjscX9OaKq/990sVhSMj581xuqLgiaPVMjjYvWaCmAJRkNQ004QfoSMEm3FoTqM4DRoquP8jvuEXScVJsc1rqQ==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/canvas-linux-arm64-gnu": {
- "version": "0.1.70",
- "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.70.tgz",
- "integrity": "sha512-LNakMOwwqwiHIwMpnMAbFRczQMQ7TkkMyATqFCOtUJNlE6LPP/QiUj/mlFrNbUn/hctqShJ60gWEb52ZTALbVw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/canvas-linux-arm64-musl": {
- "version": "0.1.70",
- "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.70.tgz",
- "integrity": "sha512-wBTOllEYNfJCHOdZj9v8gLzZ4oY3oyPX8MSRvaxPm/s7RfEXxCyZ8OhJ5xAyicsDdbE5YBZqdmaaeP5+xKxvtg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
- "version": "0.1.70",
- "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.70.tgz",
- "integrity": "sha512-GVUUPC8TuuFqHip0rxHkUqArQnlzmlXmTEBuXAWdgCv85zTCFH8nOHk/YCF5yo0Z2eOm8nOi90aWs0leJ4OE5Q==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/canvas-linux-x64-gnu": {
- "version": "0.1.70",
- "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.70.tgz",
- "integrity": "sha512-/kvUa2lZRwGNyfznSn5t1ShWJnr/m5acSlhTV3eXECafObjl0VBuA1HJw0QrilLpb4Fe0VLywkpD1NsMoVDROQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/canvas-linux-x64-musl": {
- "version": "0.1.70",
- "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.70.tgz",
- "integrity": "sha512-aqlv8MLpycoMKRmds7JWCfVwNf1fiZxaU7JwJs9/ExjTD8lX2KjsO7CTeAj5Cl4aEuzxUWbJPUUE2Qu9cZ1vfg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/canvas-win32-x64-msvc": {
- "version": "0.1.70",
- "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.70.tgz",
- "integrity": "sha512-Q9QU3WIpwBTVHk4cPfBjGHGU4U0llQYRXgJtFtYqqGNEOKVN4OT6PQ+ve63xwIPODMpZ0HHyj/KLGc9CWc3EtQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.stat": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.2.tgz",
- "integrity": "sha512-6Fyg9yQbwJR+ykVdT9sid1oc2ewejS6h4wzQltmJfSW53N60G/ah9pngXGANdy9/aaE/TcUFpWosdm7JXS1WTQ==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.2.tgz",
- "integrity": "sha512-K5GfWe+vtQ3kyEbihrimM38UgX57UqHp+oME7X/EX9Im6suwZfa7Hsr8AtzbJvukTpwMGs+4s29YMSO3rwWtsw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.2.tgz",
- "integrity": "sha512-PSN58XG/V/tzqDb9kDGutUruycgylMlUE59f40ny6QIRNsTEIZsrNQTJKUN2keMMSmlzgunMFqyaGLmly39sug==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.2.tgz",
- "integrity": "sha512-gQhK788rQJm9pzmXyfBB84VHViDERhAhzGafw+E5mUpnGKuxZGkMVDa3wgDFKT6ukLC5V7QTifzsUKdNVxp5qQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.2.tgz",
- "integrity": "sha512-eiaHgQwGPpxLC3+zTAcdKl4VsBl3r0AiJOd1Um/ArEzAjN/dbPK1nROHrVkdnoE6p7Svvn04w3f/jEZSTVHunA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.2.tgz",
- "integrity": "sha512-lhdiwQ+jf8pewYOTG4bag0Qd68Jn1v2gO1i0mTuiD+Qkt5vNfHVK/jrT7uVvycV8ZchlzXp5HDVmhpzjC6mh0g==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.2.tgz",
- "integrity": "sha512-lfqTpWjSvbgQP1vqGTXdv+/kxIznKXZlI109WkIFPbud41bjigjNmOAAKoazmRGx+k9e3rtIdbq2pQZPV1pMig==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.2.tgz",
- "integrity": "sha512-RGjqULqIurqqv+NJTyuPgdZhka8ImMLB32YwUle2BPTDqDoXNgwFjdjQC59FbSk08z0IqlRJjrJ0AvDQ5W5lpw==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.2.tgz",
- "integrity": "sha512-ZvkPiheyXtXlFqHpsdgscx+tZ7hoR59vOettvArinEspq5fxSDSgfF+L5wqqJ9R4t+n53nyn0sKxeXlik7AY9Q==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.2.tgz",
- "integrity": "sha512-UlFk+E46TZEoxD9ufLKDBzfSG7Ki03fo6hsNRRRHF+KuvNZ5vd1RRVQm8YZlGsjcJG8R252XFK0xNPay+4WV7w==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.2.tgz",
- "integrity": "sha512-hJhfsD9ykx59jZuuoQgYT1GEcNNi3RCoEmbo5OGfG8RlHOiVS7iVNev9rhLKh7UBYq409f4uEw0cclTXx8nh8Q==",
- "cpu": [
- "loong64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.2.tgz",
- "integrity": "sha512-g/O5IpgtrQqPegvqopvmdCF9vneLE7eqYfdPWW8yjPS8f63DNam3U4ARL1PNNB64XHZDHKpvO2Giftf43puB8Q==",
- "cpu": [
- "ppc64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.2.tgz",
- "integrity": "sha512-bSQijDC96M6PuooOuXHpvXUYiIwsnDmqGU8+br2U7iPoykNi9JtMUpN7K6xml29e0evK0/g0D1qbAUzWZFHY5Q==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.2.tgz",
- "integrity": "sha512-49TtdeVAsdRuiUHXPrFVucaP4SivazetGUVH8CIxVsNsaPHV4PFkpLmH9LeqU/R4Nbgky9lzX5Xe1NrzLyraVA==",
- "cpu": [
- "s390x"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.2.tgz",
- "integrity": "sha512-j+jFdfOycLIQ7FWKka9Zd3qvsIyugg5LeZuHF6kFlXo6MSOc6R1w37YUVy8VpAKd81LMWGi5g9J25P09M0SSIw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.2.tgz",
- "integrity": "sha512-aDPHyM/D2SpXfSNCVWCxyHmOqN9qb7SWkY1+vaXqMNMXslZYnwh9V/UCudl6psyG0v6Ukj7pXanIpfZwCOEMUg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.2.tgz",
- "integrity": "sha512-LQRkCyUBnAo7r8dbEdtNU08EKLCJMgAk2oP5H3R7BnUlKLqgR3dUjrLBVirmc1RK6U6qhtDw29Dimeer8d5hzQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.2.tgz",
- "integrity": "sha512-wt8OhpQUi6JuPFkm1wbVi1BByeag87LDFzeKSXzIdGcX4bMLqORTtKxLoCbV57BHYNSUSOKlSL4BYYUghainYA==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.2.tgz",
- "integrity": "sha512-rUrqINax0TvrPBXrFKg0YbQx18NpPN3NNrgmaao9xRNbTwek7lOXObhx8tQy8gelmQ/gLaGy1WptpU2eKJZImg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@sec-ant/readable-stream": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.6.0.tgz",
- "integrity": "sha512-uiBh8DrB5FN35gP6/o8JEhEQ7/ci1jUsOZO/VMUjyvTpjtV54VstOXVj1TvTj/wsT23pfX6butxxh3qufsW3+g==",
- "license": "MIT"
- },
- "node_modules/@tailwindcss/node": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.1.tgz",
- "integrity": "sha512-xvlh4pvfG/bkv0fEtJDABAm1tjtSmSyi2QmS4zyj1EKNI1UiOYiUq1IphSwDsNJ5vJ9cWEGs4rJXpUdCN2kujQ==",
- "license": "MIT",
- "dependencies": {
- "enhanced-resolve": "^5.18.1",
- "jiti": "^2.4.2",
- "lightningcss": "1.29.2",
- "tailwindcss": "4.1.1"
- }
- },
- "node_modules/@tailwindcss/oxide": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.1.tgz",
- "integrity": "sha512-7+YBgnPQ4+jv6B6WVOerJ6WOzDzNJXrRKDts674v6TKAqFqYRr9+EBtSziO7nNcwQ8JtoZNMeqA+WJDjtCM/7w==",
- "license": "MIT",
- "engines": {
- "node": ">= 10"
- },
- "optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.1.1",
- "@tailwindcss/oxide-darwin-arm64": "4.1.1",
- "@tailwindcss/oxide-darwin-x64": "4.1.1",
- "@tailwindcss/oxide-freebsd-x64": "4.1.1",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.1",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.1.1",
- "@tailwindcss/oxide-linux-arm64-musl": "4.1.1",
- "@tailwindcss/oxide-linux-x64-gnu": "4.1.1",
- "@tailwindcss/oxide-linux-x64-musl": "4.1.1",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.1.1",
- "@tailwindcss/oxide-win32-x64-msvc": "4.1.1"
- }
- },
- "node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.1.tgz",
- "integrity": "sha512-gTyRzfdParpoCU1yyUC/iN6XK6T0Ra4bDlF8Aeul5NP9cLzKEZDogdNVNGv5WZmCDkVol7qlex7TMmcfytMmmw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.1.tgz",
- "integrity": "sha512-dI0QbdMWBvLB3MtaTKetzUKG9CUUQow8JSP4Nm+OxVokeZ+N+f1OmZW/hW1LzMxpx9RQCBgSRL+IIvKRat5Wdg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.1.tgz",
- "integrity": "sha512-2Y+NPQOTRBCItshPgY/CWg4bKi7E9evMg4bgdb6h9iZObCZLOe3doPcuSxGS3DB0dKyMFKE8pTdWtFUbxZBMSA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.1.tgz",
- "integrity": "sha512-N97NGMsB/7CHShbc5ube4dcsW/bYENkBrg8yWi8ieN9boYVRdw3cZviVryV/Nfu9bKbBV9kUvduFF2qBI7rEqg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.1.tgz",
- "integrity": "sha512-33Lk6KbHnUZbXqza6RWNFo9wqPQ4+H5BAn1CkUUfC1RZ1vYbyDN6+iJPj53wmnWJ3mhRI8jWt3Jt1fO02IVdUQ==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.1.tgz",
- "integrity": "sha512-LyW35RzSUy+80WYScv03HKasAUmMFDaSbNpWfk1gG5gEE9kuRGnDzSrqMoLAmY/kzMCYP/1kqmUiAx8EFLkI2A==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.1.tgz",
- "integrity": "sha512-1KPnDMlHdqjPTUSFjx55pafvs8RZXRgxfeRgUrukwDKkuj7gFk28vW3Mx65YdiugAc9NWs3VgueZWaM1Po6uGw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.1.tgz",
- "integrity": "sha512-4WdzA+MRlsinEEE6yxNMLJxpw0kE9XVipbAKdTL8BeUpyC2TdA3TL46lBulXzKp3BIxh3nqyR/UCqzl5o+3waQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.1.tgz",
- "integrity": "sha512-q7Ugbw3ARcjCW2VMUYrcMbJ6aMQuWPArBBE2EqC/swPZTdGADvMQSlvR0VKusUM4HoSsO7ZbvcZ53YwR57+AKw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.1.tgz",
- "integrity": "sha512-0KpqsovgHcIzm7eAGzzEZsEs0/nPYXnRBv+aPq/GehpNQuE/NAQu+YgZXIIof+VflDFuyXOEnaFr7T5MZ1INhA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.1.tgz",
- "integrity": "sha512-B1mjeXNS26kBOHv5sXARf6Wd0PWHV9x1TDlW0ummrBUOUAxAy5wcy4Nii1wzNvCdvC448hgiL06ylhwAbNthmg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tailwindcss/postcss": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.1.tgz",
- "integrity": "sha512-GX9AEM+msH0i2Yh1b6CuDRaZRo3kmbvIrLbSfvJ53C3uaAgsQ//fTQAh9HMQ6t1a9zvoUptlYqG//plWsBQTCw==",
- "license": "MIT",
- "dependencies": {
- "@alloc/quick-lru": "^5.2.0",
- "@tailwindcss/node": "4.1.1",
- "@tailwindcss/oxide": "4.1.1",
- "postcss": "^8.4.41",
- "tailwindcss": "4.1.1"
- }
- },
- "node_modules/@tailwindcss/vite": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.1.tgz",
- "integrity": "sha512-tFTkRZwXq4XKr3S2dUZBxy80wbWYHdDSsu4QOB1yE1HJFKjfxKVpXtup4dyTVdQcLInoHC9lZXFPHnjoBP774g==",
- "license": "MIT",
- "dependencies": {
- "@tailwindcss/node": "4.1.1",
- "@tailwindcss/oxide": "4.1.1",
- "tailwindcss": "4.1.1"
- },
- "peerDependencies": {
- "vite": "^5.2.0 || ^6"
- }
- },
- "node_modules/@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
- }
- },
- "node_modules/@types/babel__generator": {
- "version": "7.6.8",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
- "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
- }
- },
- "node_modules/@types/babel__traverse": {
- "version": "7.20.6",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
- "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.20.7"
- }
- },
- "node_modules/@types/cookie": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
- "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
- "license": "MIT"
- },
- "node_modules/@types/debug": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
- "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
- "license": "MIT",
- "dependencies": {
- "@types/ms": "*"
- }
- },
- "node_modules/@types/estree": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
- "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
- "license": "MIT"
- },
- "node_modules/@types/estree-jsx": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
- "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
- "license": "MIT",
- "dependencies": {
- "@types/estree": "*"
- }
- },
- "node_modules/@types/hast": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
- "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "*"
- }
- },
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/katex": {
- "version": "0.16.7",
- "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz",
- "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==",
- "license": "MIT"
- },
- "node_modules/@types/linkify-it": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
- "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/markdown-it": {
- "version": "14.1.2",
- "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
- "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/linkify-it": "^5",
- "@types/mdurl": "^2"
- }
- },
- "node_modules/@types/mdast": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
- "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "*"
- }
- },
- "node_modules/@types/mdurl": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
- "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/ms": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
- "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
- "license": "MIT"
- },
- "node_modules/@types/node": {
- "version": "22.13.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz",
- "integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~6.20.0"
- }
- },
- "node_modules/@types/prop-types": {
- "version": "15.7.14",
- "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
- "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
- "license": "MIT"
- },
- "node_modules/@types/react": {
- "version": "18.3.18",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
- "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
- "license": "MIT",
- "dependencies": {
- "@types/prop-types": "*",
- "csstype": "^3.0.2"
- }
- },
- "node_modules/@types/react-dom": {
- "version": "18.3.5",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz",
- "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "^18.0.0"
- }
- },
- "node_modules/@types/unist": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
- "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
- "license": "MIT"
- },
- "node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.23.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.23.0.tgz",
- "integrity": "sha512-vBz65tJgRrA1Q5gWlRfvoH+w943dq9K1p1yDBY2pc+a1nbBLZp7fB9+Hk8DaALUbzjqlMfgaqlVPT1REJdkt/w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.23.0",
- "@typescript-eslint/type-utils": "8.23.0",
- "@typescript-eslint/utils": "8.23.0",
- "@typescript-eslint/visitor-keys": "8.23.0",
- "graphemer": "^1.4.0",
- "ignore": "^5.3.1",
- "natural-compare": "^1.4.0",
- "ts-api-utils": "^2.0.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.8.0"
- }
- },
- "node_modules/@typescript-eslint/parser": {
- "version": "8.23.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.23.0.tgz",
- "integrity": "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/scope-manager": "8.23.0",
- "@typescript-eslint/types": "8.23.0",
- "@typescript-eslint/typescript-estree": "8.23.0",
- "@typescript-eslint/visitor-keys": "8.23.0",
- "debug": "^4.3.4"
- },
- "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 <5.8.0"
- }
- },
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "8.23.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.23.0.tgz",
- "integrity": "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.23.0",
- "@typescript-eslint/visitor-keys": "8.23.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/type-utils": {
- "version": "8.23.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.23.0.tgz",
- "integrity": "sha512-iIuLdYpQWZKbiH+RkCGc6iu+VwscP5rCtQ1lyQ7TYuKLrcZoeJVpcLiG8DliXVkUxirW/PWlmS+d6yD51L9jvA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/typescript-estree": "8.23.0",
- "@typescript-eslint/utils": "8.23.0",
- "debug": "^4.3.4",
- "ts-api-utils": "^2.0.1"
- },
- "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 <5.8.0"
- }
- },
- "node_modules/@typescript-eslint/types": {
- "version": "8.23.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.23.0.tgz",
- "integrity": "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==",
- "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/@typescript-eslint/typescript-estree": {
- "version": "8.23.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.23.0.tgz",
- "integrity": "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.23.0",
- "@typescript-eslint/visitor-keys": "8.23.0",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "ts-api-utils": "^2.0.1"
- },
- "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 <5.8.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/node_modules/semver": {
- "version": "7.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
- "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@typescript-eslint/utils": {
- "version": "8.23.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.23.0.tgz",
- "integrity": "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.4.0",
- "@typescript-eslint/scope-manager": "8.23.0",
- "@typescript-eslint/types": "8.23.0",
- "@typescript-eslint/typescript-estree": "8.23.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 <5.8.0"
- }
- },
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.23.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.23.0.tgz",
- "integrity": "sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.23.0",
- "eslint-visitor-keys": "^4.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@ungap/structured-clone": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
- "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
- "license": "ISC"
- },
- "node_modules/@vitejs/plugin-react": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
- "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.26.0",
- "@babel/plugin-transform-react-jsx-self": "^7.25.9",
- "@babel/plugin-transform-react-jsx-source": "^7.25.9",
- "@types/babel__core": "^7.20.5",
- "react-refresh": "^0.14.2"
- },
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "peerDependencies": {
- "vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
- }
- },
- "node_modules/@vscode/markdown-it-katex": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@vscode/markdown-it-katex/-/markdown-it-katex-1.1.1.tgz",
- "integrity": "sha512-3KTlbsRBPJQLE2YmLL7K6nunTlU+W9T5+FjfNdWuIUKgxSS6HWLQHaO3L4MkJi7z7MpIPpY+g4N+cWNBPE/MSA==",
- "license": "MIT",
- "dependencies": {
- "katex": "^0.16.4"
- }
- },
- "node_modules/acorn": {
- "version": "8.14.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
- "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
- "devOptional": true,
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
- }
- },
- "node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true,
- "license": "Python-2.0"
- },
- "node_modules/attr-accept": {
- "version": "2.2.5",
- "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
- "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/autoprefixer": {
- "version": "10.4.20",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
- "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/autoprefixer"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.23.3",
- "caniuse-lite": "^1.0.30001646",
- "fraction.js": "^4.3.7",
- "normalize-range": "^0.1.2",
- "picocolors": "^1.0.1",
- "postcss-value-parser": "^4.2.0"
- },
- "bin": {
- "autoprefixer": "bin/autoprefixer"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- },
- "peerDependencies": {
- "postcss": "^8.1.0"
- }
- },
- "node_modules/bail": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
- "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/braces": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "license": "MIT",
- "dependencies": {
- "fill-range": "^7.1.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/browserslist": {
- "version": "4.24.4",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
- "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "caniuse-lite": "^1.0.30001688",
- "electron-to-chromium": "^1.5.73",
- "node-releases": "^2.0.19",
- "update-browserslist-db": "^1.1.1"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
- }
- },
- "node_modules/buffer-builder": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz",
- "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==",
- "devOptional": true,
- "license": "MIT/X11"
- },
- "node_modules/buffer-from": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
- "license": "MIT",
- "optional": true,
- "peer": true
- },
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001697",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001697.tgz",
- "integrity": "sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "CC-BY-4.0"
- },
- "node_modules/ccount": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
- "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/character-entities": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
- "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/character-entities-html4": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
- "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/character-entities-legacy": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
- "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/character-reference-invalid": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
- "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/colorjs.io": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
- "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
- "devOptional": true,
- "license": "MIT"
- },
- "node_modules/comma-separated-tokens": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
- "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/commander": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
- "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
- "license": "MIT",
- "engines": {
- "node": ">= 12"
- }
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cookie": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
- "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/cross-spawn": {
- "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",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/csstype": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "license": "MIT"
- },
- "node_modules/daisyui": {
- "version": "5.0.12",
- "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.0.12.tgz",
- "integrity": "sha512-01DU0eYBcHgPtuf5fxcrkGkIN6/Uyaqmkle5Yo3ZyW9YVAu036ALZbjv2KH5euvUbeQ4r9q3gAarGcf7Tywhng==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/saadeghi/daisyui?sponsor=1"
- }
- },
- "node_modules/debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/decode-named-character-reference": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
- "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==",
- "license": "MIT",
- "dependencies": {
- "character-entities": "^2.0.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true,
- "license": "MIT"
- },
- "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.3",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
- "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/devlop": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
- "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
- "license": "MIT",
- "dependencies": {
- "dequal": "^2.0.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/dexie": {
- "version": "4.0.11",
- "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.11.tgz",
- "integrity": "sha512-SOKO002EqlvBYYKQSew3iymBoN2EQ4BDw/3yprjh7kAfFzjBYkaMNa/pZvcA7HSWlcKSQb9XhPe3wKyQ0x4A8A==",
- "license": "Apache-2.0"
- },
- "node_modules/electron-to-chromium": {
- "version": "1.5.91",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.91.tgz",
- "integrity": "sha512-sNSHHyq048PFmZY4S90ax61q+gLCs0X0YmcOII9wG9S2XwbVr+h4VW2wWhnbp/Eys3cCwTxVF292W3qPaxIapQ==",
- "license": "ISC"
- },
- "node_modules/enhanced-resolve": {
- "version": "5.18.1",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
- "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
- "license": "MIT",
- "dependencies": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.2.0"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/entities": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
- "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.12"
- },
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
- "node_modules/esbuild": {
- "version": "0.24.2",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz",
- "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.24.2",
- "@esbuild/android-arm": "0.24.2",
- "@esbuild/android-arm64": "0.24.2",
- "@esbuild/android-x64": "0.24.2",
- "@esbuild/darwin-arm64": "0.24.2",
- "@esbuild/darwin-x64": "0.24.2",
- "@esbuild/freebsd-arm64": "0.24.2",
- "@esbuild/freebsd-x64": "0.24.2",
- "@esbuild/linux-arm": "0.24.2",
- "@esbuild/linux-arm64": "0.24.2",
- "@esbuild/linux-ia32": "0.24.2",
- "@esbuild/linux-loong64": "0.24.2",
- "@esbuild/linux-mips64el": "0.24.2",
- "@esbuild/linux-ppc64": "0.24.2",
- "@esbuild/linux-riscv64": "0.24.2",
- "@esbuild/linux-s390x": "0.24.2",
- "@esbuild/linux-x64": "0.24.2",
- "@esbuild/netbsd-arm64": "0.24.2",
- "@esbuild/netbsd-x64": "0.24.2",
- "@esbuild/openbsd-arm64": "0.24.2",
- "@esbuild/openbsd-x64": "0.24.2",
- "@esbuild/sunos-x64": "0.24.2",
- "@esbuild/win32-arm64": "0.24.2",
- "@esbuild/win32-ia32": "0.24.2",
- "@esbuild/win32-x64": "0.24.2"
- }
- },
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint": {
- "version": "9.19.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz",
- "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.19.0",
- "@eslint/core": "^0.10.0",
- "@eslint/eslintrc": "^3.2.0",
- "@eslint/js": "9.19.0",
- "@eslint/plugin-kit": "^0.2.5",
- "@humanfs/node": "^0.16.6",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.1",
- "@types/estree": "^1.0.6",
- "@types/json-schema": "^7.0.15",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.6",
- "debug": "^4.3.2",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.2.0",
- "eslint-visitor-keys": "^4.2.0",
- "espree": "^10.3.0",
- "esquery": "^1.5.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^8.0.0",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.3"
- },
- "bin": {
- "eslint": "bin/eslint.js"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- },
- "peerDependencies": {
- "jiti": "*"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-plugin-react-hooks": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz",
- "integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
- }
- },
- "node_modules/eslint-plugin-react-refresh": {
- "version": "0.4.18",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.18.tgz",
- "integrity": "sha512-IRGEoFn3OKalm3hjfolEWGqoF/jPqeEYFp+C8B0WMzwGwBMvlRDQd06kghDhF0C61uJ6WfSDhEZE/sAQjduKgw==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "eslint": ">=8.40"
- }
- },
- "node_modules/eslint-scope": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
- "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint-visitor-keys": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
- "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/espree": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
- "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "acorn": "^8.14.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/esquery": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "estraverse": "^5.1.0"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estree-util-is-identifier-name": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
- "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extend": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
- "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
- "license": "MIT"
- },
- "node_modules/fast-deep-equal": {
- "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": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
- "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.8"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fastq": {
- "version": "1.19.0",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz",
- "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
- "node_modules/fflate": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
- "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/file-entry-cache": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flat-cache": "^4.0.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/file-selector": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz",
- "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.7.0"
- },
- "engines": {
- "node": ">= 12"
- }
- },
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/flat-cache": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.4"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/flatted": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
- "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/fraction.js": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
- "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
- "license": "MIT",
- "engines": {
- "node": "*"
- },
- "funding": {
- "type": "patreon",
- "url": "https://github.com/sponsors/rawify"
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/globals": {
- "version": "15.14.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz",
- "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/goober": {
- "version": "2.1.16",
- "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz",
- "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==",
- "license": "MIT",
- "peerDependencies": {
- "csstype": "^3.0.10"
- }
- },
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "license": "ISC"
- },
- "node_modules/graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/hast-util-from-dom": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz",
- "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==",
- "license": "ISC",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "hastscript": "^9.0.0",
- "web-namespaces": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/hast-util-from-html": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz",
- "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "devlop": "^1.1.0",
- "hast-util-from-parse5": "^8.0.0",
- "parse5": "^7.0.0",
- "vfile": "^6.0.0",
- "vfile-message": "^4.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/hast-util-from-html-isomorphic": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz",
- "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "hast-util-from-dom": "^5.0.0",
- "hast-util-from-html": "^2.0.0",
- "unist-util-remove-position": "^5.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/hast-util-from-parse5": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.2.tgz",
- "integrity": "sha512-SfMzfdAi/zAoZ1KkFEyyeXBn7u/ShQrfd675ZEE9M3qj+PMFX05xubzRyF76CCSJu8au9jgVxDV1+okFvgZU4A==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "@types/unist": "^3.0.0",
- "devlop": "^1.0.0",
- "hastscript": "^9.0.0",
- "property-information": "^6.0.0",
- "vfile": "^6.0.0",
- "vfile-location": "^5.0.0",
- "web-namespaces": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/hast-util-is-element": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
- "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/hast-util-parse-selector": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
- "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/hast-util-to-jsx-runtime": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz",
- "integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==",
- "license": "MIT",
- "dependencies": {
- "@types/estree": "^1.0.0",
- "@types/hast": "^3.0.0",
- "@types/unist": "^3.0.0",
- "comma-separated-tokens": "^2.0.0",
- "devlop": "^1.0.0",
- "estree-util-is-identifier-name": "^3.0.0",
- "hast-util-whitespace": "^3.0.0",
- "mdast-util-mdx-expression": "^2.0.0",
- "mdast-util-mdx-jsx": "^3.0.0",
- "mdast-util-mdxjs-esm": "^2.0.0",
- "property-information": "^6.0.0",
- "space-separated-tokens": "^2.0.0",
- "style-to-object": "^1.0.0",
- "unist-util-position": "^5.0.0",
- "vfile-message": "^4.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/hast-util-to-text": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
- "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "@types/unist": "^3.0.0",
- "hast-util-is-element": "^3.0.0",
- "unist-util-find-after": "^5.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/hast-util-whitespace": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
- "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/hastscript": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.0.tgz",
- "integrity": "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "comma-separated-tokens": "^2.0.0",
- "hast-util-parse-selector": "^4.0.0",
- "property-information": "^6.0.0",
- "space-separated-tokens": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/highlight.js": {
- "version": "11.11.1",
- "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
- "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=12.0.0"
- }
- },
- "node_modules/html-url-attributes": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
- "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==",
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/immutable": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz",
- "integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==",
- "devOptional": true,
- "license": "MIT"
- },
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.8.19"
- }
- },
- "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/is-alphabetical": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
- "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/is-alphanumerical": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
- "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
- "license": "MIT",
- "dependencies": {
- "is-alphabetical": "^2.0.0",
- "is-decimal": "^2.0.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/is-decimal": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
- "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-hexadecimal": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
- "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "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==",
- "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/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",
- "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
- "license": "MIT",
- "bin": {
- "jiti": "lib/jiti-cli.mjs"
- }
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "license": "MIT"
- },
- "node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/json-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true,
- "license": "MIT"
- },
- "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",
- "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json5": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "json5": "lib/cli.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/katex": {
- "version": "0.16.21",
- "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.21.tgz",
- "integrity": "sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==",
- "funding": [
- "https://opencollective.com/katex",
- "https://github.com/sponsors/katex"
- ],
- "license": "MIT",
- "dependencies": {
- "commander": "^8.3.0"
- },
- "bin": {
- "katex": "cli.js"
- }
- },
- "node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "json-buffer": "3.0.1"
- }
- },
- "node_modules/levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/lightningcss": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
- "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==",
- "license": "MPL-2.0",
- "dependencies": {
- "detect-libc": "^2.0.3"
- },
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- },
- "optionalDependencies": {
- "lightningcss-darwin-arm64": "1.29.2",
- "lightningcss-darwin-x64": "1.29.2",
- "lightningcss-freebsd-x64": "1.29.2",
- "lightningcss-linux-arm-gnueabihf": "1.29.2",
- "lightningcss-linux-arm64-gnu": "1.29.2",
- "lightningcss-linux-arm64-musl": "1.29.2",
- "lightningcss-linux-x64-gnu": "1.29.2",
- "lightningcss-linux-x64-musl": "1.29.2",
- "lightningcss-win32-arm64-msvc": "1.29.2",
- "lightningcss-win32-x64-msvc": "1.29.2"
- }
- },
- "node_modules/lightningcss-darwin-arm64": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz",
- "integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==",
- "cpu": [
- "arm64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-darwin-x64": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz",
- "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==",
- "cpu": [
- "x64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-freebsd-x64": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz",
- "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==",
- "cpu": [
- "x64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm-gnueabihf": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz",
- "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==",
- "cpu": [
- "arm"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-gnu": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz",
- "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-arm64-musl": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz",
- "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-gnu": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz",
- "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==",
- "cpu": [
- "x64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-linux-x64-musl": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz",
- "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==",
- "cpu": [
- "x64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-win32-arm64-msvc": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz",
- "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==",
- "cpu": [
- "arm64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lightningcss-win32-x64-msvc": {
- "version": "1.29.2",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz",
- "integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==",
- "cpu": [
- "x64"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^5.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/longest-streak": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
- "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "license": "MIT",
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
- "node_modules/lowlight": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz",
- "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "devlop": "^1.0.0",
- "highlight.js": "~11.11.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "yallist": "^3.0.2"
- }
- },
- "node_modules/markdown-table": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
- "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/mdast-util-find-and-replace": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
- "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0",
- "escape-string-regexp": "^5.0.0",
- "unist-util-is": "^6.0.0",
- "unist-util-visit-parents": "^6.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
- "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/mdast-util-from-markdown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
- "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0",
- "@types/unist": "^3.0.0",
- "decode-named-character-reference": "^1.0.0",
- "devlop": "^1.0.0",
- "mdast-util-to-string": "^4.0.0",
- "micromark": "^4.0.0",
- "micromark-util-decode-numeric-character-reference": "^2.0.0",
- "micromark-util-decode-string": "^2.0.0",
- "micromark-util-normalize-identifier": "^2.0.0",
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0",
- "unist-util-stringify-position": "^4.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-gfm": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz",
- "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==",
- "license": "MIT",
- "dependencies": {
- "mdast-util-from-markdown": "^2.0.0",
- "mdast-util-gfm-autolink-literal": "^2.0.0",
- "mdast-util-gfm-footnote": "^2.0.0",
- "mdast-util-gfm-strikethrough": "^2.0.0",
- "mdast-util-gfm-table": "^2.0.0",
- "mdast-util-gfm-task-list-item": "^2.0.0",
- "mdast-util-to-markdown": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-gfm-autolink-literal": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
- "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0",
- "ccount": "^2.0.0",
- "devlop": "^1.0.0",
- "mdast-util-find-and-replace": "^3.0.0",
- "micromark-util-character": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-gfm-footnote": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz",
- "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0",
- "devlop": "^1.1.0",
- "mdast-util-from-markdown": "^2.0.0",
- "mdast-util-to-markdown": "^2.0.0",
- "micromark-util-normalize-identifier": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-gfm-strikethrough": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
- "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0",
- "mdast-util-from-markdown": "^2.0.0",
- "mdast-util-to-markdown": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-gfm-table": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
- "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0",
- "devlop": "^1.0.0",
- "markdown-table": "^3.0.0",
- "mdast-util-from-markdown": "^2.0.0",
- "mdast-util-to-markdown": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-gfm-task-list-item": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
- "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0",
- "devlop": "^1.0.0",
- "mdast-util-from-markdown": "^2.0.0",
- "mdast-util-to-markdown": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-math": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz",
- "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "@types/mdast": "^4.0.0",
- "devlop": "^1.0.0",
- "longest-streak": "^3.0.0",
- "mdast-util-from-markdown": "^2.0.0",
- "mdast-util-to-markdown": "^2.1.0",
- "unist-util-remove-position": "^5.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-mdx-expression": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
- "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
- "license": "MIT",
- "dependencies": {
- "@types/estree-jsx": "^1.0.0",
- "@types/hast": "^3.0.0",
- "@types/mdast": "^4.0.0",
- "devlop": "^1.0.0",
- "mdast-util-from-markdown": "^2.0.0",
- "mdast-util-to-markdown": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-mdx-jsx": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
- "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
- "license": "MIT",
- "dependencies": {
- "@types/estree-jsx": "^1.0.0",
- "@types/hast": "^3.0.0",
- "@types/mdast": "^4.0.0",
- "@types/unist": "^3.0.0",
- "ccount": "^2.0.0",
- "devlop": "^1.1.0",
- "mdast-util-from-markdown": "^2.0.0",
- "mdast-util-to-markdown": "^2.0.0",
- "parse-entities": "^4.0.0",
- "stringify-entities": "^4.0.0",
- "unist-util-stringify-position": "^4.0.0",
- "vfile-message": "^4.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-mdxjs-esm": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
- "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
- "license": "MIT",
- "dependencies": {
- "@types/estree-jsx": "^1.0.0",
- "@types/hast": "^3.0.0",
- "@types/mdast": "^4.0.0",
- "devlop": "^1.0.0",
- "mdast-util-from-markdown": "^2.0.0",
- "mdast-util-to-markdown": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-newline-to-break": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz",
- "integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0",
- "mdast-util-find-and-replace": "^3.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-phrasing": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
- "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0",
- "unist-util-is": "^6.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-to-hast": {
- "version": "13.2.0",
- "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
- "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "@types/mdast": "^4.0.0",
- "@ungap/structured-clone": "^1.0.0",
- "devlop": "^1.0.0",
- "micromark-util-sanitize-uri": "^2.0.0",
- "trim-lines": "^3.0.0",
- "unist-util-position": "^5.0.0",
- "unist-util-visit": "^5.0.0",
- "vfile": "^6.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-to-markdown": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
- "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0",
- "@types/unist": "^3.0.0",
- "longest-streak": "^3.0.0",
- "mdast-util-phrasing": "^4.0.0",
- "mdast-util-to-string": "^4.0.0",
- "micromark-util-classify-character": "^2.0.0",
- "micromark-util-decode-string": "^2.0.0",
- "unist-util-visit": "^5.0.0",
- "zwitch": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/mdast-util-to-string": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
- "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/micromark": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz",
- "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "@types/debug": "^4.0.0",
- "debug": "^4.0.0",
- "decode-named-character-reference": "^1.0.0",
- "devlop": "^1.0.0",
- "micromark-core-commonmark": "^2.0.0",
- "micromark-factory-space": "^2.0.0",
- "micromark-util-character": "^2.0.0",
- "micromark-util-chunked": "^2.0.0",
- "micromark-util-combine-extensions": "^2.0.0",
- "micromark-util-decode-numeric-character-reference": "^2.0.0",
- "micromark-util-encode": "^2.0.0",
- "micromark-util-normalize-identifier": "^2.0.0",
- "micromark-util-resolve-all": "^2.0.0",
- "micromark-util-sanitize-uri": "^2.0.0",
- "micromark-util-subtokenize": "^2.0.0",
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- }
- },
- "node_modules/micromark-core-commonmark": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz",
- "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "decode-named-character-reference": "^1.0.0",
- "devlop": "^1.0.0",
- "micromark-factory-destination": "^2.0.0",
- "micromark-factory-label": "^2.0.0",
- "micromark-factory-space": "^2.0.0",
- "micromark-factory-title": "^2.0.0",
- "micromark-factory-whitespace": "^2.0.0",
- "micromark-util-character": "^2.0.0",
- "micromark-util-chunked": "^2.0.0",
- "micromark-util-classify-character": "^2.0.0",
- "micromark-util-html-tag-name": "^2.0.0",
- "micromark-util-normalize-identifier": "^2.0.0",
- "micromark-util-resolve-all": "^2.0.0",
- "micromark-util-subtokenize": "^2.0.0",
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- }
- },
- "node_modules/micromark-extension-gfm": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
- "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
- "license": "MIT",
- "dependencies": {
- "micromark-extension-gfm-autolink-literal": "^2.0.0",
- "micromark-extension-gfm-footnote": "^2.0.0",
- "micromark-extension-gfm-strikethrough": "^2.0.0",
- "micromark-extension-gfm-table": "^2.0.0",
- "micromark-extension-gfm-tagfilter": "^2.0.0",
- "micromark-extension-gfm-task-list-item": "^2.0.0",
- "micromark-util-combine-extensions": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/micromark-extension-gfm-autolink-literal": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
- "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
- "license": "MIT",
- "dependencies": {
- "micromark-util-character": "^2.0.0",
- "micromark-util-sanitize-uri": "^2.0.0",
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/micromark-extension-gfm-footnote": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
- "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
- "license": "MIT",
- "dependencies": {
- "devlop": "^1.0.0",
- "micromark-core-commonmark": "^2.0.0",
- "micromark-factory-space": "^2.0.0",
- "micromark-util-character": "^2.0.0",
- "micromark-util-normalize-identifier": "^2.0.0",
- "micromark-util-sanitize-uri": "^2.0.0",
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/micromark-extension-gfm-strikethrough": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
- "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
- "license": "MIT",
- "dependencies": {
- "devlop": "^1.0.0",
- "micromark-util-chunked": "^2.0.0",
- "micromark-util-classify-character": "^2.0.0",
- "micromark-util-resolve-all": "^2.0.0",
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/micromark-extension-gfm-table": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
- "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
- "license": "MIT",
- "dependencies": {
- "devlop": "^1.0.0",
- "micromark-factory-space": "^2.0.0",
- "micromark-util-character": "^2.0.0",
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/micromark-extension-gfm-tagfilter": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
- "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
- "license": "MIT",
- "dependencies": {
- "micromark-util-types": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/micromark-extension-gfm-task-list-item": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
- "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
- "license": "MIT",
- "dependencies": {
- "devlop": "^1.0.0",
- "micromark-factory-space": "^2.0.0",
- "micromark-util-character": "^2.0.0",
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/micromark-extension-math": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz",
- "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==",
- "license": "MIT",
- "dependencies": {
- "@types/katex": "^0.16.0",
- "devlop": "^1.0.0",
- "katex": "^0.16.0",
- "micromark-factory-space": "^2.0.0",
- "micromark-util-character": "^2.0.0",
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/micromark-factory-destination": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
- "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "micromark-util-character": "^2.0.0",
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- }
- },
- "node_modules/micromark-factory-label": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
- "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "devlop": "^1.0.0",
- "micromark-util-character": "^2.0.0",
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- }
- },
- "node_modules/micromark-factory-space": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
- "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "micromark-util-character": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- }
- },
- "node_modules/micromark-factory-title": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
- "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "micromark-factory-space": "^2.0.0",
- "micromark-util-character": "^2.0.0",
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- }
- },
- "node_modules/micromark-factory-whitespace": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
- "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "micromark-factory-space": "^2.0.0",
- "micromark-util-character": "^2.0.0",
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- }
- },
- "node_modules/micromark-util-character": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
- "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- }
- },
- "node_modules/micromark-util-chunked": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
- "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "micromark-util-symbol": "^2.0.0"
- }
- },
- "node_modules/micromark-util-classify-character": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
- "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "micromark-util-character": "^2.0.0",
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- }
- },
- "node_modules/micromark-util-combine-extensions": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
- "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "micromark-util-chunked": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- }
- },
- "node_modules/micromark-util-decode-numeric-character-reference": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
- "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "micromark-util-symbol": "^2.0.0"
- }
- },
- "node_modules/micromark-util-decode-string": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
- "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "decode-named-character-reference": "^1.0.0",
- "micromark-util-character": "^2.0.0",
- "micromark-util-decode-numeric-character-reference": "^2.0.0",
- "micromark-util-symbol": "^2.0.0"
- }
- },
- "node_modules/micromark-util-encode": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
- "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT"
- },
- "node_modules/micromark-util-html-tag-name": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
- "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT"
- },
- "node_modules/micromark-util-normalize-identifier": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
- "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "micromark-util-symbol": "^2.0.0"
- }
- },
- "node_modules/micromark-util-resolve-all": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
- "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "micromark-util-types": "^2.0.0"
- }
- },
- "node_modules/micromark-util-sanitize-uri": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
- "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "micromark-util-character": "^2.0.0",
- "micromark-util-encode": "^2.0.0",
- "micromark-util-symbol": "^2.0.0"
- }
- },
- "node_modules/micromark-util-subtokenize": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.4.tgz",
- "integrity": "sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "devlop": "^1.0.0",
- "micromark-util-chunked": "^2.0.0",
- "micromark-util-symbol": "^2.0.0",
- "micromark-util-types": "^2.0.0"
- }
- },
- "node_modules/micromark-util-symbol": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
- "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT"
- },
- "node_modules/micromark-util-types": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz",
- "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
- "license": "MIT"
- },
- "node_modules/micromatch": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
- "license": "MIT",
- "dependencies": {
- "braces": "^3.0.3",
- "picomatch": "^2.3.1"
- },
- "engines": {
- "node": ">=8.6"
- }
- },
- "node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/nanoid": {
- "version": "3.3.8",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
- "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/natural-compare": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/node-releases": {
- "version": "2.0.19",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
- "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
- "license": "MIT"
- },
- "node_modules/normalize-range": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
- "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "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/optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.5"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "callsites": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/parse-entities": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
- "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^2.0.0",
- "character-entities-legacy": "^3.0.0",
- "character-reference-invalid": "^2.0.0",
- "decode-named-character-reference": "^1.0.0",
- "is-alphanumerical": "^2.0.0",
- "is-decimal": "^2.0.0",
- "is-hexadecimal": "^2.0.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/parse-entities/node_modules/@types/unist": {
- "version": "2.0.11",
- "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
- "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
- "license": "MIT"
- },
- "node_modules/parse5": {
- "version": "7.2.1",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
- "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
- "license": "MIT",
- "dependencies": {
- "entities": "^4.5.0"
- },
- "funding": {
- "url": "https://github.com/inikulin/parse5?sponsor=1"
- }
- },
- "node_modules/path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-key": {
- "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/pdfjs-dist": {
- "version": "5.2.133",
- "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.2.133.tgz",
- "integrity": "sha512-abE6ZWDxztt+gGFzfm4bX2ggfxUk9wsDEoFzIJm9LozaY3JdXR7jyLK4Bjs+XLXplCduuWS1wGhPC4tgTn/kzg==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=20.16.0 || >=22.3.0"
- },
- "optionalDependencies": {
- "@napi-rs/canvas": "^0.1.67"
- }
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/postcss": {
- "version": "8.5.1",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
- "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.8",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/postcss-value-parser": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
- "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
- "license": "MIT"
- },
- "node_modules/prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/prettier": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
- "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "prettier": "bin/prettier.cjs"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/prettier/prettier?sponsor=1"
- }
- },
- "node_modules/prop-types": {
- "version": "15.8.1",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
- "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.13.1"
- }
- },
- "node_modules/property-information": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
- "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/queue-microtask": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/react": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
- "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-dom": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
- "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0",
- "scheduler": "^0.23.2"
- },
- "peerDependencies": {
- "react": "^18.3.1"
- }
- },
- "node_modules/react-dropzone": {
- "version": "14.3.8",
- "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz",
- "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==",
- "license": "MIT",
- "dependencies": {
- "attr-accept": "^2.2.4",
- "file-selector": "^2.1.0",
- "prop-types": "^15.8.1"
- },
- "engines": {
- "node": ">= 10.13"
- },
- "peerDependencies": {
- "react": ">= 16.8 || 18.0.0"
- }
- },
- "node_modules/react-hot-toast": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz",
- "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==",
- "license": "MIT",
- "dependencies": {
- "csstype": "^3.1.3",
- "goober": "^2.1.16"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "react": ">=16",
- "react-dom": ">=16"
- }
- },
- "node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "license": "MIT"
- },
- "node_modules/react-markdown": {
- "version": "9.0.3",
- "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.3.tgz",
- "integrity": "sha512-Yk7Z94dbgYTOrdk41Z74GoKA7rThnsbbqBTRYuxoe08qvfQ9tJVhmAKw6BJS/ZORG7kTy/s1QvYzSuaoBA1qfw==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "devlop": "^1.0.0",
- "hast-util-to-jsx-runtime": "^2.0.0",
- "html-url-attributes": "^3.0.0",
- "mdast-util-to-hast": "^13.0.0",
- "remark-parse": "^11.0.0",
- "remark-rehype": "^11.0.0",
- "unified": "^11.0.0",
- "unist-util-visit": "^5.0.0",
- "vfile": "^6.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- },
- "peerDependencies": {
- "@types/react": ">=18",
- "react": ">=18"
- }
- },
- "node_modules/react-refresh": {
- "version": "0.14.2",
- "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
- "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-router": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.5.tgz",
- "integrity": "sha512-8BUF+hZEU4/z/JD201yK6S+UYhsf58bzYIDq2NS1iGpwxSXDu7F+DeGSkIXMFBuHZB21FSiCzEcUb18cQNdRkA==",
- "license": "MIT",
- "dependencies": {
- "@types/cookie": "^0.6.0",
- "cookie": "^1.0.1",
- "set-cookie-parser": "^2.6.0",
- "turbo-stream": "2.4.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "react": ">=18",
- "react-dom": ">=18"
- },
- "peerDependenciesMeta": {
- "react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/rehype-highlight": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/rehype-highlight/-/rehype-highlight-7.0.2.tgz",
- "integrity": "sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "hast-util-to-text": "^4.0.0",
- "lowlight": "^3.0.0",
- "unist-util-visit": "^5.0.0",
- "vfile": "^6.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/rehype-katex": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz",
- "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "@types/katex": "^0.16.0",
- "hast-util-from-html-isomorphic": "^2.0.0",
- "hast-util-to-text": "^4.0.0",
- "katex": "^0.16.0",
- "unist-util-visit-parents": "^6.0.0",
- "vfile": "^6.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/remark-breaks": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz",
- "integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0",
- "mdast-util-newline-to-break": "^2.0.0",
- "unified": "^11.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/remark-gfm": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz",
- "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0",
- "mdast-util-gfm": "^3.0.0",
- "micromark-extension-gfm": "^3.0.0",
- "remark-parse": "^11.0.0",
- "remark-stringify": "^11.0.0",
- "unified": "^11.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/remark-math": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz",
- "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0",
- "mdast-util-math": "^3.0.0",
- "micromark-extension-math": "^3.0.0",
- "unified": "^11.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/remark-parse": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
- "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0",
- "mdast-util-from-markdown": "^2.0.0",
- "micromark-util-types": "^2.0.0",
- "unified": "^11.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/remark-rehype": {
- "version": "11.1.1",
- "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz",
- "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==",
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "@types/mdast": "^4.0.0",
- "mdast-util-to-hast": "^13.0.0",
- "unified": "^11.0.0",
- "vfile": "^6.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/remark-stringify": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
- "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
- "license": "MIT",
- "dependencies": {
- "@types/mdast": "^4.0.0",
- "mdast-util-to-markdown": "^2.0.0",
- "unified": "^11.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/reusify": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
- "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
- }
- },
- "node_modules/rollup": {
- "version": "4.34.2",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.2.tgz",
- "integrity": "sha512-sBDUoxZEaqLu9QeNalL8v3jw6WjPku4wfZGyTU7l7m1oC+rpRihXc/n/H+4148ZkGz5Xli8CHMns//fFGKvpIQ==",
- "license": "MIT",
- "dependencies": {
- "@types/estree": "1.0.6"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.34.2",
- "@rollup/rollup-android-arm64": "4.34.2",
- "@rollup/rollup-darwin-arm64": "4.34.2",
- "@rollup/rollup-darwin-x64": "4.34.2",
- "@rollup/rollup-freebsd-arm64": "4.34.2",
- "@rollup/rollup-freebsd-x64": "4.34.2",
- "@rollup/rollup-linux-arm-gnueabihf": "4.34.2",
- "@rollup/rollup-linux-arm-musleabihf": "4.34.2",
- "@rollup/rollup-linux-arm64-gnu": "4.34.2",
- "@rollup/rollup-linux-arm64-musl": "4.34.2",
- "@rollup/rollup-linux-loongarch64-gnu": "4.34.2",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.34.2",
- "@rollup/rollup-linux-riscv64-gnu": "4.34.2",
- "@rollup/rollup-linux-s390x-gnu": "4.34.2",
- "@rollup/rollup-linux-x64-gnu": "4.34.2",
- "@rollup/rollup-linux-x64-musl": "4.34.2",
- "@rollup/rollup-win32-arm64-msvc": "4.34.2",
- "@rollup/rollup-win32-ia32-msvc": "4.34.2",
- "@rollup/rollup-win32-x64-msvc": "4.34.2",
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/run-parallel": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
- "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "queue-microtask": "^1.2.2"
- }
- },
- "node_modules/rxjs": {
- "version": "7.8.1",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
- "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
- "devOptional": true,
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.1.0"
- }
- },
- "node_modules/sass-embedded": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.83.4.tgz",
- "integrity": "sha512-Hf2burRA/y5PGxsg6jB9UpoK/xZ6g/pgrkOcdl6j+rRg1Zj8XhGKZ1MTysZGtTPUUmiiErqzkP5+Kzp95yv9GQ==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "@bufbuild/protobuf": "^2.0.0",
- "buffer-builder": "^0.2.0",
- "colorjs.io": "^0.5.0",
- "immutable": "^5.0.2",
- "rxjs": "^7.4.0",
- "supports-color": "^8.1.1",
- "sync-child-process": "^1.0.2",
- "varint": "^6.0.0"
- },
- "bin": {
- "sass": "dist/bin/sass.js"
- },
- "engines": {
- "node": ">=16.0.0"
- },
- "optionalDependencies": {
- "sass-embedded-android-arm": "1.83.4",
- "sass-embedded-android-arm64": "1.83.4",
- "sass-embedded-android-ia32": "1.83.4",
- "sass-embedded-android-riscv64": "1.83.4",
- "sass-embedded-android-x64": "1.83.4",
- "sass-embedded-darwin-arm64": "1.83.4",
- "sass-embedded-darwin-x64": "1.83.4",
- "sass-embedded-linux-arm": "1.83.4",
- "sass-embedded-linux-arm64": "1.83.4",
- "sass-embedded-linux-ia32": "1.83.4",
- "sass-embedded-linux-musl-arm": "1.83.4",
- "sass-embedded-linux-musl-arm64": "1.83.4",
- "sass-embedded-linux-musl-ia32": "1.83.4",
- "sass-embedded-linux-musl-riscv64": "1.83.4",
- "sass-embedded-linux-musl-x64": "1.83.4",
- "sass-embedded-linux-riscv64": "1.83.4",
- "sass-embedded-linux-x64": "1.83.4",
- "sass-embedded-win32-arm64": "1.83.4",
- "sass-embedded-win32-ia32": "1.83.4",
- "sass-embedded-win32-x64": "1.83.4"
- }
- },
- "node_modules/sass-embedded-android-arm": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.83.4.tgz",
- "integrity": "sha512-9Z4pJAOgEkXa3VDY/o+U6l5XvV0mZTJcSl0l/mSPHihjAHSpLYnOW6+KOWeM8dxqrsqTYcd6COzhanI/a++5Gw==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-android-arm64": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.83.4.tgz",
- "integrity": "sha512-tgX4FzmbVqnQmD67ZxQDvI+qFNABrboOQgwsG05E5bA/US42zGajW9AxpECJYiMXVOHmg+d81ICbjb0fsVHskw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-android-ia32": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.83.4.tgz",
- "integrity": "sha512-RsFOziFqPcfZXdFRULC4Ayzy9aK6R6FwQ411broCjlOBX+b0gurjRadkue3cfUEUR5mmy0KeCbp7zVKPLTK+5Q==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-android-riscv64": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.83.4.tgz",
- "integrity": "sha512-EHwh0nmQarBBrMRU928eTZkFGx19k/XW2YwbPR4gBVdWLkbTgCA5aGe8hTE6/1zStyx++3nDGvTZ78+b/VvvLg==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-android-x64": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.83.4.tgz",
- "integrity": "sha512-0PgQNuPWYy1jEOEPDVsV89KfqOsMLIp9CSbjBY7jRcwRhyVAcigqrUG6bDeNtojHUYKA1kU+Eh/85WxOHUOgBw==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-darwin-arm64": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.83.4.tgz",
- "integrity": "sha512-rp2ywymWc3nymnSnAFG5R/8hvxWCsuhK3wOnD10IDlmNB7o4rzKby1c+2ZfpQGowlYGWsWWTgz8FW2qzmZsQRw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-darwin-x64": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.83.4.tgz",
- "integrity": "sha512-kLkN2lXz9PCgGfDS8Ev5YVcl/V2173L6379en/CaFuJJi7WiyPgBymW7hOmfCt4uO4R1y7CP2Uc08DRtZsBlAA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-linux-arm": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.83.4.tgz",
- "integrity": "sha512-nL90ryxX2lNmFucr9jYUyHHx21AoAgdCL1O5Ltx2rKg2xTdytAGHYo2MT5S0LIeKLa/yKP/hjuSvrbICYNDvtA==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-linux-arm64": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.83.4.tgz",
- "integrity": "sha512-E0zjsZX2HgESwyqw31EHtI39DKa7RgK7nvIhIRco1d0QEw227WnoR9pjH3M/ZQy4gQj3GKilOFHM5Krs/omeIA==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-linux-ia32": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.83.4.tgz",
- "integrity": "sha512-ew5HpchSzgAYbQoriRh8QhlWn5Kw2nQ2jHoV9YLwGKe3fwwOWA0KDedssvDv7FWnY/FCqXyymhLd6Bxae4Xquw==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-linux-musl-arm": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.83.4.tgz",
- "integrity": "sha512-0RrJRwMrmm+gG0VOB5b5Cjs7Sd+lhqpQJa6EJNEaZHljJokEfpE5GejZsGMRMIQLxEvVphZnnxl6sonCGFE/QQ==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-linux-musl-arm64": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.83.4.tgz",
- "integrity": "sha512-IzMgalf6MZOxgp4AVCgsaWAFDP/IVWOrgVXxkyhw29fyAEoSWBJH4k87wyPhEtxSuzVHLxKNbc8k3UzdWmlBFg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-linux-musl-ia32": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.83.4.tgz",
- "integrity": "sha512-LLb4lYbcxPzX4UaJymYXC+WwokxUlfTJEFUv5VF0OTuSsHAGNRs/rslPtzVBTvMeG9TtlOQDhku1F7G6iaDotA==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-linux-musl-riscv64": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.83.4.tgz",
- "integrity": "sha512-zoKlPzD5Z13HKin1UGR74QkEy+kZEk2AkGX5RelRG494mi+IWwRuWCppXIovor9+BQb9eDWPYPoMVahwN5F7VA==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-linux-musl-x64": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.83.4.tgz",
- "integrity": "sha512-hB8+/PYhfEf2zTIcidO5Bpof9trK6WJjZ4T8g2MrxQh8REVtdPcgIkoxczRynqybf9+fbqbUwzXtiUao2GV+vQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-linux-riscv64": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.83.4.tgz",
- "integrity": "sha512-83fL4n+oeDJ0Y4KjASmZ9jHS1Vl9ESVQYHMhJE0i4xDi/P3BNarm2rsKljq/QtrwGpbqwn8ujzOu7DsNCMDSHA==",
- "cpu": [
- "riscv64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-linux-x64": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.83.4.tgz",
- "integrity": "sha512-NlnGdvCmTD5PK+LKXlK3sAuxOgbRIEoZfnHvxd157imCm/s2SYF/R28D0DAAjEViyI8DovIWghgbcqwuertXsA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-win32-arm64": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.83.4.tgz",
- "integrity": "sha512-J2BFKrEaeSrVazU2qTjyQdAk+MvbzJeTuCET0uAJEXSKtvQ3AzxvzndS7LqkDPbF32eXAHLw8GVpwcBwKbB3Uw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-win32-ia32": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.83.4.tgz",
- "integrity": "sha512-uPAe9T/5sANFhJS5dcfAOhOJy8/l2TRYG4r+UO3Wp4yhqbN7bggPvY9c7zMYS0OC8tU/bCvfYUDFHYMCl91FgA==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded-win32-x64": {
- "version": "1.83.4",
- "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.83.4.tgz",
- "integrity": "sha512-C9fkDY0jKITdJFij4UbfPFswxoXN9O/Dr79v17fJnstVwtUojzVJWKHUXvF0Zg2LIR7TCc4ju3adejKFxj7ueA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/sass-embedded/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
- "node_modules/scheduler": {
- "version": "0.23.2",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
- "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0"
- }
- },
- "node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "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==",
- "license": "MIT"
- },
- "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"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "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"
- }
- },
- "node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "license": "BSD-3-Clause",
- "optional": true,
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-support": {
- "version": "0.5.21",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
- "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
- "license": "MIT",
- "optional": true,
- "peer": true,
- "dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- }
- },
- "node_modules/space-separated-tokens": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
- "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/stringify-entities": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
- "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
- "license": "MIT",
- "dependencies": {
- "character-entities-html4": "^2.0.0",
- "character-entities-legacy": "^3.0.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/style-to-object": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz",
- "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==",
- "license": "MIT",
- "dependencies": {
- "inline-style-parser": "0.2.4"
- }
- },
- "node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/sync-child-process": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz",
- "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "sync-message-port": "^1.0.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/sync-message-port": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz",
- "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==",
- "devOptional": true,
- "license": "MIT",
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/tailwindcss": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.1.tgz",
- "integrity": "sha512-QNbdmeS979Efzim2g/bEvfuh+fTcIdp1y7gA+sb6OYSW74rt7Cr7M78AKdf6HqWT3d5AiTb7SwTT3sLQxr4/qw==",
- "license": "MIT"
- },
- "node_modules/tapable": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
- "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/terser": {
- "version": "5.39.1",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.1.tgz",
- "integrity": "sha512-Mm6+uad0ZuDtcV8/4uOZQDQ8RuiC5Pu+iZRedJtF7yA/27sPL7d++In/AJKpWZlU3SYMPPkVfwetn6sgZ66pUA==",
- "license": "BSD-2-Clause",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@jridgewell/source-map": "^0.3.3",
- "acorn": "^8.8.2",
- "commander": "^2.20.0",
- "source-map-support": "~0.5.20"
- },
- "bin": {
- "terser": "bin/terser"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/terser/node_modules/commander": {
- "version": "2.20.3",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
- "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
- "license": "MIT",
- "optional": true,
- "peer": true
- },
- "node_modules/textlinestream": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/textlinestream/-/textlinestream-1.1.1.tgz",
- "integrity": "sha512-iBHbi7BQxrFmwZUQJsT0SjNzlLLsXhvW/kg7EyOMVMBIrlnj/qYofwo1LVLZi+3GbUEo96Iu2eqToI2+lZoAEQ==",
- "license": "MIT"
- },
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
- "engines": {
- "node": ">=8.0"
- }
- },
- "node_modules/trim-lines": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
- "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/trough": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
- "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/ts-api-utils": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz",
- "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18.12"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4"
- }
- },
- "node_modules/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD"
- },
- "node_modules/turbo-stream": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
- "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
- "license": "ISC"
- },
- "node_modules/type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/typescript": {
- "version": "5.6.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
- "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
- "dev": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/typescript-eslint": {
- "version": "8.23.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.23.0.tgz",
- "integrity": "sha512-/LBRo3HrXr5LxmrdYSOCvoAMm7p2jNizNfbIpCgvG4HMsnoprRUOce/+8VJ9BDYWW68rqIENE/haVLWPeFZBVQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/eslint-plugin": "8.23.0",
- "@typescript-eslint/parser": "8.23.0",
- "@typescript-eslint/utils": "8.23.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 <5.8.0"
- }
- },
- "node_modules/undici-types": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
- "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
- "devOptional": true,
- "license": "MIT"
- },
- "node_modules/unified": {
- "version": "11.0.5",
- "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
- "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^3.0.0",
- "bail": "^2.0.0",
- "devlop": "^1.0.0",
- "extend": "^3.0.0",
- "is-plain-obj": "^4.0.0",
- "trough": "^2.0.0",
- "vfile": "^6.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/unist-util-find-after": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz",
- "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^3.0.0",
- "unist-util-is": "^6.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/unist-util-is": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
- "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^3.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/unist-util-position": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
- "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^3.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/unist-util-remove-position": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz",
- "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^3.0.0",
- "unist-util-visit": "^5.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/unist-util-stringify-position": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
- "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^3.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/unist-util-visit": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
- "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^3.0.0",
- "unist-util-is": "^6.0.0",
- "unist-util-visit-parents": "^6.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/unist-util-visit-parents": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
- "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^3.0.0",
- "unist-util-is": "^6.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/update-browserslist-db": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
- "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- },
- "bin": {
- "update-browserslist-db": "cli.js"
- },
- "peerDependencies": {
- "browserslist": ">= 4.21.0"
- }
- },
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "punycode": "^2.1.0"
- }
- },
- "node_modules/varint": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
- "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
- "devOptional": true,
- "license": "MIT"
- },
- "node_modules/vfile": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
- "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^3.0.0",
- "vfile-message": "^4.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/vfile-location": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz",
- "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^3.0.0",
- "vfile": "^6.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/vfile-message": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
- "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
- "license": "MIT",
- "dependencies": {
- "@types/unist": "^3.0.0",
- "unist-util-stringify-position": "^4.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
- "node_modules/vite": {
- "version": "6.0.11",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz",
- "integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==",
- "license": "MIT",
- "dependencies": {
- "esbuild": "^0.24.2",
- "postcss": "^8.4.49",
- "rollup": "^4.23.0"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
- "jiti": ">=1.21.0",
- "less": "*",
- "lightningcss": "^1.21.0",
- "sass": "*",
- "sass-embedded": "*",
- "stylus": "*",
- "sugarss": "*",
- "terser": "^5.16.0",
- "tsx": "^4.8.1",
- "yaml": "^2.4.2"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "jiti": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "sass-embedded": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- },
- "tsx": {
- "optional": true
- },
- "yaml": {
- "optional": true
- }
- }
- },
- "node_modules/vite-plugin-singlefile": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-2.1.0.tgz",
- "integrity": "sha512-7tJo+UgZABlKpY/nubth/wxJ4+pUGREPnEwNOknxwl2MM0zTvF14KTU4Ln1lc140gjLLV5mjDrvuoquU7OZqCg==",
- "license": "MIT",
- "dependencies": {
- "micromatch": "^4.0.8"
- },
- "engines": {
- "node": ">18.0.0"
- },
- "peerDependencies": {
- "rollup": "^4.28.1",
- "vite": "^5.4.11 || ^6.0.0"
- }
- },
- "node_modules/web-namespaces": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
- "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- },
- "node_modules/which": {
- "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"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/word-wrap": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
- "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/yaml": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
- "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
- "license": "ISC",
- "optional": true,
- "peer": true,
- "bin": {
- "yaml": "bin.mjs"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/zwitch": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
- "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
- }
- }
- }
+ "name": "webui",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "webui",
+ "version": "1.0.0",
+ "dependencies": {
+ "highlight.js": "^11.11.1",
+ "mode-watcher": "^1.1.0",
+ "pdfjs-dist": "^5.4.54",
+ "rehype-highlight": "^7.0.2",
+ "rehype-stringify": "^10.0.1",
+ "remark": "^15.0.1",
+ "remark-breaks": "^4.0.0",
+ "remark-gfm": "^4.0.1",
+ "remark-html": "^16.0.1",
+ "remark-rehype": "^11.1.2",
+ "svelte-sonner": "^1.0.5",
+ "unist-util-visit": "^5.0.0"
+ },
+ "devDependencies": {
+ "@chromatic-com/storybook": "^4.0.1",
+ "@eslint/compat": "^1.2.5",
+ "@eslint/js": "^9.18.0",
+ "@internationalized/date": "^3.8.2",
+ "@lucide/svelte": "^0.515.0",
+ "@playwright/test": "^1.49.1",
+ "@storybook/addon-a11y": "^9.0.17",
+ "@storybook/addon-docs": "^9.0.17",
+ "@storybook/addon-svelte-csf": "^5.0.7",
+ "@storybook/addon-vitest": "^9.0.17",
+ "@storybook/sveltekit": "^9.0.17",
+ "@sveltejs/adapter-static": "^3.0.8",
+ "@sveltejs/kit": "^2.22.0",
+ "@sveltejs/vite-plugin-svelte": "^6.0.0",
+ "@tailwindcss/forms": "^0.5.9",
+ "@tailwindcss/typography": "^0.5.15",
+ "@tailwindcss/vite": "^4.0.0",
+ "@types/node": "^22",
+ "@vitest/browser": "^3.2.3",
+ "bits-ui": "^2.8.11",
+ "clsx": "^2.1.1",
+ "dexie": "^4.0.11",
+ "eslint": "^9.18.0",
+ "eslint-config-prettier": "^10.0.1",
+ "eslint-plugin-storybook": "^9.0.17",
+ "eslint-plugin-svelte": "^3.0.0",
+ "fflate": "^0.8.2",
+ "globals": "^16.0.0",
+ "mdsvex": "^0.12.3",
+ "playwright": "^1.53.0",
+ "prettier": "^3.4.2",
+ "prettier-plugin-svelte": "^3.3.3",
+ "prettier-plugin-tailwindcss": "^0.6.11",
+ "rehype-katex": "^7.0.1",
+ "remark-math": "^6.0.0",
+ "storybook": "^9.0.17",
+ "svelte": "^5.0.0",
+ "svelte-check": "^4.0.0",
+ "tailwind-merge": "^3.3.1",
+ "tailwind-variants": "^1.0.0",
+ "tailwindcss": "^4.0.0",
+ "tw-animate-css": "^1.3.5",
+ "typescript": "^5.0.0",
+ "typescript-eslint": "^8.20.0",
+ "uuid": "^13.0.0",
+ "vite": "^7.0.4",
+ "vite-plugin-devtools-json": "^0.2.0",
+ "vitest": "^3.2.3",
+ "vitest-browser-svelte": "^0.1.0"
+ }
+ },
+ "node_modules/@adobe/css-tools": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz",
+ "integrity": "sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "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/runtime": {
+ "version": "7.27.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
+ "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@chromatic-com/storybook": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-4.0.1.tgz",
+ "integrity": "sha512-GQXe5lyZl3yLewLJQyFXEpOp2h+mfN2bPrzYaOFNCJjO4Js9deKbRHTOSaiP2FRwZqDLdQwy2+SEGeXPZ94yYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@neoconfetti/react": "^1.0.0",
+ "chromatic": "^12.0.0",
+ "filesize": "^10.0.12",
+ "jsonfile": "^6.1.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0",
+ "yarn": ">=1.22.18"
+ },
+ "peerDependencies": {
+ "storybook": "^0.0.0-0 || ^9.0.0 || ^9.1.0-0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
+ "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz",
+ "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz",
+ "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz",
+ "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz",
+ "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz",
+ "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz",
+ "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz",
+ "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz",
+ "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz",
+ "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz",
+ "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz",
+ "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz",
+ "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz",
+ "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz",
+ "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz",
+ "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz",
+ "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz",
+ "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz",
+ "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz",
+ "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz",
+ "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz",
+ "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz",
+ "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz",
+ "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz",
+ "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz",
+ "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/compat": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.3.1.tgz",
+ "integrity": "sha512-k8MHony59I5EPic6EQTCNOuPoVBnoYXkP+20xvwFjN7t0qI3ImyvyBgg+hIVPwC8JaxVjjUZld+cLfBLFDLucg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "peerDependencies": {
+ "eslint": "^8.40 || 9"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
+ "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.6",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
+ "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
+ "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
+ "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.31.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz",
+ "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
+ "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
+ "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.15.2",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz",
+ "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz",
+ "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.2",
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.6",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+ "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+ "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@internationalized/date": {
+ "version": "3.8.2",
+ "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.8.2.tgz",
+ "integrity": "sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "node_modules/@isaacs/fs-minipass": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
+ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
+ "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
+ "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@lucide/svelte": {
+ "version": "0.515.0",
+ "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-0.515.0.tgz",
+ "integrity": "sha512-CEAyqcZmNBfYzVgaRmK2RFJP5tnbXxekRyDk0XX/eZQRfsJmkDvmQwXNX8C869BgNeryzmrRyjHhUL6g9ZOHNA==",
+ "dev": true,
+ "license": "ISC",
+ "peerDependencies": {
+ "svelte": "^5"
+ }
+ },
+ "node_modules/@mdx-js/react": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz",
+ "integrity": "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdx": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16",
+ "react": ">=16"
+ }
+ },
+ "node_modules/@napi-rs/canvas": {
+ "version": "0.1.76",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.76.tgz",
+ "integrity": "sha512-YIk5okeNN53GzjvWmAyCQFE9xrLeQXzYpudX4TiLvqaz9SqXgIgxIuKPe4DKyB5nccsQMIev7JGKTzZaN5rFdw==",
+ "license": "MIT",
+ "optional": true,
+ "workspaces": [
+ "e2e/*"
+ ],
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@napi-rs/canvas-android-arm64": "0.1.76",
+ "@napi-rs/canvas-darwin-arm64": "0.1.76",
+ "@napi-rs/canvas-darwin-x64": "0.1.76",
+ "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.76",
+ "@napi-rs/canvas-linux-arm64-gnu": "0.1.76",
+ "@napi-rs/canvas-linux-arm64-musl": "0.1.76",
+ "@napi-rs/canvas-linux-riscv64-gnu": "0.1.76",
+ "@napi-rs/canvas-linux-x64-gnu": "0.1.76",
+ "@napi-rs/canvas-linux-x64-musl": "0.1.76",
+ "@napi-rs/canvas-win32-x64-msvc": "0.1.76"
+ }
+ },
+ "node_modules/@napi-rs/canvas-android-arm64": {
+ "version": "0.1.76",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.76.tgz",
+ "integrity": "sha512-7EAfkLBQo2QoEzpHdInFbfEUYTXsiO2hvtFo1D9zfTzcQM8n5piZdOpJ3EIkmpe8yLoSV8HLyUQtq4bv11x6Tg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-darwin-arm64": {
+ "version": "0.1.76",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.76.tgz",
+ "integrity": "sha512-Cs8WRMzaWSJWeWY8tvnCe+TuduHUbB0xFhZ0FmOrNy2prPxT4A6aU3FQu8hR9XJw8kKZ7v902wzaDmy9SdhG8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-darwin-x64": {
+ "version": "0.1.76",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.76.tgz",
+ "integrity": "sha512-ya+T6gV9XAq7YAnMa2fKhWXAuRR5cpRny2IoHacoMxgtOARnUkJO/k3hIb52FtMoq7UxLi5+IFGVHU6ZiMu4Ag==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
+ "version": "0.1.76",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.76.tgz",
+ "integrity": "sha512-fgnPb+FKVuixACvkHGldJqYXExORBwvqGgL0K80uE6SGH2t0UKD2auHw2CtBy14DUzfg82PkupO2ix2w7kB+Xw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-arm64-gnu": {
+ "version": "0.1.76",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.76.tgz",
+ "integrity": "sha512-r8OxIenvBPOa4I014k1ZWTCz2dB0ZTsxMP7+ovMOKO7jkl1Z+YZo2OTAqxArpMhN0wdEeI3Lw9zUcn2HgwEgDA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-arm64-musl": {
+ "version": "0.1.76",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.76.tgz",
+ "integrity": "sha512-smxwzKfHYaOYG7QXUuDPrFEC7WqjL3Lx4AM6mk8/FxDAS+8o0eoZJwSu+zXsaBLimEQUozEYgEGtJ2JJ0RdL4A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
+ "version": "0.1.76",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.76.tgz",
+ "integrity": "sha512-G2PsFwsP+r4syEoNLStV3n1wtNAClwf8s/qB57bexG08R4f4WaiBd+x+d4iYS0Y5o90YIEm8/ewZn4bLIa0wNQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-x64-gnu": {
+ "version": "0.1.76",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.76.tgz",
+ "integrity": "sha512-SNK+vgge4DnuONYdYE3Y09LivGgUiUPQDU+PdGNZJIzIi0hRDLcA59eag8LGeQfPmJW84c1aZD04voihybKFog==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-linux-x64-musl": {
+ "version": "0.1.76",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.76.tgz",
+ "integrity": "sha512-tWHLBI9iVoR1NsfpHz1MGERTkqcca8akbH/CzX6JQUNC+lJOeYYXeRuK8hKqMIg1LI+4QOMAtHNVeZu8NvjEug==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@napi-rs/canvas-win32-x64-msvc": {
+ "version": "0.1.76",
+ "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.76.tgz",
+ "integrity": "sha512-ifM5HOGw2hP5QLQzCB41Riw3Pq5yKAAjZpn+lJC0sYBmyS2s/Kq6KpTOKxf0CuptkI1wMcRcYQfhLRdeWiYvIg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@neoconfetti/react": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@neoconfetti/react/-/react-1.0.0.tgz",
+ "integrity": "sha512-klcSooChXXOzIm+SE5IISIAn3bYzYfPjbX7D7HoqZL84oAfgREeSg5vSIaSFH+DaGzzvImTyWe1OyrJ67vik4A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@playwright/test": {
+ "version": "1.54.1",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz",
+ "integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.54.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@polka/url": {
+ "version": "1.0.0-next.29",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
+ "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz",
+ "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz",
+ "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz",
+ "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz",
+ "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz",
+ "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz",
+ "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz",
+ "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz",
+ "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz",
+ "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz",
+ "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz",
+ "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz",
+ "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz",
+ "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz",
+ "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz",
+ "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz",
+ "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz",
+ "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz",
+ "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz",
+ "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz",
+ "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
+ "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@storybook/addon-a11y": {
+ "version": "9.0.17",
+ "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-9.0.17.tgz",
+ "integrity": "sha512-9cXNK3q/atx3hwJAt9HkJbd9vUxCXfKKiNNuSACbf8h9/j6u3jktulKOf6Xjc3B8lwn6ZpdK/x1HHZN2kTqsvg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@storybook/global": "^5.0.0",
+ "axe-core": "^4.2.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ },
+ "peerDependencies": {
+ "storybook": "^9.0.17"
+ }
+ },
+ "node_modules/@storybook/addon-docs": {
+ "version": "9.0.17",
+ "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-9.0.17.tgz",
+ "integrity": "sha512-LOX/kKgQGnyulrqZHsvf77+ZoH/nSUaplGr5hvZglW/U6ak6fO9seJyXAzVKEnC6p+F8n02kFBZbi3s+znQhSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@mdx-js/react": "^3.0.0",
+ "@storybook/csf-plugin": "9.0.17",
+ "@storybook/icons": "^1.2.12",
+ "@storybook/react-dom-shim": "9.0.17",
+ "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"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ },
+ "peerDependencies": {
+ "storybook": "^9.0.17"
+ }
+ },
+ "node_modules/@storybook/addon-svelte-csf": {
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/@storybook/addon-svelte-csf/-/addon-svelte-csf-5.0.7.tgz",
+ "integrity": "sha512-6Zmy5HjOlrrG6OoKRTGDr9LR6zRK4/Sa7raFzQRKHGASgMlfKsMdNTNO0sxnMUWCu2JMS6HsuoLtB3Ma8SlYtg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@storybook/csf": "^0.1.13",
+ "dedent": "^1.5.3",
+ "es-toolkit": "^1.26.1",
+ "esrap": "^1.2.2",
+ "magic-string": "^0.30.12",
+ "svelte-ast-print": "^0.4.0",
+ "zimmerframe": "^1.1.2"
+ },
+ "peerDependencies": {
+ "@storybook/svelte": "^0.0.0-0 || ^8.2.0 || ^9.0.0 || ^9.1.0-0",
+ "@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0 || ^6.0.0",
+ "storybook": "^0.0.0-0 || ^8.2.0 || ^9.0.0 || ^9.1.0-0",
+ "svelte": "^5.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@storybook/addon-vitest": {
+ "version": "9.0.17",
+ "resolved": "https://registry.npmjs.org/@storybook/addon-vitest/-/addon-vitest-9.0.17.tgz",
+ "integrity": "sha512-eogqcGbACR1sTedBSE2SP/4QV1ruicHYEhYjBtoPIjvYgymN1g5KSuQNysLx4f0SvAzczrcNjX2WVVLX2DVyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@storybook/global": "^5.0.0",
+ "@storybook/icons": "^1.4.0",
+ "prompts": "^2.4.0",
+ "ts-dedent": "^2.2.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ },
+ "peerDependencies": {
+ "@vitest/browser": "^3.0.0",
+ "@vitest/runner": "^3.0.0",
+ "storybook": "^9.0.17",
+ "vitest": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/runner": {
+ "optional": true
+ },
+ "vitest": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@storybook/builder-vite": {
+ "version": "9.0.17",
+ "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-9.0.17.tgz",
+ "integrity": "sha512-lyuvgGhb0NaVk1tdB4xwzky6+YXQfxlxfNQqENYZ9uYQZdPfErMa4ZTXVQTV+CQHAa2NL+p/dG2JPAeu39e9UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@storybook/csf-plugin": "9.0.17",
+ "ts-dedent": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ },
+ "peerDependencies": {
+ "storybook": "^9.0.17",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@storybook/csf": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.13.tgz",
+ "integrity": "sha512-7xOOwCLGB3ebM87eemep89MYRFTko+D8qE7EdAAq74lgdqRR5cOUtYWJLjO2dLtP94nqoOdHJo6MdLLKzg412Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^2.19.0"
+ }
+ },
+ "node_modules/@storybook/csf-plugin": {
+ "version": "9.0.17",
+ "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-9.0.17.tgz",
+ "integrity": "sha512-6Q4eo1ObrLlsnB6bIt6T8+45XAb4to2pQGNrI7QPkLQRLrZinrJcNbLY7AGkyIoCOEsEbq08n09/nClQUbu8HA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "unplugin": "^1.3.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ },
+ "peerDependencies": {
+ "storybook": "^9.0.17"
+ }
+ },
+ "node_modules/@storybook/global": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz",
+ "integrity": "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@storybook/icons": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-1.4.0.tgz",
+ "integrity": "sha512-Td73IeJxOyalzvjQL+JXx72jlIYHgs+REaHiREOqfpo3A2AYYG71AUbcv+lg7mEDIweKVCxsMQ0UKo634c8XeA==",
+ "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"
+ }
+ },
+ "node_modules/@storybook/react-dom-shim": {
+ "version": "9.0.17",
+ "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-9.0.17.tgz",
+ "integrity": "sha512-ak/x/m6MDDxdE6rCDymTltaiQF3oiKrPHSwfM+YPgQR6MVmzTTs4+qaPfeev7FZEHq23IkfDMTmSTTJtX7Vs9A==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ },
+ "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",
+ "storybook": "^9.0.17"
+ }
+ },
+ "node_modules/@storybook/svelte": {
+ "version": "9.0.17",
+ "resolved": "https://registry.npmjs.org/@storybook/svelte/-/svelte-9.0.17.tgz",
+ "integrity": "sha512-RwOswdq7S3+ZOuoM/oRrcmlsKdjcd/3wMHbuirzYoAhdwsjubSuRepMV64O9RnlXd3x7rZw4fXpq1M/SVo5XiQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ts-dedent": "^2.0.0",
+ "type-fest": "~2.19"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ },
+ "peerDependencies": {
+ "storybook": "^9.0.17",
+ "svelte": "^5.0.0"
+ }
+ },
+ "node_modules/@storybook/sveltekit": {
+ "version": "9.0.17",
+ "resolved": "https://registry.npmjs.org/@storybook/sveltekit/-/sveltekit-9.0.17.tgz",
+ "integrity": "sha512-CUOATuW5Qk3SjNvmjH+wyx2GCsMF1cvw3gwkujV9kehPebzV20NhgHpbzSoepvwF7+Bj6jl8V6UxiMWk0jJFmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@storybook/builder-vite": "9.0.17",
+ "@storybook/svelte": "9.0.17",
+ "@storybook/svelte-vite": "9.0.17"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ },
+ "peerDependencies": {
+ "storybook": "^9.0.17",
+ "svelte": "^5.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@storybook/sveltekit/node_modules/@storybook/svelte-vite": {
+ "version": "9.0.17",
+ "resolved": "https://registry.npmjs.org/@storybook/svelte-vite/-/svelte-vite-9.0.17.tgz",
+ "integrity": "sha512-fRIxOZy9IRI6BfL1LgFn+B+IckGOlT1SstD01y9ddO4pVKWih/l+vb44bnZs+Z0faJZbrG/LgfnXTOPj052Z8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@storybook/builder-vite": "9.0.17",
+ "@storybook/svelte": "9.0.17",
+ "magic-string": "^0.30.0",
+ "svelte2tsx": "^0.7.35",
+ "typescript": "^4.9.4 || ^5.0.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ },
+ "peerDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
+ "storybook": "^9.0.17",
+ "svelte": "^5.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@storybook/sveltekit/node_modules/@sveltejs/vite-plugin-svelte": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.1.tgz",
+ "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1",
+ "debug": "^4.4.1",
+ "deepmerge": "^4.3.1",
+ "kleur": "^4.1.5",
+ "magic-string": "^0.30.17",
+ "vitefu": "^1.0.6"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22"
+ },
+ "peerDependencies": {
+ "svelte": "^5.0.0",
+ "vite": "^6.0.0"
+ }
+ },
+ "node_modules/@storybook/sveltekit/node_modules/@sveltejs/vite-plugin-svelte/node_modules/@sveltejs/vite-plugin-svelte-inspector": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz",
+ "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "debug": "^4.3.7"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22"
+ },
+ "peerDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^5.0.0",
+ "svelte": "^5.0.0",
+ "vite": "^6.0.0"
+ }
+ },
+ "node_modules/@sveltejs/acorn-typescript": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz",
+ "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^8.9.0"
+ }
+ },
+ "node_modules/@sveltejs/adapter-static": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.9.tgz",
+ "integrity": "sha512-aytHXcMi7lb9ljsWUzXYQ0p5X1z9oWud2olu/EpmH7aCu4m84h7QLvb5Wp+CFirKcwoNnYvYWhyP/L8Vh1ztdw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@sveltejs/kit": "^2.0.0"
+ }
+ },
+ "node_modules/@sveltejs/kit": {
+ "version": "2.37.0",
+ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.37.0.tgz",
+ "integrity": "sha512-xgKtpjQ6Ry4mdShd01ht5AODUsW7+K1iValPDq7QX8zI1hWOKREH9GjG8SRCN5tC4K7UXmMhuQam7gbLByVcnw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "@sveltejs/acorn-typescript": "^1.0.5",
+ "@types/cookie": "^0.6.0",
+ "acorn": "^8.14.1",
+ "cookie": "^0.6.0",
+ "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": "^2.6.0",
+ "sirv": "^3.0.0"
+ },
+ "bin": {
+ "svelte-kit": "svelte-kit.js"
+ },
+ "engines": {
+ "node": ">=18.13"
+ },
+ "peerDependencies": {
+ "@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",
+ "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.1.0.tgz",
+ "integrity": "sha512-+U6lz1wvGEG/BvQyL4z/flyNdQ9xDNv5vrh+vWBWTHaebqT0c9RNggpZTo/XSPoHsSCWBlYaTlRX8pZ9GATXCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0-next.1",
+ "debug": "^4.4.1",
+ "deepmerge": "^4.3.1",
+ "kleur": "^4.1.5",
+ "magic-string": "^0.30.17",
+ "vitefu": "^1.1.1"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24"
+ },
+ "peerDependencies": {
+ "svelte": "^5.0.0",
+ "vite": "^6.3.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.0.tgz",
+ "integrity": "sha512-iwQ8Z4ET6ZFSt/gC+tVfcsSBHwsqc6RumSaiLUkAurW3BCpJam65cmHw0oOlDMTO0u+PZi9hilBRYN+LZNHTUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.1"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24"
+ },
+ "peerDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0",
+ "svelte": "^5.0.0",
+ "vite": "^6.3.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.17",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
+ "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@tailwindcss/forms": {
+ "version": "0.5.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz",
+ "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mini-svg-data-uri": "^1.2.3"
+ },
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz",
+ "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.3.0",
+ "enhanced-resolve": "^5.18.1",
+ "jiti": "^2.4.2",
+ "lightningcss": "1.30.1",
+ "magic-string": "^0.30.17",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.11"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz",
+ "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.4",
+ "tar": "^7.4.3"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.11",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.11",
+ "@tailwindcss/oxide-darwin-x64": "4.1.11",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.11",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.11",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.11",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.11",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.11",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.11"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz",
+ "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz",
+ "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz",
+ "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz",
+ "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz",
+ "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz",
+ "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz",
+ "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz",
+ "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz",
+ "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz",
+ "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@emnapi/wasi-threads": "^1.0.2",
+ "@napi-rs/wasm-runtime": "^0.2.11",
+ "@tybys/wasm-util": "^0.9.0",
+ "tslib": "^2.8.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz",
+ "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz",
+ "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/typography": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz",
+ "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lodash.castarray": "^4.4.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.merge": "^4.6.2",
+ "postcss-selector-parser": "6.0.10"
+ },
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.11.tgz",
+ "integrity": "sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.1.11",
+ "@tailwindcss/oxide": "4.1.11",
+ "tailwindcss": "4.1.11"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7"
+ }
+ },
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
+ "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "chalk": "^4.1.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/jest-dom": {
+ "version": "6.6.3",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz",
+ "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@adobe/css-tools": "^4.4.0",
+ "aria-query": "^5.0.0",
+ "chalk": "^3.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.6.3",
+ "lodash": "^4.17.21",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "node_modules/@testing-library/jest-dom/node_modules/chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
+ "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@testing-library/user-event": {
+ "version": "14.6.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
+ "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": ">=7.21.4"
+ }
+ },
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz",
+ "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*"
+ }
+ },
+ "node_modules/@types/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/katex": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz",
+ "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/mdast": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/mdx": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz",
+ "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "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==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.1.8",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
+ "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz",
+ "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.37.0",
+ "@typescript-eslint/type-utils": "8.37.0",
+ "@typescript-eslint/utils": "8.37.0",
+ "@typescript-eslint/visitor-keys": "8.37.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^7.0.0",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.37.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz",
+ "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.37.0",
+ "@typescript-eslint/types": "8.37.0",
+ "@typescript-eslint/typescript-estree": "8.37.0",
+ "@typescript-eslint/visitor-keys": "8.37.0",
+ "debug": "^4.3.4"
+ },
+ "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 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz",
+ "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.37.0",
+ "@typescript-eslint/types": "^8.37.0",
+ "debug": "^4.3.4"
+ },
+ "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 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz",
+ "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.37.0",
+ "@typescript-eslint/visitor-keys": "8.37.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz",
+ "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==",
+ "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 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz",
+ "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.37.0",
+ "@typescript-eslint/typescript-estree": "8.37.0",
+ "@typescript-eslint/utils": "8.37.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.1.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 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz",
+ "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==",
+ "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/@typescript-eslint/typescript-estree": {
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz",
+ "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.37.0",
+ "@typescript-eslint/tsconfig-utils": "8.37.0",
+ "@typescript-eslint/types": "8.37.0",
+ "@typescript-eslint/visitor-keys": "8.37.0",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.1.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 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/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/@typescript-eslint/utils": {
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz",
+ "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.37.0",
+ "@typescript-eslint/types": "8.37.0",
+ "@typescript-eslint/typescript-estree": "8.37.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 <5.9.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz",
+ "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.37.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/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "license": "ISC"
+ },
+ "node_modules/@vitest/browser": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-3.2.4.tgz",
+ "integrity": "sha512-tJxiPrWmzH8a+w9nLKlQMzAKX/7VjFs50MWgcAj7p9XQ7AQ9/35fByFYptgPELyLw+0aixTnC4pUWV+APcZ/kw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@testing-library/dom": "^10.4.0",
+ "@testing-library/user-event": "^14.6.1",
+ "@vitest/mocker": "3.2.4",
+ "@vitest/utils": "3.2.4",
+ "magic-string": "^0.30.17",
+ "sirv": "^3.0.1",
+ "tinyrainbow": "^2.0.0",
+ "ws": "^8.18.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "playwright": "*",
+ "vitest": "3.2.4",
+ "webdriverio": "^7.0.0 || ^8.0.0 || ^9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "playwright": {
+ "optional": true
+ },
+ "safaridriver": {
+ "optional": true
+ },
+ "webdriverio": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
+ "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
+ "chai": "^5.2.0",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz",
+ "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "3.2.4",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.17"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "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",
+ "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz",
+ "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "3.2.4",
+ "pathe": "^2.0.3",
+ "strip-literal": "^3.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz",
+ "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.4",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
+ "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^4.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
+ "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.4",
+ "loupe": "^3.1.4",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/ast-types": {
+ "version": "0.16.1",
+ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz",
+ "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/axe-core": {
+ "version": "4.10.3",
+ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz",
+ "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/axobject-query": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/bail": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/better-opn": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz",
+ "integrity": "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "open": "^8.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/bits-ui": {
+ "version": "2.8.11",
+ "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.8.11.tgz",
+ "integrity": "sha512-lKN9rAk69my6j7H1D4B87r8LrHuEtfEsf1xCixBj9yViql2BdI3f04HyyyT7T1GOCpgb9+8b0B+nm3LN81Konw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.1",
+ "@floating-ui/dom": "^1.7.1",
+ "esm-env": "^1.1.2",
+ "runed": "^0.29.1",
+ "svelte-toolbelt": "^0.9.3",
+ "tabbable": "^6.2.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/huntabyte"
+ },
+ "peerDependencies": {
+ "@internationalized/date": "^3.8.1",
+ "svelte": "^5.33.0"
+ }
+ },
+ "node_modules/bits-ui/node_modules/runed": {
+ "version": "0.29.2",
+ "resolved": "https://registry.npmjs.org/runed/-/runed-0.29.2.tgz",
+ "integrity": "sha512-0cq6cA6sYGZwl/FvVqjx9YN+1xEBu9sDDyuWdDW1yWX7JF2wmvmVKfH+hVCZs+csW+P3ARH92MjI3H9QTagOQA==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/huntabyte",
+ "https://github.com/sponsors/tglide"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "esm-env": "^1.0.0"
+ },
+ "peerDependencies": {
+ "svelte": "^5.7.0"
+ }
+ },
+ "node_modules/bits-ui/node_modules/svelte-toolbelt": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.9.3.tgz",
+ "integrity": "sha512-HCSWxCtVmv+c6g1ACb8LTwHVbDqLKJvHpo6J8TaqwUme2hj9ATJCpjCPNISR1OCq2Q4U1KT41if9ON0isINQZw==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/huntabyte"
+ ],
+ "dependencies": {
+ "clsx": "^2.1.1",
+ "runed": "^0.29.0",
+ "style-to-object": "^1.0.8"
+ },
+ "engines": {
+ "node": ">=18",
+ "pnpm": ">=8.7.0"
+ },
+ "peerDependencies": {
+ "svelte": "^5.30.2"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/chai": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz",
+ "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/character-entities": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-html4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+ "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
+ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chromatic": {
+ "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": {
+ "chroma": "dist/bin.js",
+ "chromatic": "dist/bin.js",
+ "chromatic-cli": "dist/bin.js"
+ },
+ "peerDependencies": {
+ "@chromatic-com/cypress": "^0.*.* || ^1.0.0",
+ "@chromatic-com/playwright": "^0.*.* || ^1.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@chromatic-com/cypress": {
+ "optional": true
+ },
+ "@chromatic-com/playwright": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/commander": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "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",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/debug": {
+ "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"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decode-named-character-reference": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
+ "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/dedent": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz",
+ "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/dedent-js": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz",
+ "integrity": "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/define-lazy-prop": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
+ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=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",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/devalue": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.3.2.tgz",
+ "integrity": "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/dexie": {
+ "version": "4.0.11",
+ "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.11.tgz",
+ "integrity": "sha512-SOKO002EqlvBYYKQSew3iymBoN2EQ4BDw/3yprjh7kAfFzjBYkaMNa/pZvcA7HSWlcKSQb9XhPe3wKyQ0x4A8A==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.2",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
+ "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es-toolkit": {
+ "version": "1.39.7",
+ "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.7.tgz",
+ "integrity": "sha512-ek/wWryKouBrZIjkwW2BFf91CWOIMvoy2AE5YYgUrfWsJQM2Su1LoLtrw8uusEpN9RfqLlV/0FVNjT0WMv8Bxw==",
+ "dev": true,
+ "license": "MIT",
+ "workspaces": [
+ "docs",
+ "benchmarks"
+ ]
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.8",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz",
+ "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.8",
+ "@esbuild/android-arm": "0.25.8",
+ "@esbuild/android-arm64": "0.25.8",
+ "@esbuild/android-x64": "0.25.8",
+ "@esbuild/darwin-arm64": "0.25.8",
+ "@esbuild/darwin-x64": "0.25.8",
+ "@esbuild/freebsd-arm64": "0.25.8",
+ "@esbuild/freebsd-x64": "0.25.8",
+ "@esbuild/linux-arm": "0.25.8",
+ "@esbuild/linux-arm64": "0.25.8",
+ "@esbuild/linux-ia32": "0.25.8",
+ "@esbuild/linux-loong64": "0.25.8",
+ "@esbuild/linux-mips64el": "0.25.8",
+ "@esbuild/linux-ppc64": "0.25.8",
+ "@esbuild/linux-riscv64": "0.25.8",
+ "@esbuild/linux-s390x": "0.25.8",
+ "@esbuild/linux-x64": "0.25.8",
+ "@esbuild/netbsd-arm64": "0.25.8",
+ "@esbuild/netbsd-x64": "0.25.8",
+ "@esbuild/openbsd-arm64": "0.25.8",
+ "@esbuild/openbsd-x64": "0.25.8",
+ "@esbuild/openharmony-arm64": "0.25.8",
+ "@esbuild/sunos-x64": "0.25.8",
+ "@esbuild/win32-arm64": "0.25.8",
+ "@esbuild/win32-ia32": "0.25.8",
+ "@esbuild/win32-x64": "0.25.8"
+ }
+ },
+ "node_modules/esbuild-register": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz",
+ "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4"
+ },
+ "peerDependencies": {
+ "esbuild": ">=0.12 <1"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.31.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz",
+ "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.0",
+ "@eslint/config-helpers": "^0.3.0",
+ "@eslint/core": "^0.15.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.31.0",
+ "@eslint/plugin-kit": "^0.3.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "10.1.8",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
+ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-config-prettier"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-storybook": {
+ "version": "9.0.17",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-9.0.17.tgz",
+ "integrity": "sha512-IuTdlwCEwoDNobdygRCxNhlKXHmsDfPtPvHGcsY35x2Bx8KItrjfekO19gJrjc1VT2CMfcZMYF8OBKaxHELupw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/utils": "^8.8.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=8",
+ "storybook": "^9.0.17"
+ }
+ },
+ "node_modules/eslint-plugin-svelte": {
+ "version": "3.11.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.11.0.tgz",
+ "integrity": "sha512-KliWlkieHyEa65aQIkRwUFfHzT5Cn4u3BQQsu3KlkJOs7c1u7ryn84EWaOjEzilbKgttT4OfBURA8Uc4JBSQIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.6.1",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "esutils": "^2.0.3",
+ "globals": "^16.0.0",
+ "known-css-properties": "^0.37.0",
+ "postcss": "^8.4.49",
+ "postcss-load-config": "^3.1.4",
+ "postcss-safe-parser": "^7.0.0",
+ "semver": "^7.6.3",
+ "svelte-eslint-parser": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ota-meshi"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.1 || ^9.0.0",
+ "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "svelte": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esm-env": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
+ "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
+ "license": "MIT"
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrap": {
+ "version": "1.4.9",
+ "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.9.tgz",
+ "integrity": "sha512-3OMlcd0a03UGuZpPeUC1HxR3nA23l+HEyCiZw3b3FumJIN9KphoGzDJKMXI1S72jVS1dsenDyQC0kJlO1U9E1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz",
+ "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "license": "MIT"
+ },
+ "node_modules/fast-deep-equal": {
+ "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": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/filesize": {
+ "version": "10.1.6",
+ "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz",
+ "integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 10.4.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz",
+ "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hast-util-from-dom": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz",
+ "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hastscript": "^9.0.0",
+ "web-namespaces": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-html": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz",
+ "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "devlop": "^1.1.0",
+ "hast-util-from-parse5": "^8.0.0",
+ "parse5": "^7.0.0",
+ "vfile": "^6.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-html-isomorphic": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz",
+ "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-from-dom": "^5.0.0",
+ "hast-util-from-html": "^2.0.0",
+ "unist-util-remove-position": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-html/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hast-util-from-html/node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-html/node_modules/vfile-message": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
+ "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-parse5": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz",
+ "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "devlop": "^1.0.0",
+ "hastscript": "^9.0.0",
+ "property-information": "^7.0.0",
+ "vfile": "^6.0.0",
+ "vfile-location": "^5.0.0",
+ "web-namespaces": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-parse5/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hast-util-is-element": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
+ "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-parse-selector": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
+ "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-sanitize": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz",
+ "integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "unist-util-position": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-html": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
+ "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "html-void-elements": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "stringify-entities": "^4.0.0",
+ "zwitch": "^2.0.4"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-html/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/hast-util-to-text": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
+ "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "hast-util-is-element": "^3.0.0",
+ "unist-util-find-after": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-text/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hastscript": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz",
+ "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-parse-selector": "^4.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/highlight.js": {
+ "version": "11.11.1",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
+ "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/html-void-elements": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
+ "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "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",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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",
+ "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/katex": {
+ "version": "0.16.22",
+ "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz",
+ "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==",
+ "dev": true,
+ "funding": [
+ "https://opencollective.com/katex",
+ "https://github.com/sponsors/katex"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^8.3.0"
+ },
+ "bin": {
+ "katex": "cli.js"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/known-css-properties": {
+ "version": "0.37.0",
+ "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz",
+ "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
+ "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-darwin-arm64": "1.30.1",
+ "lightningcss-darwin-x64": "1.30.1",
+ "lightningcss-freebsd-x64": "1.30.1",
+ "lightningcss-linux-arm-gnueabihf": "1.30.1",
+ "lightningcss-linux-arm64-gnu": "1.30.1",
+ "lightningcss-linux-arm64-musl": "1.30.1",
+ "lightningcss-linux-x64-gnu": "1.30.1",
+ "lightningcss-linux-x64-musl": "1.30.1",
+ "lightningcss-win32-arm64-msvc": "1.30.1",
+ "lightningcss-win32-x64-msvc": "1.30.1"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
+ "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz",
+ "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz",
+ "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz",
+ "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
+ "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz",
+ "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
+ "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz",
+ "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz",
+ "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
+ "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/locate-character": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
+ "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.castarray": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
+ "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/longest-streak": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/loupe": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz",
+ "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lower-case": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
+ "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/lowlight": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz",
+ "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "devlop": "^1.0.0",
+ "highlight.js": "~11.11.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/markdown-table": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
+ "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
+ "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "escape-string-regexp": "^5.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mdast-util-from-markdown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
+ "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark": "^4.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-from-markdown/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/mdast-util-from-markdown/node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
+ "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-gfm-autolink-literal": "^2.0.0",
+ "mdast-util-gfm-footnote": "^2.0.0",
+ "mdast-util-gfm-strikethrough": "^2.0.0",
+ "mdast-util-gfm-table": "^2.0.0",
+ "mdast-util-gfm-task-list-item": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-autolink-literal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
+ "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-find-and-replace": "^3.0.0",
+ "micromark-util-character": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-footnote": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz",
+ "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-strikethrough": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
+ "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-table": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
+ "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "markdown-table": "^3.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-task-list-item": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
+ "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-math": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz",
+ "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.1.0",
+ "unist-util-remove-position": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-newline-to-break": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz",
+ "integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-find-and-replace": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-phrasing": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
+ "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
+ "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-phrasing": "^4.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "unist-util-visit": "^5.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/mdast-util-to-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdsvex": {
+ "version": "0.12.6",
+ "resolved": "https://registry.npmjs.org/mdsvex/-/mdsvex-0.12.6.tgz",
+ "integrity": "sha512-pupx2gzWh3hDtm/iDW4WuCpljmyHbHi34r7ktOqpPGvyiM4MyfNgdJ3qMizXdgCErmvYC9Nn/qyjePy+4ss9Wg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.4",
+ "@types/unist": "^2.0.3",
+ "prism-svelte": "^0.4.7",
+ "prismjs": "^1.17.1",
+ "unist-util-visit": "^2.0.1",
+ "vfile-message": "^2.0.4"
+ },
+ "peerDependencies": {
+ "svelte": "^3.56.0 || ^4.0.0 || ^5.0.0-next.120"
+ }
+ },
+ "node_modules/mdsvex/node_modules/unist-util-is": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz",
+ "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdsvex/node_modules/unist-util-visit": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz",
+ "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "unist-util-is": "^4.0.0",
+ "unist-util-visit-parents": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdsvex/node_modules/unist-util-visit-parents": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz",
+ "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "unist-util-is": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromark": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@types/debug": "^4.0.0",
+ "debug": "^4.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-core-commonmark": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-factory-destination": "^2.0.0",
+ "micromark-factory-label": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-factory-title": "^2.0.0",
+ "micromark-factory-whitespace": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-html-tag-name": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-extension-gfm": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
+ "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-extension-gfm-autolink-literal": "^2.0.0",
+ "micromark-extension-gfm-footnote": "^2.0.0",
+ "micromark-extension-gfm-strikethrough": "^2.0.0",
+ "micromark-extension-gfm-table": "^2.0.0",
+ "micromark-extension-gfm-tagfilter": "^2.0.0",
+ "micromark-extension-gfm-task-list-item": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-autolink-literal": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
+ "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-footnote": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
+ "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-strikethrough": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
+ "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-table": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
+ "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-tagfilter": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
+ "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-task-list-item": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
+ "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-math": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz",
+ "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/katex": "^0.16.0",
+ "devlop": "^1.0.0",
+ "katex": "^0.16.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-factory-destination": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-label": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-space": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-title": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-whitespace": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-chunked": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-classify-character": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-combine-extensions": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-numeric-character-reference": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-string": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-html-tag-name": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-normalize-identifier": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-resolve-all": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-subtokenize": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-symbol": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-types": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mini-svg-data-uri": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
+ "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mini-svg-data-uri": "cli.js"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/minizlib": {
+ "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": {
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "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",
+ "integrity": "sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g==",
+ "license": "MIT",
+ "dependencies": {
+ "runed": "^0.25.0",
+ "svelte-toolbelt": "^0.7.1"
+ },
+ "peerDependencies": {
+ "svelte": "^5.27.0"
+ }
+ },
+ "node_modules/mri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mrmime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
+ "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/no-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
+ "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lower-case": "^2.0.2",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/open": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
+ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-lazy-prop": "^2.0.0",
+ "is-docker": "^2.1.1",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/pascal-case": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
+ "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "no-case": "^3.0.4",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "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/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
+ "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
+ "node_modules/pdfjs-dist": {
+ "version": "5.4.54",
+ "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.54.tgz",
+ "integrity": "sha512-TBAiTfQw89gU/Z4LW98Vahzd2/LoCFprVGvGbTgFt+QCB1F+woyOPmNNVgLa6djX9Z9GGTnj7qE1UzpOVJiINw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=20.16.0 || >=22.3.0"
+ },
+ "optionalDependencies": {
+ "@napi-rs/canvas": "^0.1.74"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/playwright": {
+ "version": "1.54.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz",
+ "integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.54.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.54.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz",
+ "integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
+ "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^2.0.5",
+ "yaml": "^1.10.2"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-load-config/node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/postcss-safe-parser": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz",
+ "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.31"
+ }
+ },
+ "node_modules/postcss-scss": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz",
+ "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss-scss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.29"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.0.10",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+ "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-plugin-svelte": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.4.0.tgz",
+ "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "prettier": "^3.0.0",
+ "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
+ }
+ },
+ "node_modules/prettier-plugin-tailwindcss": {
+ "version": "0.6.14",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz",
+ "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.21.3"
+ },
+ "peerDependencies": {
+ "@ianvs/prettier-plugin-sort-imports": "*",
+ "@prettier/plugin-hermes": "*",
+ "@prettier/plugin-oxc": "*",
+ "@prettier/plugin-pug": "*",
+ "@shopify/prettier-plugin-liquid": "*",
+ "@trivago/prettier-plugin-sort-imports": "*",
+ "@zackad/prettier-plugin-twig": "*",
+ "prettier": "^3.0",
+ "prettier-plugin-astro": "*",
+ "prettier-plugin-css-order": "*",
+ "prettier-plugin-import-sort": "*",
+ "prettier-plugin-jsdoc": "*",
+ "prettier-plugin-marko": "*",
+ "prettier-plugin-multiline-arrays": "*",
+ "prettier-plugin-organize-attributes": "*",
+ "prettier-plugin-organize-imports": "*",
+ "prettier-plugin-sort-imports": "*",
+ "prettier-plugin-style-order": "*",
+ "prettier-plugin-svelte": "*"
+ },
+ "peerDependenciesMeta": {
+ "@ianvs/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@prettier/plugin-hermes": {
+ "optional": true
+ },
+ "@prettier/plugin-oxc": {
+ "optional": true
+ },
+ "@prettier/plugin-pug": {
+ "optional": true
+ },
+ "@shopify/prettier-plugin-liquid": {
+ "optional": true
+ },
+ "@trivago/prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "@zackad/prettier-plugin-twig": {
+ "optional": true
+ },
+ "prettier-plugin-astro": {
+ "optional": true
+ },
+ "prettier-plugin-css-order": {
+ "optional": true
+ },
+ "prettier-plugin-import-sort": {
+ "optional": true
+ },
+ "prettier-plugin-jsdoc": {
+ "optional": true
+ },
+ "prettier-plugin-marko": {
+ "optional": true
+ },
+ "prettier-plugin-multiline-arrays": {
+ "optional": true
+ },
+ "prettier-plugin-organize-attributes": {
+ "optional": true
+ },
+ "prettier-plugin-organize-imports": {
+ "optional": true
+ },
+ "prettier-plugin-sort-imports": {
+ "optional": true
+ },
+ "prettier-plugin-style-order": {
+ "optional": true
+ },
+ "prettier-plugin-svelte": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/prism-svelte": {
+ "version": "0.4.7",
+ "resolved": "https://registry.npmjs.org/prism-svelte/-/prism-svelte-0.4.7.tgz",
+ "integrity": "sha512-yABh19CYbM24V7aS7TuPYRNMqthxwbvx6FF/Rw920YbyBWO3tnyPIqRMgHuSVsLmuHkkBS1Akyof463FVdkeDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/prismjs": {
+ "version": "1.30.0",
+ "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
+ "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "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",
+ "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
+ "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
+ "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.26.0"
+ },
+ "peerDependencies": {
+ "react": "^19.1.0"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/recast": {
+ "version": "0.23.11",
+ "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz",
+ "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ast-types": "^0.16.1",
+ "esprima": "~4.0.0",
+ "source-map": "~0.6.1",
+ "tiny-invariant": "^1.3.3",
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/rehype-highlight": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/rehype-highlight/-/rehype-highlight-7.0.2.tgz",
+ "integrity": "sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-to-text": "^4.0.0",
+ "lowlight": "^3.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-katex": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz",
+ "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/katex": "^0.16.0",
+ "hast-util-from-html-isomorphic": "^2.0.0",
+ "hast-util-to-text": "^4.0.0",
+ "katex": "^0.16.0",
+ "unist-util-visit-parents": "^6.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-stringify": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz",
+ "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-to-html": "^9.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark": {
+ "version": "15.0.1",
+ "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz",
+ "integrity": "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-stringify": "^11.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-breaks": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz",
+ "integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-newline-to-break": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-gfm": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
+ "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-gfm": "^3.0.0",
+ "micromark-extension-gfm": "^3.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-stringify": "^11.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-html": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-16.0.1.tgz",
+ "integrity": "sha512-B9JqA5i0qZe0Nsf49q3OXyGvyXuZFDzAP2iOFLEumymuYJITVpiH1IgsTEwTpdptDmZlMDMWeDmSawdaJIGCXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "hast-util-sanitize": "^5.0.0",
+ "hast-util-to-html": "^9.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-math": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz",
+ "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-math": "^3.0.0",
+ "micromark-extension-math": "^3.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-parse": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-rehype": {
+ "version": "11.1.2",
+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz",
+ "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "unified": "^11.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-stringify": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
+ "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.45.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz",
+ "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.45.1",
+ "@rollup/rollup-android-arm64": "4.45.1",
+ "@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/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/runed": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/runed/-/runed-0.25.0.tgz",
+ "integrity": "sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg==",
+ "funding": [
+ "https://github.com/sponsors/huntabyte",
+ "https://github.com/sponsors/tglide"
+ ],
+ "dependencies": {
+ "esm-env": "^1.0.0"
+ },
+ "peerDependencies": {
+ "svelte": "^5.7.0"
+ }
+ },
+ "node_modules/sade": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
+ "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mri": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
+ "dev": true,
+ "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==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "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"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/sirv": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz",
+ "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@polka/url": "^1.0.0-next.24",
+ "mrmime": "^2.0.0",
+ "totalist": "^3.0.0"
+ },
+ "engines": {
+ "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",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
+ "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/storybook": {
+ "version": "9.0.17",
+ "resolved": "https://registry.npmjs.org/storybook/-/storybook-9.0.17.tgz",
+ "integrity": "sha512-O+9jgJ+Trlq9VGD1uY4OBLKQWHHDKM/A/pA8vMW6PVehhGHNvpzcIC1bngr6mL5gGHZP2nBv+9XG8pTMcggMmg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@storybook/global": "^5.0.0",
+ "@testing-library/jest-dom": "^6.6.3",
+ "@testing-library/user-event": "^14.6.1",
+ "@vitest/expect": "3.2.4",
+ "@vitest/spy": "3.2.4",
+ "better-opn": "^3.0.2",
+ "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-register": "^3.5.0",
+ "recast": "^0.23.5",
+ "semver": "^7.6.2",
+ "ws": "^8.18.0"
+ },
+ "bin": {
+ "storybook": "bin/index.cjs"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ },
+ "peerDependencies": {
+ "prettier": "^2 || ^3"
+ },
+ "peerDependenciesMeta": {
+ "prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/stringify-entities": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities-html4": "^2.0.0",
+ "character-entities-legacy": "^3.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "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",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-literal": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz",
+ "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/strip-literal/node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/style-to-object": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz",
+ "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==",
+ "license": "MIT",
+ "dependencies": {
+ "inline-style-parser": "0.2.4"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/svelte": {
+ "version": "5.36.12",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.36.12.tgz",
+ "integrity": "sha512-c3mWT+b0yBLl3gPGSHiy4pdSQCsPNTjLC0tVoOhrGJ6PPfCzD/RQpAmAfJtQZ304CAae2ph+L3C4aqds3R3seQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.3.0",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@sveltejs/acorn-typescript": "^1.0.5",
+ "@types/estree": "^1.0.5",
+ "acorn": "^8.12.1",
+ "aria-query": "^5.3.1",
+ "axobject-query": "^4.1.0",
+ "clsx": "^2.1.1",
+ "esm-env": "^1.2.1",
+ "esrap": "^2.1.0",
+ "is-reference": "^3.0.3",
+ "locate-character": "^3.0.0",
+ "magic-string": "^0.30.11",
+ "zimmerframe": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/svelte-ast-print": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/svelte-ast-print/-/svelte-ast-print-0.4.2.tgz",
+ "integrity": "sha512-hRHHufbJoArFmDYQKCpCvc0xUuIEfwYksvyLYEQyH+1xb5LD5sM/IthfooCdXZQtOIqXz6xm7NmaqdfwG4kh6w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/xeho91"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/xeho91"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "esrap": "1.2.2",
+ "zimmerframe": "1.1.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "svelte": "^5.0.0"
+ }
+ },
+ "node_modules/svelte-ast-print/node_modules/esrap": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz",
+ "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15",
+ "@types/estree": "^1.0.1"
+ }
+ },
+ "node_modules/svelte-check": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.0.tgz",
+ "integrity": "sha512-Iz8dFXzBNAM7XlEIsUjUGQhbEE+Pvv9odb9+0+ITTgFWZBGeJRRYqHUUglwe2EkLD5LIsQaAc4IUJyvtKuOO5w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "chokidar": "^4.0.1",
+ "fdir": "^6.2.0",
+ "picocolors": "^1.0.0",
+ "sade": "^1.7.4"
+ },
+ "bin": {
+ "svelte-check": "bin/svelte-check"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "peerDependencies": {
+ "svelte": "^4.0.0 || ^5.0.0-next.0",
+ "typescript": ">=5.0.0"
+ }
+ },
+ "node_modules/svelte-eslint-parser": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.3.0.tgz",
+ "integrity": "sha512-VCgMHKV7UtOGcGLGNFSbmdm6kEKjtzo5nnpGU/mnx4OsFY6bZ7QwRF5DUx+Hokw5Lvdyo8dpk8B1m8mliomrNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-scope": "^8.2.0",
+ "eslint-visitor-keys": "^4.0.0",
+ "espree": "^10.0.0",
+ "postcss": "^8.4.49",
+ "postcss-scss": "^4.0.9",
+ "postcss-selector-parser": "^7.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ota-meshi"
+ },
+ "peerDependencies": {
+ "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "svelte": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/svelte-eslint-parser/node_modules/postcss-selector-parser": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
+ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/svelte-sonner": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-1.0.5.tgz",
+ "integrity": "sha512-9dpGPFqKb/QWudYqGnEz93vuY+NgCEvyNvxoCLMVGw6sDN/3oVeKV1xiEirW2E1N3vJEyj5imSBNOGltQHA7mg==",
+ "license": "MIT",
+ "dependencies": {
+ "runed": "^0.28.0"
+ },
+ "peerDependencies": {
+ "svelte": "^5.0.0"
+ }
+ },
+ "node_modules/svelte-sonner/node_modules/runed": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/runed/-/runed-0.28.0.tgz",
+ "integrity": "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ==",
+ "funding": [
+ "https://github.com/sponsors/huntabyte",
+ "https://github.com/sponsors/tglide"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "esm-env": "^1.0.0"
+ },
+ "peerDependencies": {
+ "svelte": "^5.7.0"
+ }
+ },
+ "node_modules/svelte-toolbelt": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.7.1.tgz",
+ "integrity": "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ==",
+ "funding": [
+ "https://github.com/sponsors/huntabyte"
+ ],
+ "dependencies": {
+ "clsx": "^2.1.1",
+ "runed": "^0.23.2",
+ "style-to-object": "^1.0.8"
+ },
+ "engines": {
+ "node": ">=18",
+ "pnpm": ">=8.7.0"
+ },
+ "peerDependencies": {
+ "svelte": "^5.0.0"
+ }
+ },
+ "node_modules/svelte-toolbelt/node_modules/runed": {
+ "version": "0.23.4",
+ "resolved": "https://registry.npmjs.org/runed/-/runed-0.23.4.tgz",
+ "integrity": "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==",
+ "funding": [
+ "https://github.com/sponsors/huntabyte",
+ "https://github.com/sponsors/tglide"
+ ],
+ "dependencies": {
+ "esm-env": "^1.0.0"
+ },
+ "peerDependencies": {
+ "svelte": "^5.7.0"
+ }
+ },
+ "node_modules/svelte/node_modules/aria-query": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/svelte/node_modules/esrap": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz",
+ "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ }
+ },
+ "node_modules/svelte/node_modules/is-reference": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
+ "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.6"
+ }
+ },
+ "node_modules/svelte2tsx": {
+ "version": "0.7.41",
+ "resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.7.41.tgz",
+ "integrity": "sha512-/TUwpyn/Qc1wcGuayf2GSwvZ7htdAOzpo0JFFm96srKnRXoTD0gy4n06g+XgH8w016S3lPtyFVtFAm+0yJ0BZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dedent-js": "^1.0.1",
+ "pascal-case": "^3.1.1"
+ },
+ "peerDependencies": {
+ "svelte": "^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0",
+ "typescript": "^4.9.4 || ^5.0.0"
+ }
+ },
+ "node_modules/tabbable": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
+ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tailwind-merge": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz",
+ "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwind-variants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-1.0.0.tgz",
+ "integrity": "sha512-2WSbv4ulEEyuBKomOunut65D8UZwxrHoRfYnxGcQNnHqlSCp2+B7Yz2W+yrNDrxRodOXtGD/1oCcKGNBnUqMqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tailwind-merge": "3.0.2"
+ },
+ "engines": {
+ "node": ">=16.x",
+ "pnpm": ">=7.x"
+ },
+ "peerDependencies": {
+ "tailwindcss": "*"
+ }
+ },
+ "node_modules/tailwind-variants/node_modules/tailwind-merge": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz",
+ "integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
+ "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
+ "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tar": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
+ "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@isaacs/fs-minipass": "^4.0.0",
+ "chownr": "^3.0.0",
+ "minipass": "^7.1.2",
+ "minizlib": "^3.0.1",
+ "mkdirp": "^3.0.1",
+ "yallist": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.14",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
+ "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinypool": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
+ "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz",
+ "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/totalist": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
+ "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/trough": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/ts-dedent": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
+ "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.10"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD"
+ },
+ "node_modules/tw-animate-css": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.5.tgz",
+ "integrity": "sha512-t3u+0YNoloIhj1mMXs779P6MO9q3p3mvGn4k1n3nJPqJw/glZcuijG2qTSN4z4mgNRfW5ZC3aXJFLwDtiipZXA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Wombosvideo"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.8.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.37.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz",
+ "integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.37.0",
+ "@typescript-eslint/parser": "8.37.0",
+ "@typescript-eslint/typescript-estree": "8.37.0",
+ "@typescript-eslint/utils": "8.37.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 <5.9.0"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unified": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "bail": "^2.0.0",
+ "devlop": "^1.0.0",
+ "extend": "^3.0.0",
+ "is-plain-obj": "^4.0.0",
+ "trough": "^2.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unified/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/unist-util-find-after": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz",
+ "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-find-after/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/unist-util-is": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
+ "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-is/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/unist-util-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-position/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/unist-util-remove-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz",
+ "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-visit": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-remove-position/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unist-util-stringify-position": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
+ "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.2"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
+ "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
+ "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/unist-util-visit/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/unplugin": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz",
+ "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.14.0",
+ "webpack-virtual-modules": "^0.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/uuid": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
+ "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist-node/bin/uuid"
+ }
+ },
+ "node_modules/vfile": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-location": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz",
+ "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-location/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vfile-message": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz",
+ "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "unist-util-stringify-position": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile/node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/vfile/node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile/node_modules/vfile-message": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
+ "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.5.tgz",
+ "integrity": "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.6",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.6",
+ "rollup": "^4.40.0",
+ "tinyglobby": "^0.2.14"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
+ "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.4.1",
+ "es-module-lexer": "^1.7.0",
+ "pathe": "^2.0.3",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vite-plugin-devtools-json": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/vite-plugin-devtools-json/-/vite-plugin-devtools-json-0.2.1.tgz",
+ "integrity": "sha512-5aiNvf/iLTuLR1dUqoI5CLLGgeK2hd6u+tA+RIp7GUZDyAcM6ECaUEWOOtGpidbcxbkKq++KtmSqA3jhMbPwMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "uuid": "^11.1.0"
+ },
+ "peerDependencies": {
+ "vite": "^2.7.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/vite-plugin-devtools-json/node_modules/uuid": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
+ "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/esm/bin/uuid"
+ }
+ },
+ "node_modules/vite/node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/vitefu": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz",
+ "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==",
+ "dev": true,
+ "license": "MIT",
+ "workspaces": [
+ "tests/deps/*",
+ "tests/projects/*",
+ "tests/projects/workspace/packages/*"
+ ],
+ "peerDependencies": {
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
+ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/expect": "3.2.4",
+ "@vitest/mocker": "3.2.4",
+ "@vitest/pretty-format": "^3.2.4",
+ "@vitest/runner": "3.2.4",
+ "@vitest/snapshot": "3.2.4",
+ "@vitest/spy": "3.2.4",
+ "@vitest/utils": "3.2.4",
+ "chai": "^5.2.0",
+ "debug": "^4.4.1",
+ "expect-type": "^1.2.1",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.2",
+ "std-env": "^3.9.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.2",
+ "tinyglobby": "^0.2.14",
+ "tinypool": "^1.1.1",
+ "tinyrainbow": "^2.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
+ "vite-node": "3.2.4",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/debug": "^4.1.12",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@vitest/browser": "3.2.4",
+ "@vitest/ui": "3.2.4",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/debug": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest-browser-svelte": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/vitest-browser-svelte/-/vitest-browser-svelte-0.1.0.tgz",
+ "integrity": "sha512-YB6ZUZZQNqU1T9NzvTEDpwpPv35Ng1NZMPBh81zDrLEdOgROGE6nJb79NWb1Eu/a8VkHifqArpOZfJfALge6xQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@vitest/browser": "^2.1.0 || ^3.0.0-0",
+ "svelte": ">3.0.0",
+ "vitest": "^2.1.0 || ^3.0.0-0"
+ }
+ },
+ "node_modules/web-namespaces": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
+ "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/webpack-virtual-modules": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/which": {
+ "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"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
+ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zimmerframe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz",
+ "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
+ "license": "MIT"
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ }
+ }
}
{
- "name": "webui",
- "private": true,
- "version": "0.0.0",
- "type": "module",
- "scripts": {
- "dev": "vite",
- "build": "npm run format && tsc -b && vite build",
- "format": "eslint . && prettier --write .",
- "lint": "eslint .",
- "preview": "vite preview"
- },
- "dependencies": {
- "@heroicons/react": "^2.2.0",
- "@sec-ant/readable-stream": "^0.6.0",
- "@tailwindcss/postcss": "^4.1.1",
- "@tailwindcss/vite": "^4.1.1",
- "@vscode/markdown-it-katex": "^1.1.1",
- "autoprefixer": "^10.4.20",
- "daisyui": "^5.0.12",
- "dexie": "^4.0.11",
- "highlight.js": "^11.10.0",
- "katex": "^0.16.15",
- "pdfjs-dist": "^5.2.133",
- "postcss": "^8.4.49",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
- "react-dropzone": "^14.3.8",
- "react-hot-toast": "^2.5.2",
- "react-markdown": "^9.0.3",
- "react-router": "^7.1.5",
- "rehype-highlight": "^7.0.2",
- "rehype-katex": "^7.0.1",
- "remark-breaks": "^4.0.0",
- "remark-gfm": "^4.0.0",
- "remark-math": "^6.0.0",
- "tailwindcss": "^4.1.1",
- "textlinestream": "^1.1.1",
- "vite-plugin-singlefile": "^2.0.3"
- },
- "devDependencies": {
- "@eslint/js": "^9.17.0",
- "@types/markdown-it": "^14.1.2",
- "@types/node": "^22.13.1",
- "@types/react": "^18.3.18",
- "@types/react-dom": "^18.3.5",
- "@vitejs/plugin-react": "^4.3.4",
- "eslint": "^9.17.0",
- "eslint-plugin-react-hooks": "^5.0.0",
- "eslint-plugin-react-refresh": "^0.4.16",
- "fflate": "^0.8.2",
- "globals": "^15.14.0",
- "prettier": "^3.4.2",
- "sass-embedded": "^1.83.4",
- "typescript": "~5.6.2",
- "typescript-eslint": "^8.18.2",
- "vite": "^6.0.5"
- },
- "prettier": {
- "trailingComma": "es5",
- "tabWidth": 2,
- "semi": true,
- "singleQuote": true,
- "bracketSameLine": false
- }
+ "name": "webui",
+ "private": true,
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite dev --host 0.0.0.0 & storybook dev -p 6006 --ci",
+ "build": "vite build && ./scripts/post-build.sh",
+ "preview": "vite preview",
+ "prepare": "svelte-kit sync || echo ''",
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
+ "reset": "rm -rf .svelte-kit node_modules",
+ "format": "prettier --write .",
+ "lint": "prettier --check . && eslint .",
+ "test": "npm run test:ui -- --run && npm run test:client -- --run && npm run test:server -- --run && npm run test:e2e",
+ "test:e2e": "playwright test",
+ "test:client": "vitest --project=client",
+ "test:server": "vitest --project=server",
+ "test:ui": "vitest --project=ui",
+ "test:unit": "vitest",
+ "storybook": "storybook dev -p 6006",
+ "build-storybook": "storybook build"
+ },
+ "devDependencies": {
+ "@chromatic-com/storybook": "^4.0.1",
+ "@eslint/compat": "^1.2.5",
+ "@eslint/js": "^9.18.0",
+ "@internationalized/date": "^3.8.2",
+ "@lucide/svelte": "^0.515.0",
+ "@playwright/test": "^1.49.1",
+ "@storybook/addon-a11y": "^9.0.17",
+ "@storybook/addon-docs": "^9.0.17",
+ "@storybook/addon-svelte-csf": "^5.0.7",
+ "@storybook/addon-vitest": "^9.0.17",
+ "@storybook/sveltekit": "^9.0.17",
+ "@sveltejs/adapter-static": "^3.0.8",
+ "@sveltejs/kit": "^2.22.0",
+ "@sveltejs/vite-plugin-svelte": "^6.0.0",
+ "@tailwindcss/forms": "^0.5.9",
+ "@tailwindcss/typography": "^0.5.15",
+ "@tailwindcss/vite": "^4.0.0",
+ "@types/node": "^22",
+ "@vitest/browser": "^3.2.3",
+ "bits-ui": "^2.8.11",
+ "clsx": "^2.1.1",
+ "dexie": "^4.0.11",
+ "eslint": "^9.18.0",
+ "eslint-config-prettier": "^10.0.1",
+ "eslint-plugin-storybook": "^9.0.17",
+ "eslint-plugin-svelte": "^3.0.0",
+ "fflate": "^0.8.2",
+ "globals": "^16.0.0",
+ "mdsvex": "^0.12.3",
+ "playwright": "^1.53.0",
+ "prettier": "^3.4.2",
+ "prettier-plugin-svelte": "^3.3.3",
+ "prettier-plugin-tailwindcss": "^0.6.11",
+ "rehype-katex": "^7.0.1",
+ "remark-math": "^6.0.0",
+ "storybook": "^9.0.17",
+ "svelte": "^5.0.0",
+ "svelte-check": "^4.0.0",
+ "tailwind-merge": "^3.3.1",
+ "tailwind-variants": "^1.0.0",
+ "tailwindcss": "^4.0.0",
+ "tw-animate-css": "^1.3.5",
+ "typescript": "^5.0.0",
+ "typescript-eslint": "^8.20.0",
+ "uuid": "^13.0.0",
+ "vite": "^7.0.4",
+ "vite-plugin-devtools-json": "^0.2.0",
+ "vitest": "^3.2.3",
+ "vitest-browser-svelte": "^0.1.0"
+ },
+ "dependencies": {
+ "highlight.js": "^11.11.1",
+ "mode-watcher": "^1.1.0",
+ "pdfjs-dist": "^5.4.54",
+ "rehype-highlight": "^7.0.2",
+ "rehype-stringify": "^10.0.1",
+ "remark": "^15.0.1",
+ "remark-breaks": "^4.0.0",
+ "remark-gfm": "^4.0.1",
+ "remark-html": "^16.0.1",
+ "remark-rehype": "^11.1.2",
+ "svelte-sonner": "^1.0.5",
+ "unist-util-visit": "^5.0.0"
+ }
}
--- /dev/null
+import { defineConfig } from '@playwright/test';
+
+export default defineConfig({
+ webServer: {
+ command: 'npm run build && npx http-server ../public -p 8181',
+ port: 8181
+ },
+ testDir: 'e2e'
+});
+++ /dev/null
-export default {
- plugins: {
- "@tailwindcss/postcss": {},
- },
-}
+++ /dev/null
-{
- "demo": true,
- "id": "conv-1734086746930",
- "lastModified": 1734087548943,
- "messages": [
- {
- "id": 1734086764521,
- "role": "user",
- "content": "this is a demo conversation, used in dev mode"
- },
- {
- "id": 1734087548327,
- "role": "assistant",
- "content": "This is the formula:\n\n$\\frac{e^{x_i}}{\\sum_{j=1}^{n}e^{x_j}}$\n\nGiven an input vector \\(\\mathbf{x} = [x_1, x_2, \\ldots, x_n]\\)\n\n\\[\ny_i = \\frac{e^{x_i}}{\\sum_{j=1}^n e^{x_j}}\n\\]\n\n$2x + y = z$\n\nCode block latex:\n```latex\n\\frac{e^{x_i}}{\\sum_{j=1}^{n}e^{x_j}}\n```\n\nTest dollar sign: $1234 $4567\n\nInvalid latex syntax: $E = mc^$ and $$E = mc^$$",
- "timings": {
- "prompt_n": 1,
- "prompt_ms": 28.923,
- "predicted_n": 25,
- "predicted_ms": 573.016
- }
- },
- {
- "id": 1734087548328,
- "role": "user",
- "content": "this is a demo conversation, used in dev mode"
- },
- {
- "id": 1734087548329,
- "role": "assistant",
- "content": "Code block:\n```js\nconsole.log('hello world')\n```\n```sh\nls -la /dev\n```"
- }
- ]
-}
--- /dev/null
+#!/bin/bash
+
+# Script to install pre-commit and post-commit hooks for webui
+# Pre-commit: formats code and builds, stashes unstaged changes
+# Post-commit: automatically unstashes changes
+
+REPO_ROOT=$(git rev-parse --show-toplevel)
+PRE_COMMIT_HOOK="$REPO_ROOT/.git/hooks/pre-commit"
+POST_COMMIT_HOOK="$REPO_ROOT/.git/hooks/post-commit"
+
+echo "Installing pre-commit and post-commit hooks for webui..."
+
+# Create the pre-commit hook
+cat > "$PRE_COMMIT_HOOK" << 'EOF'
+#!/bin/bash
+
+# Check if there are any changes in the webui directory
+if git diff --cached --name-only | grep -q "^tools/server/webui/"; then
+ echo "Formatting webui code..."
+
+ # Change to webui directory and run format
+ cd tools/server/webui
+
+ # Check if npm is available and package.json exists
+ if [ ! -f "package.json" ]; then
+ echo "Error: package.json not found in tools/server/webui"
+ exit 1
+ fi
+
+ # Stash any unstaged changes to avoid conflicts during format/build
+ echo "Stashing unstaged changes..."
+ git stash push --keep-index --include-untracked -m "Pre-commit hook: stashed unstaged changes"
+ STASH_CREATED=$?
+
+ # Run the format command
+ npm run format
+
+ # Check if format command succeeded
+ if [ $? -ne 0 ]; then
+ echo "Error: npm run format failed"
+ if [ $STASH_CREATED -eq 0 ]; then
+ echo "You can restore your unstaged changes with: git stash pop"
+ fi
+ exit 1
+ fi
+
+ # Run the check command
+ npm run check
+
+ # Check if check command succeeded
+ if [ $? -ne 0 ]; then
+ echo "Error: npm run check failed"
+ if [ $STASH_CREATED -eq 0 ]; then
+ echo "You can restore your unstaged changes with: git stash pop"
+ fi
+ exit 1
+ fi
+
+ # Run the build command
+ npm run build
+
+ # Check if build command succeeded
+ if [ $? -ne 0 ]; then
+ echo "Error: npm run build failed"
+ if [ $STASH_CREATED -eq 0 ]; then
+ echo "You can restore your unstaged changes with: git stash pop"
+ fi
+ exit 1
+ fi
+
+ # Go back to repo root to add build output
+ cd ../../..
+
+ # Add the build output to staging area
+ git add tools/server/public/index.html.gz
+
+ if [ $STASH_CREATED -eq 0 ]; then
+ echo "✅ Build completed. Your unstaged changes have been stashed."
+ echo "They will be automatically restored after the commit."
+ # Create a marker file to indicate stash was created by pre-commit hook
+ touch .git/WEBUI_STASH_MARKER
+ fi
+
+ echo "Webui code formatted successfully"
+fi
+
+exit 0
+EOF
+
+# Create the post-commit hook
+cat > "$POST_COMMIT_HOOK" << 'EOF'
+#!/bin/bash
+
+# Check if we have a stash marker from the pre-commit hook
+if [ -f .git/WEBUI_STASH_MARKER ]; then
+ echo "Restoring your unstaged changes..."
+ git stash pop
+ rm -f .git/WEBUI_STASH_MARKER
+ echo "✅ Your unstaged changes have been restored."
+fi
+
+exit 0
+EOF
+
+# Make both hooks executable
+chmod +x "$PRE_COMMIT_HOOK"
+chmod +x "$POST_COMMIT_HOOK"
+
+if [ $? -eq 0 ]; then
+ echo "✅ Pre-commit and post-commit hooks installed successfully!"
+ echo " Pre-commit: $PRE_COMMIT_HOOK"
+ echo " Post-commit: $POST_COMMIT_HOOK"
+ echo ""
+ echo "The hooks will automatically:"
+ echo " • Format and build webui code before commits"
+ echo " • Stash unstaged changes during the process"
+ echo " • Restore your unstaged changes after the commit"
+ echo ""
+ echo "To test the hooks, make a change to a file in the webui directory and commit it."
+else
+ echo "❌ Failed to make hooks executable"
+ exit 1
+fi
--- /dev/null
+rm -rf ../public/_app;
+rm ../public/favicon.svg;
+rm ../public/index.html;
\ No newline at end of file
+++ /dev/null
-import { HashRouter, Outlet, Route, Routes } from 'react-router';
-import Header from './components/Header';
-import Sidebar from './components/Sidebar';
-import { AppContextProvider, useAppContext } from './utils/app.context';
-import ChatScreen from './components/ChatScreen';
-import SettingDialog from './components/SettingDialog';
-import { Toaster } from 'react-hot-toast';
-import { ModalProvider } from './components/ModalProvider';
-
-function App() {
- return (
- <ModalProvider>
- <HashRouter>
- <div className="flex flex-row drawer lg:drawer-open">
- <AppContextProvider>
- <Routes>
- <Route element={<AppLayout />}>
- <Route path="/chat/:convId" element={<ChatScreen />} />
- <Route path="*" element={<ChatScreen />} />
- </Route>
- </Routes>
- </AppContextProvider>
- </div>
- </HashRouter>
- </ModalProvider>
- );
-}
-
-function AppLayout() {
- const { showSettings, setShowSettings } = useAppContext();
- return (
- <>
- <Sidebar />
- <main
- className="drawer-content grow flex flex-col h-screen mx-auto px-4 overflow-auto bg-base-100"
- id="main-scroll"
- >
- <Header />
- <Outlet />
- </main>
- {
- <SettingDialog
- show={showSettings}
- onClose={() => setShowSettings(false)}
- />
- }
- <Toaster />
- </>
- );
-}
-
-export default App;
+++ /dev/null
-import daisyuiThemes from 'daisyui/theme/object';
-import { isNumeric } from './utils/misc';
-
-export const isDev = import.meta.env.MODE === 'development';
-
-// constants
-export const BASE_URL = new URL('.', document.baseURI).href
- .toString()
- .replace(/\/$/, '');
-
-export const CONFIG_DEFAULT = {
- // 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: '',
- showTokensPerSecond: false,
- showThoughtInProgress: false,
- excludeThoughtOnReq: true,
- pasteLongTextToFileLen: 2500,
- pdfAsImage: false,
- // make sure these default values are in sync with `common.h`
- samplers: 'edkypmxt',
- temperature: 0.8,
- dynatemp_range: 0.0,
- dynatemp_exponent: 1.0,
- top_k: 40,
- top_p: 0.95,
- min_p: 0.05,
- xtc_probability: 0.0,
- xtc_threshold: 0.1,
- typical_p: 1.0,
- repeat_last_n: 64,
- repeat_penalty: 1.0,
- presence_penalty: 0.0,
- frequency_penalty: 0.0,
- dry_multiplier: 0.0,
- dry_base: 1.75,
- dry_allowed_length: 2,
- dry_penalty_last_n: -1,
- max_tokens: -1,
- custom: '', // custom json-stringified object
- // experimental features
- pyIntepreterEnabled: false,
-};
-export const CONFIG_INFO: Record<string, string> = {
- apiKey: 'Set the API Key if you are using --api-key option for the server.',
- systemMessage: 'The starting message that defines how model should behave.',
- pasteLongTextToFileLen:
- 'On pasting long text, it will be converted to a file. You can control the file length by setting the value of this parameter. Value 0 means disable.',
- samplers:
- 'The order at which samplers are applied, in simplified way. Default is "dkypmxt": dry->top_k->typ_p->top_p->min_p->xtc->temperature',
- temperature:
- 'Controls the randomness of the generated text by affecting the probability distribution of the output tokens. Higher = more random, lower = more focused.',
- dynatemp_range:
- 'Addon for the temperature sampler. The added value to the range of dynamic temperature, which adjusts probabilities by entropy of tokens.',
- dynatemp_exponent:
- 'Addon for the temperature sampler. Smoothes out the probability redistribution based on the most probable token.',
- top_k: 'Keeps only k top tokens.',
- top_p:
- 'Limits tokens to those that together have a cumulative probability of at least p',
- min_p:
- 'Limits tokens based on the minimum probability for a token to be considered, relative to the probability of the most likely token.',
- xtc_probability:
- 'XTC sampler cuts out top tokens; this parameter controls the chance of cutting tokens at all. 0 disables XTC.',
- xtc_threshold:
- 'XTC sampler cuts out top tokens; this parameter controls the token probability that is required to cut that token.',
- typical_p:
- 'Sorts and limits tokens based on the difference between log-probability and entropy.',
- repeat_last_n: 'Last n tokens to consider for penalizing repetition',
- repeat_penalty:
- 'Controls the repetition of token sequences in the generated text',
- presence_penalty:
- 'Limits tokens based on whether they appear in the output or not.',
- frequency_penalty:
- 'Limits tokens based on how often they appear in the output.',
- dry_multiplier:
- 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the DRY sampling multiplier.',
- dry_base:
- 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the DRY sampling base value.',
- dry_allowed_length:
- 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the allowed length for DRY sampling.',
- dry_penalty_last_n:
- 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets DRY penalty for the last n tokens.',
- max_tokens: 'The maximum number of token per output.',
- custom: '', // custom json-stringified object
-};
-// config keys having numeric value (i.e. temperature, top_k, top_p, etc)
-export const CONFIG_NUMERIC_KEYS = Object.entries(CONFIG_DEFAULT)
- .filter((e) => isNumeric(e[1]))
- .map((e) => e[0]);
-// list of themes supported by daisyui
-export const THEMES = ['light', 'dark']
- // make sure light & dark are always at the beginning
- .concat(
- Object.keys(daisyuiThemes).filter((t) => t !== 'light' && t !== 'dark')
- );
--- /dev/null
+@import 'tailwindcss';
+
+@import 'tw-animate-css';
+
+@custom-variant dark (&:is(.dark *));
+
+:root {
+ --radius: 0.625rem;
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.875 0 0);
+ --input: oklch(0.92 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+ --code-background: oklch(0.225 0 0);
+ --code-foreground: oklch(0.875 0 0);
+}
+
+.dark {
+ --background: oklch(0.16 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.205 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.205 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.922 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 30%);
+ --input: oklch(1 0 0 / 30%);
+ --ring: oklch(0.556 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.556 0 0);
+}
+
+@theme inline {
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
--- /dev/null
+// See https://svelte.dev/docs/kit/types#app.d.ts
+// for information about these interfaces
+
+// Import chat types from dedicated module
+
+import type {
+ ApiChatCompletionRequest,
+ ApiChatCompletionResponse,
+ ApiChatCompletionStreamChunk,
+ ApiChatMessageData,
+ ApiChatMessageContentPart,
+ ApiContextSizeError,
+ ApiErrorResponse,
+ ApiLlamaCppServerProps,
+ ApiProcessingState
+} from '$lib/types/api';
+
+import type {
+ ChatMessageType,
+ ChatRole,
+ ChatUploadedFile,
+ ChatMessageSiblingInfo,
+ ChatMessagePromptProgress,
+ ChatMessageTimings
+} from '$lib/types/chat';
+
+import type {
+ DatabaseConversation,
+ DatabaseMessage,
+ DatabaseMessageExtra,
+ DatabaseMessageExtraAudioFile,
+ DatabaseMessageExtraImageFile,
+ DatabaseMessageExtraTextFile,
+ DatabaseMessageExtraPdfFile
+} from '$lib/types/database';
+
+import type {
+ SettingsConfigValue,
+ SettingsFieldConfig,
+ SettingsConfigType
+} from '$lib/types/settings';
+
+declare global {
+ // namespace App {
+ // interface Error {}
+ // interface Locals {}
+ // interface PageData {}
+ // interface PageState {}
+ // interface Platform {}
+ // }
+
+ export {
+ ApiChatCompletionRequest,
+ ApiChatCompletionResponse,
+ ApiChatCompletionStreamChunk,
+ ApiChatMessageData,
+ ApiChatMessageContentPart,
+ ApiContextSizeError,
+ ApiErrorResponse,
+ ApiLlamaCppServerProps,
+ ApiProcessingState,
+ ChatMessageData,
+ ChatMessagePromptProgress,
+ ChatMessageSiblingInfo,
+ ChatMessageTimings,
+ ChatMessageType,
+ ChatRole,
+ ChatUploadedFile,
+ DatabaseConversation,
+ DatabaseMessage,
+ DatabaseMessageExtra,
+ DatabaseMessageExtraAudioFile,
+ DatabaseMessageExtraImageFile,
+ DatabaseMessageExtraTextFile,
+ DatabaseMessageExtraPdfFile,
+ SettingsConfigValue,
+ SettingsFieldConfig,
+ SettingsConfigType,
+ SettingsChatServiceOptions
+ };
+}
--- /dev/null
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <link rel="icon" href="%sveltekit.assets%/favicon.svg" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ %sveltekit.head%
+ </head>
+ <body data-sveltekit-preload-data="hover">
+ <div style="display: contents">%sveltekit.body%</div>
+ </body>
+</html>
+++ /dev/null
-import { useEffect, useState } from 'react';
-import { useAppContext } from '../utils/app.context';
-import { OpenInNewTab, XCloseButton } from '../utils/common';
-import { CanvasType } from '../utils/types';
-import { PlayIcon, StopIcon } from '@heroicons/react/24/outline';
-import StorageUtils from '../utils/storage';
-
-const canInterrupt = typeof SharedArrayBuffer === 'function';
-
-// adapted from https://pyodide.org/en/stable/usage/webworker.html
-const WORKER_CODE = `
-importScripts("https://cdn.jsdelivr.net/pyodide/v0.27.2/full/pyodide.js");
-
-let stdOutAndErr = [];
-
-let pyodideReadyPromise = loadPyodide({
- stdout: (data) => stdOutAndErr.push(data),
- stderr: (data) => stdOutAndErr.push(data),
-});
-
-let alreadySetBuff = false;
-
-self.onmessage = async (event) => {
- stdOutAndErr = [];
-
- // make sure loading is done
- const pyodide = await pyodideReadyPromise;
- const { id, python, context, interruptBuffer } = event.data;
-
- if (interruptBuffer && !alreadySetBuff) {
- pyodide.setInterruptBuffer(interruptBuffer);
- alreadySetBuff = true;
- }
-
- // Now load any packages we need, run the code, and send the result back.
- await pyodide.loadPackagesFromImports(python);
-
- // make a Python dictionary with the data from content
- const dict = pyodide.globals.get("dict");
- const globals = dict(Object.entries(context));
- try {
- self.postMessage({ id, running: true });
- // Execute the python code in this context
- const result = pyodide.runPython(python, { globals });
- self.postMessage({ result, id, stdOutAndErr });
- } catch (error) {
- self.postMessage({ error: error.message, id });
- }
- interruptBuffer[0] = 0;
-};
-`;
-
-let worker: Worker;
-const interruptBuffer = canInterrupt
- ? new Uint8Array(new SharedArrayBuffer(1))
- : null;
-
-const startWorker = () => {
- if (!worker) {
- worker = new Worker(
- URL.createObjectURL(new Blob([WORKER_CODE], { type: 'text/javascript' }))
- );
- }
-};
-
-if (StorageUtils.getConfig().pyIntepreterEnabled) {
- startWorker();
-}
-
-const runCodeInWorker = (
- pyCode: string,
- callbackRunning: () => void
-): {
- donePromise: Promise<string>;
- interrupt: () => void;
-} => {
- startWorker();
- const id = Math.random() * 1e8;
- const context = {};
- if (interruptBuffer) {
- interruptBuffer[0] = 0;
- }
-
- const donePromise = new Promise<string>((resolve) => {
- worker.onmessage = (event) => {
- const { error, stdOutAndErr, running } = event.data;
- if (id !== event.data.id) return;
- if (running) {
- callbackRunning();
- return;
- } else if (error) {
- resolve(error.toString());
- } else {
- resolve(stdOutAndErr.join('\n'));
- }
- };
- worker.postMessage({ id, python: pyCode, context, interruptBuffer });
- });
-
- const interrupt = () => {
- console.log('Interrupting...');
- console.trace();
- if (interruptBuffer) {
- interruptBuffer[0] = 2;
- }
- };
-
- return { donePromise, interrupt };
-};
-
-export default function CanvasPyInterpreter() {
- const { canvasData, setCanvasData } = useAppContext();
-
- const [code, setCode] = useState(canvasData?.content ?? ''); // copy to avoid direct mutation
- const [running, setRunning] = useState(false);
- const [output, setOutput] = useState('');
- const [interruptFn, setInterruptFn] = useState<() => void>();
- const [showStopBtn, setShowStopBtn] = useState(false);
-
- const runCode = async (pycode: string) => {
- interruptFn?.();
- setRunning(true);
- setOutput('Loading Pyodide...');
- const { donePromise, interrupt } = runCodeInWorker(pycode, () => {
- setOutput('Running...');
- setShowStopBtn(canInterrupt);
- });
- setInterruptFn(() => interrupt);
- const out = await donePromise;
- setOutput(out);
- setRunning(false);
- setShowStopBtn(false);
- };
-
- // run code on mount
- useEffect(() => {
- setCode(canvasData?.content ?? '');
- runCode(canvasData?.content ?? '');
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [canvasData?.content]);
-
- if (canvasData?.type !== CanvasType.PY_INTERPRETER) {
- return null;
- }
-
- return (
- <div className="card bg-base-200 w-full h-full shadow-xl">
- <div className="card-body">
- <div className="flex justify-between items-center mb-4">
- <span className="text-lg font-bold">Python Interpreter</span>
- <XCloseButton
- className="bg-base-100"
- onClick={() => setCanvasData(null)}
- />
- </div>
- <div className="grid grid-rows-3 gap-4 h-full">
- <textarea
- className="textarea textarea-bordered w-full h-full font-mono"
- value={code}
- onChange={(e) => setCode(e.target.value)}
- ></textarea>
- <div className="font-mono flex flex-col row-span-2">
- <div className="flex items-center mb-2">
- <button
- className="btn btn-sm bg-base-100"
- onClick={() => runCode(code)}
- disabled={running}
- >
- <PlayIcon className="h-6 w-6" /> Run
- </button>
- {showStopBtn && (
- <button
- className="btn btn-sm bg-base-100 ml-2"
- onClick={() => interruptFn?.()}
- >
- <StopIcon className="h-6 w-6" /> Stop
- </button>
- )}
- <span className="grow text-right text-xs">
- <OpenInNewTab href="https://github.com/ggerganov/llama.cpp/issues/11762">
- Report a bug
- </OpenInNewTab>
- </span>
- </div>
- <textarea
- className="textarea textarea-bordered h-full dark-color"
- value={output}
- readOnly
- ></textarea>
- </div>
- </div>
- </div>
- </div>
- );
-}
+++ /dev/null
-import {
- DocumentTextIcon,
- SpeakerWaveIcon,
- XMarkIcon,
-} from '@heroicons/react/24/outline';
-import { MessageExtra } from '../utils/types';
-import { useState } from 'react';
-import { classNames } from '../utils/misc';
-
-export default function ChatInputExtraContextItem({
- items,
- removeItem,
- clickToShow,
-}: {
- items?: MessageExtra[];
- removeItem?: (index: number) => void;
- clickToShow?: boolean;
-}) {
- const [show, setShow] = useState(-1);
- const showingItem = show >= 0 ? items?.[show] : undefined;
-
- if (!items) return null;
-
- return (
- <div
- className="flex flex-row gap-4 overflow-x-auto py-2 px-1 mb-1"
- role="group"
- aria-description="Selected files"
- >
- {items.map((item, i) => (
- <div
- className="indicator"
- key={i}
- onClick={() => clickToShow && setShow(i)}
- tabIndex={0}
- aria-description={
- clickToShow ? `Click to show: ${item.name}` : undefined
- }
- role={clickToShow ? 'button' : 'menuitem'}
- >
- {removeItem && (
- <div className="indicator-item indicator-top">
- <button
- aria-label="Remove file"
- className="btn btn-neutral btn-sm w-4 h-4 p-0 rounded-full"
- onClick={() => removeItem(i)}
- >
- <XMarkIcon className="h-3 w-3" />
- </button>
- </div>
- )}
-
- <div
- className={classNames({
- 'flex flex-row rounded-md shadow-sm items-center m-0 p-0': true,
- 'cursor-pointer hover:shadow-md': !!clickToShow,
- })}
- >
- {item.type === 'imageFile' ? (
- <>
- <img
- src={item.base64Url}
- alt={`Preview image for ${item.name}`}
- className="w-14 h-14 object-cover rounded-md"
- />
- </>
- ) : (
- <>
- <div
- className="w-14 h-14 flex items-center justify-center"
- aria-description="Document icon"
- >
- {item.type === 'audioFile' ? (
- <SpeakerWaveIcon className="h-8 w-8 text-gray-500" />
- ) : (
- <DocumentTextIcon className="h-8 w-8 text-gray-500" />
- )}
- </div>
-
- <div className="text-xs pr-4">
- <b>{item.name ?? 'Extra content'}</b>
- </div>
- </>
- )}
- </div>
- </div>
- ))}
-
- {showingItem && (
- <dialog
- className="modal modal-open"
- aria-description={`Preview ${showingItem.name}`}
- >
- <div className="modal-box">
- <div className="flex justify-between items-center mb-4">
- <b>{showingItem.name ?? 'Extra content'}</b>
- <button
- className="btn btn-ghost btn-sm"
- aria-label="Close preview dialog"
- >
- <XMarkIcon className="h-5 w-5" onClick={() => setShow(-1)} />
- </button>
- </div>
- {showingItem.type === 'imageFile' ? (
- <img
- src={showingItem.base64Url}
- alt={`Preview image for ${showingItem.name}`}
- />
- ) : showingItem.type === 'audioFile' ? (
- <audio
- controls
- className="w-full"
- aria-description={`Audio file ${showingItem.name}`}
- >
- <source
- src={`data:${showingItem.mimeType};base64,${showingItem.base64Data}`}
- type={showingItem.mimeType}
- aria-description={`Audio file ${showingItem.name}`}
- />
- Your browser does not support the audio element.
- </audio>
- ) : (
- <div className="overflow-x-auto">
- <pre className="whitespace-pre-wrap break-words text-sm">
- {showingItem.content}
- </pre>
- </div>
- )}
- </div>
- <div className="modal-backdrop" onClick={() => setShow(-1)}></div>
- </dialog>
- )}
- </div>
- );
-}
+++ /dev/null
-import { useMemo, useState } from 'react';
-import { useAppContext } from '../utils/app.context';
-import { Message, PendingMessage } from '../utils/types';
-import { classNames } from '../utils/misc';
-import MarkdownDisplay, { CopyButton } from './MarkdownDisplay';
-import {
- ArrowPathIcon,
- ChevronLeftIcon,
- ChevronRightIcon,
- PencilSquareIcon,
-} from '@heroicons/react/24/outline';
-import ChatInputExtraContextItem from './ChatInputExtraContextItem';
-import { BtnWithTooltips } from '../utils/common';
-
-interface SplitMessage {
- content: PendingMessage['content'];
- thought?: string;
- isThinking?: boolean;
-}
-
-export default function ChatMessage({
- msg,
- siblingLeafNodeIds,
- siblingCurrIdx,
- id,
- onRegenerateMessage,
- onEditMessage,
- onChangeSibling,
- isPending,
-}: {
- msg: Message | PendingMessage;
- siblingLeafNodeIds: Message['id'][];
- siblingCurrIdx: number;
- id?: string;
- onRegenerateMessage(msg: Message): void;
- onEditMessage(msg: Message, content: string): void;
- onChangeSibling(sibling: Message['id']): void;
- isPending?: boolean;
-}) {
- const { viewingChat, config } = useAppContext();
- const [editingContent, setEditingContent] = useState<string | null>(null);
- const timings = useMemo(
- () =>
- msg.timings
- ? {
- ...msg.timings,
- prompt_per_second:
- (msg.timings.prompt_n / msg.timings.prompt_ms) * 1000,
- predicted_per_second:
- (msg.timings.predicted_n / msg.timings.predicted_ms) * 1000,
- }
- : null,
- [msg.timings]
- );
- const nextSibling = siblingLeafNodeIds[siblingCurrIdx + 1];
- const prevSibling = siblingLeafNodeIds[siblingCurrIdx - 1];
-
- // for reasoning model, we split the message into content and thought
- // TODO: implement this as remark/rehype plugin in the future
- const { content, thought, isThinking }: SplitMessage = useMemo(() => {
- if (msg.content === null || msg.role !== 'assistant') {
- return { content: msg.content };
- }
- const REGEX_THINK_OPEN = /<think>|<\|channel\|>analysis<\|message\|>/;
- const REGEX_THINK_CLOSE = /<\/think>|<\|end\|>/;
- let actualContent = '';
- let thought = '';
- let isThinking = false;
- let thinkSplit = msg.content.split(REGEX_THINK_OPEN, 2);
- actualContent += thinkSplit[0];
- while (thinkSplit[1] !== undefined) {
- // <think> tag found
- thinkSplit = thinkSplit[1].split(REGEX_THINK_CLOSE, 2);
- thought += thinkSplit[0];
- isThinking = true;
- if (thinkSplit[1] !== undefined) {
- // </think> closing tag found
- isThinking = false;
- thinkSplit = thinkSplit[1].split(REGEX_THINK_OPEN, 2);
- actualContent += thinkSplit[0];
- }
- }
- return { content: actualContent, thought, isThinking };
- }, [msg]);
-
- if (!viewingChat) return null;
-
- const isUser = msg.role === 'user';
-
- return (
- <div
- className="group"
- id={id}
- role="group"
- aria-description={`Message from ${msg.role}`}
- >
- <div
- className={classNames({
- chat: true,
- 'chat-start': !isUser,
- 'chat-end': isUser,
- })}
- >
- {msg.extra && msg.extra.length > 0 && (
- <ChatInputExtraContextItem items={msg.extra} clickToShow />
- )}
-
- <div
- className={classNames({
- 'chat-bubble markdown': true,
- 'chat-bubble bg-transparent': !isUser,
- })}
- >
- {/* textarea for editing message */}
- {editingContent !== null && (
- <>
- <textarea
- dir="auto"
- className="textarea textarea-bordered bg-base-100 text-base-content max-w-2xl w-[calc(90vw-8em)] h-24"
- value={editingContent}
- onChange={(e) => setEditingContent(e.target.value)}
- ></textarea>
- <br />
- <button
- className="btn btn-ghost mt-2 mr-2"
- onClick={() => setEditingContent(null)}
- >
- Cancel
- </button>
- <button
- className="btn mt-2"
- onClick={() => {
- if (msg.content !== null) {
- setEditingContent(null);
- onEditMessage(msg as Message, editingContent);
- }
- }}
- >
- Submit
- </button>
- </>
- )}
- {/* not editing content, render message */}
- {editingContent === null && (
- <>
- {content === null ? (
- <>
- {/* show loading dots for pending message */}
- <span className="loading loading-dots loading-md"></span>
- </>
- ) : (
- <>
- {/* render message as markdown */}
- <div dir="auto" tabIndex={0}>
- {thought && (
- <ThoughtProcess
- isThinking={!!isThinking && !!isPending}
- content={thought}
- open={config.showThoughtInProgress}
- />
- )}
-
- <MarkdownDisplay
- content={content}
- isGenerating={isPending}
- />
- </div>
- </>
- )}
- {/* render timings if enabled */}
- {timings && config.showTokensPerSecond && (
- <div className="dropdown dropdown-hover dropdown-top mt-2">
- <div
- tabIndex={0}
- role="button"
- className="cursor-pointer font-semibold text-sm opacity-60"
- >
- Speed: {timings.predicted_per_second.toFixed(1)} t/s
- </div>
- <div className="dropdown-content bg-base-100 z-10 w-64 p-2 shadow mt-4">
- <b>Prompt</b>
- <br />- Tokens: {timings.prompt_n}
- <br />- Time: {timings.prompt_ms} ms
- <br />- Speed: {timings.prompt_per_second.toFixed(1)} t/s
- <br />
- <b>Generation</b>
- <br />- Tokens: {timings.predicted_n}
- <br />- Time: {timings.predicted_ms} ms
- <br />- Speed: {timings.predicted_per_second.toFixed(1)} t/s
- <br />
- </div>
- </div>
- )}
- </>
- )}
- </div>
- </div>
-
- {/* actions for each message */}
- {msg.content !== null && (
- <div
- className={classNames({
- 'flex items-center gap-2 mx-4 mt-2 mb-2': true,
- 'flex-row-reverse': msg.role === 'user',
- })}
- >
- {siblingLeafNodeIds && siblingLeafNodeIds.length > 1 && (
- <div
- className="flex gap-1 items-center opacity-60 text-sm"
- role="navigation"
- aria-description={`Message version ${siblingCurrIdx + 1} of ${siblingLeafNodeIds.length}`}
- >
- <button
- className={classNames({
- 'btn btn-sm btn-ghost p-1': true,
- 'opacity-20': !prevSibling,
- })}
- onClick={() => prevSibling && onChangeSibling(prevSibling)}
- aria-label="Previous message version"
- >
- <ChevronLeftIcon className="h-4 w-4" />
- </button>
- <span>
- {siblingCurrIdx + 1} / {siblingLeafNodeIds.length}
- </span>
- <button
- className={classNames({
- 'btn btn-sm btn-ghost p-1': true,
- 'opacity-20': !nextSibling,
- })}
- onClick={() => nextSibling && onChangeSibling(nextSibling)}
- aria-label="Next message version"
- >
- <ChevronRightIcon className="h-4 w-4" />
- </button>
- </div>
- )}
- {/* user message */}
- {msg.role === 'user' && (
- <BtnWithTooltips
- className="btn-mini w-8 h-8"
- onClick={() => setEditingContent(msg.content)}
- disabled={msg.content === null}
- tooltipsContent="Edit message"
- >
- <PencilSquareIcon className="h-4 w-4" />
- </BtnWithTooltips>
- )}
- {/* assistant message */}
- {msg.role === 'assistant' && (
- <>
- {!isPending && (
- <BtnWithTooltips
- className="btn-mini w-8 h-8"
- onClick={() => {
- if (msg.content !== null) {
- onRegenerateMessage(msg as Message);
- }
- }}
- disabled={msg.content === null}
- tooltipsContent="Regenerate response"
- >
- <ArrowPathIcon className="h-4 w-4" />
- </BtnWithTooltips>
- )}
- </>
- )}
- <CopyButton className="btn-mini w-8 h-8" content={msg.content} />
- </div>
- )}
- </div>
- );
-}
-
-function ThoughtProcess({
- isThinking,
- content,
- open,
-}: {
- isThinking: boolean;
- content: string;
- open: boolean;
-}) {
- return (
- <div
- role="button"
- aria-label="Toggle thought process display"
- tabIndex={0}
- className={classNames({
- 'collapse bg-none': true,
- })}
- >
- <input type="checkbox" defaultChecked={open} />
- <div className="collapse-title px-0">
- <div className="btn rounded-xl">
- {isThinking ? (
- <span>
- <span
- className="loading loading-spinner loading-md mr-2"
- style={{ verticalAlign: 'middle' }}
- ></span>
- Thinking
- </span>
- ) : (
- <>Thought Process</>
- )}
- </div>
- </div>
- <div
- className="collapse-content text-base-content/70 text-sm p-1"
- tabIndex={0}
- aria-description="Thought process content"
- >
- <div className="border-l-2 border-base-content/20 pl-4 mb-4">
- <MarkdownDisplay content={content} />
- </div>
- </div>
- </div>
- );
-}
+++ /dev/null
-import { ClipboardEvent, useEffect, useMemo, useRef, useState } from 'react';
-import { CallbackGeneratedChunk, useAppContext } from '../utils/app.context';
-import ChatMessage from './ChatMessage';
-import { CanvasType, Message, PendingMessage } from '../utils/types';
-import { classNames, cleanCurrentUrl } from '../utils/misc';
-import CanvasPyInterpreter from './CanvasPyInterpreter';
-import StorageUtils from '../utils/storage';
-import { useVSCodeContext } from '../utils/llama-vscode';
-import { useChatTextarea, ChatTextareaApi } from './useChatTextarea.ts';
-import {
- ArrowUpIcon,
- StopIcon,
- PaperClipIcon,
-} from '@heroicons/react/24/solid';
-import {
- ChatExtraContextApi,
- useChatExtraContext,
-} from './useChatExtraContext.tsx';
-import Dropzone from 'react-dropzone';
-import toast from 'react-hot-toast';
-import ChatInputExtraContextItem from './ChatInputExtraContextItem.tsx';
-import { scrollToBottom, useChatScroll } from './useChatScroll.tsx';
-
-/**
- * A message display is a message node with additional information for rendering.
- * For example, siblings of the message node are stored as their last node (aka leaf node).
- */
-export interface MessageDisplay {
- msg: Message | PendingMessage;
- siblingLeafNodeIds: Message['id'][];
- siblingCurrIdx: number;
- isPending?: boolean;
-}
-
-/**
- * If the current URL contains "?m=...", prefill the message input with the value.
- * If the current URL contains "?q=...", prefill and SEND the message.
- */
-const prefilledMsg = {
- content() {
- const url = new URL(window.location.href);
- return url.searchParams.get('m') ?? url.searchParams.get('q') ?? '';
- },
- shouldSend() {
- const url = new URL(window.location.href);
- return url.searchParams.has('q');
- },
- clear() {
- cleanCurrentUrl(['m', 'q']);
- },
-};
-
-function getListMessageDisplay(
- msgs: Readonly<Message[]>,
- leafNodeId: Message['id']
-): MessageDisplay[] {
- const currNodes = StorageUtils.filterByLeafNodeId(msgs, leafNodeId, true);
- const res: MessageDisplay[] = [];
- const nodeMap = new Map<Message['id'], Message>();
- for (const msg of msgs) {
- nodeMap.set(msg.id, msg);
- }
- // find leaf node from a message node
- const findLeafNode = (msgId: Message['id']): Message['id'] => {
- let currNode: Message | undefined = nodeMap.get(msgId);
- while (currNode) {
- if (currNode.children.length === 0) break;
- currNode = nodeMap.get(currNode.children.at(-1) ?? -1);
- }
- return currNode?.id ?? -1;
- };
- // traverse the current nodes
- for (const msg of currNodes) {
- const parentNode = nodeMap.get(msg.parent ?? -1);
- if (!parentNode) continue;
- const siblings = parentNode.children;
- if (msg.type !== 'root') {
- res.push({
- msg,
- siblingLeafNodeIds: siblings.map(findLeafNode),
- siblingCurrIdx: siblings.indexOf(msg.id),
- });
- }
- }
- return res;
-}
-
-export default function ChatScreen() {
- const {
- viewingChat,
- sendMessage,
- isGenerating,
- stopGenerating,
- pendingMessages,
- canvasData,
- replaceMessageAndGenerate,
- } = useAppContext();
-
- const textarea: ChatTextareaApi = useChatTextarea(prefilledMsg.content());
- const extraContext = useChatExtraContext();
- useVSCodeContext(textarea, extraContext);
-
- const msgListRef = useRef<HTMLDivElement>(null);
- useChatScroll(msgListRef);
-
- // keep track of leaf node for rendering
- const [currNodeId, setCurrNodeId] = useState<number>(-1);
- const messages: MessageDisplay[] = useMemo(() => {
- if (!viewingChat) return [];
- else return getListMessageDisplay(viewingChat.messages, currNodeId);
- }, [currNodeId, viewingChat]);
-
- const currConvId = viewingChat?.conv.id ?? null;
- const pendingMsg: PendingMessage | undefined =
- pendingMessages[currConvId ?? ''];
-
- useEffect(() => {
- // reset to latest node when conversation changes
- setCurrNodeId(-1);
- // scroll to bottom when conversation changes
- scrollToBottom(false, 1);
- }, [currConvId]);
-
- const onChunk: CallbackGeneratedChunk = (currLeafNodeId?: Message['id']) => {
- if (currLeafNodeId) {
- setCurrNodeId(currLeafNodeId);
- }
- // useChatScroll will handle the auto scroll
- };
-
- const sendNewMessage = async () => {
- const lastInpMsg = textarea.value();
- if (lastInpMsg.trim().length === 0 || isGenerating(currConvId ?? '')) {
- toast.error('Please enter a message');
- return;
- }
- textarea.setValue('');
- scrollToBottom(false);
- setCurrNodeId(-1);
- // get the last message node
- const lastMsgNodeId = messages.at(-1)?.msg.id ?? null;
- if (
- !(await sendMessage(
- currConvId,
- lastMsgNodeId,
- lastInpMsg,
- extraContext.items,
- onChunk
- ))
- ) {
- // restore the input message if failed
- textarea.setValue(lastInpMsg);
- }
- // OK
- extraContext.clearItems();
- };
-
- // for vscode context
- textarea.refOnSubmit.current = sendNewMessage;
-
- const handleEditMessage = async (msg: Message, content: string) => {
- if (!viewingChat) return;
- setCurrNodeId(msg.id);
- scrollToBottom(false);
- await replaceMessageAndGenerate(
- viewingChat.conv.id,
- msg.parent,
- content,
- msg.extra,
- onChunk
- );
- setCurrNodeId(-1);
- scrollToBottom(false);
- };
-
- const handleRegenerateMessage = async (msg: Message) => {
- if (!viewingChat) return;
- setCurrNodeId(msg.parent);
- scrollToBottom(false);
- await replaceMessageAndGenerate(
- viewingChat.conv.id,
- msg.parent,
- null,
- msg.extra,
- onChunk
- );
- setCurrNodeId(-1);
- scrollToBottom(false);
- };
-
- const hasCanvas = !!canvasData;
-
- useEffect(() => {
- if (prefilledMsg.shouldSend()) {
- // send the prefilled message if needed
- sendNewMessage();
- } else {
- // otherwise, focus on the input
- textarea.focus();
- }
- prefilledMsg.clear();
- // no need to keep track of sendNewMessage
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [textarea.ref]);
-
- // due to some timing issues of StorageUtils.appendMsg(), we need to make sure the pendingMsg is not duplicated upon rendering (i.e. appears once in the saved conversation and once in the pendingMsg)
- const pendingMsgDisplay: MessageDisplay[] =
- pendingMsg && messages.at(-1)?.msg.id !== pendingMsg.id
- ? [
- {
- msg: pendingMsg,
- siblingLeafNodeIds: [],
- siblingCurrIdx: 0,
- isPending: true,
- },
- ]
- : [];
-
- return (
- <div
- className={classNames({
- 'grid lg:gap-8 grow transition-[300ms]': true,
- 'grid-cols-[1fr_0fr] lg:grid-cols-[1fr_1fr]': hasCanvas, // adapted for mobile
- 'grid-cols-[1fr_0fr]': !hasCanvas,
- })}
- >
- <div
- className={classNames({
- 'flex flex-col w-full max-w-[900px] mx-auto': true,
- 'hidden lg:flex': hasCanvas, // adapted for mobile
- flex: !hasCanvas,
- })}
- >
- {/* chat messages */}
- <div id="messages-list" className="grow" ref={msgListRef}>
- <div className="mt-auto flex flex-col items-center">
- {/* placeholder to shift the message to the bottom */}
- {viewingChat ? (
- ''
- ) : (
- <>
- <div className="mb-4">Send a message to start</div>
- <ServerInfo />
- </>
- )}
- </div>
- {[...messages, ...pendingMsgDisplay].map((msg) => (
- <ChatMessage
- key={msg.msg.id}
- msg={msg.msg}
- siblingLeafNodeIds={msg.siblingLeafNodeIds}
- siblingCurrIdx={msg.siblingCurrIdx}
- onRegenerateMessage={handleRegenerateMessage}
- onEditMessage={handleEditMessage}
- onChangeSibling={setCurrNodeId}
- isPending={msg.isPending}
- />
- ))}
- </div>
-
- {/* chat input */}
- <ChatInput
- textarea={textarea}
- extraContext={extraContext}
- onSend={sendNewMessage}
- onStop={() => stopGenerating(currConvId ?? '')}
- isGenerating={isGenerating(currConvId ?? '')}
- />
- </div>
- <div className="w-full sticky top-[7em] h-[calc(100vh-9em)]">
- {canvasData?.type === CanvasType.PY_INTERPRETER && (
- <CanvasPyInterpreter />
- )}
- </div>
- </div>
- );
-}
-
-function ServerInfo() {
- const { serverProps } = useAppContext();
- const modalities = [];
- if (serverProps?.modalities?.audio) {
- modalities.push('audio');
- }
- if (serverProps?.modalities?.vision) {
- modalities.push('vision');
- }
- return (
- <div
- className="card card-sm shadow-sm border-1 border-base-content/20 text-base-content/70 mb-6"
- tabIndex={0}
- aria-description="Server information"
- >
- <div className="card-body">
- <b>Server Info</b>
- <p>
- <b>Model</b>: {serverProps?.model_path?.split(/(\\|\/)/).pop()}
- <br />
- <b>Build</b>: {serverProps?.build_info}
- <br />
- {modalities.length > 0 ? (
- <>
- <b>Supported modalities:</b> {modalities.join(', ')}
- </>
- ) : (
- ''
- )}
- </p>
- </div>
- </div>
- );
-}
-
-function ChatInput({
- textarea,
- extraContext,
- onSend,
- onStop,
- isGenerating,
-}: {
- textarea: ChatTextareaApi;
- extraContext: ChatExtraContextApi;
- onSend: () => void;
- onStop: () => void;
- isGenerating: boolean;
-}) {
- const { config } = useAppContext();
- const [isDrag, setIsDrag] = useState(false);
-
- return (
- <div
- role="group"
- aria-label="Chat input"
- className={classNames({
- 'flex items-end pt-8 pb-6 sticky bottom-0 bg-base-100': true,
- 'opacity-50': isDrag, // simply visual feedback to inform user that the file will be accepted
- })}
- >
- <Dropzone
- noClick
- onDrop={(files: File[]) => {
- setIsDrag(false);
- extraContext.onFileAdded(files);
- }}
- onDragEnter={() => setIsDrag(true)}
- onDragLeave={() => setIsDrag(false)}
- multiple={true}
- >
- {({ getRootProps, getInputProps }) => (
- <div
- className="flex flex-col rounded-xl border-1 border-base-content/30 p-3 w-full"
- // when a file is pasted to the input, we handle it here
- // if a text is pasted, and if it is long text, we will convert it to a file
- onPasteCapture={(e: ClipboardEvent<HTMLInputElement>) => {
- const text = e.clipboardData.getData('text/plain');
- if (
- text.length > 0 &&
- config.pasteLongTextToFileLen > 0 &&
- text.length > config.pasteLongTextToFileLen
- ) {
- // if the text is too long, we will convert it to a file
- extraContext.addItems([
- {
- type: 'context',
- name: 'Pasted Content',
- content: text,
- },
- ]);
- e.preventDefault();
- return;
- }
-
- // if a file is pasted, we will handle it here
- const files = Array.from(e.clipboardData.items)
- .filter((item) => item.kind === 'file')
- .map((item) => item.getAsFile())
- .filter((file) => file !== null);
-
- if (files.length > 0) {
- e.preventDefault();
- extraContext.onFileAdded(files);
- }
- }}
- {...getRootProps()}
- >
- {!isGenerating && (
- <ChatInputExtraContextItem
- items={extraContext.items}
- removeItem={extraContext.removeItem}
- />
- )}
-
- <div className="flex flex-row w-full">
- <textarea
- // Default (mobile): Enable vertical resize, overflow auto for scrolling if needed
- // Large screens (lg:): Disable manual resize, apply max-height for autosize limit
- className="text-md outline-none border-none w-full resize-vertical lg:resize-none lg:max-h-48 lg:overflow-y-auto" // Adjust lg:max-h-48 as needed (e.g., lg:max-h-60)
- placeholder="Type a message (Shift+Enter to add a new line)"
- ref={textarea.ref}
- onInput={textarea.onInput} // Hook's input handler (will only resize height on lg+ screens)
- onKeyDown={(e) => {
- if (e.nativeEvent.isComposing || e.keyCode === 229) return;
- if (e.key === 'Enter' && !e.shiftKey) {
- e.preventDefault();
- onSend();
- }
- }}
- id="msg-input"
- dir="auto"
- // Set a base height of 2 rows for mobile views
- // On lg+ screens, the hook will calculate and set the initial height anyway
- rows={2}
- ></textarea>
-
- {/* buttons area */}
- <div className="flex flex-row gap-2 ml-2">
- <label
- htmlFor="file-upload"
- className={classNames({
- 'btn w-8 h-8 p-0 rounded-full': true,
- 'btn-disabled': isGenerating,
- })}
- aria-label="Upload file"
- tabIndex={0}
- role="button"
- >
- <PaperClipIcon className="h-5 w-5" />
- </label>
- <input
- id="file-upload"
- type="file"
- disabled={isGenerating}
- {...getInputProps()}
- hidden
- />
- {isGenerating ? (
- <button
- className="btn btn-neutral w-8 h-8 p-0 rounded-full"
- onClick={onStop}
- >
- <StopIcon className="h-5 w-5" />
- </button>
- ) : (
- <button
- className="btn btn-primary w-8 h-8 p-0 rounded-full"
- onClick={onSend}
- aria-label="Send message"
- >
- <ArrowUpIcon className="h-5 w-5" />
- </button>
- )}
- </div>
- </div>
- </div>
- )}
- </Dropzone>
- </div>
- );
-}
+++ /dev/null
-import { useEffect, useState } from 'react';
-import StorageUtils from '../utils/storage';
-import { useAppContext } from '../utils/app.context';
-import { classNames } from '../utils/misc';
-import daisyuiThemes from 'daisyui/theme/object';
-import { THEMES } from '../Config';
-import {
- Cog8ToothIcon,
- MoonIcon,
- Bars3Icon,
-} from '@heroicons/react/24/outline';
-
-export default function Header() {
- const [selectedTheme, setSelectedTheme] = useState(StorageUtils.getTheme());
- const { setShowSettings } = useAppContext();
-
- const setTheme = (theme: string) => {
- StorageUtils.setTheme(theme);
- setSelectedTheme(theme);
- };
-
- useEffect(() => {
- document.body.setAttribute('data-theme', selectedTheme);
- document.body.setAttribute(
- 'data-color-scheme',
- daisyuiThemes[selectedTheme]?.['color-scheme'] ?? 'auto'
- );
- }, [selectedTheme]);
-
- return (
- <div className="flex flex-row items-center pt-6 pb-6 sticky top-0 z-10 bg-base-100">
- {/* open sidebar button */}
- <label htmlFor="toggle-drawer" className="btn btn-ghost lg:hidden">
- <Bars3Icon className="h-5 w-5" />
- </label>
-
- <div className="grow text-2xl font-bold ml-2">llama.cpp</div>
-
- {/* action buttons (top right) */}
- <div className="flex items-center">
- <div
- className="tooltip tooltip-bottom"
- data-tip="Settings"
- onClick={() => setShowSettings(true)}
- >
- <button className="btn" aria-hidden={true}>
- {/* settings button */}
- <Cog8ToothIcon className="w-5 h-5" />
- </button>
- </div>
-
- {/* theme controller is copied from https://daisyui.com/components/theme-controller/ */}
- <div className="tooltip tooltip-bottom" data-tip="Themes">
- <div className="dropdown dropdown-end dropdown-bottom">
- <div tabIndex={0} role="button" className="btn m-1">
- <MoonIcon className="w-5 h-5" />
- </div>
- <ul
- tabIndex={0}
- className="dropdown-content bg-base-300 rounded-box z-[1] w-52 p-2 shadow-2xl h-80 overflow-y-auto"
- >
- <li>
- <button
- className={classNames({
- 'btn btn-sm btn-block btn-ghost justify-start': true,
- 'btn-active': selectedTheme === 'auto',
- })}
- onClick={() => setTheme('auto')}
- >
- auto
- </button>
- </li>
- {THEMES.map((theme) => (
- <li key={theme}>
- <input
- type="radio"
- name="theme-dropdown"
- className="theme-controller btn btn-sm btn-block btn-ghost justify-start"
- aria-label={theme}
- value={theme}
- checked={selectedTheme === theme}
- onChange={(e) => e.target.checked && setTheme(theme)}
- />
- </li>
- ))}
- </ul>
- </div>
- </div>
- </div>
- </div>
- );
-}
+++ /dev/null
-import React, { useMemo, useState } from 'react';
-import Markdown, { ExtraProps } from 'react-markdown';
-import remarkGfm from 'remark-gfm';
-import rehypeHightlight from 'rehype-highlight';
-import rehypeKatex from 'rehype-katex';
-import remarkMath from 'remark-math';
-import remarkBreaks from 'remark-breaks';
-import 'katex/dist/katex.min.css';
-import { classNames, copyStr } from '../utils/misc';
-import { ElementContent, Root } from 'hast';
-import { visit } from 'unist-util-visit';
-import { useAppContext } from '../utils/app.context';
-import { CanvasType } from '../utils/types';
-import { BtnWithTooltips } from '../utils/common';
-import { DocumentDuplicateIcon, PlayIcon } from '@heroicons/react/24/outline';
-
-export default function MarkdownDisplay({
- content,
- isGenerating,
-}: {
- content: string;
- isGenerating?: boolean;
-}) {
- const preprocessedContent = useMemo(
- () => preprocessLaTeX(content),
- [content]
- );
- return (
- <Markdown
- remarkPlugins={[remarkGfm, remarkMath, remarkBreaks]}
- rehypePlugins={[rehypeHightlight, rehypeKatex, rehypeCustomCopyButton]}
- components={{
- button: (props) => (
- <CodeBlockButtons
- {...props}
- isGenerating={isGenerating}
- origContent={preprocessedContent}
- />
- ),
- // note: do not use "pre", "p" or other basic html elements here, it will cause the node to re-render when the message is being generated (this should be a bug with react-markdown, not sure how to fix it)
- }}
- >
- {preprocessedContent}
- </Markdown>
- );
-}
-
-const CodeBlockButtons: React.ElementType<
- React.ClassAttributes<HTMLButtonElement> &
- React.HTMLAttributes<HTMLButtonElement> &
- ExtraProps & { origContent: string; isGenerating?: boolean }
-> = ({ node, origContent, isGenerating }) => {
- const { config } = useAppContext();
- const startOffset = node?.position?.start.offset ?? 0;
- const endOffset = node?.position?.end.offset ?? 0;
-
- const copiedContent = useMemo(
- () =>
- origContent
- .substring(startOffset, endOffset)
- .replace(/^```[^\n]+\n/g, '')
- .replace(/```$/g, ''),
- [origContent, startOffset, endOffset]
- );
-
- const codeLanguage = useMemo(
- () =>
- origContent
- .substring(startOffset, startOffset + 10)
- .match(/^```([^\n]+)\n/)?.[1] ?? '',
- [origContent, startOffset]
- );
-
- const canRunCode =
- !isGenerating &&
- config.pyIntepreterEnabled &&
- codeLanguage.startsWith('py');
-
- return (
- <div
- className={classNames({
- 'text-right sticky top-[7em] mb-2 mr-2 h-0': true,
- 'display-none': !node?.position,
- })}
- >
- <CopyButton
- className="badge btn-mini btn-soft shadow-sm"
- content={copiedContent}
- />
- {canRunCode && (
- <RunPyCodeButton
- className="badge btn-mini shadow-sm ml-2"
- content={copiedContent}
- />
- )}
- </div>
- );
-};
-
-export const CopyButton = ({
- content,
- className,
-}: {
- content: string;
- className?: string;
-}) => {
- const [copied, setCopied] = useState(false);
- return (
- <BtnWithTooltips
- className={className}
- onClick={() => {
- copyStr(content);
- setCopied(true);
- }}
- onMouseLeave={() => setCopied(false)}
- tooltipsContent={copied ? 'Copied!' : 'Copy'}
- >
- <DocumentDuplicateIcon className="h-4 w-4" />
- </BtnWithTooltips>
- );
-};
-
-export const RunPyCodeButton = ({
- content,
- className,
-}: {
- content: string;
- className?: string;
-}) => {
- const { setCanvasData } = useAppContext();
- return (
- <>
- <BtnWithTooltips
- className={className}
- onClick={() =>
- setCanvasData({
- type: CanvasType.PY_INTERPRETER,
- content,
- })
- }
- tooltipsContent="Run code"
- >
- <PlayIcon className="h-4 w-4" />
- </BtnWithTooltips>
- </>
- );
-};
-
-/**
- * This injects the "button" element before each "pre" element.
- * The actual button will be replaced with a react component in the MarkdownDisplay.
- * We don't replace "pre" node directly because it will cause the node to re-render, which causes this bug: https://github.com/ggerganov/llama.cpp/issues/9608
- */
-function rehypeCustomCopyButton() {
- return function (tree: Root) {
- visit(tree, 'element', function (node) {
- if (node.tagName === 'pre' && !node.properties.visited) {
- const preNode = { ...node };
- // replace current node
- preNode.properties.visited = 'true';
- node.tagName = 'div';
- node.properties = {};
- // add node for button
- const btnNode: ElementContent = {
- type: 'element',
- tagName: 'button',
- properties: {},
- children: [],
- position: node.position,
- };
- node.children = [btnNode, preNode];
- }
- });
- };
-}
-
-/**
- * The part below is copied and adapted from:
- * https://github.com/danny-avila/LibreChat/blob/main/client/src/utils/latex.ts
- * (MIT License)
- */
-
-// Regex to check if the processed content contains any potential LaTeX patterns
-const containsLatexRegex =
- /\\\(.*?\\\)|\\\[.*?\\\]|\$.*?\$|\\begin\{equation\}.*?\\end\{equation\}/;
-
-// Regex for inline and block LaTeX expressions
-const inlineLatex = new RegExp(/\\\((.+?)\\\)/, 'g');
-const blockLatex = new RegExp(/\\\[(.*?[^\\])\\\]/, 'gs');
-
-// Function to restore code blocks
-const restoreCodeBlocks = (content: string, codeBlocks: string[]) => {
- return content.replace(
- /<<CODE_BLOCK_(\d+)>>/g,
- (_, index) => codeBlocks[index]
- );
-};
-
-// Regex to identify code blocks and inline code
-const codeBlockRegex = /(```[\s\S]*?```|`.*?`)/g;
-
-export const processLaTeX = (_content: string) => {
- let content = _content;
- // Temporarily replace code blocks and inline code with placeholders
- const codeBlocks: string[] = [];
- let index = 0;
- content = content.replace(codeBlockRegex, (match) => {
- codeBlocks[index] = match;
- return `<<CODE_BLOCK_${index++}>>`;
- });
-
- // Escape dollar signs followed by a digit or space and digit
- let processedContent = content.replace(/(\$)(?=\s?\d)/g, '\\$');
-
- // If no LaTeX patterns are found, restore code blocks and return the processed content
- if (!containsLatexRegex.test(processedContent)) {
- return restoreCodeBlocks(processedContent, codeBlocks);
- }
-
- // Convert LaTeX expressions to a markdown compatible format
- processedContent = processedContent
- .replace(inlineLatex, (_: string, equation: string) => `$${equation}$`) // Convert inline LaTeX
- .replace(blockLatex, (_: string, equation: string) => `$$${equation}$$`); // Convert block LaTeX
-
- // Restore code blocks
- return restoreCodeBlocks(processedContent, codeBlocks);
-};
-
-/**
- * Preprocesses LaTeX content by replacing delimiters and escaping certain characters.
- *
- * @param content The input string containing LaTeX expressions.
- * @returns The processed string with replaced delimiters and escaped characters.
- */
-export function preprocessLaTeX(content: string): string {
- // Step 1: Protect code blocks
- const codeBlocks: string[] = [];
- content = content.replace(/(```[\s\S]*?```|`[^`\n]+`)/g, (_, code) => {
- codeBlocks.push(code);
- return `<<CODE_BLOCK_${codeBlocks.length - 1}>>`;
- });
-
- // Step 2: Protect existing LaTeX expressions
- const latexExpressions: string[] = [];
-
- // Protect block math ($$...$$), \[...\], and \(...\) as before.
- content = content.replace(
- /(\$\$[\s\S]*?\$\$|\\\[[\s\S]*?\\\]|\\\(.*?\\\))/g,
- (match) => {
- latexExpressions.push(match);
- return `<<LATEX_${latexExpressions.length - 1}>>`;
- }
- );
-
- // Protect inline math ($...$) only if it does NOT match a currency pattern.
- // We assume a currency pattern is one where the inner content is purely numeric (with optional decimals).
- content = content.replace(/\$([^$]+)\$/g, (match, inner) => {
- if (/^\s*\d+(?:\.\d+)?\s*$/.test(inner)) {
- // This looks like a currency value (e.g. "$123" or "$12.34"),
- // so don't protect it.
- return match;
- } else {
- // Otherwise, treat it as a LaTeX expression.
- latexExpressions.push(match);
- return `<<LATEX_${latexExpressions.length - 1}>>`;
- }
- });
-
- // Step 3: Escape dollar signs that are likely currency indicators.
- // (Now that inline math is protected, this will only escape dollars not already protected)
- content = content.replace(/\$(?=\d)/g, '\\$');
-
- // Step 4: Restore LaTeX expressions
- content = content.replace(
- /<<LATEX_(\d+)>>/g,
- (_, index) => latexExpressions[parseInt(index)]
- );
-
- // Step 5: Restore code blocks
- content = content.replace(
- /<<CODE_BLOCK_(\d+)>>/g,
- (_, index) => codeBlocks[parseInt(index)]
- );
-
- // Step 6: Apply additional escaping functions
- content = escapeBrackets(content);
- content = escapeMhchem(content);
-
- return content;
-}
-
-export function escapeBrackets(text: string): string {
- const pattern =
- /(```[\S\s]*?```|`.*?`)|\\\[([\S\s]*?[^\\])\\]|\\\((.*?)\\\)/g;
- return text.replace(
- pattern,
- (
- match: string,
- codeBlock: string | undefined,
- squareBracket: string | undefined,
- roundBracket: string | undefined
- ): string => {
- if (codeBlock != null) {
- return codeBlock;
- } else if (squareBracket != null) {
- return `$$${squareBracket}$$`;
- } else if (roundBracket != null) {
- return `$${roundBracket}$`;
- }
- return match;
- }
- );
-}
-
-export function escapeMhchem(text: string) {
- return text.replaceAll('$\\ce{', '$\\\\ce{').replaceAll('$\\pu{', '$\\\\pu{');
-}
+++ /dev/null
-import React, { createContext, useState, useContext } from 'react';
-
-type ModalContextType = {
- showConfirm: (message: string) => Promise<boolean>;
- showPrompt: (
- message: string,
- defaultValue?: string
- ) => Promise<string | undefined>;
- showAlert: (message: string) => Promise<void>;
-};
-const ModalContext = createContext<ModalContextType>(null!);
-
-interface ModalState<T> {
- isOpen: boolean;
- message: string;
- defaultValue?: string;
- resolve: ((value: T) => void) | null;
-}
-
-export function ModalProvider({ children }: { children: React.ReactNode }) {
- const [confirmState, setConfirmState] = useState<ModalState<boolean>>({
- isOpen: false,
- message: '',
- resolve: null,
- });
- const [promptState, setPromptState] = useState<
- ModalState<string | undefined>
- >({ isOpen: false, message: '', resolve: null });
- const [alertState, setAlertState] = useState<ModalState<void>>({
- isOpen: false,
- message: '',
- resolve: null,
- });
- const inputRef = React.useRef<HTMLInputElement>(null);
-
- const showConfirm = (message: string): Promise<boolean> => {
- return new Promise((resolve) => {
- setConfirmState({ isOpen: true, message, resolve });
- });
- };
-
- const showPrompt = (
- message: string,
- defaultValue?: string
- ): Promise<string | undefined> => {
- return new Promise((resolve) => {
- setPromptState({ isOpen: true, message, defaultValue, resolve });
- });
- };
-
- const showAlert = (message: string): Promise<void> => {
- return new Promise((resolve) => {
- setAlertState({ isOpen: true, message, resolve });
- });
- };
-
- const handleConfirm = (result: boolean) => {
- confirmState.resolve?.(result);
- setConfirmState({ isOpen: false, message: '', resolve: null });
- };
-
- const handlePrompt = (result?: string) => {
- promptState.resolve?.(result);
- setPromptState({ isOpen: false, message: '', resolve: null });
- };
-
- const handleAlertClose = () => {
- alertState.resolve?.();
- setAlertState({ isOpen: false, message: '', resolve: null });
- };
-
- return (
- <ModalContext.Provider value={{ showConfirm, showPrompt, showAlert }}>
- {children}
-
- {/* Confirm Modal */}
- {confirmState.isOpen && (
- <dialog className="modal modal-open z-[1100]">
- <div className="modal-box">
- <h3 className="font-bold text-lg">{confirmState.message}</h3>
- <div className="modal-action">
- <button
- className="btn btn-ghost"
- onClick={() => handleConfirm(false)}
- >
- Cancel
- </button>
- <button
- className="btn btn-error"
- onClick={() => handleConfirm(true)}
- >
- Confirm
- </button>
- </div>
- </div>
- </dialog>
- )}
-
- {/* Prompt Modal */}
- {promptState.isOpen && (
- <dialog className="modal modal-open z-[1100]">
- <div className="modal-box">
- <h3 className="font-bold text-lg">{promptState.message}</h3>
- <input
- type="text"
- className="input input-bordered w-full mt-2"
- defaultValue={promptState.defaultValue}
- ref={inputRef}
- onKeyDown={(e) => {
- if (e.key === 'Enter') {
- handlePrompt((e.target as HTMLInputElement).value);
- }
- }}
- />
- <div className="modal-action">
- <button className="btn btn-ghost" onClick={() => handlePrompt()}>
- Cancel
- </button>
- <button
- className="btn btn-primary"
- onClick={() => handlePrompt(inputRef.current?.value)}
- >
- Submit
- </button>
- </div>
- </div>
- </dialog>
- )}
-
- {/* Alert Modal */}
- {alertState.isOpen && (
- <dialog className="modal modal-open z-[1100]">
- <div className="modal-box">
- <h3 className="font-bold text-lg">{alertState.message}</h3>
- <div className="modal-action">
- <button className="btn" onClick={handleAlertClose}>
- OK
- </button>
- </div>
- </div>
- </dialog>
- )}
- </ModalContext.Provider>
- );
-}
-
-export function useModals() {
- const context = useContext(ModalContext);
- if (!context) throw new Error('useModals must be used within ModalProvider');
- return context;
-}
+++ /dev/null
-import { useState } from 'react';
-import { useAppContext } from '../utils/app.context';
-import { CONFIG_DEFAULT, CONFIG_INFO } from '../Config';
-import { isDev } from '../Config';
-import StorageUtils from '../utils/storage';
-import { classNames, isBoolean, isNumeric, isString } from '../utils/misc';
-import {
- BeakerIcon,
- ChatBubbleOvalLeftEllipsisIcon,
- Cog6ToothIcon,
- FunnelIcon,
- HandRaisedIcon,
- SquaresPlusIcon,
-} from '@heroicons/react/24/outline';
-import { OpenInNewTab } from '../utils/common';
-import { useModals } from './ModalProvider';
-
-type SettKey = keyof typeof CONFIG_DEFAULT;
-
-const BASIC_KEYS: SettKey[] = [
- 'temperature',
- 'top_k',
- 'top_p',
- 'min_p',
- 'max_tokens',
-];
-const SAMPLER_KEYS: SettKey[] = [
- 'dynatemp_range',
- 'dynatemp_exponent',
- 'typical_p',
- 'xtc_probability',
- 'xtc_threshold',
-];
-const PENALTY_KEYS: SettKey[] = [
- 'repeat_last_n',
- 'repeat_penalty',
- 'presence_penalty',
- 'frequency_penalty',
- 'dry_multiplier',
- 'dry_base',
- 'dry_allowed_length',
- 'dry_penalty_last_n',
-];
-
-enum SettingInputType {
- SHORT_INPUT,
- LONG_INPUT,
- CHECKBOX,
- CUSTOM,
-}
-
-interface SettingFieldInput {
- type: Exclude<SettingInputType, SettingInputType.CUSTOM>;
- label: string | React.ReactElement;
- help?: string | React.ReactElement;
- key: SettKey;
-}
-
-interface SettingFieldCustom {
- type: SettingInputType.CUSTOM;
- key: SettKey;
- component:
- | string
- | React.FC<{
- value: string | boolean | number;
- onChange: (value: string) => void;
- }>;
-}
-
-interface SettingSection {
- title: React.ReactElement;
- fields: (SettingFieldInput | SettingFieldCustom)[];
-}
-
-const ICON_CLASSNAME = 'w-4 h-4 mr-1 inline';
-
-const SETTING_SECTIONS: SettingSection[] = [
- {
- title: (
- <>
- <Cog6ToothIcon className={ICON_CLASSNAME} />
- General
- </>
- ),
- fields: [
- {
- type: SettingInputType.SHORT_INPUT,
- label: 'API Key',
- key: 'apiKey',
- },
- {
- type: SettingInputType.LONG_INPUT,
- label: 'System Message (will be disabled if left empty)',
- key: 'systemMessage',
- },
- ...BASIC_KEYS.map(
- (key) =>
- ({
- type: SettingInputType.SHORT_INPUT,
- label: key,
- key,
- }) as SettingFieldInput
- ),
- {
- type: SettingInputType.SHORT_INPUT,
- label: 'Paste length to file',
- key: 'pasteLongTextToFileLen',
- },
- {
- type: SettingInputType.CHECKBOX,
- label: 'Parse PDF as image instead of text',
- key: 'pdfAsImage',
- },
- ],
- },
- {
- title: (
- <>
- <FunnelIcon className={ICON_CLASSNAME} />
- Samplers
- </>
- ),
- fields: [
- {
- type: SettingInputType.SHORT_INPUT,
- label: 'Samplers queue',
- key: 'samplers',
- },
- ...SAMPLER_KEYS.map(
- (key) =>
- ({
- type: SettingInputType.SHORT_INPUT,
- label: key,
- key,
- }) as SettingFieldInput
- ),
- ],
- },
- {
- title: (
- <>
- <HandRaisedIcon className={ICON_CLASSNAME} />
- Penalties
- </>
- ),
- fields: PENALTY_KEYS.map((key) => ({
- type: SettingInputType.SHORT_INPUT,
- label: key,
- key,
- })),
- },
- {
- title: (
- <>
- <ChatBubbleOvalLeftEllipsisIcon className={ICON_CLASSNAME} />
- Reasoning
- </>
- ),
- fields: [
- {
- type: SettingInputType.CHECKBOX,
- label: 'Expand thought process by default when generating messages',
- key: 'showThoughtInProgress',
- },
- {
- type: SettingInputType.CHECKBOX,
- label:
- 'Exclude thought process when sending requests to API (Recommended for DeepSeek-R1)',
- key: 'excludeThoughtOnReq',
- },
- ],
- },
- {
- title: (
- <>
- <SquaresPlusIcon className={ICON_CLASSNAME} />
- Advanced
- </>
- ),
- fields: [
- {
- type: SettingInputType.CUSTOM,
- key: 'custom', // dummy key, won't be used
- component: () => {
- const debugImportDemoConv = async () => {
- const res = await fetch('/demo-conversation.json');
- const demoConv = await res.json();
- StorageUtils.remove(demoConv.id);
- for (const msg of demoConv.messages) {
- StorageUtils.appendMsg(demoConv.id, msg);
- }
- };
- return (
- <button className="btn" onClick={debugImportDemoConv}>
- (debug) Import demo conversation
- </button>
- );
- },
- },
- {
- type: SettingInputType.CHECKBOX,
- label: 'Show tokens per second',
- key: 'showTokensPerSecond',
- },
- {
- type: SettingInputType.LONG_INPUT,
- label: (
- <>
- Custom JSON config (For more info, refer to{' '}
- <OpenInNewTab href="https://github.com/ggerganov/llama.cpp/blob/master/tools/server/README.md">
- server documentation
- </OpenInNewTab>
- )
- </>
- ),
- key: 'custom',
- },
- ],
- },
- {
- title: (
- <>
- <BeakerIcon className={ICON_CLASSNAME} />
- Experimental
- </>
- ),
- fields: [
- {
- type: SettingInputType.CUSTOM,
- key: 'custom', // dummy key, won't be used
- component: () => (
- <>
- <p className="mb-8">
- Experimental features are not guaranteed to work correctly.
- <br />
- <br />
- If you encounter any problems, create a{' '}
- <OpenInNewTab href="https://github.com/ggerganov/llama.cpp/issues/new?template=019-bug-misc.yml">
- Bug (misc.)
- </OpenInNewTab>{' '}
- report on Github. Please also specify <b>webui/experimental</b> on
- the report title and include screenshots.
- <br />
- <br />
- Some features may require packages downloaded from CDN, so they
- need internet connection.
- </p>
- </>
- ),
- },
- {
- type: SettingInputType.CHECKBOX,
- label: (
- <>
- <b>Enable Python interpreter</b>
- <br />
- <small className="text-xs">
- This feature uses{' '}
- <OpenInNewTab href="https://pyodide.org">pyodide</OpenInNewTab>,
- downloaded from CDN. To use this feature, ask the LLM to generate
- Python code inside a Markdown code block. You will see a "Run"
- button on the code block, near the "Copy" button.
- </small>
- </>
- ),
- key: 'pyIntepreterEnabled',
- },
- ],
- },
-];
-
-export default function SettingDialog({
- show,
- onClose,
-}: {
- show: boolean;
- onClose: () => void;
-}) {
- const { config, saveConfig } = useAppContext();
- const [sectionIdx, setSectionIdx] = useState(0);
-
- // clone the config object to prevent direct mutation
- const [localConfig, setLocalConfig] = useState<typeof CONFIG_DEFAULT>(
- JSON.parse(JSON.stringify(config))
- );
- const { showConfirm, showAlert } = useModals();
-
- const resetConfig = async () => {
- if (await showConfirm('Are you sure you want to reset all settings?')) {
- setLocalConfig(CONFIG_DEFAULT);
- }
- };
-
- const handleSave = async () => {
- // copy the local config to prevent direct mutation
- const newConfig: typeof CONFIG_DEFAULT = JSON.parse(
- JSON.stringify(localConfig)
- );
- // validate the config
- for (const key in newConfig) {
- const value = newConfig[key as SettKey];
- const mustBeBoolean = isBoolean(CONFIG_DEFAULT[key as SettKey]);
- const mustBeString = isString(CONFIG_DEFAULT[key as SettKey]);
- const mustBeNumeric = isNumeric(CONFIG_DEFAULT[key as SettKey]);
- if (mustBeString) {
- if (!isString(value)) {
- await showAlert(`Value for ${key} must be string`);
- return;
- }
- } else if (mustBeNumeric) {
- const trimmedValue = value.toString().trim();
- const numVal = Number(trimmedValue);
- if (isNaN(numVal) || !isNumeric(numVal) || trimmedValue.length === 0) {
- await showAlert(`Value for ${key} must be numeric`);
- return;
- }
- // force conversion to number
- // @ts-expect-error this is safe
- newConfig[key] = numVal;
- } else if (mustBeBoolean) {
- if (!isBoolean(value)) {
- await showAlert(`Value for ${key} must be boolean`);
- return;
- }
- } else {
- console.error(`Unknown default type for key ${key}`);
- }
- }
- if (isDev) console.log('Saving config', newConfig);
- saveConfig(newConfig);
- onClose();
- };
-
- const onChange = (key: SettKey) => (value: string | boolean) => {
- // note: we do not perform validation here, because we may get incomplete value as user is still typing it
- setLocalConfig({ ...localConfig, [key]: value });
- };
-
- return (
- <dialog
- className={classNames({ modal: true, 'modal-open': show })}
- aria-label="Settings dialog"
- >
- <div className="modal-box w-11/12 max-w-3xl">
- <h3 className="text-lg font-bold mb-6">Settings</h3>
- <div className="flex flex-col md:flex-row h-[calc(90vh-12rem)]">
- {/* Left panel, showing sections - Desktop version */}
- <div
- className="hidden md:flex flex-col items-stretch pr-4 mr-4 border-r-2 border-base-200"
- role="complementary"
- aria-description="Settings sections"
- tabIndex={0}
- >
- {SETTING_SECTIONS.map((section, idx) => (
- <button
- key={idx}
- className={classNames({
- 'btn btn-ghost justify-start font-normal w-44 mb-1': true,
- 'btn-active': sectionIdx === idx,
- })}
- onClick={() => setSectionIdx(idx)}
- dir="auto"
- >
- {section.title}
- </button>
- ))}
- </div>
-
- {/* Left panel, showing sections - Mobile version */}
- {/* This menu is skipped on a11y, otherwise it's repeated the desktop version */}
- <div
- className="md:hidden flex flex-row gap-2 mb-4"
- aria-disabled={true}
- >
- <details className="dropdown">
- <summary className="btn bt-sm w-full m-1">
- {SETTING_SECTIONS[sectionIdx].title}
- </summary>
- <ul className="menu dropdown-content bg-base-100 rounded-box z-[1] w-52 p-2 shadow">
- {SETTING_SECTIONS.map((section, idx) => (
- <div
- key={idx}
- className={classNames({
- 'btn btn-ghost justify-start font-normal': true,
- 'btn-active': sectionIdx === idx,
- })}
- onClick={() => setSectionIdx(idx)}
- dir="auto"
- >
- {section.title}
- </div>
- ))}
- </ul>
- </details>
- </div>
-
- {/* Right panel, showing setting fields */}
- <div className="grow overflow-y-auto px-4">
- {SETTING_SECTIONS[sectionIdx].fields.map((field, idx) => {
- const key = `${sectionIdx}-${idx}`;
- if (field.type === SettingInputType.SHORT_INPUT) {
- return (
- <SettingsModalShortInput
- key={key}
- configKey={field.key}
- value={localConfig[field.key]}
- onChange={onChange(field.key)}
- label={field.label as string}
- />
- );
- } else if (field.type === SettingInputType.LONG_INPUT) {
- return (
- <SettingsModalLongInput
- key={key}
- configKey={field.key}
- value={localConfig[field.key].toString()}
- onChange={onChange(field.key)}
- label={field.label as string}
- />
- );
- } else if (field.type === SettingInputType.CHECKBOX) {
- return (
- <SettingsModalCheckbox
- key={key}
- configKey={field.key}
- value={!!localConfig[field.key]}
- onChange={onChange(field.key)}
- label={field.label as string}
- />
- );
- } else if (field.type === SettingInputType.CUSTOM) {
- return (
- <div key={key} className="mb-2">
- {typeof field.component === 'string'
- ? field.component
- : field.component({
- value: localConfig[field.key],
- onChange: onChange(field.key),
- })}
- </div>
- );
- }
- })}
-
- <p className="opacity-40 mb-6 text-sm mt-8">
- Settings are saved in browser's localStorage
- </p>
- </div>
- </div>
-
- <div className="modal-action">
- <button className="btn" onClick={resetConfig}>
- Reset to default
- </button>
- <button className="btn" onClick={onClose}>
- Close
- </button>
- <button className="btn btn-primary" onClick={handleSave}>
- Save
- </button>
- </div>
- </div>
- </dialog>
- );
-}
-
-function SettingsModalLongInput({
- configKey,
- value,
- onChange,
- label,
-}: {
- configKey: SettKey;
- value: string;
- onChange: (value: string) => void;
- label?: string;
-}) {
- return (
- <label className="form-control">
- <div className="label inline text-sm">{label || configKey}</div>
- <textarea
- className="textarea textarea-bordered h-24 mb-2"
- placeholder={`Default: ${CONFIG_DEFAULT[configKey] || 'none'}`}
- value={value}
- onChange={(e) => onChange(e.target.value)}
- />
- </label>
- );
-}
-
-function SettingsModalShortInput({
- configKey,
- value,
- onChange,
- label,
-}: {
- configKey: SettKey;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- value: any;
- onChange: (value: string) => void;
- label?: string;
-}) {
- const helpMsg = CONFIG_INFO[configKey];
-
- return (
- <>
- {/* on mobile, we simply show the help message here */}
- {helpMsg && (
- <div className="block mb-1 opacity-75">
- <p className="text-xs">{helpMsg}</p>
- </div>
- )}
- <label className="input input-bordered join-item grow flex items-center gap-2 mb-2">
- <div className="dropdown dropdown-hover">
- <div tabIndex={0} role="button" className="font-bold hidden md:block">
- {label || configKey}
- </div>
- </div>
- <input
- type="text"
- className="grow"
- placeholder={`Default: ${CONFIG_DEFAULT[configKey] || 'none'}`}
- value={value}
- onChange={(e) => onChange(e.target.value)}
- />
- </label>
- </>
- );
-}
-
-function SettingsModalCheckbox({
- configKey,
- value,
- onChange,
- label,
-}: {
- configKey: SettKey;
- value: boolean;
- onChange: (value: boolean) => void;
- label: string;
-}) {
- return (
- <div className="flex flex-row items-center mb-2">
- <input
- type="checkbox"
- className="toggle"
- checked={value}
- onChange={(e) => onChange(e.target.checked)}
- />
- <span className="ml-4">{label || configKey}</span>
- </div>
- );
-}
+++ /dev/null
-import { useEffect, useMemo, useState } from 'react';
-import { classNames } from '../utils/misc';
-import { Conversation } from '../utils/types';
-import StorageUtils from '../utils/storage';
-import { useNavigate, useParams } from 'react-router';
-import {
- ArrowDownTrayIcon,
- EllipsisVerticalIcon,
- PencilIcon,
- PencilSquareIcon,
- TrashIcon,
- XMarkIcon,
-} from '@heroicons/react/24/outline';
-import { BtnWithTooltips } from '../utils/common';
-import { useAppContext } from '../utils/app.context';
-import toast from 'react-hot-toast';
-import { useModals } from './ModalProvider';
-
-export default function Sidebar() {
- const params = useParams();
- const navigate = useNavigate();
-
- const { isGenerating } = useAppContext();
-
- const [conversations, setConversations] = useState<Conversation[]>([]);
- const [currConv, setCurrConv] = useState<Conversation | null>(null);
-
- useEffect(() => {
- StorageUtils.getOneConversation(params.convId ?? '').then(setCurrConv);
- }, [params.convId]);
-
- useEffect(() => {
- const handleConversationChange = async () => {
- setConversations(await StorageUtils.getAllConversations());
- };
- StorageUtils.onConversationChanged(handleConversationChange);
- handleConversationChange();
- return () => {
- StorageUtils.offConversationChanged(handleConversationChange);
- };
- }, []);
- const { showConfirm, showPrompt } = useModals();
-
- const groupedConv = useMemo(
- () => groupConversationsByDate(conversations),
- [conversations]
- );
-
- return (
- <>
- <input
- id="toggle-drawer"
- type="checkbox"
- className="drawer-toggle"
- aria-label="Toggle sidebar"
- defaultChecked
- />
-
- <div
- className="drawer-side h-screen lg:h-screen z-50 lg:max-w-64"
- role="complementary"
- aria-label="Sidebar"
- tabIndex={0}
- >
- <label
- htmlFor="toggle-drawer"
- aria-label="Close sidebar"
- className="drawer-overlay"
- ></label>
-
- <a
- href="#main-scroll"
- className="absolute -left-80 top-0 w-1 h-1 overflow-hidden"
- >
- Skip to main content
- </a>
-
- <div className="flex flex-col bg-base-200 min-h-full max-w-64 py-4 px-4">
- <div className="flex flex-row items-center justify-between mb-4 mt-4">
- <h2 className="font-bold ml-4" role="heading">
- Conversations
- </h2>
-
- {/* close sidebar button */}
- <label
- htmlFor="toggle-drawer"
- className="btn btn-ghost lg:hidden"
- aria-label="Close sidebar"
- role="button"
- tabIndex={0}
- >
- <XMarkIcon className="w-5 h-5" />
- </label>
- </div>
-
- {/* new conversation button */}
- <button
- className={classNames({
- 'btn btn-ghost justify-start px-2': true,
- 'btn-soft': !currConv,
- })}
- onClick={() => navigate('/')}
- aria-label="New conversation"
- >
- <PencilSquareIcon className="w-5 h-5" />
- New conversation
- </button>
-
- {/* list of conversations */}
- {groupedConv.map((group, i) => (
- <div key={i} role="group">
- {/* group name (by date) */}
- {group.title ? (
- // we use btn class here to make sure that the padding/margin are aligned with the other items
- <b
- className="btn btn-ghost btn-xs bg-none btn-disabled block text-xs text-base-content text-start px-2 mb-0 mt-6 font-bold"
- role="note"
- aria-description={group.title}
- tabIndex={0}
- >
- {group.title}
- </b>
- ) : (
- <div className="h-2" />
- )}
-
- {group.conversations.map((conv) => (
- <ConversationItem
- key={conv.id}
- conv={conv}
- isCurrConv={currConv?.id === conv.id}
- onSelect={() => {
- navigate(`/chat/${conv.id}`);
- }}
- onDelete={async () => {
- if (isGenerating(conv.id)) {
- toast.error(
- 'Cannot delete conversation while generating'
- );
- return;
- }
- if (
- await showConfirm(
- 'Are you sure to delete this conversation?'
- )
- ) {
- toast.success('Conversation deleted');
- StorageUtils.remove(conv.id);
- navigate('/');
- }
- }}
- onDownload={() => {
- if (isGenerating(conv.id)) {
- toast.error(
- 'Cannot download conversation while generating'
- );
- return;
- }
- const conversationJson = JSON.stringify(conv, null, 2);
- const blob = new Blob([conversationJson], {
- type: 'application/json',
- });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = `conversation_${conv.id}.json`;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- }}
- onRename={async () => {
- if (isGenerating(conv.id)) {
- toast.error(
- 'Cannot rename conversation while generating'
- );
- return;
- }
- const newName = await showPrompt(
- 'Enter new name for the conversation',
- conv.name
- );
- if (newName && newName.trim().length > 0) {
- StorageUtils.updateConversationName(conv.id, newName);
- }
- }}
- />
- ))}
- </div>
- ))}
- <div className="text-center text-xs opacity-40 mt-auto mx-4 pt-8">
- Conversations are saved to browser's IndexedDB
- </div>
- </div>
- </div>
- </>
- );
-}
-
-function ConversationItem({
- conv,
- isCurrConv,
- onSelect,
- onDelete,
- onDownload,
- onRename,
-}: {
- conv: Conversation;
- isCurrConv: boolean;
- onSelect: () => void;
- onDelete: () => void;
- onDownload: () => void;
- onRename: () => void;
-}) {
- return (
- <div
- role="menuitem"
- tabIndex={0}
- aria-label={conv.name}
- className={classNames({
- 'group flex flex-row btn btn-ghost justify-start items-center font-normal px-2 h-9':
- true,
- 'btn-soft': isCurrConv,
- })}
- >
- <button
- key={conv.id}
- className="w-full overflow-hidden truncate text-start"
- onClick={onSelect}
- dir="auto"
- >
- {conv.name}
- </button>
- <div tabIndex={0} className="dropdown dropdown-end h-5">
- <BtnWithTooltips
- // on mobile, we always show the ellipsis icon
- // on desktop, we only show it when the user hovers over the conversation item
- // we use opacity instead of hidden to avoid layout shift
- className="cursor-pointer opacity-100 md:opacity-0 group-hover:opacity-100"
- onClick={() => {}}
- tooltipsContent="More"
- >
- <EllipsisVerticalIcon className="w-5 h-5" />
- </BtnWithTooltips>
- {/* dropdown menu */}
- <ul
- aria-label="More options"
- tabIndex={0}
- className="dropdown-content menu bg-base-100 rounded-box z-[1] p-2 shadow"
- >
- <li onClick={onRename} tabIndex={0}>
- <a>
- <PencilIcon className="w-4 h-4" />
- Rename
- </a>
- </li>
- <li onClick={onDownload} tabIndex={0}>
- <a>
- <ArrowDownTrayIcon className="w-4 h-4" />
- Download
- </a>
- </li>
- <li className="text-error" onClick={onDelete} tabIndex={0}>
- <a>
- <TrashIcon className="w-4 h-4" />
- Delete
- </a>
- </li>
- </ul>
- </div>
- </div>
- );
-}
-
-// WARN: vibe code below
-
-export interface GroupedConversations {
- title?: string;
- conversations: Conversation[];
-}
-
-// TODO @ngxson : add test for this function
-// Group conversations by date
-// - "Previous 7 Days"
-// - "Previous 30 Days"
-// - "Month Year" (e.g., "April 2023")
-export function groupConversationsByDate(
- conversations: Conversation[]
-): GroupedConversations[] {
- const now = new Date();
- const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); // Start of today
-
- const sevenDaysAgo = new Date(today);
- sevenDaysAgo.setDate(today.getDate() - 7);
-
- const thirtyDaysAgo = new Date(today);
- thirtyDaysAgo.setDate(today.getDate() - 30);
-
- const groups: { [key: string]: Conversation[] } = {
- Today: [],
- 'Previous 7 Days': [],
- 'Previous 30 Days': [],
- };
- const monthlyGroups: { [key: string]: Conversation[] } = {}; // Key format: "Month Year" e.g., "April 2023"
-
- // Sort conversations by lastModified date in descending order (newest first)
- // This helps when adding to groups, but the final output order of groups is fixed.
- const sortedConversations = [...conversations].sort(
- (a, b) => b.lastModified - a.lastModified
- );
-
- for (const conv of sortedConversations) {
- const convDate = new Date(conv.lastModified);
-
- if (convDate >= today) {
- groups['Today'].push(conv);
- } else if (convDate >= sevenDaysAgo) {
- groups['Previous 7 Days'].push(conv);
- } else if (convDate >= thirtyDaysAgo) {
- groups['Previous 30 Days'].push(conv);
- } else {
- const monthName = convDate.toLocaleString('default', { month: 'long' });
- const year = convDate.getFullYear();
- const monthYearKey = `${monthName} ${year}`;
- if (!monthlyGroups[monthYearKey]) {
- monthlyGroups[monthYearKey] = [];
- }
- monthlyGroups[monthYearKey].push(conv);
- }
- }
-
- const result: GroupedConversations[] = [];
-
- if (groups['Today'].length > 0) {
- result.push({
- title: undefined, // no title for Today
- conversations: groups['Today'],
- });
- }
-
- if (groups['Previous 7 Days'].length > 0) {
- result.push({
- title: 'Previous 7 Days',
- conversations: groups['Previous 7 Days'],
- });
- }
-
- if (groups['Previous 30 Days'].length > 0) {
- result.push({
- title: 'Previous 30 Days',
- conversations: groups['Previous 30 Days'],
- });
- }
-
- // Sort monthly groups by date (most recent month first)
- const sortedMonthKeys = Object.keys(monthlyGroups).sort((a, b) => {
- const dateA = new Date(a); // "Month Year" can be parsed by Date constructor
- const dateB = new Date(b);
- return dateB.getTime() - dateA.getTime();
- });
-
- for (const monthKey of sortedMonthKeys) {
- if (monthlyGroups[monthKey].length > 0) {
- result.push({ title: monthKey, conversations: monthlyGroups[monthKey] });
- }
- }
-
- return result;
-}
+++ /dev/null
-import { useState } from 'react';
-import { MessageExtra } from '../utils/types';
-import toast from 'react-hot-toast';
-import { useAppContext } from '../utils/app.context';
-import * as pdfjs from 'pdfjs-dist';
-import pdfjsWorkerSrc from 'pdfjs-dist/build/pdf.worker.min.mjs?url';
-import { TextContent, TextItem } from 'pdfjs-dist/types/src/display/api';
-
-pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorkerSrc;
-
-// This file handles uploading extra context items (a.k.a files)
-// It allows processing these kinds of files:
-// - image files (converted to base64)
-// - audio files (converted to base64)
-// - text files (including code files)
-// - pdf (converted to text)
-
-// Interface describing the API returned by the hook
-export interface ChatExtraContextApi {
- items?: MessageExtra[]; // undefined if empty, similar to Message['extra']
- addItems: (items: MessageExtra[]) => void;
- removeItem: (idx: number) => void;
- clearItems: () => void;
- onFileAdded: (files: File[]) => void; // used by "upload" button
-}
-
-export function useChatExtraContext(): ChatExtraContextApi {
- const { serverProps, config } = useAppContext();
- const [items, setItems] = useState<MessageExtra[]>([]);
-
- const addItems = (newItems: MessageExtra[]) => {
- setItems((prev) => [...prev, ...newItems]);
- };
-
- const removeItem = (idx: number) => {
- setItems((prev) => prev.filter((_, i) => i !== idx));
- };
-
- const clearItems = () => {
- setItems([]);
- };
-
- const isSupportVision = serverProps?.modalities?.vision;
-
- const onFileAdded = async (files: File[]) => {
- try {
- for (const file of files) {
- const mimeType = file.type;
-
- // this limit is only to prevent accidental uploads of huge files
- // it can potentially crashes the browser because we read the file as base64
- if (file.size > 500 * 1024 * 1024) {
- toast.error('File is too large. Maximum size is 500MB.');
- break;
- }
-
- if (mimeType.startsWith('image/')) {
- if (!isSupportVision) {
- toast.error('Multimodal is not supported by this server or model.');
- break;
- }
-
- let base64Url = await getFileAsBase64(file);
- if (mimeType === 'image/svg+xml') {
- // Convert SVG to PNG
- base64Url = await svgBase64UrlToPngDataURL(base64Url);
- }
- addItems([
- {
- type: 'imageFile',
- name: file.name,
- base64Url,
- },
- ]);
- } else if (mimeType.startsWith('video/')) {
- toast.error('Video files are not supported yet.');
- break;
- } else if (mimeType.startsWith('audio/')) {
- if (!/mpeg|wav/.test(mimeType)) {
- toast.error('Only mp3 and wav audio files are supported.');
- break;
- }
-
- // plain base64, not a data URL
- const base64Data = await getFileAsBase64(file, false);
- addItems([
- {
- type: 'audioFile',
- name: file.name,
- mimeType,
- base64Data,
- },
- ]);
- } else if (mimeType.startsWith('application/pdf')) {
- if (config.pdfAsImage && !isSupportVision) {
- toast(
- 'Multimodal is not supported, PDF will be converted to text instead of image.'
- );
- break;
- }
-
- if (config.pdfAsImage && isSupportVision) {
- // Convert PDF to images
- const base64Urls = await convertPDFToImage(file);
- addItems(
- base64Urls.map((base64Url) => ({
- type: 'imageFile',
- name: file.name,
- base64Url,
- }))
- );
- } else {
- // Convert PDF to text
- const content = await convertPDFToText(file);
- addItems([
- {
- type: 'textFile',
- name: file.name,
- content,
- },
- ]);
- if (isSupportVision) {
- toast.success(
- 'PDF file converted to text. You can also convert it to image, see in Settings.'
- );
- }
- }
- break;
- } else {
- // Because there can be many text file types (like code file), we will not check the mime type
- // and will just check if the file is not binary.
- const reader = new FileReader();
- reader.onload = (event) => {
- if (event.target?.result) {
- const content = event.target.result as string;
- if (!isLikelyNotBinary(content)) {
- toast.error('File is binary. Please upload a text file.');
- return;
- }
- addItems([
- {
- type: 'textFile',
- name: file.name,
- content,
- },
- ]);
- }
- };
- reader.readAsText(file);
- }
- }
- } catch (error) {
- const message = error instanceof Error ? error.message : String(error);
- const errorMessage = `Error processing file: ${message}`;
- toast.error(errorMessage);
- }
- };
-
- return {
- items: items.length > 0 ? items : undefined,
- addItems,
- removeItem,
- clearItems,
- onFileAdded,
- };
-}
-
-async function getFileAsBase64(file: File, outputUrl = true): Promise<string> {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = (event) => {
- if (event.target?.result) {
- let result = event.target.result as string;
- if (!outputUrl) {
- // remove base64 url prefix and correct characters
- result = result.substring(result.indexOf(',') + 1);
- }
- resolve(result);
- } else {
- reject(new Error('Failed to read file.'));
- }
- };
- reader.readAsDataURL(file);
- });
-}
-
-async function getFileAsBuffer(file: File): Promise<ArrayBuffer> {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = (event) => {
- if (event.target?.result) {
- resolve(event.target.result as ArrayBuffer);
- } else {
- reject(new Error('Failed to read file.'));
- }
- };
- reader.readAsArrayBuffer(file);
- });
-}
-
-async function convertPDFToText(file: File): Promise<string> {
- const buffer = await getFileAsBuffer(file);
- const pdf = await pdfjs.getDocument(buffer).promise;
- const numPages = pdf.numPages;
- const textContentPromises: Promise<TextContent>[] = [];
- for (let i = 1; i <= numPages; i++) {
- textContentPromises.push(
- pdf.getPage(i).then((page) => page.getTextContent())
- );
- }
- const textContents = await Promise.all(textContentPromises);
- const textItems = textContents.flatMap((textContent: TextContent) =>
- textContent.items.map((item) => (item as TextItem).str ?? '')
- );
- return textItems.join('\n');
-}
-
-// returns list of base64 images
-async function convertPDFToImage(file: File): Promise<string[]> {
- const buffer = await getFileAsBuffer(file);
- const doc = await pdfjs.getDocument(buffer).promise;
- const pages: Promise<string>[] = [];
-
- for (let i = 1; i <= doc.numPages; i++) {
- const page = await doc.getPage(i);
- const viewport = page.getViewport({ scale: 1.5 });
- const canvas = document.createElement('canvas');
- const ctx = canvas.getContext('2d');
- canvas.width = viewport.width;
- canvas.height = viewport.height;
- if (!ctx) {
- throw new Error('Failed to get 2D context from canvas');
- }
- const task = page.render({ canvasContext: ctx, viewport: viewport });
- pages.push(
- task.promise.then(() => {
- return canvas.toDataURL();
- })
- );
- }
-
- return await Promise.all(pages);
-}
-
-// WARN: vibe code below
-// This code is a heuristic to determine if a string is likely not binary.
-// It is necessary because input file can have various mime types which we don't have time to investigate.
-// For example, a python file can be text/plain, application/x-python, etc.
-function isLikelyNotBinary(str: string): boolean {
- const options = {
- prefixLength: 1024 * 10, // Check the first 10KB of the string
- suspiciousCharThresholdRatio: 0.15, // Allow up to 15% suspicious chars
- maxAbsoluteNullBytes: 2,
- };
-
- if (!str) {
- return true; // Empty string is considered "not binary" or trivially text.
- }
-
- const sampleLength = Math.min(str.length, options.prefixLength);
- if (sampleLength === 0) {
- return true; // Effectively an empty string after considering prefixLength.
- }
-
- let suspiciousCharCount = 0;
- let nullByteCount = 0;
-
- for (let i = 0; i < sampleLength; i++) {
- const charCode = str.charCodeAt(i);
-
- // 1. Check for Unicode Replacement Character (U+FFFD)
- // This is a strong indicator if the string was created from decoding bytes as UTF-8.
- if (charCode === 0xfffd) {
- suspiciousCharCount++;
- continue;
- }
-
- // 2. Check for Null Bytes (U+0000)
- if (charCode === 0x0000) {
- nullByteCount++;
- // We also count nulls towards the general suspicious character count,
- // as they are less common in typical text files.
- suspiciousCharCount++;
- continue;
- }
-
- // 3. Check for C0 Control Characters (U+0001 to U+001F)
- // Exclude common text control characters: TAB (9), LF (10), CR (13).
- // We can also be a bit lenient with BEL (7) and BS (8) which sometimes appear in logs.
- if (charCode < 32) {
- if (
- charCode !== 9 && // TAB
- charCode !== 10 && // LF
- charCode !== 13 && // CR
- charCode !== 7 && // BEL (Bell) - sometimes in logs
- charCode !== 8 // BS (Backspace) - less common, but possible
- ) {
- suspiciousCharCount++;
- }
- }
- // Characters from 32 (space) up to 126 (~) are printable ASCII.
- // Characters 127 (DEL) is a control character.
- // Characters >= 128 are extended ASCII / multi-byte Unicode.
- // If they resulted in U+FFFD, we caught it. Otherwise, they are valid
- // (though perhaps unusual) Unicode characters from JS's perspective.
- // The main concern is if those higher characters came from misinterpreting
- // a single-byte encoding as UTF-8, which again, U+FFFD would usually flag.
- }
-
- // Check absolute null byte count
- if (nullByteCount > options.maxAbsoluteNullBytes) {
- return false; // Too many null bytes is a strong binary indicator
- }
-
- // Check ratio of suspicious characters
- const ratio = suspiciousCharCount / sampleLength;
- return ratio <= options.suspiciousCharThresholdRatio;
-}
-
-// WARN: vibe code below
-// Converts a Base64URL encoded SVG string to a PNG Data URL using browser Canvas API.
-function svgBase64UrlToPngDataURL(base64UrlSvg: string): Promise<string> {
- const backgroundColor = 'white'; // Default background color for PNG
-
- return new Promise((resolve, reject) => {
- try {
- const img = new Image();
-
- img.onload = () => {
- const canvas = document.createElement('canvas');
- const ctx = canvas.getContext('2d');
-
- if (!ctx) {
- reject(new Error('Failed to get 2D canvas context.'));
- return;
- }
-
- // Use provided dimensions or SVG's natural dimensions, with fallbacks
- // Fallbacks (e.g., 300x300) are for SVGs without explicit width/height
- // or when naturalWidth/Height might be 0 before full processing.
- const targetWidth = img.naturalWidth || 300;
- const targetHeight = img.naturalHeight || 300;
-
- canvas.width = targetWidth;
- canvas.height = targetHeight;
-
- if (backgroundColor) {
- ctx.fillStyle = backgroundColor;
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- }
-
- ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
- resolve(canvas.toDataURL('image/png'));
- };
-
- img.onerror = () => {
- reject(
- new Error('Failed to load SVG image. Ensure the SVG data is valid.')
- );
- };
-
- // Load SVG string into an Image element
- img.src = base64UrlSvg;
- } catch (error) {
- const message = error instanceof Error ? error.message : String(error);
- const errorMessage = `Error converting SVG to PNG: ${message}`;
- toast.error(errorMessage);
- reject(new Error(errorMessage));
- }
- });
-}
+++ /dev/null
-import React, { useEffect } from 'react';
-import { throttle } from '../utils/misc';
-
-export const scrollToBottom = (requiresNearBottom: boolean, delay?: number) => {
- const mainScrollElem = document.getElementById('main-scroll');
- if (!mainScrollElem) return;
- const spaceToBottom =
- mainScrollElem.scrollHeight -
- mainScrollElem.scrollTop -
- mainScrollElem.clientHeight;
- if (!requiresNearBottom || spaceToBottom < 100) {
- setTimeout(
- () => mainScrollElem.scrollTo({ top: mainScrollElem.scrollHeight }),
- delay ?? 80
- );
- }
-};
-
-const scrollToBottomThrottled = throttle(scrollToBottom, 80);
-
-export function useChatScroll(msgListRef: React.RefObject<HTMLDivElement>) {
- useEffect(() => {
- if (!msgListRef.current) return;
-
- const resizeObserver = new ResizeObserver((_) => {
- scrollToBottomThrottled(true, 10);
- });
-
- resizeObserver.observe(msgListRef.current);
- return () => {
- resizeObserver.disconnect();
- };
- }, [msgListRef]);
-}
+++ /dev/null
-import { useEffect, useRef, useState, useCallback } from 'react';
-import { throttle } from '../utils/misc';
-
-// Media Query for detecting "large" screens (matching Tailwind's lg: breakpoint)
-const LARGE_SCREEN_MQ = '(min-width: 1024px)';
-
-// Calculates and sets the textarea height based on its scrollHeight
-const adjustTextareaHeight = throttle(
- (textarea: HTMLTextAreaElement | null) => {
- if (!textarea) return;
-
- // Only perform auto-sizing on large screens
- if (!window.matchMedia(LARGE_SCREEN_MQ).matches) {
- // On small screens, reset inline height and max-height styles.
- // This allows CSS (e.g., `rows` attribute or classes) to control the height,
- // and enables manual resizing if `resize-vertical` is set.
- textarea.style.height = ''; // Use 'auto' or '' to reset
- textarea.style.maxHeight = '';
- return; // Do not adjust height programmatically on small screens
- }
-
- const computedStyle = window.getComputedStyle(textarea);
- // Get the max-height specified by CSS (e.g., from `lg:max-h-48`)
- const currentMaxHeight = computedStyle.maxHeight;
-
- // Temporarily remove max-height to allow scrollHeight to be calculated correctly
- textarea.style.maxHeight = 'none';
- // Reset height to 'auto' to measure the actual scrollHeight needed
- textarea.style.height = 'auto';
- // Set the height to the calculated scrollHeight
- textarea.style.height = `${textarea.scrollHeight}px`;
- // Re-apply the original max-height from CSS to enforce the limit
- textarea.style.maxHeight = currentMaxHeight;
- },
- 100
-); // Throttle to prevent excessive calls
-
-// Interface describing the API returned by the hook
-export interface ChatTextareaApi {
- value: () => string;
- setValue: (value: string) => void;
- focus: () => void;
- ref: React.RefObject<HTMLTextAreaElement>;
- refOnSubmit: React.MutableRefObject<(() => void) | null>; // Submit handler
- onInput: (event: React.FormEvent<HTMLTextAreaElement>) => void; // Input handler
-}
-
-// This is a workaround to prevent the textarea from re-rendering when the inner content changes
-// See https://github.com/ggml-org/llama.cpp/pull/12299
-// combined now with auto-sizing logic.
-export function useChatTextarea(initValue: string): ChatTextareaApi {
- const [savedInitValue, setSavedInitValue] = useState<string>(initValue);
- const textareaRef = useRef<HTMLTextAreaElement>(null);
- const onSubmitRef = useRef<(() => void) | null>(null);
-
- // Effect to set initial value and height on mount or when initValue changes
- useEffect(() => {
- const textarea = textareaRef.current;
- if (textarea) {
- if (typeof savedInitValue === 'string' && savedInitValue.length > 0) {
- textarea.value = savedInitValue;
- // Call adjustTextareaHeight - it will check screen size internally
- setTimeout(() => adjustTextareaHeight(textarea), 0);
- setSavedInitValue(''); // Reset after applying
- } else {
- // Adjust height even if there's no initial value (for initial render)
- setTimeout(() => adjustTextareaHeight(textarea), 0);
- }
- }
- }, [textareaRef, savedInitValue]); // Depend on ref and savedInitValue
-
- // On input change, we adjust the height of the textarea
- const handleInput = useCallback(
- (event: React.FormEvent<HTMLTextAreaElement>) => {
- // Call adjustTextareaHeight on every input - it will decide whether to act
- adjustTextareaHeight(event.currentTarget);
- },
- []
- );
-
- return {
- // Method to get the current value directly from the textarea
- value: () => {
- return textareaRef.current?.value ?? '';
- },
- // Method to programmatically set the value and trigger height adjustment
- setValue: (value: string) => {
- const textarea = textareaRef.current;
- if (textarea) {
- textarea.value = value;
- // Call adjustTextareaHeight - it will check screen size internally
- setTimeout(() => adjustTextareaHeight(textarea), 0);
- }
- },
- focus: () => {
- if (textareaRef.current) {
- textareaRef.current.focus();
- }
- },
- ref: textareaRef,
- refOnSubmit: onSubmitRef,
- onInput: handleInput, // for adjusting height on input
- };
-}
--- /dev/null
+import { describe, it, expect } from 'vitest';
+
+describe('sum test', () => {
+ it('adds 1 + 2 to equal 3', () => {
+ expect(1 + 2).toBe(3);
+ });
+});
+++ /dev/null
-@use 'sass:meta';
-@use 'tailwindcss';
-
-@plugin 'daisyui' {
- themes: all;
-}
-
-html {
- scrollbar-gutter: auto;
-}
-
-.markdown {
- h1,
- h2,
- h3,
- h4,
- h5,
- h6,
- ul,
- ol,
- li {
- all: revert;
- }
- pre {
- @apply whitespace-pre-wrap rounded-lg p-2 mb-3;
- border: 1px solid currentColor;
- }
- p {
- @apply mb-2;
- }
- hr {
- @apply my-4 border-base-content/20 border-1;
- }
- table {
- @apply w-full border-collapse text-sm font-sans my-4 text-base-content;
- }
- thead {
- @apply bg-base-200 text-base-content;
- }
- th {
- @apply border border-base-300 px-4 py-2 text-left font-semibold;
- }
- td {
- @apply border border-base-300 px-4 py-2 align-top;
- }
- tbody tr:nth-child(even) {
- @apply bg-base-100;
- }
- tbody tr:hover {
- @apply bg-base-200;
- }
-}
-
-.btn-mini {
- @apply cursor-pointer;
-}
-.chat-screen {
- max-width: 900px;
-}
-
-.chat-bubble {
- @apply break-words;
-}
-
-.chat-bubble-base-300 {
- --tw-bg-opacity: 1;
- --tw-text-opacity: 1;
- @apply bg-base-300 text-base-content;
-}
-
-/* Highlight.js */
-[data-color-scheme='light'] {
- @include meta.load-css('highlight.js/styles/stackoverflow-light');
- .dark-color {
- @apply bg-base-content text-base-100;
- }
-}
-[data-color-scheme='dark'] {
- @include meta.load-css('highlight.js/styles/stackoverflow-dark');
-}
-[data-color-scheme='auto'] {
- @media (prefers-color-scheme: light) {
- @include meta.load-css('highlight.js/styles/stackoverflow-light');
- .dark-color {
- @apply bg-base-content text-base-100;
- }
- }
- @media (prefers-color-scheme: dark) {
- @include meta.load-css('highlight.js/styles/stackoverflow-dark');
- }
-}
-.hljs {
- background: transparent !important;
- padding: 0.5em !important;
-}
-
-.katex-display {
- margin: 0 0 !important;
-}
--- /dev/null
+<script lang="ts">
+ import { X } from '@lucide/svelte';
+ import { Button } from '$lib/components/ui/button';
+ import { formatFileSize, getFileTypeLabel, getPreviewText } from '$lib/utils/file-preview';
+ import { FileTypeCategory, MimeTypeText } from '$lib/enums/files';
+
+ interface Props {
+ class?: string;
+ id: string;
+ onClick?: (event?: MouseEvent) => void;
+ onRemove?: (id: string) => void;
+ name: string;
+ readonly?: boolean;
+ size?: number;
+ textContent?: string;
+ type: string;
+ }
+
+ let {
+ class: className = '',
+ id,
+ onClick,
+ onRemove,
+ name,
+ readonly = false,
+ size,
+ textContent,
+ type
+ }: Props = $props();
+</script>
+
+{#if type === MimeTypeText.PLAIN || type === FileTypeCategory.TEXT}
+ {#if readonly}
+ <!-- Readonly mode (ChatMessage) -->
+ <button
+ class="cursor-pointer rounded-lg border border-border bg-muted p-3 transition-shadow hover:shadow-md {className} w-full max-w-2xl"
+ onclick={onClick}
+ aria-label={`Preview ${name}`}
+ type="button"
+ >
+ <div class="flex items-start gap-3">
+ <div class="flex min-w-0 flex-1 flex-col items-start text-left">
+ <span class="w-full truncate text-sm font-medium text-foreground">{name}</span>
+
+ {#if size}
+ <span class="text-xs text-muted-foreground">{formatFileSize(size)}</span>
+ {/if}
+
+ {#if textContent && type === 'text'}
+ <div class="relative mt-2 w-full">
+ <div
+ class="overflow-hidden font-mono text-xs leading-relaxed break-words whitespace-pre-wrap text-muted-foreground"
+ >
+ {getPreviewText(textContent)}
+ </div>
+
+ {#if textContent.length > 150}
+ <div
+ class="pointer-events-none absolute right-0 bottom-0 left-0 h-6 bg-gradient-to-t from-muted to-transparent"
+ ></div>
+ {/if}
+ </div>
+ {/if}
+ </div>
+ </div>
+ </button>
+ {:else}
+ <!-- Non-readonly mode (ChatForm) -->
+ <div class="relative rounded-lg border border-border bg-muted p-3 {className} w-64">
+ <Button
+ type="button"
+ variant="ghost"
+ size="sm"
+ class="absolute top-2 right-2 h-6 w-6 bg-white/20 p-0 hover:bg-white/30"
+ onclick={() => onRemove?.(id)}
+ aria-label="Remove file"
+ >
+ <X class="h-3 w-3" />
+ </Button>
+
+ <div class="pr-8">
+ <span class="mb-3 block truncate text-sm font-medium text-foreground">{name}</span>
+
+ {#if textContent}
+ <div class="relative">
+ <div
+ class="overflow-hidden font-mono text-xs leading-relaxed break-words whitespace-pre-wrap text-muted-foreground"
+ style="max-height: 3.6em; line-height: 1.2em;"
+ >
+ {getPreviewText(textContent)}
+ </div>
+
+ {#if textContent.length > 150}
+ <div
+ class="pointer-events-none absolute right-0 bottom-0 left-0 h-4 bg-gradient-to-t from-muted to-transparent"
+ ></div>
+ {/if}
+ </div>
+ {/if}
+ </div>
+ </div>
+ {/if}
+{:else}
+ <button
+ class="flex items-center gap-2 gap-3 rounded-lg border border-border bg-muted p-3 {className}"
+ onclick={onClick}
+ >
+ <div
+ class="flex h-8 w-8 items-center justify-center rounded bg-primary/10 text-xs font-medium text-primary"
+ >
+ {getFileTypeLabel(type)}
+ </div>
+
+ <div class="flex flex-col gap-1">
+ <span class="max-w-36 truncate text-sm font-medium text-foreground md:max-w-72">
+ {name}
+ </span>
+
+ {#if size}
+ <span class="text-left text-xs text-muted-foreground">{formatFileSize(size)}</span>
+ {/if}
+ </div>
+
+ {#if !readonly}
+ <Button
+ type="button"
+ variant="ghost"
+ size="sm"
+ class="h-6 w-6 p-0"
+ onclick={(e) => {
+ e.stopPropagation();
+ onRemove?.(id);
+ }}
+ >
+ <X class="h-3 w-3" />
+ </Button>
+ {/if}
+ </button>
+{/if}
--- /dev/null
+<script lang="ts">
+ import { X } from '@lucide/svelte';
+ import { Button } from '$lib/components/ui/button';
+
+ interface Props {
+ id: string;
+ name: string;
+ preview: string;
+ readonly?: boolean;
+ onRemove?: (id: string) => void;
+ onClick?: (event?: MouseEvent) => void;
+ class?: string;
+ // Customizable size props
+ width?: string;
+ height?: string;
+ imageClass?: string;
+ }
+
+ let {
+ id,
+ name,
+ preview,
+ readonly = false,
+ onRemove,
+ onClick,
+ class: className = '',
+ // Default to small size for form previews
+ width = 'w-auto',
+ height = 'h-24',
+ imageClass = ''
+ }: Props = $props();
+</script>
+
+<div class="relative overflow-hidden rounded-lg border border-border bg-muted {className}">
+ {#if onClick}
+ <button
+ type="button"
+ class="block h-full w-full rounded-lg focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:outline-none"
+ onclick={onClick}
+ aria-label="Preview {name}"
+ >
+ <img
+ src={preview}
+ alt={name}
+ class="{height} {width} cursor-pointer object-cover {imageClass}"
+ />
+ </button>
+ {:else}
+ <img
+ src={preview}
+ alt={name}
+ class="{height} {width} cursor-pointer object-cover {imageClass}"
+ />
+ {/if}
+
+ {#if !readonly}
+ <div
+ class="absolute top-1 right-1 flex items-center justify-center opacity-0 transition-opacity hover:opacity-100"
+ >
+ <Button
+ type="button"
+ variant="ghost"
+ size="sm"
+ class="h-6 w-6 bg-white/20 p-0 text-white hover:bg-white/30"
+ onclick={() => onRemove?.(id)}
+ >
+ <X class="h-3 w-3" />
+ </Button>
+ </div>
+ {/if}
+</div>
--- /dev/null
+<script lang="ts">
+ import * as Dialog from '$lib/components/ui/dialog';
+ import { FileText, Image, Music, FileIcon, Eye } from '@lucide/svelte';
+ import { FileTypeCategory, MimeTypeApplication } from '$lib/enums/files';
+ import { convertPDFToImage } from '$lib/utils/pdf-processing';
+ import { Button } from '$lib/components/ui/button';
+ import { getFileTypeCategory } from '$lib/utils/file-type';
+ import { formatFileSize } from '$lib/utils/file-preview';
+
+ interface Props {
+ open: boolean;
+ // Either an uploaded file or a stored attachment
+ uploadedFile?: ChatUploadedFile;
+ attachment?: DatabaseMessageExtra;
+ // For uploaded files
+ preview?: string;
+ name?: string;
+ type?: string;
+ size?: number;
+ textContent?: string;
+ }
+
+ let {
+ open = $bindable(),
+ uploadedFile,
+ attachment,
+ preview,
+ name,
+ type,
+ size,
+ textContent
+ }: Props = $props();
+
+ let displayName = $derived(uploadedFile?.name || attachment?.name || name || 'Unknown File');
+
+ let displayPreview = $derived(
+ uploadedFile?.preview || (attachment?.type === 'imageFile' ? attachment.base64Url : preview)
+ );
+
+ let displayType = $derived(
+ uploadedFile?.type ||
+ (attachment?.type === 'imageFile'
+ ? 'image'
+ : attachment?.type === 'textFile'
+ ? 'text'
+ : attachment?.type === 'audioFile'
+ ? attachment.mimeType || 'audio'
+ : attachment?.type === 'pdfFile'
+ ? MimeTypeApplication.PDF
+ : type || 'unknown')
+ );
+
+ let displaySize = $derived(uploadedFile?.size || size);
+
+ let displayTextContent = $derived(
+ uploadedFile?.textContent ||
+ (attachment?.type === 'textFile'
+ ? attachment.content
+ : attachment?.type === 'pdfFile'
+ ? attachment.content
+ : textContent)
+ );
+
+ let isAudio = $derived(
+ getFileTypeCategory(displayType) === FileTypeCategory.AUDIO || displayType === 'audio'
+ );
+
+ let isImage = $derived(
+ getFileTypeCategory(displayType) === FileTypeCategory.IMAGE || displayType === 'image'
+ );
+
+ let isPdf = $derived(displayType === MimeTypeApplication.PDF);
+
+ let isText = $derived(
+ getFileTypeCategory(displayType) === FileTypeCategory.TEXT || displayType === 'text'
+ );
+
+ let IconComponent = $derived(() => {
+ if (isImage) return Image;
+ if (isText || isPdf) return FileText;
+ if (isAudio) return Music;
+
+ return FileIcon;
+ });
+
+ let pdfViewMode = $state<'text' | 'pages'>('pages');
+
+ let pdfImages = $state<string[]>([]);
+
+ let pdfImagesLoading = $state(false);
+
+ let pdfImagesError = $state<string | null>(null);
+
+ async function loadPdfImages() {
+ if (!isPdf || pdfImages.length > 0 || pdfImagesLoading) return;
+
+ pdfImagesLoading = true;
+ pdfImagesError = null;
+
+ try {
+ let file: File | null = null;
+
+ if (uploadedFile?.file) {
+ file = uploadedFile.file;
+ } else if (attachment?.type === 'pdfFile') {
+ // Check if we have pre-processed images
+ if (attachment.images && Array.isArray(attachment.images)) {
+ pdfImages = attachment.images;
+ return;
+ }
+
+ // Convert base64 back to File for processing
+ if (attachment.base64Data) {
+ const base64Data = attachment.base64Data;
+ const byteCharacters = atob(base64Data);
+ const byteNumbers = new Array(byteCharacters.length);
+ for (let i = 0; i < byteCharacters.length; i++) {
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
+ }
+ const byteArray = new Uint8Array(byteNumbers);
+ file = new File([byteArray], displayName, { type: MimeTypeApplication.PDF });
+ }
+ }
+
+ if (file) {
+ pdfImages = await convertPDFToImage(file);
+ } else {
+ throw new Error('No PDF file available for conversion');
+ }
+ } catch (error) {
+ pdfImagesError = error instanceof Error ? error.message : 'Failed to load PDF images';
+ } finally {
+ pdfImagesLoading = false;
+ }
+ }
+
+ $effect(() => {
+ if (open && isPdf && pdfViewMode === 'pages') {
+ loadPdfImages();
+ }
+ });
+</script>
+
+<Dialog.Root bind:open>
+ <Dialog.Content class="grid max-h-[90vh] max-w-5xl overflow-hidden !p-10 sm:w-auto sm:max-w-6xl">
+ <Dialog.Header class="flex-shrink-0">
+ <div class="flex items-center justify-between">
+ <div class="flex items-center gap-3">
+ {#if IconComponent}
+ <IconComponent class="h-5 w-5 text-muted-foreground" />
+ {/if}
+
+ <div>
+ <Dialog.Title class="text-left">{displayName}</Dialog.Title>
+
+ <div class="flex items-center gap-2 text-sm text-muted-foreground">
+ <span>{displayType}</span>
+
+ {#if displaySize}
+ <span>•</span>
+
+ <span>{formatFileSize(displaySize)}</span>
+ {/if}
+ </div>
+ </div>
+ </div>
+
+ {#if isPdf}
+ <div class="flex items-center gap-2">
+ <Button
+ variant={pdfViewMode === 'text' ? 'default' : 'outline'}
+ size="sm"
+ onclick={() => (pdfViewMode = 'text')}
+ disabled={pdfImagesLoading}
+ >
+ <FileText class="mr-1 h-4 w-4" />
+
+ Text
+ </Button>
+
+ <Button
+ variant={pdfViewMode === 'pages' ? 'default' : 'outline'}
+ size="sm"
+ onclick={() => {
+ pdfViewMode = 'pages';
+ loadPdfImages();
+ }}
+ disabled={pdfImagesLoading}
+ >
+ {#if pdfImagesLoading}
+ <div
+ class="mr-1 h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent"
+ ></div>
+ {:else}
+ <Eye class="mr-1 h-4 w-4" />
+ {/if}
+
+ Pages
+ </Button>
+ </div>
+ {/if}
+ </div>
+ </Dialog.Header>
+
+ <div class="flex-1 overflow-auto">
+ {#if isImage && displayPreview}
+ <div class="flex items-center justify-center">
+ <img
+ src={displayPreview}
+ alt={displayName}
+ class="max-h-full rounded-lg object-contain shadow-lg"
+ />
+ </div>
+ {:else if isPdf && pdfViewMode === 'pages'}
+ {#if pdfImagesLoading}
+ <div class="flex items-center justify-center p-8">
+ <div class="text-center">
+ <div
+ class="mx-auto mb-4 h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"
+ ></div>
+
+ <p class="text-muted-foreground">Converting PDF to images...</p>
+ </div>
+ </div>
+ {:else if pdfImagesError}
+ <div class="flex items-center justify-center p-8">
+ <div class="text-center">
+ <FileText class="mx-auto mb-4 h-16 w-16 text-muted-foreground" />
+
+ <p class="mb-4 text-muted-foreground">Failed to load PDF images</p>
+
+ <p class="text-sm text-muted-foreground">{pdfImagesError}</p>
+
+ <Button class="mt-4" onclick={() => (pdfViewMode = 'text')}>View as Text</Button>
+ </div>
+ </div>
+ {:else if pdfImages.length > 0}
+ <div class="max-h-[70vh] space-y-4 overflow-auto">
+ {#each pdfImages as image, index (image)}
+ <div class="text-center">
+ <p class="mb-2 text-sm text-muted-foreground">Page {index + 1}</p>
+
+ <img
+ src={image}
+ alt="PDF Page {index + 1}"
+ class="mx-auto max-w-full rounded-lg shadow-lg"
+ />
+ </div>
+ {/each}
+ </div>
+ {:else}
+ <div class="flex items-center justify-center p-8">
+ <div class="text-center">
+ <FileText class="mx-auto mb-4 h-16 w-16 text-muted-foreground" />
+
+ <p class="mb-4 text-muted-foreground">No PDF pages available</p>
+ </div>
+ </div>
+ {/if}
+ {:else if (isText || (isPdf && pdfViewMode === 'text')) && displayTextContent}
+ <div
+ class="max-h-[60vh] overflow-auto rounded-lg bg-muted p-4 font-mono text-sm break-words whitespace-pre-wrap"
+ >
+ {displayTextContent}
+ </div>
+ {:else if isAudio}
+ <div class="flex items-center justify-center p-8">
+ <div class="w-full max-w-md text-center">
+ <Music class="mx-auto mb-4 h-16 w-16 text-muted-foreground" />
+
+ {#if attachment?.type === 'audioFile'}
+ <audio
+ controls
+ class="mb-4 w-full"
+ src="data:{attachment.mimeType};base64,{attachment.base64Data}"
+ >
+ Your browser does not support the audio element.
+ </audio>
+ {:else if uploadedFile?.preview}
+ <audio controls class="mb-4 w-full" src={uploadedFile.preview}>
+ Your browser does not support the audio element.
+ </audio>
+ {:else}
+ <p class="mb-4 text-muted-foreground">Audio preview not available</p>
+ {/if}
+
+ <p class="text-sm text-muted-foreground">
+ {displayName}
+ </p>
+ </div>
+ </div>
+ {:else}
+ <div class="flex items-center justify-center p-8">
+ <div class="text-center">
+ {#if IconComponent}
+ <IconComponent class="mx-auto mb-4 h-16 w-16 text-muted-foreground" />
+ {/if}
+
+ <p class="mb-4 text-muted-foreground">Preview not available for this file type</p>
+ </div>
+ </div>
+ {/if}
+ </div>
+ </Dialog.Content>
+</Dialog.Root>
--- /dev/null
+<script lang="ts">
+ import { ChatAttachmentImagePreview, ChatAttachmentFilePreview } from '$lib/components/app';
+ import { FileTypeCategory } from '$lib/enums/files';
+ import { getFileTypeCategory } from '$lib/utils/file-type';
+ import ChatAttachmentPreviewDialog from './ChatAttachmentPreviewDialog.svelte';
+
+ interface Props {
+ class?: string;
+ // For ChatMessage - stored attachments
+ attachments?: DatabaseMessageExtra[];
+ readonly?: boolean;
+ // For ChatForm - pending uploads
+ onFileRemove?: (fileId: string) => void;
+ uploadedFiles?: ChatUploadedFile[];
+ // Image size customization
+ imageClass?: string;
+ imageHeight?: string;
+ imageWidth?: string;
+ }
+
+ let {
+ class: className = '',
+ attachments = [],
+ readonly = false,
+ onFileRemove,
+ uploadedFiles = $bindable([]),
+ // Default to small size for form previews
+ imageClass = '',
+ imageHeight = 'h-24',
+ imageWidth = 'w-auto'
+ }: Props = $props();
+
+ let displayItems = $derived(getDisplayItems());
+
+ // Preview dialog state
+ let previewDialogOpen = $state(false);
+ let previewItem = $state<{
+ uploadedFile?: ChatUploadedFile;
+ attachment?: DatabaseMessageExtra;
+ preview?: string;
+ name?: string;
+ type?: string;
+ size?: number;
+ textContent?: string;
+ } | null>(null);
+
+ function getDisplayItems() {
+ const items: Array<{
+ id: string;
+ name: string;
+ size?: number;
+ preview?: string;
+ type: string;
+ isImage: boolean;
+ uploadedFile?: ChatUploadedFile;
+ attachment?: DatabaseMessageExtra;
+ attachmentIndex?: number;
+ textContent?: string;
+ }> = [];
+
+ // Add uploaded files (ChatForm)
+ for (const file of uploadedFiles) {
+ items.push({
+ id: file.id,
+ name: file.name,
+ size: file.size,
+ preview: file.preview,
+ type: file.type,
+ isImage: getFileTypeCategory(file.type) === FileTypeCategory.IMAGE,
+ uploadedFile: file,
+ textContent: file.textContent
+ });
+ }
+
+ // Add stored attachments (ChatMessage)
+ for (const [index, attachment] of attachments.entries()) {
+ if (attachment.type === 'imageFile') {
+ items.push({
+ id: `attachment-${index}`,
+ name: attachment.name,
+ preview: attachment.base64Url,
+ type: 'image',
+ isImage: true,
+ attachment,
+ attachmentIndex: index
+ });
+ } else if (attachment.type === 'textFile') {
+ items.push({
+ id: `attachment-${index}`,
+ name: attachment.name,
+ type: 'text',
+ isImage: false,
+ attachment,
+ attachmentIndex: index,
+ textContent: attachment.content
+ });
+ } else if (attachment.type === 'audioFile') {
+ items.push({
+ id: `attachment-${index}`,
+ name: attachment.name,
+ type: attachment.mimeType || 'audio',
+ isImage: false,
+ attachment,
+ attachmentIndex: index
+ });
+ } else if (attachment.type === 'pdfFile') {
+ items.push({
+ id: `attachment-${index}`,
+ name: attachment.name,
+ type: 'application/pdf',
+ isImage: false,
+ attachment,
+ attachmentIndex: index,
+ textContent: attachment.content
+ });
+ }
+ }
+
+ return items;
+ }
+
+ function openPreview(item: (typeof displayItems)[0], event?: Event) {
+ if (event) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ previewItem = {
+ uploadedFile: item.uploadedFile,
+ attachment: item.attachment,
+ preview: item.preview,
+ name: item.name,
+ type: item.type,
+ size: item.size,
+ textContent: item.textContent
+ };
+ previewDialogOpen = true;
+ }
+</script>
+
+{#if displayItems.length > 0}
+ <div class="flex flex-wrap items-start {readonly ? 'justify-end' : ''} gap-3 {className}">
+ {#each displayItems as item (item.id)}
+ {#if item.isImage && item.preview}
+ <ChatAttachmentImagePreview
+ class="cursor-pointer"
+ id={item.id}
+ name={item.name}
+ preview={item.preview}
+ {readonly}
+ onRemove={onFileRemove}
+ height={imageHeight}
+ width={imageWidth}
+ {imageClass}
+ onClick={(event) => openPreview(item, event)}
+ />
+ {:else}
+ <ChatAttachmentFilePreview
+ class="cursor-pointer"
+ id={item.id}
+ name={item.name}
+ type={item.type}
+ size={item.size}
+ {readonly}
+ onRemove={onFileRemove}
+ textContent={item.textContent}
+ onClick={(event) => openPreview(item, event)}
+ />
+ {/if}
+ {/each}
+ </div>
+{/if}
+
+{#if previewItem}
+ <ChatAttachmentPreviewDialog
+ bind:open={previewDialogOpen}
+ uploadedFile={previewItem.uploadedFile}
+ attachment={previewItem.attachment}
+ preview={previewItem.preview}
+ name={previewItem.name}
+ type={previewItem.type}
+ size={previewItem.size}
+ textContent={previewItem.textContent}
+ />
+{/if}
--- /dev/null
+<script lang="ts">
+ import { afterNavigate } from '$app/navigation';
+ import {
+ ChatAttachmentsList,
+ ChatFormActions,
+ ChatFormFileInputInvisible,
+ ChatFormHelperText,
+ ChatFormTextarea
+ } from '$lib/components/app';
+ import { INPUT_CLASSES } from '$lib/constants/input-classes';
+ import { config } from '$lib/stores/settings.svelte';
+ import { FileTypeCategory, MimeTypeApplication } from '$lib/enums/files';
+ import {
+ AudioRecorder,
+ convertToWav,
+ createAudioFile,
+ isAudioRecordingSupported
+ } from '$lib/utils/audio-recording';
+ import { onMount } from 'svelte';
+ import {
+ FileExtensionAudio,
+ FileExtensionImage,
+ FileExtensionPdf,
+ FileExtensionText,
+ MimeTypeAudio,
+ MimeTypeImage,
+ MimeTypeText
+ } from '$lib/enums/files';
+
+ interface Props {
+ class?: string;
+ disabled?: boolean;
+ isLoading?: boolean;
+ onFileRemove?: (fileId: string) => void;
+ onFileUpload?: (files: File[]) => void;
+ onSend?: (message: string, files?: ChatUploadedFile[]) => Promise<boolean>;
+ onStop?: () => void;
+ showHelperText?: boolean;
+ uploadedFiles?: ChatUploadedFile[];
+ }
+
+ let {
+ class: className,
+ disabled = false,
+ isLoading = false,
+ onFileRemove,
+ onFileUpload,
+ onSend,
+ onStop,
+ showHelperText = true,
+ uploadedFiles = $bindable([])
+ }: Props = $props();
+
+ let audioRecorder: AudioRecorder | undefined;
+ let currentConfig = $derived(config());
+ let fileAcceptString = $state<string | undefined>(undefined);
+ let fileInputRef: ChatFormFileInputInvisible | undefined = $state(undefined);
+ let isRecording = $state(false);
+ let message = $state('');
+ let pasteLongTextToFileLength = $derived(Number(currentConfig.pasteLongTextToFileLen) || 2500);
+ let previousIsLoading = $state(isLoading);
+ let recordingSupported = $state(false);
+ let textareaRef: ChatFormTextarea | undefined = $state(undefined);
+
+ function getAcceptStringForFileType(fileType: FileTypeCategory): string {
+ switch (fileType) {
+ case FileTypeCategory.IMAGE:
+ return [...Object.values(FileExtensionImage), ...Object.values(MimeTypeImage)].join(',');
+ case FileTypeCategory.AUDIO:
+ return [...Object.values(FileExtensionAudio), ...Object.values(MimeTypeAudio)].join(',');
+ case FileTypeCategory.PDF:
+ return [...Object.values(FileExtensionPdf), ...Object.values(MimeTypeApplication)].join(
+ ','
+ );
+ case FileTypeCategory.TEXT:
+ return [...Object.values(FileExtensionText), MimeTypeText.PLAIN].join(',');
+ default:
+ return '';
+ }
+ }
+
+ function handleFileSelect(files: File[]) {
+ onFileUpload?.(files);
+ }
+
+ function handleFileUpload(fileType?: FileTypeCategory) {
+ if (fileType) {
+ fileAcceptString = getAcceptStringForFileType(fileType);
+ } else {
+ fileAcceptString = undefined;
+ }
+
+ // Use setTimeout to ensure the accept attribute is applied before opening dialog
+ setTimeout(() => {
+ fileInputRef?.click();
+ }, 10);
+ }
+
+ async function handleKeydown(event: KeyboardEvent) {
+ if (event.key === 'Enter' && !event.shiftKey) {
+ event.preventDefault();
+
+ if ((!message.trim() && uploadedFiles.length === 0) || disabled || isLoading) return;
+
+ const messageToSend = message.trim();
+ const filesToSend = [...uploadedFiles];
+
+ message = '';
+ uploadedFiles = [];
+
+ textareaRef?.resetHeight();
+
+ const success = await onSend?.(messageToSend, filesToSend);
+
+ if (!success) {
+ message = messageToSend;
+ uploadedFiles = filesToSend;
+ }
+ }
+ }
+
+ function handlePaste(event: ClipboardEvent) {
+ if (!event.clipboardData) return;
+
+ const files = Array.from(event.clipboardData.items)
+ .filter((item) => item.kind === 'file')
+ .map((item) => item.getAsFile())
+ .filter((file): file is File => file !== null);
+
+ if (files.length > 0) {
+ event.preventDefault();
+ onFileUpload?.(files);
+ return;
+ }
+
+ const text = event.clipboardData.getData(MimeTypeText.PLAIN);
+
+ if (
+ text.length > 0 &&
+ pasteLongTextToFileLength > 0 &&
+ text.length > pasteLongTextToFileLength
+ ) {
+ event.preventDefault();
+
+ const textFile = new File([text], 'Pasted', {
+ type: MimeTypeText.PLAIN
+ });
+
+ onFileUpload?.([textFile]);
+ }
+ }
+
+ async function handleMicClick() {
+ if (!audioRecorder || !recordingSupported) {
+ console.warn('Audio recording not supported');
+ return;
+ }
+
+ if (isRecording) {
+ try {
+ const audioBlob = await audioRecorder.stopRecording();
+ const wavBlob = await convertToWav(audioBlob);
+ const audioFile = createAudioFile(wavBlob);
+
+ onFileUpload?.([audioFile]);
+ isRecording = false;
+ } catch (error) {
+ console.error('Failed to stop recording:', error);
+ isRecording = false;
+ }
+ } else {
+ try {
+ await audioRecorder.startRecording();
+ isRecording = true;
+ } catch (error) {
+ console.error('Failed to start recording:', error);
+ }
+ }
+ }
+
+ function handleStop() {
+ onStop?.();
+ }
+
+ async function handleSubmit(event: SubmitEvent) {
+ event.preventDefault();
+ if ((!message.trim() && uploadedFiles.length === 0) || disabled || isLoading) return;
+
+ const messageToSend = message.trim();
+ const filesToSend = [...uploadedFiles];
+
+ message = '';
+ uploadedFiles = [];
+
+ textareaRef?.resetHeight();
+
+ const success = await onSend?.(messageToSend, filesToSend);
+
+ if (!success) {
+ message = messageToSend;
+ uploadedFiles = filesToSend;
+ }
+ }
+
+ onMount(() => {
+ setTimeout(() => textareaRef?.focus(), 10);
+ recordingSupported = isAudioRecordingSupported();
+ audioRecorder = new AudioRecorder();
+ });
+
+ afterNavigate(() => {
+ setTimeout(() => textareaRef?.focus(), 10);
+ });
+
+ $effect(() => {
+ if (previousIsLoading && !isLoading) {
+ setTimeout(() => textareaRef?.focus(), 10);
+ }
+
+ previousIsLoading = isLoading;
+ });
+</script>
+
+<ChatFormFileInputInvisible
+ bind:this={fileInputRef}
+ bind:accept={fileAcceptString}
+ onFileSelect={handleFileSelect}
+/>
+
+<form
+ onsubmit={handleSubmit}
+ class="{INPUT_CLASSES} border-radius-bottom-none mx-auto max-w-[48rem] overflow-hidden rounded-3xl backdrop-blur-md {className}"
+>
+ <ChatAttachmentsList bind:uploadedFiles {onFileRemove} class="mb-3 px-5 pt-5" />
+
+ <div
+ class="flex-column relative min-h-[48px] items-center rounded-3xl px-5 py-3 shadow-sm transition-all focus-within:shadow-md"
+ onpaste={handlePaste}
+ >
+ <ChatFormTextarea
+ bind:this={textareaRef}
+ bind:value={message}
+ onKeydown={handleKeydown}
+ {disabled}
+ />
+
+ <ChatFormActions
+ canSend={message.trim().length > 0 || uploadedFiles.length > 0}
+ {disabled}
+ {isLoading}
+ {isRecording}
+ onFileUpload={handleFileUpload}
+ onMicClick={handleMicClick}
+ onStop={handleStop}
+ />
+ </div>
+</form>
+
+<ChatFormHelperText show={showHelperText} />
--- /dev/null
+<script lang="ts">
+ import { Paperclip, Image, FileText, File, Volume2 } from '@lucide/svelte';
+ import { Button } from '$lib/components/ui/button';
+ import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
+ import * as Tooltip from '$lib/components/ui/tooltip';
+ import { TOOLTIP_DELAY_DURATION } from '$lib/constants/tooltip-config';
+ import { FileTypeCategory } from '$lib/enums/files';
+ import { supportsAudio, supportsVision } from '$lib/stores/server.svelte';
+
+ interface Props {
+ class?: string;
+ disabled?: boolean;
+ onFileUpload?: (fileType?: FileTypeCategory) => void;
+ }
+
+ let { class: className = '', disabled = false, onFileUpload }: Props = $props();
+
+ const fileUploadTooltipText = $derived.by(() => {
+ return !supportsVision()
+ ? 'Text files and PDFs supported. Images, audio, and video require vision models.'
+ : 'Attach files';
+ });
+
+ function handleFileUpload(fileType?: FileTypeCategory) {
+ onFileUpload?.(fileType);
+ }
+</script>
+
+<div class="flex items-center gap-1 {className}">
+ <DropdownMenu.Root>
+ <DropdownMenu.Trigger name="Attach files">
+ <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
+ <Tooltip.Trigger>
+ <Button
+ class="file-upload-button h-8 w-8 rounded-full bg-transparent p-0 text-muted-foreground hover:bg-foreground/10 hover:text-foreground"
+ {disabled}
+ type="button"
+ >
+ <span class="sr-only">Attach files</span>
+
+ <Paperclip class="h-4 w-4" />
+ </Button>
+ </Tooltip.Trigger>
+
+ <Tooltip.Content>
+ <p>{fileUploadTooltipText}</p>
+ </Tooltip.Content>
+ </Tooltip.Root>
+ </DropdownMenu.Trigger>
+
+ <DropdownMenu.Content align="start" class="w-48">
+ <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
+ <Tooltip.Trigger class="w-full">
+ <DropdownMenu.Item
+ class="images-button flex cursor-pointer items-center gap-2"
+ disabled={!supportsVision()}
+ onclick={() => handleFileUpload(FileTypeCategory.IMAGE)}
+ >
+ <Image class="h-4 w-4" />
+
+ <span>Images</span>
+ </DropdownMenu.Item>
+ </Tooltip.Trigger>
+
+ {#if !supportsVision()}
+ <Tooltip.Content>
+ <p>Images require vision models to be processed</p>
+ </Tooltip.Content>
+ {/if}
+ </Tooltip.Root>
+
+ <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
+ <Tooltip.Trigger class="w-full">
+ <DropdownMenu.Item
+ class="audio-button flex cursor-pointer items-center gap-2"
+ disabled={!supportsAudio()}
+ onclick={() => handleFileUpload(FileTypeCategory.AUDIO)}
+ >
+ <Volume2 class="h-4 w-4" />
+
+ <span>Audio Files</span>
+ </DropdownMenu.Item>
+ </Tooltip.Trigger>
+
+ {#if !supportsAudio()}
+ <Tooltip.Content>
+ <p>Audio files require audio models to be processed</p>
+ </Tooltip.Content>
+ {/if}
+ </Tooltip.Root>
+
+ <DropdownMenu.Item
+ class="flex cursor-pointer items-center gap-2"
+ onclick={() => handleFileUpload(FileTypeCategory.TEXT)}
+ >
+ <FileText class="h-4 w-4" />
+
+ <span>Text Files</span>
+ </DropdownMenu.Item>
+
+ <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
+ <Tooltip.Trigger class="w-full">
+ <DropdownMenu.Item
+ class="flex cursor-pointer items-center gap-2"
+ onclick={() => handleFileUpload(FileTypeCategory.PDF)}
+ >
+ <File class="h-4 w-4" />
+
+ <span>PDF Files</span>
+ </DropdownMenu.Item>
+ </Tooltip.Trigger>
+
+ {#if !supportsVision()}
+ <Tooltip.Content>
+ <p>PDFs will be converted to text. Image-based PDFs may not work properly.</p>
+ </Tooltip.Content>
+ {/if}
+ </Tooltip.Root>
+ </DropdownMenu.Content>
+ </DropdownMenu.Root>
+</div>
--- /dev/null
+<script lang="ts">
+ import { Mic } from '@lucide/svelte';
+ import { Button } from '$lib/components/ui/button';
+ import * as Tooltip from '$lib/components/ui/tooltip';
+ import { supportsAudio } from '$lib/stores/server.svelte';
+
+ interface Props {
+ class?: string;
+ disabled?: boolean;
+ isLoading?: boolean;
+ isRecording?: boolean;
+ onMicClick?: () => void;
+ }
+
+ let {
+ class: className = '',
+ disabled = false,
+ isLoading = false,
+ isRecording = false,
+ onMicClick
+ }: Props = $props();
+</script>
+
+<div class="flex items-center gap-1 {className}">
+ <Tooltip.Root delayDuration={100}>
+ <Tooltip.Trigger>
+ <Button
+ class="h-8 w-8 rounded-full p-0 {isRecording
+ ? 'animate-pulse bg-red-500 text-white hover:bg-red-600'
+ : 'bg-transparent text-muted-foreground hover:bg-foreground/10 hover:text-foreground'} {!supportsAudio()
+ ? 'cursor-not-allowed opacity-50'
+ : ''}"
+ disabled={disabled || isLoading || !supportsAudio()}
+ onclick={onMicClick}
+ type="button"
+ >
+ <span class="sr-only">{isRecording ? 'Stop recording' : 'Start recording'}</span>
+
+ <Mic class="h-4 w-4" />
+ </Button>
+ </Tooltip.Trigger>
+
+ {#if !supportsAudio()}
+ <Tooltip.Content>
+ <p>Current model does not support audio</p>
+ </Tooltip.Content>
+ {/if}
+ </Tooltip.Root>
+</div>
--- /dev/null
+<script lang="ts">
+ import { Square, ArrowUp } from '@lucide/svelte';
+ import { Button } from '$lib/components/ui/button';
+ import ChatFormActionFileAttachments from './ChatFormActionFileAttachments.svelte';
+ import ChatFormActionRecord from './ChatFormActionRecord.svelte';
+ import type { FileTypeCategory } from '$lib/enums/files';
+
+ interface Props {
+ canSend?: boolean;
+ class?: string;
+ disabled?: boolean;
+ isLoading?: boolean;
+ isRecording?: boolean;
+ onFileUpload?: (fileType?: FileTypeCategory) => void;
+ onMicClick?: () => void;
+ onStop?: () => void;
+ }
+
+ let {
+ canSend = false,
+ class: className = '',
+ disabled = false,
+ isLoading = false,
+ isRecording = false,
+ onFileUpload,
+ onMicClick,
+ onStop
+ }: Props = $props();
+</script>
+
+<div class="flex items-center justify-between gap-1 {className}">
+ <ChatFormActionFileAttachments {disabled} {onFileUpload} />
+
+ <div class="flex gap-2">
+ {#if isLoading}
+ <Button
+ type="button"
+ onclick={onStop}
+ class="h-8 w-8 bg-transparent p-0 hover:bg-destructive/20"
+ >
+ <span class="sr-only">Stop</span>
+ <Square class="h-8 w-8 fill-destructive stroke-destructive" />
+ </Button>
+ {:else}
+ <ChatFormActionRecord {disabled} {isLoading} {isRecording} {onMicClick} />
+
+ <Button
+ type="submit"
+ disabled={!canSend || disabled || isLoading}
+ class="h-8 w-8 rounded-full p-0"
+ >
+ <span class="sr-only">Send</span>
+ <ArrowUp class="h-12 w-12" />
+ </Button>
+ {/if}
+ </div>
+</div>
--- /dev/null
+<script lang="ts">
+ import { generateModalityAwareAcceptString } from '$lib/utils/modality-file-validation';
+
+ interface Props {
+ accept?: string;
+ class?: string;
+ multiple?: boolean;
+ onFileSelect?: (files: File[]) => void;
+ }
+
+ let {
+ accept = $bindable(),
+ class: className = '',
+ multiple = true,
+ onFileSelect
+ }: Props = $props();
+
+ let fileInputElement: HTMLInputElement | undefined;
+
+ // Use modality-aware accept string by default, but allow override
+ let finalAccept = $derived(accept ?? generateModalityAwareAcceptString());
+
+ export function click() {
+ fileInputElement?.click();
+ }
+
+ function handleFileSelect(event: Event) {
+ const input = event.target as HTMLInputElement;
+ if (input.files) {
+ onFileSelect?.(Array.from(input.files));
+ }
+ }
+</script>
+
+<input
+ bind:this={fileInputElement}
+ type="file"
+ {multiple}
+ accept={finalAccept}
+ onchange={handleFileSelect}
+ class="hidden {className}"
+/>
--- /dev/null
+<script lang="ts">
+ interface Props {
+ class?: string;
+ show?: boolean;
+ }
+
+ let { class: className = '', show = true }: Props = $props();
+</script>
+
+{#if show}
+ <div class="mt-4 flex items-center justify-center {className}">
+ <p class="text-xs text-muted-foreground">
+ Press <kbd class="rounded bg-muted px-1 py-0.5 font-mono text-xs">Enter</kbd> to send,
+ <kbd class="rounded bg-muted px-1 py-0.5 font-mono text-xs">Shift + Enter</kbd> for new line
+ </p>
+ </div>
+{/if}
--- /dev/null
+<script lang="ts">
+ import autoResizeTextarea from '$lib/utils/autoresize-textarea';
+ import { onMount } from 'svelte';
+
+ interface Props {
+ class?: string;
+ disabled?: boolean;
+ onKeydown?: (event: KeyboardEvent) => void;
+ onPaste?: (event: ClipboardEvent) => void;
+ placeholder?: string;
+ value?: string;
+ }
+
+ let {
+ class: className = '',
+ disabled = false,
+ onKeydown,
+ onPaste,
+ placeholder = 'Ask anything...',
+ value = $bindable('')
+ }: Props = $props();
+
+ let textareaElement: HTMLTextAreaElement | undefined;
+
+ onMount(() => {
+ if (textareaElement) {
+ textareaElement.focus();
+ }
+ });
+
+ // Expose the textarea element for external access
+ export function getElement() {
+ return textareaElement;
+ }
+
+ export function focus() {
+ textareaElement?.focus();
+ }
+
+ export function resetHeight() {
+ if (textareaElement) {
+ textareaElement.style.height = '1rem';
+ }
+ }
+</script>
+
+<div class="flex-1 {className}">
+ <textarea
+ bind:this={textareaElement}
+ bind:value
+ class="text-md max-h-32 min-h-12 w-full resize-none border-0 bg-transparent p-0 leading-6 outline-none placeholder:text-muted-foreground focus-visible:ring-0 focus-visible:ring-offset-0"
+ class:cursor-not-allowed={disabled}
+ {disabled}
+ onkeydown={onKeydown}
+ oninput={(event) => autoResizeTextarea(event.currentTarget)}
+ onpaste={onPaste}
+ {placeholder}
+ ></textarea>
+</div>
--- /dev/null
+<script lang="ts">
+ import { getDeletionInfo } from '$lib/stores/chat.svelte';
+ import { copyToClipboard } from '$lib/utils/copy';
+ import { parseThinkingContent } from '$lib/utils/thinking';
+ import ChatMessageAssistant from './ChatMessageAssistant.svelte';
+ import ChatMessageUser from './ChatMessageUser.svelte';
+
+ interface Props {
+ class?: string;
+ message: DatabaseMessage;
+ onCopy?: (message: DatabaseMessage) => void;
+ onDelete?: (message: DatabaseMessage) => void;
+ onEditWithBranching?: (message: DatabaseMessage, newContent: string) => void;
+ onEditWithReplacement?: (
+ message: DatabaseMessage,
+ newContent: string,
+ shouldBranch: boolean
+ ) => void;
+ onNavigateToSibling?: (siblingId: string) => void;
+ onRegenerateWithBranching?: (message: DatabaseMessage) => void;
+ siblingInfo?: ChatMessageSiblingInfo | null;
+ }
+
+ let {
+ class: className = '',
+ message,
+ onCopy,
+ onDelete,
+ onEditWithBranching,
+ onEditWithReplacement,
+ onNavigateToSibling,
+ onRegenerateWithBranching,
+ siblingInfo = null
+ }: Props = $props();
+
+ let deletionInfo = $state<{
+ totalCount: number;
+ userMessages: number;
+ assistantMessages: number;
+ messageTypes: string[];
+ } | null>(null);
+ let editedContent = $state(message.content);
+ let isEditing = $state(false);
+ let showDeleteDialog = $state(false);
+ let shouldBranchAfterEdit = $state(false);
+ let textareaElement: HTMLTextAreaElement | undefined = $state();
+
+ let thinkingContent = $derived.by(() => {
+ if (message.role === 'assistant') {
+ if (message.thinking) {
+ return message.thinking;
+ }
+
+ const parsed = parseThinkingContent(message.content);
+
+ return parsed.thinking;
+ }
+ return null;
+ });
+
+ let messageContent = $derived.by(() => {
+ if (message.role === 'assistant') {
+ const parsed = parseThinkingContent(message.content);
+ return parsed.cleanContent?.replace('<|channel|>analysis', '');
+ }
+
+ return message.content?.replace('<|channel|>analysis', '');
+ });
+
+ function handleCancelEdit() {
+ isEditing = false;
+ editedContent = message.content;
+ }
+
+ async function handleCopy() {
+ await copyToClipboard(message.content, 'Message copied to clipboard');
+ onCopy?.(message);
+ }
+
+ function handleConfirmDelete() {
+ onDelete?.(message);
+ showDeleteDialog = false;
+ }
+
+ async function handleDelete() {
+ deletionInfo = await getDeletionInfo(message.id);
+ showDeleteDialog = true;
+ }
+
+ function handleEdit() {
+ isEditing = true;
+ editedContent = message.content;
+
+ setTimeout(() => {
+ if (textareaElement) {
+ textareaElement.focus();
+ textareaElement.setSelectionRange(
+ textareaElement.value.length,
+ textareaElement.value.length
+ );
+ }
+ }, 0);
+ }
+
+ function handleEditedContentChange(content: string) {
+ editedContent = content;
+ }
+
+ function handleEditKeydown(event: KeyboardEvent) {
+ if (event.key === 'Enter' && !event.shiftKey) {
+ event.preventDefault();
+ handleSaveEdit();
+ } else if (event.key === 'Escape') {
+ event.preventDefault();
+ handleCancelEdit();
+ }
+ }
+
+ function handleRegenerate() {
+ onRegenerateWithBranching?.(message);
+ }
+
+ function handleSaveEdit() {
+ if (message.role === 'user') {
+ onEditWithBranching?.(message, editedContent.trim());
+ } else {
+ onEditWithReplacement?.(message, editedContent.trim(), shouldBranchAfterEdit);
+ }
+
+ isEditing = false;
+ shouldBranchAfterEdit = false;
+ }
+
+ function handleShowDeleteDialogChange(show: boolean) {
+ showDeleteDialog = show;
+ }
+</script>
+
+{#if message.role === 'user'}
+ <ChatMessageUser
+ bind:textareaElement
+ class={className}
+ {deletionInfo}
+ {editedContent}
+ {isEditing}
+ {message}
+ onCancelEdit={handleCancelEdit}
+ onConfirmDelete={handleConfirmDelete}
+ onCopy={handleCopy}
+ onDelete={handleDelete}
+ onEdit={handleEdit}
+ onEditKeydown={handleEditKeydown}
+ onEditedContentChange={handleEditedContentChange}
+ {onNavigateToSibling}
+ onSaveEdit={handleSaveEdit}
+ onShowDeleteDialogChange={handleShowDeleteDialogChange}
+ {showDeleteDialog}
+ {siblingInfo}
+ />
+{:else}
+ <ChatMessageAssistant
+ bind:textareaElement
+ class={className}
+ {deletionInfo}
+ {editedContent}
+ {isEditing}
+ {message}
+ {messageContent}
+ onCancelEdit={handleCancelEdit}
+ onConfirmDelete={handleConfirmDelete}
+ onCopy={handleCopy}
+ onDelete={handleDelete}
+ onEdit={handleEdit}
+ onEditKeydown={handleEditKeydown}
+ onEditedContentChange={handleEditedContentChange}
+ {onNavigateToSibling}
+ onRegenerate={handleRegenerate}
+ onSaveEdit={handleSaveEdit}
+ onShowDeleteDialogChange={handleShowDeleteDialogChange}
+ {shouldBranchAfterEdit}
+ onShouldBranchAfterEditChange={(value) => (shouldBranchAfterEdit = value)}
+ {showDeleteDialog}
+ {siblingInfo}
+ {thinkingContent}
+ />
+{/if}
--- /dev/null
+<script lang="ts">
+ import { Edit, Copy, RefreshCw, Trash2 } from '@lucide/svelte';
+ import { ActionButton, ConfirmationDialog } from '$lib/components/app';
+ import ChatMessageBranchingControls from './ChatMessageBranchingControls.svelte';
+
+ interface Props {
+ message: DatabaseMessage;
+ role: 'user' | 'assistant';
+ justify: 'start' | 'end';
+ actionsPosition: 'left' | 'right';
+ siblingInfo?: ChatMessageSiblingInfo | null;
+ showDeleteDialog: boolean;
+ deletionInfo: {
+ totalCount: number;
+ userMessages: number;
+ assistantMessages: number;
+ messageTypes: string[];
+ } | null;
+ onCopy: () => void;
+ onEdit?: () => void;
+ onRegenerate?: () => void;
+ onDelete: () => void;
+ onConfirmDelete: () => void;
+ onNavigateToSibling?: (siblingId: string) => void;
+ onShowDeleteDialogChange: (show: boolean) => void;
+ }
+
+ let {
+ actionsPosition,
+ deletionInfo,
+ justify,
+ message,
+ onCopy,
+ onEdit,
+ onConfirmDelete,
+ onDelete,
+ onNavigateToSibling,
+ onShowDeleteDialogChange,
+ onRegenerate,
+ role,
+ siblingInfo = null,
+ showDeleteDialog
+ }: Props = $props();
+
+ function handleConfirmDelete() {
+ onConfirmDelete();
+ onShowDeleteDialogChange(false);
+ }
+</script>
+
+<div class="relative {justify === 'start' ? 'mt-2' : ''} flex h-6 items-center justify-{justify}">
+ <div
+ class="flex items-center text-xs text-muted-foreground transition-opacity group-hover:opacity-0"
+ >
+ {new Date(message.timestamp).toLocaleTimeString(undefined, {
+ hour: '2-digit',
+ minute: '2-digit'
+ })}
+ </div>
+
+ <div
+ class="absolute top-0 {actionsPosition === 'left'
+ ? 'left-0'
+ : 'right-0'} flex items-center gap-2 opacity-0 transition-opacity group-hover:opacity-100"
+ >
+ {#if siblingInfo && siblingInfo.totalSiblings > 1}
+ <ChatMessageBranchingControls {siblingInfo} {onNavigateToSibling} />
+ {/if}
+
+ <div
+ class="pointer-events-none inset-0 flex items-center gap-1 opacity-0 transition-all duration-150 group-hover:pointer-events-auto group-hover:opacity-100"
+ >
+ <ActionButton icon={Copy} tooltip="Copy" onclick={onCopy} />
+
+ {#if onEdit}
+ <ActionButton icon={Edit} tooltip="Edit" onclick={onEdit} />
+ {/if}
+
+ {#if role === 'assistant' && onRegenerate}
+ <ActionButton icon={RefreshCw} tooltip="Regenerate" onclick={onRegenerate} />
+ {/if}
+
+ <ActionButton icon={Trash2} tooltip="Delete" onclick={onDelete} />
+ </div>
+ </div>
+</div>
+
+<ConfirmationDialog
+ bind:open={showDeleteDialog}
+ title="Delete Message"
+ description={deletionInfo && deletionInfo.totalCount > 1
+ ? `This will delete ${deletionInfo.totalCount} messages including: ${deletionInfo.userMessages} user message${deletionInfo.userMessages > 1 ? 's' : ''} and ${deletionInfo.assistantMessages} assistant response${deletionInfo.assistantMessages > 1 ? 's' : ''}. All messages in this branch and their responses will be permanently removed. This action cannot be undone.`
+ : 'Are you sure you want to delete this message? This action cannot be undone.'}
+ confirmText={deletionInfo && deletionInfo.totalCount > 1
+ ? `Delete ${deletionInfo.totalCount} Messages`
+ : 'Delete'}
+ cancelText="Cancel"
+ variant="destructive"
+ icon={Trash2}
+ onConfirm={handleConfirmDelete}
+ onCancel={() => onShowDeleteDialogChange(false)}
+/>
--- /dev/null
+<script lang="ts">
+ import { ChatMessageThinkingBlock, MarkdownContent } from '$lib/components/app';
+ import { useProcessingState } from '$lib/hooks/use-processing-state.svelte';
+ import { isLoading } from '$lib/stores/chat.svelte';
+ import { fade } from 'svelte/transition';
+ import { Check, X } from '@lucide/svelte';
+ import { Button } from '$lib/components/ui/button';
+ import { Checkbox } from '$lib/components/ui/checkbox';
+ import { INPUT_CLASSES } from '$lib/constants/input-classes';
+ import ChatMessageActions from './ChatMessageActions.svelte';
+ import Label from '$lib/components/ui/label/label.svelte';
+
+ interface Props {
+ class?: string;
+ deletionInfo: {
+ totalCount: number;
+ userMessages: number;
+ assistantMessages: number;
+ messageTypes: string[];
+ } | null;
+ editedContent?: string;
+ isEditing?: boolean;
+ message: DatabaseMessage;
+ messageContent: string | undefined;
+ onCancelEdit?: () => void;
+ onCopy: () => void;
+ onConfirmDelete: () => void;
+ onDelete: () => void;
+ onEdit?: () => void;
+ onEditKeydown?: (event: KeyboardEvent) => void;
+ onEditedContentChange?: (content: string) => void;
+ onNavigateToSibling?: (siblingId: string) => void;
+ onRegenerate: () => void;
+ onSaveEdit?: () => void;
+ onShowDeleteDialogChange: (show: boolean) => void;
+ onShouldBranchAfterEditChange?: (value: boolean) => void;
+ showDeleteDialog: boolean;
+ shouldBranchAfterEdit?: boolean;
+ siblingInfo?: ChatMessageSiblingInfo | null;
+ textareaElement?: HTMLTextAreaElement;
+ thinkingContent: string | null;
+ }
+
+ let {
+ class: className = '',
+ deletionInfo,
+ editedContent = '',
+ isEditing = false,
+ message,
+ messageContent,
+ onCancelEdit,
+ onConfirmDelete,
+ onCopy,
+ onDelete,
+ onEdit,
+ onEditKeydown,
+ onEditedContentChange,
+ onNavigateToSibling,
+ onRegenerate,
+ onSaveEdit,
+ onShowDeleteDialogChange,
+ onShouldBranchAfterEditChange,
+ showDeleteDialog,
+ shouldBranchAfterEdit = false,
+ siblingInfo = null,
+ textareaElement = $bindable(),
+ thinkingContent
+ }: Props = $props();
+
+ const processingState = useProcessingState();
+</script>
+
+<div
+ class="text-md group w-full leading-7.5 {className}"
+ role="group"
+ aria-label="Assistant message with actions"
+>
+ {#if thinkingContent}
+ <ChatMessageThinkingBlock
+ reasoningContent={thinkingContent}
+ isStreaming={!message.timestamp}
+ hasRegularContent={!!messageContent?.trim()}
+ />
+ {/if}
+
+ {#if message?.role === 'assistant' && isLoading() && !message?.content?.trim()}
+ <div class="mt-6 w-full max-w-[48rem]" in:fade>
+ <div class="processing-container">
+ <span class="processing-text">
+ {processingState.getProcessingMessage()}
+ </span>
+ </div>
+ </div>
+ {/if}
+
+ {#if isEditing}
+ <div class="w-full">
+ <textarea
+ bind:this={textareaElement}
+ bind:value={editedContent}
+ class="min-h-[50vh] w-full resize-y rounded-2xl px-3 py-2 text-sm {INPUT_CLASSES}"
+ onkeydown={onEditKeydown}
+ oninput={(e) => onEditedContentChange?.(e.currentTarget.value)}
+ placeholder="Edit assistant message..."
+ ></textarea>
+
+ <div class="mt-2 flex items-center justify-between">
+ <div class="flex items-center space-x-2">
+ <Checkbox
+ id="branch-after-edit"
+ bind:checked={shouldBranchAfterEdit}
+ onCheckedChange={(checked) => onShouldBranchAfterEditChange?.(checked === true)}
+ />
+ <Label for="branch-after-edit" class="cursor-pointer text-sm text-muted-foreground">
+ Branch conversation after edit
+ </Label>
+ </div>
+ <div class="flex gap-2">
+ <Button class="h-8 px-3" onclick={onCancelEdit} size="sm" variant="outline">
+ <X class="mr-1 h-3 w-3" />
+ Cancel
+ </Button>
+
+ <Button class="h-8 px-3" onclick={onSaveEdit} disabled={!editedContent?.trim()} size="sm">
+ <Check class="mr-1 h-3 w-3" />
+ Save
+ </Button>
+ </div>
+ </div>
+ </div>
+ {:else if message.role === 'assistant'}
+ <MarkdownContent content={messageContent || ''} />
+ {:else}
+ <div class="text-sm whitespace-pre-wrap">
+ {messageContent}
+ </div>
+ {/if}
+
+ {#if message.timestamp && !isEditing}
+ <ChatMessageActions
+ {message}
+ role="assistant"
+ justify="start"
+ actionsPosition="left"
+ {siblingInfo}
+ {showDeleteDialog}
+ {deletionInfo}
+ {onCopy}
+ {onEdit}
+ {onRegenerate}
+ {onDelete}
+ {onConfirmDelete}
+ {onNavigateToSibling}
+ {onShowDeleteDialogChange}
+ />
+ {/if}
+</div>
+
+<style>
+ .processing-container {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.5rem;
+ }
+
+ .processing-text {
+ background: linear-gradient(
+ 90deg,
+ var(--muted-foreground),
+ var(--foreground),
+ var(--muted-foreground)
+ );
+ background-size: 200% 100%;
+ background-clip: text;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ animation: shine 1s linear infinite;
+ font-weight: 500;
+ font-size: 0.875rem;
+ }
+
+ @keyframes shine {
+ to {
+ background-position: -200% 0;
+ }
+ }
+</style>
--- /dev/null
+<script lang="ts">
+ import { ChevronLeft, ChevronRight } from '@lucide/svelte';
+ import { Button } from '$lib/components/ui/button';
+ import * as Tooltip from '$lib/components/ui/tooltip';
+
+ interface Props {
+ class?: string;
+ siblingInfo: ChatMessageSiblingInfo | null;
+ onNavigateToSibling?: (siblingId: string) => void;
+ }
+
+ let { class: className = '', siblingInfo, onNavigateToSibling }: Props = $props();
+
+ let hasPrevious = $derived(siblingInfo && siblingInfo.currentIndex > 0);
+ let hasNext = $derived(siblingInfo && siblingInfo.currentIndex < siblingInfo.totalSiblings - 1);
+ let nextSiblingId = $derived(
+ hasNext ? siblingInfo!.siblingIds[siblingInfo!.currentIndex + 1] : null
+ );
+ let previousSiblingId = $derived(
+ hasPrevious ? siblingInfo!.siblingIds[siblingInfo!.currentIndex - 1] : null
+ );
+
+ function handleNext() {
+ if (nextSiblingId) {
+ onNavigateToSibling?.(nextSiblingId);
+ }
+ }
+
+ function handlePrevious() {
+ if (previousSiblingId) {
+ onNavigateToSibling?.(previousSiblingId);
+ }
+ }
+</script>
+
+{#if siblingInfo && siblingInfo.totalSiblings > 1}
+ <div
+ aria-label="Message version {siblingInfo.currentIndex + 1} of {siblingInfo.totalSiblings}"
+ class="flex items-center gap-1 text-xs text-muted-foreground {className}"
+ role="navigation"
+ >
+ <Tooltip.Root>
+ <Tooltip.Trigger>
+ <Button
+ aria-label="Previous message version"
+ class="h-5 w-5 p-0 {!hasPrevious ? 'cursor-not-allowed opacity-30' : ''}"
+ disabled={!hasPrevious}
+ onclick={handlePrevious}
+ size="sm"
+ variant="ghost"
+ >
+ <ChevronLeft class="h-3 w-3" />
+ </Button>
+ </Tooltip.Trigger>
+
+ <Tooltip.Content>
+ <p>Previous version</p>
+ </Tooltip.Content>
+ </Tooltip.Root>
+
+ <span class="px-1 font-mono text-xs">
+ {siblingInfo.currentIndex + 1}/{siblingInfo.totalSiblings}
+ </span>
+
+ <Tooltip.Root>
+ <Tooltip.Trigger>
+ <Button
+ aria-label="Next message version"
+ class="h-5 w-5 p-0 {!hasNext ? 'cursor-not-allowed opacity-30' : ''}"
+ disabled={!hasNext}
+ onclick={handleNext}
+ size="sm"
+ variant="ghost"
+ >
+ <ChevronRight class="h-3 w-3" />
+ </Button>
+ </Tooltip.Trigger>
+
+ <Tooltip.Content>
+ <p>Next version</p>
+ </Tooltip.Content>
+ </Tooltip.Root>
+ </div>
+{/if}
--- /dev/null
+<script lang="ts">
+ import { Brain } from '@lucide/svelte';
+ import ChevronsUpDownIcon from '@lucide/svelte/icons/chevrons-up-down';
+ import * as Collapsible from '$lib/components/ui/collapsible/index.js';
+ import { buttonVariants } from '$lib/components/ui/button/index.js';
+ import { Card } from '$lib/components/ui/card';
+ import { MarkdownContent } from '$lib/components/app';
+ import { config } from '$lib/stores/settings.svelte';
+
+ interface Props {
+ class?: string;
+ hasRegularContent?: boolean;
+ isStreaming?: boolean;
+ reasoningContent: string | null;
+ }
+
+ let {
+ class: className = '',
+ hasRegularContent = false,
+ isStreaming = false,
+ reasoningContent
+ }: Props = $props();
+
+ const currentConfig = config();
+
+ let isExpanded = $state(currentConfig.showThoughtInProgress);
+
+ $effect(() => {
+ if (hasRegularContent && reasoningContent && currentConfig.showThoughtInProgress) {
+ isExpanded = false;
+ }
+ });
+</script>
+
+<Collapsible.Root bind:open={isExpanded} class="mb-6 {className}">
+ <Card class="gap-0 border-muted bg-muted/30 py-0">
+ <Collapsible.Trigger class="flex cursor-pointer items-center justify-between p-3">
+ <div class="flex items-center gap-2 text-muted-foreground">
+ <Brain class="h-4 w-4" />
+
+ <span class="text-sm font-medium">
+ {isStreaming ? 'Reasoning...' : 'Reasoning'}
+ </span>
+ </div>
+
+ <div
+ class={buttonVariants({
+ variant: 'ghost',
+ size: 'sm',
+ class: 'h-6 w-6 p-0 text-muted-foreground hover:text-foreground'
+ })}
+ >
+ <ChevronsUpDownIcon class="h-4 w-4" />
+
+ <span class="sr-only">Toggle reasoning content</span>
+ </div>
+ </Collapsible.Trigger>
+
+ <Collapsible.Content>
+ <div class="border-t border-muted px-3 pb-3">
+ <div class="pt-3">
+ <MarkdownContent content={reasoningContent || ''} class="text-xs leading-relaxed" />
+ </div>
+ </div>
+ </Collapsible.Content>
+ </Card>
+</Collapsible.Root>
--- /dev/null
+<script lang="ts">
+ import { Check, X } from '@lucide/svelte';
+ import { Card } from '$lib/components/ui/card';
+ import { Button } from '$lib/components/ui/button';
+ import { ChatAttachmentsList } from '$lib/components/app';
+ import { INPUT_CLASSES } from '$lib/constants/input-classes';
+ import ChatMessageActions from './ChatMessageActions.svelte';
+
+ interface Props {
+ class?: string;
+ message: DatabaseMessage;
+ isEditing: boolean;
+ editedContent: string;
+ siblingInfo?: ChatMessageSiblingInfo | null;
+ showDeleteDialog: boolean;
+ deletionInfo: {
+ totalCount: number;
+ userMessages: number;
+ assistantMessages: number;
+ messageTypes: string[];
+ } | null;
+ onCancelEdit: () => void;
+ onSaveEdit: () => void;
+ onEditKeydown: (event: KeyboardEvent) => void;
+ onEditedContentChange: (content: string) => void;
+ onCopy: () => void;
+ onEdit: () => void;
+ onDelete: () => void;
+ onConfirmDelete: () => void;
+ onNavigateToSibling?: (siblingId: string) => void;
+ onShowDeleteDialogChange: (show: boolean) => void;
+ textareaElement?: HTMLTextAreaElement;
+ }
+
+ let {
+ class: className = '',
+ message,
+ isEditing,
+ editedContent,
+ siblingInfo = null,
+ showDeleteDialog,
+ deletionInfo,
+ onCancelEdit,
+ onSaveEdit,
+ onEditKeydown,
+ onEditedContentChange,
+ onCopy,
+ onEdit,
+ onDelete,
+ onConfirmDelete,
+ onNavigateToSibling,
+ onShowDeleteDialogChange,
+ textareaElement = $bindable()
+ }: Props = $props();
+</script>
+
+<div
+ aria-label="User message with actions"
+ class="group flex flex-col items-end gap-2 {className}"
+ role="group"
+>
+ {#if isEditing}
+ <div class="w-full max-w-[80%]">
+ <textarea
+ bind:this={textareaElement}
+ bind:value={editedContent}
+ class="min-h-[60px] w-full resize-none rounded-2xl px-3 py-2 text-sm {INPUT_CLASSES}"
+ onkeydown={onEditKeydown}
+ oninput={(e) => onEditedContentChange(e.currentTarget.value)}
+ placeholder="Edit your message..."
+ ></textarea>
+
+ <div class="mt-2 flex justify-end gap-2">
+ <Button class="h-8 px-3" onclick={onCancelEdit} size="sm" variant="outline">
+ <X class="mr-1 h-3 w-3" />
+
+ Cancel
+ </Button>
+
+ <Button class="h-8 px-3" onclick={onSaveEdit} disabled={!editedContent.trim()} size="sm">
+ <Check class="mr-1 h-3 w-3" />
+
+ Send
+ </Button>
+ </div>
+ </div>
+ {:else}
+ {#if message.extra && message.extra.length > 0}
+ <div class="mb-2 max-w-[80%]">
+ <ChatAttachmentsList attachments={message.extra} readonly={true} imageHeight="h-80" />
+ </div>
+ {/if}
+
+ {#if message.content.trim()}
+ <Card class="max-w-[80%] rounded-2xl bg-primary px-2.5 py-1.5 text-primary-foreground">
+ <div class="text-md whitespace-pre-wrap">
+ {message.content}
+ </div>
+ </Card>
+ {/if}
+
+ {#if message.timestamp}
+ <div class="max-w-[80%]">
+ <ChatMessageActions
+ actionsPosition="right"
+ {deletionInfo}
+ justify="end"
+ {message}
+ {onConfirmDelete}
+ {onCopy}
+ {onDelete}
+ {onEdit}
+ {onNavigateToSibling}
+ {onShowDeleteDialogChange}
+ {siblingInfo}
+ {showDeleteDialog}
+ role="user"
+ />
+ </div>
+ {/if}
+ {/if}
+</div>
--- /dev/null
+<script lang="ts">
+ import { ChatMessage } from '$lib/components/app';
+ import { DatabaseStore } from '$lib/stores/database';
+ import {
+ activeConversation,
+ deleteMessage,
+ navigateToSibling,
+ editMessageWithBranching,
+ editAssistantMessage,
+ regenerateMessageWithBranching
+ } from '$lib/stores/chat.svelte';
+ import { getMessageSiblings } from '$lib/utils/branching';
+
+ interface Props {
+ class?: string;
+ messages?: DatabaseMessage[];
+ onUserAction?: () => void;
+ }
+
+ let { class: className, messages = [], onUserAction }: Props = $props();
+
+ let allConversationMessages = $state<DatabaseMessage[]>([]);
+
+ function refreshAllMessages() {
+ const conversation = activeConversation();
+
+ if (conversation) {
+ DatabaseStore.getConversationMessages(conversation.id).then((messages) => {
+ allConversationMessages = messages;
+ });
+ } else {
+ allConversationMessages = [];
+ }
+ }
+
+ // Single effect that tracks both conversation and message changes
+ $effect(() => {
+ const conversation = activeConversation();
+
+ if (conversation) {
+ refreshAllMessages();
+ }
+ });
+
+ let displayMessages = $derived.by(() => {
+ if (!messages.length) {
+ return [];
+ }
+
+ return messages.map((message) => {
+ const siblingInfo = getMessageSiblings(allConversationMessages, message.id);
+
+ return {
+ message,
+ siblingInfo: siblingInfo || {
+ message,
+ siblingIds: [message.id],
+ currentIndex: 0,
+ totalSiblings: 1
+ }
+ };
+ });
+ });
+
+ async function handleNavigateToSibling(siblingId: string) {
+ await navigateToSibling(siblingId);
+ }
+
+ async function handleEditWithBranching(message: DatabaseMessage, newContent: string) {
+ onUserAction?.();
+
+ await editMessageWithBranching(message.id, newContent);
+
+ refreshAllMessages();
+ }
+
+ async function handleEditWithReplacement(
+ message: DatabaseMessage,
+ newContent: string,
+ shouldBranch: boolean
+ ) {
+ onUserAction?.();
+
+ await editAssistantMessage(message.id, newContent, shouldBranch);
+
+ refreshAllMessages();
+ }
+
+ async function handleRegenerateWithBranching(message: DatabaseMessage) {
+ onUserAction?.();
+
+ await regenerateMessageWithBranching(message.id);
+
+ refreshAllMessages();
+ }
+ async function handleDeleteMessage(message: DatabaseMessage) {
+ await deleteMessage(message.id);
+
+ refreshAllMessages();
+ }
+</script>
+
+<div class="flex h-full flex-col space-y-10 pt-16 md:pt-24 {className}" style="height: auto; ">
+ {#each displayMessages as { message, siblingInfo } (message.id)}
+ <ChatMessage
+ class="mx-auto w-full max-w-[48rem]"
+ {message}
+ {siblingInfo}
+ onDelete={handleDeleteMessage}
+ onNavigateToSibling={handleNavigateToSibling}
+ onEditWithBranching={handleEditWithBranching}
+ onEditWithReplacement={handleEditWithReplacement}
+ onRegenerateWithBranching={handleRegenerateWithBranching}
+ />
+ {/each}
+</div>
--- /dev/null
+<script lang="ts">
+ import { PROCESSING_INFO_TIMEOUT } from '$lib/constants/processing-info';
+ import { useProcessingState } from '$lib/hooks/use-processing-state.svelte';
+ import { slotsService } from '$lib/services/slots';
+ import { isLoading, activeMessages, activeConversation } from '$lib/stores/chat.svelte';
+ import { config } from '$lib/stores/settings.svelte';
+
+ const processingState = useProcessingState();
+
+ let processingDetails = $derived(processingState.getProcessingDetails());
+
+ let showSlotsInfo = $derived(isLoading() || config().keepStatsVisible);
+
+ $effect(() => {
+ const keepStatsVisible = config().keepStatsVisible;
+
+ if (keepStatsVisible || isLoading()) {
+ processingState.startMonitoring();
+ }
+
+ if (!isLoading() && !keepStatsVisible) {
+ setTimeout(() => {
+ if (!config().keepStatsVisible) {
+ processingState.stopMonitoring();
+ }
+ }, PROCESSING_INFO_TIMEOUT);
+ }
+ });
+
+ $effect(() => {
+ activeConversation();
+
+ const messages = activeMessages() as DatabaseMessage[];
+ const keepStatsVisible = config().keepStatsVisible;
+
+ if (keepStatsVisible) {
+ if (messages.length === 0) {
+ slotsService.clearState();
+ return;
+ }
+
+ let foundTimingData = false;
+
+ for (let i = messages.length - 1; i >= 0; i--) {
+ const message = messages[i];
+ if (message.role === 'assistant' && message.timings) {
+ foundTimingData = true;
+
+ slotsService
+ .updateFromTimingData({
+ prompt_n: message.timings.prompt_n || 0,
+ 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
+ })
+ .catch((error) => {
+ console.warn('Failed to update processing state from stored timings:', error);
+ });
+ break;
+ }
+ }
+
+ if (!foundTimingData) {
+ slotsService.clearState();
+ }
+ }
+ });
+</script>
+
+<div class="chat-processing-info-container" class:visible={showSlotsInfo}>
+ <div class="chat-processing-info-content">
+ {#each processingDetails as detail (detail)}
+ <span class="chat-processing-info-detail">{detail}</span>
+ {/each}
+ </div>
+</div>
+
+<style>
+ .chat-processing-info-container {
+ position: sticky;
+ top: 0;
+ z-index: 10;
+ padding: 1.5rem 1rem;
+ opacity: 0;
+ transform: translateY(50%);
+ pointer-events: none;
+ transition:
+ opacity 300ms ease-out,
+ transform 300ms ease-out;
+ }
+
+ .chat-processing-info-container.visible {
+ opacity: 1;
+ pointer-events: auto;
+ transform: translateY(0);
+ }
+
+ .chat-processing-info-content {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 1rem;
+ justify-content: center;
+ max-width: 48rem;
+ margin: 0 auto;
+ }
+
+ .chat-processing-info-detail {
+ color: var(--muted-foreground);
+ font-size: 0.75rem;
+ padding: 0.25rem 0.75rem;
+ background: var(--muted);
+ border-radius: 0.375rem;
+ font-family:
+ ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
+ white-space: nowrap;
+ }
+
+ @media (max-width: 768px) {
+ .chat-processing-info-content {
+ gap: 0.5rem;
+ }
+
+ .chat-processing-info-detail {
+ font-size: 0.7rem;
+ padding: 0.2rem 0.5rem;
+ }
+ }
+</style>
--- /dev/null
+<script lang="ts">
+ import { afterNavigate } from '$app/navigation';
+ import {
+ ChatForm,
+ ChatScreenHeader,
+ ChatMessages,
+ ChatProcessingInfo,
+ EmptyFileAlertDialog,
+ ServerInfo,
+ ServerLoadingSplash,
+ ConfirmationDialog
+ } from '$lib/components/app';
+ import * as AlertDialog from '$lib/components/ui/alert-dialog';
+ import {
+ AUTO_SCROLL_AT_BOTTOM_THRESHOLD,
+ AUTO_SCROLL_INTERVAL,
+ INITIAL_SCROLL_DELAY
+ } from '$lib/constants/auto-scroll';
+ import {
+ activeMessages,
+ activeConversation,
+ deleteConversation,
+ isLoading,
+ sendMessage,
+ stopGeneration,
+ setMaxContextError
+ } from '$lib/stores/chat.svelte';
+ import {
+ supportsVision,
+ supportsAudio,
+ serverLoading,
+ serverStore
+ } from '$lib/stores/server.svelte';
+ import { contextService } from '$lib/services';
+ import { parseFilesToMessageExtras } from '$lib/utils/convert-files-to-extra';
+ import { isFileTypeSupported } from '$lib/utils/file-type';
+ import { filterFilesByModalities } from '$lib/utils/modality-file-validation';
+ import { processFilesToChatUploaded } from '$lib/utils/process-uploaded-files';
+ import { onMount } from 'svelte';
+ import { fade, fly, slide } from 'svelte/transition';
+ import { Trash2 } from '@lucide/svelte';
+ import ChatScreenDragOverlay from './ChatScreenDragOverlay.svelte';
+
+ let { showCenteredEmpty = false } = $props();
+
+ let autoScrollEnabled = $state(true);
+ let chatScrollContainer: HTMLDivElement | undefined = $state();
+ let dragCounter = $state(0);
+ let isDragOver = $state(false);
+ let lastScrollTop = $state(0);
+ let scrollInterval: ReturnType<typeof setInterval> | undefined;
+ let scrollTimeout: ReturnType<typeof setTimeout> | undefined;
+ let showFileErrorDialog = $state(false);
+ let uploadedFiles = $state<ChatUploadedFile[]>([]);
+ let userScrolledUp = $state(false);
+
+ let fileErrorData = $state<{
+ generallyUnsupported: File[];
+ modalityUnsupported: File[];
+ modalityReasons: Record<string, string>;
+ supportedTypes: string[];
+ }>({
+ generallyUnsupported: [],
+ modalityUnsupported: [],
+ modalityReasons: {},
+ supportedTypes: []
+ });
+
+ let showDeleteDialog = $state(false);
+
+ let showEmptyFileDialog = $state(false);
+
+ let emptyFileNames = $state<string[]>([]);
+
+ let isEmpty = $derived(
+ showCenteredEmpty && !activeConversation() && activeMessages().length === 0 && !isLoading()
+ );
+
+ let isServerLoading = $derived(serverLoading());
+
+ async function handleDeleteConfirm() {
+ const conversation = activeConversation();
+ if (conversation) {
+ await deleteConversation(conversation.id);
+ }
+ showDeleteDialog = false;
+ }
+
+ function handleDragEnter(event: DragEvent) {
+ event.preventDefault();
+ dragCounter++;
+ if (event.dataTransfer?.types.includes('Files')) {
+ isDragOver = true;
+ }
+ }
+
+ function handleDragLeave(event: DragEvent) {
+ event.preventDefault();
+ dragCounter--;
+ if (dragCounter === 0) {
+ isDragOver = false;
+ }
+ }
+
+ function handleDragOver(event: DragEvent) {
+ event.preventDefault();
+ }
+
+ function handleDrop(event: DragEvent) {
+ event.preventDefault();
+ isDragOver = false;
+ dragCounter = 0;
+
+ if (event.dataTransfer?.files) {
+ processFiles(Array.from(event.dataTransfer.files));
+ }
+ }
+
+ function handleFileRemove(fileId: string) {
+ uploadedFiles = uploadedFiles.filter((f) => f.id !== fileId);
+ }
+
+ function handleFileUpload(files: File[]) {
+ processFiles(files);
+ }
+
+ function handleKeydown(event: KeyboardEvent) {
+ const isCtrlOrCmd = event.ctrlKey || event.metaKey;
+
+ if (isCtrlOrCmd && event.shiftKey && (event.key === 'd' || event.key === 'D')) {
+ event.preventDefault();
+ if (activeConversation()) {
+ showDeleteDialog = true;
+ }
+ }
+ }
+
+ function handleScroll() {
+ if (!chatScrollContainer) return;
+
+ const { scrollTop, scrollHeight, clientHeight } = chatScrollContainer;
+ const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
+ const isAtBottom = distanceFromBottom < AUTO_SCROLL_AT_BOTTOM_THRESHOLD;
+
+ if (scrollTop < lastScrollTop && !isAtBottom) {
+ userScrolledUp = true;
+ autoScrollEnabled = false;
+ } else if (isAtBottom && userScrolledUp) {
+ userScrolledUp = false;
+ autoScrollEnabled = true;
+ }
+
+ if (scrollTimeout) {
+ clearTimeout(scrollTimeout);
+ }
+
+ scrollTimeout = setTimeout(() => {
+ if (isAtBottom) {
+ userScrolledUp = false;
+ autoScrollEnabled = true;
+ }
+ }, AUTO_SCROLL_INTERVAL);
+
+ lastScrollTop = scrollTop;
+ }
+
+ async function handleSendMessage(message: string, files?: ChatUploadedFile[]): Promise<boolean> {
+ const result = files ? await parseFilesToMessageExtras(files) : undefined;
+
+ if (result?.emptyFiles && result.emptyFiles.length > 0) {
+ emptyFileNames = result.emptyFiles;
+ showEmptyFileDialog = true;
+
+ if (files) {
+ const emptyFileNamesSet = new Set(result.emptyFiles);
+ uploadedFiles = uploadedFiles.filter((file) => !emptyFileNamesSet.has(file.name));
+ }
+ return false;
+ }
+
+ const extras = result?.extras;
+
+ // Check context limit using real-time slots data
+ const contextCheck = await contextService.checkContextLimit();
+
+ if (contextCheck && contextCheck.wouldExceed) {
+ const errorMessage = contextService.getContextErrorMessage(contextCheck);
+
+ setMaxContextError({
+ message: errorMessage,
+ estimatedTokens: contextCheck.currentUsage,
+ maxContext: contextCheck.maxContext
+ });
+
+ return false;
+ }
+
+ // Enable autoscroll for user-initiated message sending
+ userScrolledUp = false;
+ autoScrollEnabled = true;
+ await sendMessage(message, extras);
+ scrollChatToBottom();
+
+ return true;
+ }
+
+ async function processFiles(files: File[]) {
+ const generallySupported: File[] = [];
+ const generallyUnsupported: File[] = [];
+
+ for (const file of files) {
+ if (isFileTypeSupported(file.name, file.type)) {
+ generallySupported.push(file);
+ } else {
+ generallyUnsupported.push(file);
+ }
+ }
+
+ const { supportedFiles, unsupportedFiles, modalityReasons } =
+ filterFilesByModalities(generallySupported);
+
+ const allUnsupportedFiles = [...generallyUnsupported, ...unsupportedFiles];
+
+ if (allUnsupportedFiles.length > 0) {
+ const supportedTypes: string[] = ['text files', 'PDFs'];
+
+ if (supportsVision()) supportedTypes.push('images');
+ if (supportsAudio()) supportedTypes.push('audio files');
+
+ fileErrorData = {
+ generallyUnsupported,
+ modalityUnsupported: unsupportedFiles,
+ modalityReasons,
+ supportedTypes
+ };
+ showFileErrorDialog = true;
+ }
+
+ if (supportedFiles.length > 0) {
+ const processed = await processFilesToChatUploaded(supportedFiles);
+ uploadedFiles = [...uploadedFiles, ...processed];
+ }
+ }
+
+ function scrollChatToBottom(behavior: ScrollBehavior = 'smooth') {
+ chatScrollContainer?.scrollTo({
+ top: chatScrollContainer?.scrollHeight,
+ behavior
+ });
+ }
+
+ afterNavigate(() => {
+ setTimeout(() => scrollChatToBottom('instant'), INITIAL_SCROLL_DELAY);
+ });
+
+ onMount(() => {
+ setTimeout(() => scrollChatToBottom('instant'), INITIAL_SCROLL_DELAY);
+ });
+
+ $effect(() => {
+ if (isLoading() && autoScrollEnabled) {
+ scrollInterval = setInterval(scrollChatToBottom, AUTO_SCROLL_INTERVAL);
+ } else if (scrollInterval) {
+ clearInterval(scrollInterval);
+ scrollInterval = undefined;
+ }
+ });
+</script>
+
+{#if isDragOver}
+ <ChatScreenDragOverlay />
+{/if}
+
+<svelte:window onkeydown={handleKeydown} />
+
+<ChatScreenHeader />
+
+{#if !isEmpty}
+ <div
+ bind:this={chatScrollContainer}
+ aria-label="Chat interface with file drop zone"
+ class="flex h-full flex-col overflow-y-auto px-4 md:px-6"
+ ondragenter={handleDragEnter}
+ ondragleave={handleDragLeave}
+ ondragover={handleDragOver}
+ ondrop={handleDrop}
+ onscroll={handleScroll}
+ role="main"
+ >
+ <ChatMessages
+ class="mb-16 md:mb-24"
+ messages={activeMessages()}
+ onUserAction={() => {
+ userScrolledUp = false;
+ autoScrollEnabled = true;
+ scrollChatToBottom();
+ }}
+ />
+
+ <div
+ class="pointer-events-none sticky right-0 bottom-0 left-0 mt-auto"
+ in:slide={{ duration: 150, axis: 'y' }}
+ >
+ <ChatProcessingInfo />
+
+ <div class="conversation-chat-form pointer-events-auto rounded-t-3xl pb-4">
+ <ChatForm
+ isLoading={isLoading()}
+ onFileRemove={handleFileRemove}
+ onFileUpload={handleFileUpload}
+ onSend={handleSendMessage}
+ onStop={() => stopGeneration()}
+ showHelperText={false}
+ bind:uploadedFiles
+ />
+ </div>
+ </div>
+ </div>
+{:else if isServerLoading}
+ <!-- Server Loading State -->
+ <ServerLoadingSplash />
+{:else if serverStore.modelName}
+ <div
+ aria-label="Welcome screen with file drop zone"
+ class="flex h-full items-center justify-center"
+ ondragenter={handleDragEnter}
+ ondragleave={handleDragLeave}
+ ondragover={handleDragOver}
+ ondrop={handleDrop}
+ role="main"
+ >
+ <div class="w-full max-w-2xl px-4">
+ <div class="mb-8 text-center" in:fade={{ duration: 300 }}>
+ <h1 class="mb-2 text-3xl font-semibold tracking-tight">llama.cpp</h1>
+
+ <p class="text-lg text-muted-foreground">How can I help you today?</p>
+ </div>
+
+ <div class="mb-6 flex justify-center" in:fly={{ y: 10, duration: 300, delay: 200 }}>
+ <ServerInfo />
+ </div>
+
+ <div in:fly={{ y: 10, duration: 250, delay: 300 }}>
+ <ChatForm
+ isLoading={isLoading()}
+ onFileRemove={handleFileRemove}
+ onFileUpload={handleFileUpload}
+ onSend={handleSendMessage}
+ onStop={() => stopGeneration()}
+ showHelperText={true}
+ bind:uploadedFiles
+ />
+ </div>
+ </div>
+ </div>
+{/if}
+
+<!-- File Upload Error Alert Dialog -->
+<AlertDialog.Root bind:open={showFileErrorDialog}>
+ <AlertDialog.Portal>
+ <AlertDialog.Overlay />
+
+ <AlertDialog.Content class="max-w-md">
+ <AlertDialog.Header>
+ <AlertDialog.Title>File Upload Error</AlertDialog.Title>
+
+ <AlertDialog.Description class="text-sm text-muted-foreground">
+ Some files cannot be uploaded with the current model.
+ </AlertDialog.Description>
+ </AlertDialog.Header>
+
+ <div class="space-y-4">
+ {#if fileErrorData.generallyUnsupported.length > 0}
+ <div class="space-y-2">
+ <h4 class="text-sm font-medium text-destructive">Unsupported File Types</h4>
+
+ <div class="space-y-1">
+ {#each fileErrorData.generallyUnsupported as file (file.name)}
+ <div class="rounded-md bg-destructive/10 px-3 py-2">
+ <p class="font-mono text-sm break-all text-destructive">
+ {file.name}
+ </p>
+
+ <p class="mt-1 text-xs text-muted-foreground">File type not supported</p>
+ </div>
+ {/each}
+ </div>
+ </div>
+ {/if}
+
+ {#if fileErrorData.modalityUnsupported.length > 0}
+ <div class="space-y-2">
+ <h4 class="text-sm font-medium text-destructive">Model Compatibility Issues</h4>
+
+ <div class="space-y-1">
+ {#each fileErrorData.modalityUnsupported as file (file.name)}
+ <div class="rounded-md bg-destructive/10 px-3 py-2">
+ <p class="font-mono text-sm break-all text-destructive">
+ {file.name}
+ </p>
+
+ <p class="mt-1 text-xs text-muted-foreground">
+ {fileErrorData.modalityReasons[file.name] || 'Not supported by current model'}
+ </p>
+ </div>
+ {/each}
+ </div>
+ </div>
+ {/if}
+
+ <div class="rounded-md bg-muted/50 p-3">
+ <h4 class="mb-2 text-sm font-medium">This model supports:</h4>
+
+ <p class="text-sm text-muted-foreground">
+ {fileErrorData.supportedTypes.join(', ')}
+ </p>
+ </div>
+ </div>
+
+ <AlertDialog.Footer>
+ <AlertDialog.Action onclick={() => (showFileErrorDialog = false)}>
+ Got it
+ </AlertDialog.Action>
+ </AlertDialog.Footer>
+ </AlertDialog.Content>
+ </AlertDialog.Portal>
+</AlertDialog.Root>
+
+<ConfirmationDialog
+ bind:open={showDeleteDialog}
+ title="Delete Conversation"
+ description="Are you sure you want to delete this conversation? This action cannot be undone and will permanently remove all messages in this conversation."
+ confirmText="Delete"
+ cancelText="Cancel"
+ variant="destructive"
+ icon={Trash2}
+ onConfirm={handleDeleteConfirm}
+ onCancel={() => (showDeleteDialog = false)}
+/>
+
+<EmptyFileAlertDialog
+ bind:open={showEmptyFileDialog}
+ emptyFiles={emptyFileNames}
+ onOpenChange={(open) => {
+ if (!open) {
+ emptyFileNames = [];
+ }
+ }}
+/>
+
+<style>
+ .conversation-chat-form {
+ position: relative;
+
+ &::after {
+ content: '';
+ position: fixed;
+ bottom: 0;
+ z-index: -1;
+ left: 0;
+ right: 0;
+ width: 100%;
+ height: 2.375rem;
+ background-color: var(--background);
+ }
+ }
+</style>
--- /dev/null
+<script>
+ import { Upload } from '@lucide/svelte';
+</script>
+
+<div
+ class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
+>
+ <div
+ class="flex flex-col items-center justify-center rounded-2xl border-2 border-dashed border-border bg-background p-12 shadow-lg"
+ >
+ <Upload class="mb-4 h-12 w-12 text-muted-foreground" />
+
+ <p class="text-lg font-medium text-foreground">Attach a file</p>
+
+ <p class="text-sm text-muted-foreground">Drop your files here to upload</p>
+ </div>
+</div>
--- /dev/null
+<script lang="ts">
+ import { Settings } from '@lucide/svelte';
+ import { ChatSettingsDialog } from '$lib/components/app';
+ import { Button } from '$lib/components/ui/button';
+
+ let settingsOpen = $state(false);
+
+ function toggleSettings() {
+ settingsOpen = true;
+ }
+</script>
+
+<header
+ class="md:background-transparent pointer-events-none fixed top-0 right-0 left-0 z-50 flex items-center justify-end bg-background/40 p-4 backdrop-blur-xl md:left-[var(--sidebar-width)]"
+>
+ <div class="pointer-events-auto flex items-center space-x-2">
+ <Button variant="ghost" size="sm" onclick={toggleSettings}>
+ <Settings class="h-4 w-4" />
+ </Button>
+ </div>
+</header>
+
+<ChatSettingsDialog open={settingsOpen} onOpenChange={(open) => (settingsOpen = open)} />
--- /dev/null
+<script lang="ts">
+ import { Settings, Funnel, AlertTriangle, Brain, Cog, Monitor, Sun, Moon } from '@lucide/svelte';
+ import { ChatSettingsFooter, ChatSettingsSection } from '$lib/components/app';
+ import { Checkbox } from '$lib/components/ui/checkbox';
+ import * as Dialog from '$lib/components/ui/dialog';
+ import { Input } from '$lib/components/ui/input';
+ import Label from '$lib/components/ui/label/label.svelte';
+ import { ScrollArea } from '$lib/components/ui/scroll-area';
+ import * as Select from '$lib/components/ui/select';
+ import { Textarea } from '$lib/components/ui/textarea';
+ import { SETTING_CONFIG_DEFAULT, SETTING_CONFIG_INFO } from '$lib/constants/settings-config';
+ import { supportsVision } from '$lib/stores/server.svelte';
+ import { config, updateMultipleConfig, resetConfig } from '$lib/stores/settings.svelte';
+ import { setMode } from 'mode-watcher';
+ import type { Component } from 'svelte';
+
+ interface Props {
+ onOpenChange?: (open: boolean) => void;
+ open?: boolean;
+ }
+
+ let { onOpenChange, open = false }: Props = $props();
+
+ const settingSections: Array<{
+ fields: SettingsFieldConfig[];
+ icon: Component;
+ title: string;
+ }> = [
+ {
+ title: 'General',
+ icon: Settings,
+ fields: [
+ { key: 'apiKey', label: 'API Key', type: 'input' },
+ {
+ key: 'systemMessage',
+ label: 'System Message (will be disabled if left empty)',
+ type: 'textarea'
+ },
+ {
+ key: 'theme',
+ label: 'Theme',
+ type: 'select',
+ options: [
+ { value: 'system', label: 'System', icon: Monitor },
+ { value: 'light', label: 'Light', icon: Sun },
+ { value: 'dark', label: 'Dark', icon: Moon }
+ ]
+ },
+ {
+ key: 'showTokensPerSecond',
+ label: 'Show tokens per second',
+ type: 'checkbox'
+ },
+ {
+ key: 'keepStatsVisible',
+ label: 'Keep stats visible after generation',
+ type: 'checkbox'
+ },
+ {
+ key: 'askForTitleConfirmation',
+ label: 'Ask for confirmation before changing conversation title',
+ type: 'checkbox'
+ },
+ {
+ key: 'pasteLongTextToFileLen',
+ label: 'Paste long text to file length',
+ type: 'input'
+ },
+ {
+ key: 'pdfAsImage',
+ label: 'Parse PDF as image',
+ type: 'checkbox'
+ }
+ ]
+ },
+ {
+ title: 'Samplers',
+ icon: Funnel,
+ fields: [
+ {
+ key: 'samplers',
+ label: 'Samplers',
+ type: 'input'
+ }
+ ]
+ },
+ {
+ title: 'Penalties',
+ icon: AlertTriangle,
+ fields: [
+ {
+ key: 'repeat_last_n',
+ label: 'Repeat last N',
+ type: 'input'
+ },
+ {
+ key: 'repeat_penalty',
+ label: 'Repeat penalty',
+ type: 'input'
+ },
+ {
+ key: 'presence_penalty',
+ label: 'Presence penalty',
+ type: 'input'
+ },
+ {
+ key: 'frequency_penalty',
+ label: 'Frequency penalty',
+ type: 'input'
+ },
+ {
+ key: 'dry_multiplier',
+ label: 'DRY multiplier',
+ type: 'input'
+ },
+ {
+ key: 'dry_base',
+ label: 'DRY base',
+ type: 'input'
+ },
+ {
+ key: 'dry_allowed_length',
+ label: 'DRY allowed length',
+ type: 'input'
+ },
+ {
+ key: 'dry_penalty_last_n',
+ label: 'DRY penalty last N',
+ type: 'input'
+ }
+ ]
+ },
+ {
+ title: 'Reasoning',
+ icon: Brain,
+ fields: [
+ {
+ key: 'showThoughtInProgress',
+ label: 'Show thought in progress',
+ type: 'checkbox'
+ }
+ ]
+ },
+ {
+ title: 'Advanced',
+ icon: Cog,
+ fields: [
+ {
+ key: 'temperature',
+ label: 'Temperature',
+ type: 'input'
+ },
+ {
+ key: 'dynatemp_range',
+ label: 'Dynamic temperature range',
+ type: 'input'
+ },
+ {
+ key: 'dynatemp_exponent',
+ label: 'Dynamic temperature exponent',
+ type: 'input'
+ },
+ {
+ key: 'top_k',
+ label: 'Top K',
+ type: 'input'
+ },
+ {
+ key: 'top_p',
+ label: 'Top P',
+ type: 'input'
+ },
+ {
+ key: 'min_p',
+ label: 'Min P',
+ type: 'input'
+ },
+ {
+ key: 'xtc_probability',
+ label: 'XTC probability',
+ type: 'input'
+ },
+ {
+ key: 'xtc_threshold',
+ label: 'XTC threshold',
+ type: 'input'
+ },
+ {
+ key: 'typ_p',
+ label: 'Typical P',
+ type: 'input'
+ },
+ {
+ key: 'max_tokens',
+ label: 'Max tokens',
+ type: 'input'
+ },
+ {
+ key: 'custom',
+ label: 'Custom JSON',
+ type: 'textarea'
+ }
+ ]
+ }
+ // TODO: Experimental features section will be implemented after initial release
+ // This includes Python interpreter (Pyodide integration) and other experimental features
+ // {
+ // title: 'Experimental',
+ // icon: Beaker,
+ // fields: [
+ // {
+ // key: 'pyInterpreterEnabled',
+ // label: 'Enable Python interpreter',
+ // type: 'checkbox'
+ // }
+ // ]
+ // }
+ ];
+
+ let activeSection = $state('General');
+ let currentSection = $derived(
+ settingSections.find((section) => section.title === activeSection) || settingSections[0]
+ );
+ let localConfig: SettingsConfigType = $state({ ...config() });
+ let originalTheme: string = $state('');
+
+ function handleThemeChange(newTheme: string) {
+ localConfig.theme = newTheme;
+
+ setMode(newTheme as 'light' | 'dark' | 'system');
+ }
+
+ function handleClose() {
+ if (localConfig.theme !== originalTheme) {
+ setMode(originalTheme as 'light' | 'dark' | 'system');
+ }
+ onOpenChange?.(false);
+ }
+
+ function handleReset() {
+ resetConfig();
+
+ localConfig = { ...SETTING_CONFIG_DEFAULT };
+
+ setMode(SETTING_CONFIG_DEFAULT.theme as 'light' | 'dark' | 'system');
+ originalTheme = SETTING_CONFIG_DEFAULT.theme as string;
+ }
+
+ function handleSave() {
+ // Validate custom JSON if provided
+ if (localConfig.custom && typeof localConfig.custom === 'string' && localConfig.custom.trim()) {
+ try {
+ JSON.parse(localConfig.custom);
+ } catch (error) {
+ alert('Invalid JSON in custom parameters. Please check the format and try again.');
+ console.error(error);
+ return;
+ }
+ }
+
+ // Convert numeric strings to numbers for numeric fields
+ const processedConfig = { ...localConfig };
+ const numericFields = [
+ 'temperature',
+ 'top_k',
+ 'top_p',
+ 'min_p',
+ 'max_tokens',
+ 'pasteLongTextToFileLen',
+ 'dynatemp_range',
+ 'dynatemp_exponent',
+ 'typ_p',
+ 'xtc_probability',
+ 'xtc_threshold',
+ 'repeat_last_n',
+ 'repeat_penalty',
+ 'presence_penalty',
+ 'frequency_penalty',
+ 'dry_multiplier',
+ 'dry_base',
+ 'dry_allowed_length',
+ 'dry_penalty_last_n'
+ ];
+
+ for (const field of numericFields) {
+ if (processedConfig[field] !== undefined && processedConfig[field] !== '') {
+ const numValue = Number(processedConfig[field]);
+ if (!isNaN(numValue)) {
+ processedConfig[field] = numValue;
+ } else {
+ alert(`Invalid numeric value for ${field}. Please enter a valid number.`);
+ return;
+ }
+ }
+ }
+
+ updateMultipleConfig(processedConfig);
+ onOpenChange?.(false);
+ }
+
+ $effect(() => {
+ if (open) {
+ localConfig = { ...config() };
+ originalTheme = config().theme as string;
+ }
+ });
+</script>
+
+<Dialog.Root {open} onOpenChange={handleClose}>
+ <Dialog.Content class="flex h-[64vh] flex-col gap-0 p-0" style="max-width: 48rem;">
+ <div class="flex flex-1 overflow-hidden">
+ <div class="w-64 border-r border-border/30 p-6">
+ <nav class="space-y-1 py-2">
+ <Dialog.Title class="mb-6 flex items-center gap-2">Settings</Dialog.Title>
+
+ {#each settingSections as section (section.title)}
+ <button
+ class="flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-left text-sm transition-colors hover:bg-accent {activeSection ===
+ section.title
+ ? 'bg-accent text-accent-foreground'
+ : 'text-muted-foreground'}"
+ onclick={() => (activeSection = section.title)}
+ >
+ <section.icon class="h-4 w-4" />
+
+ <span class="ml-2">{section.title}</span>
+ </button>
+ {/each}
+ </nav>
+ </div>
+
+ <ScrollArea class="flex-1">
+ <div class="space-y-6 p-6">
+ <ChatSettingsSection title={currentSection.title} Icon={currentSection.icon}>
+ {#each currentSection.fields as field (field.key)}
+ <div class="space-y-2">
+ {#if field.type === 'input'}
+ <Label for={field.key} class="block text-sm font-medium">
+ {field.label}
+ </Label>
+
+ <Input
+ id={field.key}
+ value={String(localConfig[field.key] || '')}
+ onchange={(e) => (localConfig[field.key] = e.currentTarget.value)}
+ placeholder={`Default: ${SETTING_CONFIG_DEFAULT[field.key] || 'none'}`}
+ class="max-w-md"
+ />
+ {#if field.help || SETTING_CONFIG_INFO[field.key]}
+ <p class="mt-1 text-xs text-muted-foreground">
+ {field.help || SETTING_CONFIG_INFO[field.key]}
+ </p>
+ {/if}
+ {:else if field.type === 'textarea'}
+ <Label for={field.key} class="block text-sm font-medium">
+ {field.label}
+ </Label>
+
+ <Textarea
+ id={field.key}
+ value={String(localConfig[field.key] || '')}
+ onchange={(e) => (localConfig[field.key] = e.currentTarget.value)}
+ placeholder={`Default: ${SETTING_CONFIG_DEFAULT[field.key] || 'none'}`}
+ class="min-h-[100px] max-w-2xl"
+ />
+ {#if field.help || SETTING_CONFIG_INFO[field.key]}
+ <p class="mt-1 text-xs text-muted-foreground">
+ {field.help || SETTING_CONFIG_INFO[field.key]}
+ </p>
+ {/if}
+ {:else if field.type === 'select'}
+ {@const selectedOption = field.options?.find(
+ (opt: { value: string; label: string; icon?: Component }) =>
+ opt.value === localConfig[field.key]
+ )}
+
+ <Label for={field.key} class="block text-sm font-medium">
+ {field.label}
+ </Label>
+
+ <Select.Root
+ type="single"
+ value={localConfig[field.key]}
+ onValueChange={(value) => {
+ if (field.key === 'theme' && value) {
+ handleThemeChange(value);
+ } else {
+ localConfig[field.key] = value;
+ }
+ }}
+ >
+ <Select.Trigger class="max-w-md">
+ <div class="flex items-center gap-2">
+ {#if selectedOption?.icon}
+ {@const IconComponent = selectedOption.icon}
+ <IconComponent class="h-4 w-4" />
+ {/if}
+
+ {selectedOption?.label || `Select ${field.label.toLowerCase()}`}
+ </div>
+ </Select.Trigger>
+ <Select.Content>
+ {#if field.options}
+ {#each field.options as option (option.value)}
+ <Select.Item value={option.value} label={option.label}>
+ <div class="flex items-center gap-2">
+ {#if option.icon}
+ {@const IconComponent = option.icon}
+ <IconComponent class="h-4 w-4" />
+ {/if}
+ {option.label}
+ </div>
+ </Select.Item>
+ {/each}
+ {/if}
+ </Select.Content>
+ </Select.Root>
+ {#if field.help || SETTING_CONFIG_INFO[field.key]}
+ <p class="mt-1 text-xs text-muted-foreground">
+ {field.help || SETTING_CONFIG_INFO[field.key]}
+ </p>
+ {/if}
+ {:else if field.type === 'checkbox'}
+ {@const isDisabled = field.key === 'pdfAsImage' && !supportsVision()}
+ <div class="flex items-start space-x-3">
+ <Checkbox
+ id={field.key}
+ checked={Boolean(localConfig[field.key])}
+ disabled={isDisabled}
+ onCheckedChange={(checked) => (localConfig[field.key] = checked)}
+ class="mt-1"
+ />
+
+ <div class="space-y-1">
+ <label
+ for={field.key}
+ class="cursor-pointer text-sm leading-none font-medium {isDisabled
+ ? 'text-muted-foreground'
+ : ''}"
+ >
+ {field.label}
+ </label>
+
+ {#if field.help || SETTING_CONFIG_INFO[field.key]}
+ <p class="text-xs text-muted-foreground">
+ {field.help || SETTING_CONFIG_INFO[field.key]}
+ </p>
+ {:else if field.key === 'pdfAsImage' && !supportsVision()}
+ <p class="text-xs text-muted-foreground">
+ PDF-to-image processing requires a vision-capable model. PDFs will be
+ processed as text.
+ </p>
+ {/if}
+ </div>
+ </div>
+ {/if}
+ </div>
+ {/each}
+ </ChatSettingsSection>
+
+ <div class="mt-8 border-t pt-6">
+ <p class="text-xs text-muted-foreground">
+ Settings are saved in browser's localStorage
+ </p>
+ </div>
+ </div>
+ </ScrollArea>
+ </div>
+
+ <ChatSettingsFooter onClose={handleClose} onReset={handleReset} onSave={handleSave} />
+ </Dialog.Content>
+</Dialog.Root>
--- /dev/null
+<script lang="ts">
+ import { Button } from '$lib/components/ui/button';
+
+ interface Props {
+ onClose?: () => void;
+ onReset?: () => void;
+ onSave?: () => void;
+ }
+
+ let { onClose, onReset, onSave }: Props = $props();
+
+ function handleClose() {
+ onClose?.();
+ }
+
+ function handleReset() {
+ onReset?.();
+ }
+
+ function handleSave() {
+ onSave?.();
+ }
+</script>
+
+<div class="flex justify-between border-t border-border/30 p-6">
+ <Button variant="outline" onclick={handleReset}>Reset to default</Button>
+
+ <div class="flex gap-2">
+ <Button variant="outline" onclick={handleClose}>Close</Button>
+
+ <Button onclick={handleSave}>Save</Button>
+ </div>
+</div>
--- /dev/null
+<script lang="ts">
+ import type { Component, Snippet } from 'svelte';
+
+ interface Props {
+ children: Snippet;
+ title: string;
+ Icon: Component;
+ }
+
+ let { children, title, Icon }: Props = $props();
+</script>
+
+<div>
+ <div class="mb-6 flex items-center gap-2 border-b border-border/30 pb-6">
+ <Icon class="h-5 w-5" />
+
+ <h3 class="text-lg font-semibold">{title}</h3>
+ </div>
+
+ <div class="space-y-6">
+ {@render children()}
+ </div>
+</div>
--- /dev/null
+<script lang="ts">
+ import { goto } from '$app/navigation';
+ import { page } from '$app/state';
+ import { ChatSidebarConversationItem } from '$lib/components/app';
+ import ScrollArea from '$lib/components/ui/scroll-area/scroll-area.svelte';
+ import * as Sidebar from '$lib/components/ui/sidebar';
+ import {
+ conversations,
+ deleteConversation,
+ updateConversationName
+ } from '$lib/stores/chat.svelte';
+ import ChatSidebarActions from './ChatSidebarActions.svelte';
+
+ const sidebar = Sidebar.useSidebar();
+
+ let currentChatId = $derived(page.params.id);
+ let isSearchModeActive = $state(false);
+ let searchQuery = $state('');
+
+ let filteredConversations = $derived.by(() => {
+ if (searchQuery.trim().length > 0) {
+ return conversations().filter((conversation: { name: string }) =>
+ conversation.name.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+ }
+
+ return conversations();
+ });
+
+ async function editConversation(id: string, name: string) {
+ await updateConversationName(id, name);
+ }
+
+ async function handleDeleteConversation(id: string) {
+ await deleteConversation(id);
+ }
+
+ export function handleMobileSidebarItemClick() {
+ if (sidebar.isMobile) {
+ sidebar.toggle();
+ }
+ }
+
+ export function activateSearchMode() {
+ isSearchModeActive = true;
+ }
+
+ export function editActiveConversation() {
+ if (currentChatId) {
+ const activeConversation = filteredConversations.find((conv) => conv.id === currentChatId);
+
+ if (activeConversation) {
+ const event = new CustomEvent('edit-active-conversation', {
+ detail: { conversationId: currentChatId }
+ });
+ document.dispatchEvent(event);
+ }
+ }
+ }
+
+ async function selectConversation(id: string) {
+ if (isSearchModeActive) {
+ isSearchModeActive = false;
+ searchQuery = '';
+ }
+
+ await goto(`/chat/${id}`);
+ }
+</script>
+
+<ScrollArea class="h-[100vh]">
+ <Sidebar.Header class=" top-0 z-10 gap-6 bg-sidebar/50 px-4 pt-4 pb-2 backdrop-blur-lg md:sticky">
+ <a href="/" onclick={handleMobileSidebarItemClick}>
+ <h1 class="inline-flex items-center gap-1 px-2 text-xl font-semibold">llama.cpp</h1>
+ </a>
+
+ <ChatSidebarActions {handleMobileSidebarItemClick} bind:isSearchModeActive bind:searchQuery />
+ </Sidebar.Header>
+
+ <Sidebar.Group class="mt-4 space-y-2 p-0 px-4">
+ {#if (filteredConversations.length > 0 && isSearchModeActive) || !isSearchModeActive}
+ <Sidebar.GroupLabel>
+ {isSearchModeActive ? 'Search results' : 'Conversations'}
+ </Sidebar.GroupLabel>
+ {/if}
+
+ <Sidebar.GroupContent>
+ <Sidebar.Menu>
+ {#each filteredConversations as conversation (conversation.id)}
+ <Sidebar.MenuItem class="mb-1" onclick={handleMobileSidebarItemClick}>
+ <ChatSidebarConversationItem
+ conversation={{
+ id: conversation.id,
+ name: conversation.name,
+ lastModified: conversation.lastModified,
+ currNode: conversation.currNode
+ }}
+ isActive={currentChatId === conversation.id}
+ onSelect={selectConversation}
+ onEdit={editConversation}
+ onDelete={handleDeleteConversation}
+ />
+ </Sidebar.MenuItem>
+ {/each}
+
+ {#if filteredConversations.length === 0}
+ <div class="px-2 py-4 text-center">
+ <p class="mb-4 p-4 text-sm text-muted-foreground">
+ {searchQuery.length > 0
+ ? 'No results found'
+ : isSearchModeActive
+ ? 'Start typing to see results'
+ : 'No conversations yet'}
+ </p>
+ </div>
+ {/if}
+ </Sidebar.Menu>
+ </Sidebar.GroupContent>
+ </Sidebar.Group>
+
+ <div class="bottom-0 z-10 bg-sidebar bg-sidebar/50 px-4 py-4 backdrop-blur-lg md:sticky">
+ <p class="text-xs text-muted-foreground">Conversations are stored locally in your browser.</p>
+ </div>
+</ScrollArea>
--- /dev/null
+<script lang="ts">
+ import { Search, SquarePen, X } from '@lucide/svelte';
+ import { KeyboardShortcutInfo } from '$lib/components/app';
+ import { Button } from '$lib/components/ui/button';
+ import { Input } from '$lib/components/ui/input';
+
+ interface Props {
+ handleMobileSidebarItemClick: () => void;
+ isSearchModeActive: boolean;
+ searchQuery: string;
+ }
+
+ let {
+ handleMobileSidebarItemClick,
+ isSearchModeActive = $bindable(),
+ searchQuery = $bindable()
+ }: Props = $props();
+
+ let searchInput: HTMLInputElement | null = $state(null);
+
+ function handleSearchModeDeactivate() {
+ isSearchModeActive = false;
+ searchQuery = '';
+ }
+
+ $effect(() => {
+ if (isSearchModeActive) {
+ searchInput?.focus();
+ }
+ });
+</script>
+
+<div class="space-y-0.5">
+ {#if isSearchModeActive}
+ <div class="relative">
+ <Search class="absolute top-2.5 left-2 h-4 w-4 text-muted-foreground" />
+
+ <Input
+ bind:ref={searchInput}
+ bind:value={searchQuery}
+ onkeydown={(e) => e.key === 'Escape' && handleSearchModeDeactivate()}
+ placeholder="Search conversations..."
+ class="pl-8"
+ />
+
+ <X
+ class="cursor-pointertext-muted-foreground absolute top-2.5 right-2 h-4 w-4"
+ onclick={handleSearchModeDeactivate}
+ />
+ </div>
+ {:else}
+ <Button
+ class="w-full justify-between hover:[&>kbd]:opacity-100"
+ href="/?new_chat=true"
+ onclick={handleMobileSidebarItemClick}
+ variant="ghost"
+ >
+ <div class="flex items-center gap-2">
+ <SquarePen class="h-4 w-4" />
+ New chat
+ </div>
+
+ <KeyboardShortcutInfo keys={['shift', 'cmd', 'o']} />
+ </Button>
+
+ <Button
+ class="w-full justify-between hover:[&>kbd]:opacity-100"
+ onclick={() => {
+ isSearchModeActive = true;
+ }}
+ variant="ghost"
+ >
+ <div class="flex items-center gap-2">
+ <Search class="h-4 w-4" />
+ Search conversations
+ </div>
+
+ <KeyboardShortcutInfo keys={['cmd', 'k']} />
+ </Button>
+ {/if}
+</div>
--- /dev/null
+<script lang="ts">
+ import { Trash2, Pencil, MoreHorizontal } from '@lucide/svelte';
+ import { ActionDropdown, ConfirmationDialog } from '$lib/components/app';
+ import * as AlertDialog from '$lib/components/ui/alert-dialog';
+ import Input from '$lib/components/ui/input/input.svelte';
+ import { onMount } from 'svelte';
+
+ interface Props {
+ isActive?: boolean;
+ conversation: DatabaseConversation;
+ onDelete?: (id: string) => void;
+ onEdit?: (id: string, name: string) => void;
+ onSelect?: (id: string) => void;
+ showLastModified?: boolean;
+ }
+
+ let {
+ conversation,
+ onDelete,
+ onEdit,
+ onSelect,
+ isActive = false,
+ showLastModified = false
+ }: Props = $props();
+
+ let editedName = $state('');
+ let showDeleteDialog = $state(false);
+ let showDropdown = $state(false);
+ let showEditDialog = $state(false);
+
+ function formatLastModified(timestamp: number) {
+ const now = Date.now();
+ const diff = now - timestamp;
+ const minutes = Math.floor(diff / (1000 * 60));
+ const hours = Math.floor(diff / (1000 * 60 * 60));
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
+
+ if (minutes < 1) return 'Just now';
+ if (minutes < 60) return `${minutes}m ago`;
+ if (hours < 24) return `${hours}h ago`;
+ return `${days}d ago`;
+ }
+
+ function handleConfirmDelete() {
+ onDelete?.(conversation.id);
+ }
+
+ function handleConfirmEdit() {
+ if (!editedName.trim()) return;
+ onEdit?.(conversation.id, editedName);
+ }
+
+ function handleEdit(event: Event) {
+ event.stopPropagation();
+ editedName = conversation.name;
+ showEditDialog = true;
+ }
+
+ function handleSelect() {
+ onSelect?.(conversation.id);
+ }
+
+ function handleGlobalEditEvent(event: Event) {
+ const customEvent = event as CustomEvent<{ conversationId: string }>;
+ if (customEvent.detail.conversationId === conversation.id && isActive) {
+ handleEdit(event);
+ }
+ }
+
+ onMount(() => {
+ document.addEventListener('edit-active-conversation', handleGlobalEditEvent as EventListener);
+
+ return () => {
+ document.removeEventListener(
+ 'edit-active-conversation',
+ handleGlobalEditEvent as EventListener
+ );
+ };
+ });
+</script>
+
+<button
+ class="group flex w-full cursor-pointer items-center justify-between space-x-3 rounded-lg px-3 py-1.5 text-left transition-colors hover:bg-foreground/10 {isActive
+ ? 'bg-foreground/5 text-accent-foreground'
+ : ''}"
+ onclick={handleSelect}
+>
+ <div class="text flex min-w-0 flex-1 items-center space-x-3">
+ <div class="min-w-0 flex-1">
+ <p class="truncate text-sm font-medium">{conversation.name}</p>
+
+ {#if showLastModified}
+ <div class="mt-2 flex flex-wrap items-center space-y-2 space-x-2">
+ <span class="w-full text-xs text-muted-foreground">
+ {formatLastModified(conversation.lastModified)}
+ </span>
+ </div>
+ {/if}
+ </div>
+ </div>
+
+ <div class="actions flex items-center">
+ <ActionDropdown
+ triggerIcon={MoreHorizontal}
+ triggerTooltip="More actions"
+ bind:open={showDropdown}
+ actions={[
+ {
+ icon: Pencil,
+ label: 'Edit',
+ onclick: handleEdit,
+ shortcut: ['shift', 'cmd', 'e']
+ },
+ {
+ icon: Trash2,
+ label: 'Delete',
+ onclick: (e) => {
+ e.stopPropagation();
+ showDeleteDialog = true;
+ },
+ variant: 'destructive',
+ shortcut: ['shift', 'cmd', 'd'],
+ separator: true
+ }
+ ]}
+ />
+
+ <ConfirmationDialog
+ bind:open={showDeleteDialog}
+ title="Delete Conversation"
+ description={`Are you sure you want to delete "${conversation.name}"? This action cannot be undone and will permanently remove all messages in this conversation.`}
+ confirmText="Delete"
+ cancelText="Cancel"
+ variant="destructive"
+ icon={Trash2}
+ onConfirm={handleConfirmDelete}
+ onCancel={() => (showDeleteDialog = false)}
+ />
+
+ <AlertDialog.Root bind:open={showEditDialog}>
+ <AlertDialog.Content>
+ <AlertDialog.Header>
+ <AlertDialog.Title>Edit Conversation Name</AlertDialog.Title>
+
+ <AlertDialog.Description>
+ <Input
+ class="mt-4 text-foreground"
+ onkeydown={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ handleConfirmEdit();
+ showEditDialog = false;
+ }
+ }}
+ placeholder="Enter a new name"
+ type="text"
+ bind:value={editedName}
+ />
+ </AlertDialog.Description>
+ </AlertDialog.Header>
+
+ <AlertDialog.Footer>
+ <AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
+
+ <AlertDialog.Action onclick={handleConfirmEdit}>Save</AlertDialog.Action>
+ </AlertDialog.Footer>
+ </AlertDialog.Content>
+ </AlertDialog.Root>
+ </div>
+</button>
+
+<style>
+ button {
+ :global([data-slot='dropdown-menu-trigger']:not([data-state='open'])) {
+ opacity: 0;
+ }
+
+ &:is(:hover) :global([data-slot='dropdown-menu-trigger']) {
+ opacity: 1;
+ }
+ }
+</style>
--- /dev/null
+<script lang="ts">
+ import { Input } from '$lib/components/ui/input';
+ import { Search } from '@lucide/svelte';
+
+ interface Props {
+ value?: string;
+ placeholder?: string;
+ onInput?: (value: string) => void;
+ class?: string;
+ }
+
+ let {
+ value = $bindable(''),
+ placeholder = 'Search conversations...',
+ onInput,
+ class: className
+ }: Props = $props();
+
+ function handleInput(event: Event) {
+ const target = event.target as HTMLInputElement;
+
+ value = target.value;
+ onInput?.(target.value);
+ }
+</script>
+
+<div class="relative mb-4 {className}">
+ <Search
+ class="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 transform text-muted-foreground"
+ />
+
+ <Input bind:value class="pl-10" oninput={handleInput} {placeholder} type="search" />
+</div>
--- /dev/null
+import { useSidebar } from '$lib/components/ui/sidebar';
+
+const sidebar = useSidebar();
+
+export function handleMobileSidebarItemClick() {
+ if (sidebar.isMobile) {
+ sidebar.toggle();
+ }
+}
--- /dev/null
+<script lang="ts">
+ import * as AlertDialog from '$lib/components/ui/alert-dialog';
+ import type { Component } from 'svelte';
+
+ interface Props {
+ open: boolean;
+ title: string;
+ description: string;
+ confirmText?: string;
+ cancelText?: string;
+ variant?: 'default' | 'destructive';
+ icon?: Component;
+ onConfirm: () => void;
+ onCancel: () => void;
+ onKeydown?: (event: KeyboardEvent) => void;
+ }
+
+ let {
+ open = $bindable(),
+ title,
+ description,
+ confirmText = 'Confirm',
+ cancelText = 'Cancel',
+ variant = 'default',
+ icon,
+ onConfirm,
+ onCancel,
+ onKeydown
+ }: Props = $props();
+
+ function handleKeydown(event: KeyboardEvent) {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ onConfirm();
+ }
+ onKeydown?.(event);
+ }
+
+ function handleOpenChange(newOpen: boolean) {
+ if (!newOpen) {
+ onCancel();
+ }
+ }
+</script>
+
+<AlertDialog.Root {open} onOpenChange={handleOpenChange}>
+ <AlertDialog.Content onkeydown={handleKeydown}>
+ <AlertDialog.Header>
+ <AlertDialog.Title class="flex items-center gap-2">
+ {#if icon}
+ {@const IconComponent = icon}
+ <IconComponent class="h-5 w-5 {variant === 'destructive' ? 'text-destructive' : ''}" />
+ {/if}
+ {title}
+ </AlertDialog.Title>
+
+ <AlertDialog.Description>
+ {description}
+ </AlertDialog.Description>
+ </AlertDialog.Header>
+
+ <AlertDialog.Footer>
+ <AlertDialog.Cancel onclick={onCancel}>{cancelText}</AlertDialog.Cancel>
+ <AlertDialog.Action
+ onclick={onConfirm}
+ class={variant === 'destructive' ? 'bg-destructive text-white hover:bg-destructive/80' : ''}
+ >
+ {confirmText}
+ </AlertDialog.Action>
+ </AlertDialog.Footer>
+ </AlertDialog.Content>
+</AlertDialog.Root>
--- /dev/null
+<script lang="ts">
+ import * as AlertDialog from '$lib/components/ui/alert-dialog';
+ import { Button } from '$lib/components/ui/button';
+
+ interface Props {
+ open: boolean;
+ currentTitle: string;
+ newTitle: string;
+ onConfirm: () => void;
+ onCancel: () => void;
+ }
+
+ let { open = $bindable(), currentTitle, newTitle, onConfirm, onCancel }: Props = $props();
+</script>
+
+<AlertDialog.Root bind:open>
+ <AlertDialog.Content>
+ <AlertDialog.Header>
+ <AlertDialog.Title>Update Conversation Title?</AlertDialog.Title>
+
+ <AlertDialog.Description>
+ Do you want to update the conversation title to match the first message content?
+ </AlertDialog.Description>
+ </AlertDialog.Header>
+
+ <div class="space-y-4 pt-2 pb-6">
+ <div class="space-y-2">
+ <p class="text-sm font-medium text-muted-foreground">Current title:</p>
+
+ <p class="rounded-md bg-muted/50 p-3 text-sm font-medium">{currentTitle}</p>
+ </div>
+
+ <div class="space-y-2">
+ <p class="text-sm font-medium text-muted-foreground">New title would be:</p>
+
+ <p class="rounded-md bg-muted/50 p-3 text-sm font-medium">{newTitle}</p>
+ </div>
+ </div>
+
+ <AlertDialog.Footer>
+ <Button variant="outline" onclick={onCancel}>Keep Current Title</Button>
+
+ <Button onclick={onConfirm}>Update Title</Button>
+ </AlertDialog.Footer>
+ </AlertDialog.Content>
+</AlertDialog.Root>
--- /dev/null
+<script lang="ts">
+ import * as AlertDialog from '$lib/components/ui/alert-dialog';
+ import { FileX } from '@lucide/svelte';
+
+ interface Props {
+ open: boolean;
+ emptyFiles: string[];
+ onOpenChange?: (open: boolean) => void;
+ }
+
+ let { open = $bindable(), emptyFiles, onOpenChange }: Props = $props();
+
+ function handleOpenChange(newOpen: boolean) {
+ open = newOpen;
+ onOpenChange?.(newOpen);
+ }
+</script>
+
+<AlertDialog.Root {open} onOpenChange={handleOpenChange}>
+ <AlertDialog.Content>
+ <AlertDialog.Header>
+ <AlertDialog.Title class="flex items-center gap-2">
+ <FileX class="h-5 w-5 text-destructive" />
+
+ Empty Files Detected
+ </AlertDialog.Title>
+
+ <AlertDialog.Description>
+ The following files are empty and have been removed from your attachments:
+ </AlertDialog.Description>
+ </AlertDialog.Header>
+
+ <div class="space-y-3 text-sm">
+ <div class="rounded-lg bg-muted p-3">
+ <div class="mb-2 font-medium">Empty Files:</div>
+
+ <ul class="list-inside list-disc space-y-1 text-muted-foreground">
+ {#each emptyFiles as fileName (fileName)}
+ <li class="font-mono text-sm">{fileName}</li>
+ {/each}
+ </ul>
+ </div>
+
+ <div>
+ <div class="mb-2 font-medium">What happened:</div>
+
+ <ul class="list-inside list-disc space-y-1 text-muted-foreground">
+ <li>Empty files cannot be processed or sent to the AI model</li>
+
+ <li>These files have been automatically removed from your attachments</li>
+
+ <li>You can try uploading files with content instead</li>
+ </ul>
+ </div>
+ </div>
+
+ <AlertDialog.Footer>
+ <AlertDialog.Action onclick={() => handleOpenChange(false)}>Got it</AlertDialog.Action>
+ </AlertDialog.Footer>
+ </AlertDialog.Content>
+</AlertDialog.Root>
--- /dev/null
+<script lang="ts">
+ import { AlertTriangle } from '@lucide/svelte';
+ import * as AlertDialog from '$lib/components/ui/alert-dialog';
+ import { maxContextError, clearMaxContextError } from '$lib/stores/chat.svelte';
+</script>
+
+<AlertDialog.Root
+ open={maxContextError() !== null}
+ onOpenChange={(open) => !open && clearMaxContextError()}
+>
+ <AlertDialog.Content>
+ <AlertDialog.Header>
+ <AlertDialog.Title class="flex items-center gap-2">
+ <AlertTriangle class="h-5 w-5 text-destructive" />
+
+ Message Too Long
+ </AlertDialog.Title>
+
+ <AlertDialog.Description>
+ Your message exceeds the model's context window and cannot be processed.
+ </AlertDialog.Description>
+ </AlertDialog.Header>
+
+ {#if maxContextError()}
+ <div class="space-y-3 text-sm">
+ <div class="rounded-lg bg-muted p-3">
+ <div class="mb-2 font-medium">Token Usage:</div>
+
+ <div class="space-y-1 text-muted-foreground">
+ <div>
+ Estimated tokens:
+
+ <span class="font-mono">
+ {maxContextError()?.estimatedTokens.toLocaleString()}
+ </span>
+ </div>
+
+ <div>
+ Context window:
+
+ <span class="font-mono">
+ {maxContextError()?.maxContext.toLocaleString()}
+ </span>
+ </div>
+ </div>
+ </div>
+
+ <div>
+ <div class="mb-2 font-medium">Suggestions:</div>
+
+ <ul class="list-inside list-disc space-y-1 text-muted-foreground">
+ <li>Shorten your message</li>
+
+ <li>Remove some file attachments</li>
+
+ <li>Start a new conversation</li>
+ </ul>
+ </div>
+ </div>
+ {/if}
+
+ <AlertDialog.Footer>
+ <AlertDialog.Action onclick={() => clearMaxContextError()}>Got it</AlertDialog.Action>
+ </AlertDialog.Footer>
+ </AlertDialog.Content>
+</AlertDialog.Root>
--- /dev/null
+export { default as ChatAttachmentsList } from './chat/ChatAttachments/ChatAttachmentsList.svelte';
+export { default as ChatAttachmentFilePreview } from './chat/ChatAttachments/ChatAttachmentFilePreview.svelte';
+export { default as ChatAttachmentImagePreview } from './chat/ChatAttachments/ChatAttachmentImagePreview.svelte';
+export { default as ChatAttachmentPreviewDialog } from './chat/ChatAttachments/ChatAttachmentPreviewDialog.svelte';
+
+export { default as ChatForm } from './chat/ChatForm/ChatForm.svelte';
+export { default as ChatFormTextarea } from './chat/ChatForm/ChatFormTextarea.svelte';
+export { default as ChatFormActions } from './chat/ChatForm/ChatFormActions.svelte';
+export { default as ChatFormActionFileAttachments } from './chat/ChatForm/ChatFormActionFileAttachments.svelte';
+export { default as ChatFormActionRecord } from './chat/ChatForm/ChatFormActionRecord.svelte';
+export { default as ChatFormHelperText } from './chat/ChatForm/ChatFormHelperText.svelte';
+export { default as ChatFormFileInputInvisible } from './chat/ChatForm/ChatFormFileInputInvisible.svelte';
+
+export { default as ChatMessage } from './chat/ChatMessages/ChatMessage.svelte';
+export { default as ChatMessages } from './chat/ChatMessages/ChatMessages.svelte';
+export { default as ChatMessageThinkingBlock } from './chat/ChatMessages/ChatMessageThinkingBlock.svelte';
+export { default as MessageBranchingControls } from './chat/ChatMessages/ChatMessageBranchingControls.svelte';
+
+export { default as ChatProcessingInfo } from './chat/ChatProcessingInfo.svelte';
+
+export { default as ChatScreenHeader } from './chat/ChatScreen/ChatScreenHeader.svelte';
+export { default as ChatScreen } from './chat/ChatScreen/ChatScreen.svelte';
+
+export { default as ChatSettingsDialog } from './chat/ChatSettings/ChatSettingsDialog.svelte';
+export { default as ChatSettingsSection } from './chat/ChatSettings/ChatSettingsSection.svelte';
+export { default as ChatSettingsFooter } from './chat/ChatSettings/ChatSettingsFooter.svelte';
+
+export { default as ChatSidebar } from './chat/ChatSidebar/ChatSidebar.svelte';
+export { default as ChatSidebarConversationItem } from './chat/ChatSidebar/ChatSidebarConversationItem.svelte';
+export { default as ChatSidebarSearch } from './chat/ChatSidebar/ChatSidebarSearch.svelte';
+
+export { default as EmptyFileAlertDialog } from './dialogs/EmptyFileAlertDialog.svelte';
+
+export { default as ConversationTitleUpdateDialog } from './dialogs/ConversationTitleUpdateDialog.svelte';
+
+export { default as MaximumContextAlertDialog } from './dialogs/MaximumContextAlertDialog.svelte';
+
+export { default as KeyboardShortcutInfo } from './misc/KeyboardShortcutInfo.svelte';
+
+export { default as MarkdownContent } from './misc/MarkdownContent.svelte';
+
+export { default as ServerStatus } from './server/ServerStatus.svelte';
+export { default as ServerErrorSplash } from './server/ServerErrorSplash.svelte';
+export { default as ServerLoadingSplash } from './server/ServerLoadingSplash.svelte';
+export { default as ServerInfo } from './server/ServerInfo.svelte';
+
+// Shared components
+export { default as ActionButton } from './misc/ActionButton.svelte';
+export { default as ActionDropdown } from './misc/ActionDropdown.svelte';
+export { default as ConfirmationDialog } from './dialogs/ConfirmationDialog.svelte';
--- /dev/null
+<script lang="ts">
+ import { Button } from '$lib/components/ui/button';
+ import * as Tooltip from '$lib/components/ui/tooltip';
+ import { TOOLTIP_DELAY_DURATION } from '$lib/constants/tooltip-config';
+ import type { Component } from 'svelte';
+
+ interface Props {
+ icon: Component;
+ tooltip: string;
+ variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
+ size?: 'default' | 'sm' | 'lg' | 'icon';
+ class?: string;
+ disabled?: boolean;
+ onclick: () => void;
+ 'aria-label'?: string;
+ }
+
+ let {
+ icon,
+ tooltip,
+ variant = 'ghost',
+ size = 'sm',
+ class: className = '',
+ disabled = false,
+ onclick,
+ 'aria-label': ariaLabel
+ }: Props = $props();
+</script>
+
+<Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
+ <Tooltip.Trigger>
+ <Button
+ {variant}
+ {size}
+ {disabled}
+ {onclick}
+ class="h-6 w-6 p-0 {className}"
+ aria-label={ariaLabel || tooltip}
+ >
+ {@const IconComponent = icon}
+ <IconComponent class="h-3 w-3" />
+ </Button>
+ </Tooltip.Trigger>
+
+ <Tooltip.Content>
+ <p>{tooltip}</p>
+ </Tooltip.Content>
+</Tooltip.Root>
--- /dev/null
+<script lang="ts">
+ import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
+ import * as Tooltip from '$lib/components/ui/tooltip';
+ import { KeyboardShortcutInfo } from '$lib/components/app';
+ import { TOOLTIP_DELAY_DURATION } from '$lib/constants/tooltip-config';
+ import type { Component } from 'svelte';
+
+ interface ActionItem {
+ icon: Component;
+ label: string;
+ onclick: (event: Event) => void;
+ variant?: 'default' | 'destructive';
+ disabled?: boolean;
+ shortcut?: string[];
+ separator?: boolean;
+ }
+
+ interface Props {
+ triggerIcon: Component;
+ triggerTooltip?: string;
+ triggerClass?: string;
+ actions: ActionItem[];
+ align?: 'start' | 'center' | 'end';
+ open?: boolean;
+ }
+
+ let {
+ triggerIcon,
+ triggerTooltip,
+ triggerClass = '',
+ actions,
+ align = 'end',
+ open = $bindable(false)
+ }: Props = $props();
+</script>
+
+<DropdownMenu.Root bind:open>
+ <DropdownMenu.Trigger
+ class="flex h-6 w-6 cursor-pointer items-center justify-center rounded-md p-0 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground {triggerClass}"
+ >
+ {#if triggerTooltip}
+ <Tooltip.Root delayDuration={TOOLTIP_DELAY_DURATION}>
+ <Tooltip.Trigger>
+ {@render iconComponent(triggerIcon, 'h-3 w-3')}
+ <span class="sr-only">{triggerTooltip}</span>
+ </Tooltip.Trigger>
+ <Tooltip.Content>
+ <p>{triggerTooltip}</p>
+ </Tooltip.Content>
+ </Tooltip.Root>
+ {:else}
+ {@render iconComponent(triggerIcon, 'h-3 w-3')}
+ {/if}
+ </DropdownMenu.Trigger>
+
+ <DropdownMenu.Content {align} class="z-999 w-48">
+ {#each actions as action, index (action.label)}
+ {#if action.separator && index > 0}
+ <DropdownMenu.Separator />
+ {/if}
+
+ <DropdownMenu.Item
+ onclick={action.onclick}
+ variant={action.variant}
+ disabled={action.disabled}
+ class="flex items-center justify-between hover:[&>kbd]:opacity-100"
+ >
+ <div class="flex items-center gap-2">
+ {@render iconComponent(
+ action.icon,
+ `h-4 w-4 ${action.variant === 'destructive' ? 'text-destructive' : ''}`
+ )}
+ {action.label}
+ </div>
+
+ {#if action.shortcut}
+ <KeyboardShortcutInfo keys={action.shortcut} variant={action.variant} />
+ {/if}
+ </DropdownMenu.Item>
+ {/each}
+ </DropdownMenu.Content>
+</DropdownMenu.Root>
+
+{#snippet iconComponent(IconComponent: Component, className: string)}
+ <IconComponent class={className} />
+{/snippet}
--- /dev/null
+<script lang="ts">
+ import { ArrowBigUp } from '@lucide/svelte';
+
+ interface Props {
+ keys: string[];
+ variant?: 'default' | 'destructive';
+ class?: string;
+ }
+
+ let { keys, variant = 'default', class: className = '' }: Props = $props();
+
+ let baseClasses =
+ 'px-1 pointer-events-none inline-flex select-none items-center gap-0.5 font-sans text-md font-medium opacity-0 transition-opacity -my-1';
+ let variantClasses = variant === 'destructive' ? 'text-destructive' : 'text-muted-foreground';
+</script>
+
+<kbd class="{baseClasses} {variantClasses} {className}">
+ {#each keys as key, index (index)}
+ {#if key === 'shift'}
+ <ArrowBigUp class="h-1 w-1 {variant === 'destructive' ? 'text-destructive' : ''} -mr-1" />
+ {:else if key === 'cmd'}
+ <span class={variant === 'destructive' ? 'text-destructive' : ''}>⌘</span>
+ {:else}
+ {key.toUpperCase()}
+ {/if}
+
+ {#if index < keys.length - 1}
+ <span> </span>
+ {/if}
+ {/each}
+</kbd>
--- /dev/null
+<script lang="ts">
+ import { remark } from 'remark';
+ import remarkBreaks from 'remark-breaks';
+ import remarkGfm from 'remark-gfm';
+ import remarkMath from 'remark-math';
+ import rehypeHighlight from 'rehype-highlight';
+ import remarkRehype from 'remark-rehype';
+ import rehypeKatex from 'rehype-katex';
+ import rehypeStringify from 'rehype-stringify';
+ import { copyCodeToClipboard } from '$lib/utils/copy';
+ import 'highlight.js/styles/github-dark.css';
+ import 'katex/dist/katex.min.css';
+
+ interface Props {
+ content: string;
+ class?: string;
+ }
+
+ let { content, class: className = '' }: Props = $props();
+
+ let containerRef = $state<HTMLDivElement>();
+ let processedHtml = $state('');
+
+ let processor = $derived(() => {
+ return remark()
+ .use(remarkGfm) // GitHub Flavored Markdown
+ .use(remarkMath) // Parse $inline$ and $$block$$ math
+ .use(remarkBreaks) // Convert line breaks to <br>
+ .use(remarkRehype) // Convert to rehype (HTML AST)
+ .use(rehypeKatex) // Render math using KaTeX
+ .use(rehypeHighlight) // Add syntax highlighting
+ .use(rehypeStringify); // Convert to HTML string
+ });
+
+ function enhanceLinks(html: string): string {
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = html;
+
+ // Make all links open in new tabs
+ const linkElements = tempDiv.querySelectorAll('a[href]');
+ for (const link of linkElements) {
+ link.setAttribute('target', '_blank');
+ link.setAttribute('rel', 'noopener noreferrer');
+ }
+
+ return tempDiv.innerHTML;
+ }
+
+ function enhanceCodeBlocks(html: string): string {
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = html;
+
+ const preElements = tempDiv.querySelectorAll('pre');
+
+ for (const [index, pre] of Array.from(preElements).entries()) {
+ const codeElement = pre.querySelector('code');
+
+ if (!codeElement) continue;
+
+ let language = 'text';
+ const classList = Array.from(codeElement.classList);
+
+ for (const className of classList) {
+ if (className.startsWith('language-')) {
+ language = className.replace('language-', '');
+ break;
+ }
+ }
+
+ const rawCode = codeElement.textContent || '';
+ const codeId = `code-${Date.now()}-${index}`;
+
+ codeElement.setAttribute('data-code-id', codeId);
+ codeElement.setAttribute('data-raw-code', rawCode);
+
+ const wrapper = document.createElement('div');
+ wrapper.className = 'code-block-wrapper';
+
+ const header = document.createElement('div');
+ header.className = 'code-block-header';
+
+ const languageLabel = document.createElement('span');
+ languageLabel.className = 'code-language';
+ languageLabel.textContent = language;
+
+ const copyButton = document.createElement('button');
+ copyButton.className = 'copy-code-btn';
+ copyButton.setAttribute('data-code-id', codeId);
+ copyButton.setAttribute('title', 'Copy code');
+ copyButton.setAttribute('type', 'button');
+
+ copyButton.innerHTML = `
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy-icon lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
+ `;
+
+ header.appendChild(languageLabel);
+ header.appendChild(copyButton);
+ wrapper.appendChild(header);
+
+ const clonedPre = pre.cloneNode(true) as HTMLElement;
+ wrapper.appendChild(clonedPre);
+
+ pre.parentNode?.replaceChild(wrapper, pre);
+ }
+
+ return tempDiv.innerHTML;
+ }
+
+ async function processMarkdown(text: string): Promise<string> {
+ try {
+ const result = await processor().process(text);
+ const html = String(result);
+ const enhancedLinks = enhanceLinks(html);
+
+ return enhanceCodeBlocks(enhancedLinks);
+ } catch (error) {
+ console.error('Markdown processing error:', error);
+
+ // Fallback to plain text with line breaks
+ return text.replace(/\n/g, '<br>');
+ }
+ }
+
+ function setupCopyButtons() {
+ if (!containerRef) return;
+
+ const copyButtons = containerRef.querySelectorAll('.copy-code-btn');
+
+ for (const button of copyButtons) {
+ button.addEventListener('click', async (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const target = e.currentTarget as HTMLButtonElement;
+ const codeId = target.getAttribute('data-code-id');
+
+ if (!codeId) {
+ console.error('No code ID found on button');
+ return;
+ }
+
+ // Find the code element within the same wrapper
+ const wrapper = target.closest('.code-block-wrapper');
+ if (!wrapper) {
+ console.error('No wrapper found');
+ return;
+ }
+
+ const codeElement = wrapper.querySelector('code[data-code-id]');
+ if (!codeElement) {
+ console.error('No code element found in wrapper');
+ return;
+ }
+
+ const rawCode = codeElement.getAttribute('data-raw-code');
+ if (!rawCode) {
+ console.error('No raw code found');
+ return;
+ }
+
+ try {
+ await copyCodeToClipboard(rawCode);
+ } catch (error) {
+ console.error('Failed to copy code:', error);
+ }
+ });
+ }
+ }
+
+ $effect(() => {
+ if (content) {
+ processMarkdown(content)
+ .then((result) => {
+ processedHtml = result;
+ })
+ .catch((error) => {
+ console.error('Failed to process markdown:', error);
+ processedHtml = content.replace(/\n/g, '<br>');
+ });
+ } else {
+ processedHtml = '';
+ }
+ });
+
+ $effect(() => {
+ if (containerRef && processedHtml) {
+ setupCopyButtons();
+ }
+ });
+</script>
+
+<div bind:this={containerRef} class={className}>
+ <!-- eslint-disable-next-line no-at-html-tags -->
+ {@html processedHtml}
+</div>
+
+<style>
+ /* Base typography styles */
+ div :global(p) {
+ margin-bottom: 1rem;
+ line-height: 1.75;
+ }
+
+ /* Headers with consistent spacing */
+ div :global(h1) {
+ font-size: 1.875rem;
+ font-weight: 700;
+ margin: 1.5rem 0 0.75rem 0;
+ line-height: 1.2;
+ }
+
+ div :global(h2) {
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin: 1.25rem 0 0.5rem 0;
+ line-height: 1.3;
+ }
+
+ div :global(h3) {
+ font-size: 1.25rem;
+ font-weight: 600;
+ margin: 1.5rem 0 0.5rem 0;
+ line-height: 1.4;
+ }
+
+ div :global(h4) {
+ font-size: 1.125rem;
+ font-weight: 600;
+ margin: 0.75rem 0 0.25rem 0;
+ }
+
+ div :global(h5) {
+ font-size: 1rem;
+ font-weight: 600;
+ margin: 0.5rem 0 0.25rem 0;
+ }
+
+ div :global(h6) {
+ font-size: 0.875rem;
+ font-weight: 600;
+ margin: 0.5rem 0 0.25rem 0;
+ }
+
+ /* Text formatting */
+ div :global(strong) {
+ font-weight: 600;
+ }
+
+ div :global(em) {
+ font-style: italic;
+ }
+
+ div :global(del) {
+ text-decoration: line-through;
+ opacity: 0.7;
+ }
+
+ /* Inline code */
+ div :global(code:not(pre code)) {
+ background: var(--muted);
+ color: var(--muted-foreground);
+ padding: 0.125rem 0.375rem;
+ border-radius: 0.375rem;
+ font-size: 0.875rem;
+ font-family:
+ ui-monospace, SFMono-Regular, 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas,
+ 'Liberation Mono', Menlo, monospace;
+ }
+
+ /* Links */
+ div :global(a) {
+ color: var(--primary);
+ text-decoration: underline;
+ text-underline-offset: 2px;
+ transition: color 0.2s ease;
+ }
+
+ div :global(a:hover) {
+ color: var(--primary);
+ }
+
+ /* Lists */
+ div :global(ul) {
+ list-style-type: disc;
+ margin-left: 1.5rem;
+ margin-bottom: 1rem;
+ }
+
+ div :global(ol) {
+ list-style-type: decimal;
+ margin-left: 1.5rem;
+ margin-bottom: 1rem;
+ }
+
+ div :global(li) {
+ margin-bottom: 0.25rem;
+ padding-left: 0.5rem;
+ }
+
+ div :global(li::marker) {
+ color: var(--muted-foreground);
+ }
+
+ /* Nested lists */
+ div :global(ul ul) {
+ list-style-type: circle;
+ margin-top: 0.25rem;
+ margin-bottom: 0.25rem;
+ }
+
+ div :global(ol ol) {
+ list-style-type: lower-alpha;
+ margin-top: 0.25rem;
+ margin-bottom: 0.25rem;
+ }
+
+ /* Task lists */
+ div :global(.task-list-item) {
+ list-style: none;
+ margin-left: 0;
+ padding-left: 0;
+ }
+
+ div :global(.task-list-item-checkbox) {
+ margin-right: 0.5rem;
+ margin-top: 0.125rem;
+ }
+
+ /* Blockquotes */
+ div :global(blockquote) {
+ border-left: 4px solid var(--border);
+ padding: 0.5rem 1rem;
+ margin: 1.5rem 0;
+ font-style: italic;
+ color: var(--muted-foreground);
+ background: var(--muted);
+ border-radius: 0 0.375rem 0.375rem 0;
+ }
+
+ /* Tables */
+ div :global(table) {
+ width: 100%;
+ margin: 1.5rem 0;
+ border-collapse: collapse;
+ border: 1px solid var(--border);
+ border-radius: 0.375rem;
+ overflow: hidden;
+ }
+
+ div :global(th) {
+ background: hsl(var(--muted) / 0.3);
+ border: 1px solid var(--border);
+ padding: 0.5rem 0.75rem;
+ text-align: left;
+ font-weight: 600;
+ }
+
+ div :global(td) {
+ border: 1px solid var(--border);
+ padding: 0.5rem 0.75rem;
+ }
+
+ div :global(tr:nth-child(even)) {
+ background: hsl(var(--muted) / 0.1);
+ }
+
+ /* Horizontal rules */
+ div :global(hr) {
+ border: none;
+ border-top: 1px solid var(--border);
+ margin: 1.5rem 0;
+ }
+
+ /* Images */
+ div :global(img) {
+ border-radius: 0.5rem;
+ box-shadow:
+ 0 1px 3px 0 rgb(0 0 0 / 0.1),
+ 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ margin: 1.5rem 0;
+ max-width: 100%;
+ height: auto;
+ }
+
+ /* Code blocks */
+
+ div :global(.code-block-wrapper) {
+ margin: 1.5rem 0;
+ border-radius: 0.75rem;
+ overflow: hidden;
+ border: 1px solid var(--border);
+ background: var(--code-background);
+ }
+
+ div :global(.code-block-header) {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.5rem 1rem;
+ background: hsl(var(--muted) / 0.5);
+ border-bottom: 1px solid var(--border);
+ font-size: 0.875rem;
+ }
+
+ div :global(.code-language) {
+ color: var(--code-foreground);
+ font-weight: 500;
+ font-family:
+ ui-monospace, SFMono-Regular, 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas,
+ 'Liberation Mono', Menlo, monospace;
+ text-transform: uppercase;
+ font-size: 0.75rem;
+ letter-spacing: 0.05em;
+ }
+
+ div :global(.copy-code-btn) {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+ background: transparent;
+ color: var(--code-foreground);
+ cursor: pointer;
+ transition: all 0.2s ease;
+ }
+
+ div :global(.copy-code-btn:hover) {
+ transform: scale(1.05);
+ }
+
+ div :global(.copy-code-btn:active) {
+ transform: scale(0.95);
+ }
+
+ div :global(.code-block-wrapper pre) {
+ background: transparent;
+ padding: 1rem;
+ margin: 0;
+ overflow-x: auto;
+ border-radius: 0;
+ border: none;
+ font-size: 0.875rem;
+ line-height: 1.5;
+ }
+
+ div :global(pre) {
+ background: var(--muted);
+ margin: 1.5rem 0;
+ overflow-x: auto;
+ border-radius: 1rem;
+ border: none;
+ }
+
+ div :global(code) {
+ background: transparent;
+ color: var(--code-foreground);
+ }
+
+ /* Mentions and hashtags */
+ div :global(.mention) {
+ color: hsl(var(--primary));
+ font-weight: 500;
+ text-decoration: none;
+ }
+
+ div :global(.mention:hover) {
+ text-decoration: underline;
+ }
+
+ div :global(.hashtag) {
+ color: hsl(var(--primary));
+ font-weight: 500;
+ text-decoration: none;
+ }
+
+ div :global(.hashtag:hover) {
+ text-decoration: underline;
+ }
+
+ /* Advanced table enhancements */
+ div :global(table) {
+ transition: all 0.2s ease;
+ }
+
+ div :global(table:hover) {
+ box-shadow:
+ 0 4px 6px -1px rgb(0 0 0 / 0.1),
+ 0 2px 4px -2px rgb(0 0 0 / 0.1);
+ }
+
+ div :global(th:hover),
+ div :global(td:hover) {
+ background: var(--muted);
+ }
+
+ /* Enhanced blockquotes */
+ div :global(blockquote) {
+ transition: all 0.2s ease;
+ position: relative;
+ }
+
+ div :global(blockquote:hover) {
+ border-left-width: 6px;
+ background: var(--muted);
+ transform: translateX(2px);
+ }
+
+ div :global(blockquote::before) {
+ content: '"';
+ position: absolute;
+ top: -0.5rem;
+ left: 0.5rem;
+ font-size: 3rem;
+ color: var(--muted-foreground);
+ font-family: serif;
+ line-height: 1;
+ }
+
+ /* Enhanced images */
+ div :global(img) {
+ transition: all 0.3s ease;
+ cursor: pointer;
+ }
+
+ div :global(img:hover) {
+ transform: scale(1.02);
+ box-shadow:
+ 0 10px 15px -3px rgb(0 0 0 / 0.1),
+ 0 4px 6px -4px rgb(0 0 0 / 0.1);
+ }
+
+ /* Image zoom overlay */
+ div :global(.image-zoom-overlay) {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.8);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ cursor: pointer;
+ }
+
+ div :global(.image-zoom-overlay img) {
+ max-width: 90vw;
+ max-height: 90vh;
+ border-radius: 0.5rem;
+ box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
+ }
+
+ /* Enhanced horizontal rules */
+ div :global(hr) {
+ border: none;
+ height: 2px;
+ background: linear-gradient(to right, transparent, var(--border), transparent);
+ margin: 2rem 0;
+ position: relative;
+ }
+
+ div :global(hr::after) {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 1rem;
+ height: 1rem;
+ background: var(--border);
+ border-radius: 50%;
+ }
+
+ /* Scrollable tables */
+ div :global(.table-wrapper) {
+ overflow-x: auto;
+ margin: 1.5rem 0;
+ border-radius: 0.5rem;
+ border: 1px solid var(--border);
+ }
+
+ div :global(.table-wrapper table) {
+ margin: 0;
+ border: none;
+ }
+
+ /* Responsive adjustments */
+ @media (max-width: 640px) {
+ div :global(h1) {
+ font-size: 1.5rem;
+ }
+
+ div :global(h2) {
+ font-size: 1.25rem;
+ }
+
+ div :global(h3) {
+ font-size: 1.125rem;
+ }
+
+ div :global(table) {
+ font-size: 0.875rem;
+ }
+
+ div :global(th),
+ div :global(td) {
+ padding: 0.375rem 0.5rem;
+ }
+
+ div :global(.table-wrapper) {
+ margin: 0.5rem -1rem;
+ border-radius: 0;
+ border-left: none;
+ border-right: none;
+ }
+ }
+
+ /* Dark mode adjustments */
+ @media (prefers-color-scheme: dark) {
+ div :global(blockquote:hover) {
+ background: var(--muted);
+ }
+ }
+</style>
--- /dev/null
+<script lang="ts">
+ import { AlertTriangle, RefreshCw, Key, CheckCircle, XCircle } from '@lucide/svelte';
+ import { goto } from '$app/navigation';
+ import { Button } from '$lib/components/ui/button';
+ import { Input } from '$lib/components/ui/input';
+ import Label from '$lib/components/ui/label/label.svelte';
+ import { serverStore, serverLoading } from '$lib/stores/server.svelte';
+ import { config, updateConfig } from '$lib/stores/settings.svelte';
+ import { fade, fly, scale } from 'svelte/transition';
+
+ interface Props {
+ class?: string;
+ error: string;
+ onRetry?: () => void;
+ showRetry?: boolean;
+ showTroubleshooting?: boolean;
+ }
+
+ let {
+ class: className = '',
+ error,
+ onRetry,
+ showRetry = true,
+ showTroubleshooting = false
+ }: Props = $props();
+
+ let isServerLoading = $derived(serverLoading());
+ let isAccessDeniedError = $derived(
+ error.toLowerCase().includes('access denied') ||
+ error.toLowerCase().includes('invalid api key') ||
+ error.toLowerCase().includes('unauthorized') ||
+ error.toLowerCase().includes('401') ||
+ error.toLowerCase().includes('403')
+ );
+
+ let apiKeyInput = $state('');
+ let showApiKeyInput = $state(false);
+ let apiKeyState = $state<'idle' | 'validating' | 'success' | 'error'>('idle');
+ let apiKeyError = $state('');
+
+ function handleRetryConnection() {
+ if (onRetry) {
+ onRetry();
+ } else {
+ serverStore.fetchServerProps();
+ }
+ }
+
+ function handleShowApiKeyInput() {
+ showApiKeyInput = true;
+ // Pre-fill with current API key if it exists
+ const currentConfig = config();
+ apiKeyInput = currentConfig.apiKey?.toString() || '';
+ }
+
+ async function handleSaveApiKey() {
+ if (!apiKeyInput.trim()) return;
+
+ apiKeyState = 'validating';
+ apiKeyError = '';
+
+ try {
+ // Update the API key in settings first
+ updateConfig('apiKey', apiKeyInput.trim());
+
+ // Test the API key by making a real request to the server
+ const response = await fetch('/props', {
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${apiKeyInput.trim()}`
+ }
+ });
+
+ if (response.ok) {
+ // API key is valid - User Story B
+ apiKeyState = 'success';
+
+ // Show success state briefly, then navigate to home
+ setTimeout(() => {
+ goto('/');
+ }, 1000);
+ } else {
+ // API key is invalid - User Story A
+ apiKeyState = 'error';
+
+ if (response.status === 401 || response.status === 403) {
+ apiKeyError = 'Invalid API key - please check and try again';
+ } else {
+ apiKeyError = `Authentication failed (${response.status})`;
+ }
+
+ // Reset to idle state after showing error (don't reload UI)
+ setTimeout(() => {
+ apiKeyState = 'idle';
+ }, 3000);
+ }
+ } catch (error) {
+ // Network or other errors - User Story A
+ apiKeyState = 'error';
+
+ if (error instanceof Error) {
+ if (error.message.includes('fetch')) {
+ apiKeyError = 'Cannot connect to server - check if server is running';
+ } else {
+ apiKeyError = error.message;
+ }
+ } else {
+ apiKeyError = 'Connection error - please try again';
+ }
+
+ // Reset to idle state after showing error (don't reload UI)
+ setTimeout(() => {
+ apiKeyState = 'idle';
+ }, 3000);
+ }
+ }
+
+ function handleApiKeyKeydown(event: KeyboardEvent) {
+ if (event.key === 'Enter') {
+ handleSaveApiKey();
+ }
+ }
+</script>
+
+<div class="flex h-full items-center justify-center {className}">
+ <div class="w-full max-w-md px-4 text-center">
+ <div class="mb-6" in:fade={{ duration: 300 }}>
+ <div
+ class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-destructive/10"
+ >
+ <AlertTriangle class="h-8 w-8 text-destructive" />
+ </div>
+
+ <h2 class="mb-2 text-xl font-semibold">Server Connection Error</h2>
+
+ <p class="mb-4 text-sm text-muted-foreground">
+ {error}
+ </p>
+ </div>
+
+ {#if isAccessDeniedError && !showApiKeyInput}
+ <div in:fly={{ y: 10, duration: 300, delay: 200 }} class="mb-4">
+ <Button onclick={handleShowApiKeyInput} variant="outline" class="w-full">
+ <Key class="h-4 w-4" />
+ Enter API Key
+ </Button>
+ </div>
+ {/if}
+
+ {#if showApiKeyInput}
+ <div in:fly={{ y: 10, duration: 300, delay: 200 }} class="mb-4 space-y-3 text-left">
+ <div class="space-y-2">
+ <Label for="api-key-input" class="text-sm font-medium">API Key</Label>
+
+ <div class="relative">
+ <Input
+ id="api-key-input"
+ placeholder="Enter your API key..."
+ bind:value={apiKeyInput}
+ onkeydown={handleApiKeyKeydown}
+ class="w-full pr-10 {apiKeyState === 'error'
+ ? 'border-destructive'
+ : apiKeyState === 'success'
+ ? 'border-green-500'
+ : ''}"
+ disabled={apiKeyState === 'validating'}
+ />
+ {#if apiKeyState === 'validating'}
+ <div class="absolute top-1/2 right-3 -translate-y-1/2">
+ <RefreshCw class="h-4 w-4 animate-spin text-muted-foreground" />
+ </div>
+ {:else if apiKeyState === 'success'}
+ <div
+ class="absolute top-1/2 right-3 -translate-y-1/2"
+ in:scale={{ duration: 200, start: 0.8 }}
+ >
+ <CheckCircle class="h-4 w-4 text-green-500" />
+ </div>
+ {:else if apiKeyState === 'error'}
+ <div
+ class="absolute top-1/2 right-3 -translate-y-1/2"
+ in:scale={{ duration: 200, start: 0.8 }}
+ >
+ <XCircle class="h-4 w-4 text-destructive" />
+ </div>
+ {/if}
+ </div>
+ {#if apiKeyError}
+ <p class="text-sm text-destructive" in:fly={{ y: -10, duration: 200 }}>
+ {apiKeyError}
+ </p>
+ {/if}
+ {#if apiKeyState === 'success'}
+ <p class="text-sm text-green-600" in:fly={{ y: -10, duration: 200 }}>
+ ✓ API key validated successfully! Connecting...
+ </p>
+ {/if}
+ </div>
+ <div class="flex gap-2">
+ <Button
+ onclick={handleSaveApiKey}
+ disabled={!apiKeyInput.trim() ||
+ apiKeyState === 'validating' ||
+ apiKeyState === 'success'}
+ class="flex-1"
+ >
+ {#if apiKeyState === 'validating'}
+ <RefreshCw class="h-4 w-4 animate-spin" />
+ Validating...
+ {:else if apiKeyState === 'success'}
+ Success!
+ {:else}
+ Save & Retry
+ {/if}
+ </Button>
+ <Button
+ onclick={() => {
+ showApiKeyInput = false;
+ apiKeyState = 'idle';
+ apiKeyError = '';
+ }}
+ variant="outline"
+ class="flex-1"
+ disabled={apiKeyState === 'validating'}
+ >
+ Cancel
+ </Button>
+ </div>
+ </div>
+ {/if}
+
+ {#if showRetry}
+ <div in:fly={{ y: 10, duration: 300, delay: 200 }}>
+ <Button onclick={handleRetryConnection} disabled={isServerLoading} class="w-full">
+ {#if isServerLoading}
+ <RefreshCw class="h-4 w-4 animate-spin" />
+
+ Connecting...
+ {:else}
+ <RefreshCw class="h-4 w-4" />
+
+ Retry Connection
+ {/if}
+ </Button>
+ </div>
+ {/if}
+
+ {#if showTroubleshooting}
+ <div class="mt-4 text-left" in:fly={{ y: 10, duration: 300, delay: 400 }}>
+ <details class="text-sm">
+ <summary class="cursor-pointer text-muted-foreground hover:text-foreground">
+ Troubleshooting
+ </summary>
+
+ <div class="mt-2 space-y-3 text-xs text-muted-foreground">
+ <div class="space-y-2">
+ <p class="mb-4 font-medium">Start the llama-server:</p>
+
+ <div class="rounded bg-muted/50 px-2 py-1 font-mono text-xs">
+ <p>llama-server -hf ggml-org/gemma-3-4b-it-GGUF</p>
+ </div>
+
+ <p>or</p>
+
+ <div class="rounded bg-muted/50 px-2 py-1 font-mono text-xs">
+ <p class="mt-1">llama-server -m locally-stored-model.gguf</p>
+ </div>
+ </div>
+ <ul class="list-disc space-y-1 pl-4">
+ <li>Check that the server is accessible at the correct URL</li>
+
+ <li>Verify your network connection</li>
+
+ <li>Check server logs for any error messages</li>
+ </ul>
+ </div>
+ </details>
+ </div>
+ {/if}
+ </div>
+</div>
--- /dev/null
+<script lang="ts">
+ import { Server, Eye, Mic } from '@lucide/svelte';
+ import { Badge } from '$lib/components/ui/badge';
+ import { serverStore } from '$lib/stores/server.svelte';
+
+ let modalities = $derived(serverStore.supportedModalities);
+ let model = $derived(serverStore.modelName);
+ let props = $derived(serverStore.serverProps);
+</script>
+
+{#if props}
+ <div class="flex flex-wrap items-center justify-center gap-4 text-sm text-muted-foreground">
+ {#if model}
+ <Badge variant="outline" class="text-xs">
+ <Server class="mr-1 h-3 w-3" />
+
+ <span class="block max-w-[50vw] truncate">{model}</span>
+ </Badge>
+ {/if}
+
+ <div class="flex gap-4">
+ {#if props.default_generation_settings.n_ctx}
+ <Badge variant="secondary" class="text-xs">
+ ctx: {props.default_generation_settings.n_ctx.toLocaleString()}
+ </Badge>
+ {/if}
+
+ {#if modalities.length > 0}
+ {#each modalities as modality (modality)}
+ <Badge variant="secondary" class="text-xs">
+ {#if modality === 'vision'}
+ <Eye class="mr-1 h-3 w-3" />
+ {:else if modality === 'audio'}
+ <Mic class="mr-1 h-3 w-3" />
+ {/if}
+
+ {modality}
+ </Badge>
+ {/each}
+ {/if}
+ </div>
+ </div>
+{/if}
--- /dev/null
+<script lang="ts">
+ import { Server } from '@lucide/svelte';
+ import { ServerStatus } from '$lib/components/app';
+ import { fade } from 'svelte/transition';
+
+ interface Props {
+ class?: string;
+ message?: string;
+ }
+
+ let { class: className = '', message = 'Initializing connection to llama.cpp server...' }: Props =
+ $props();
+</script>
+
+<div class="flex h-full items-center justify-center {className}">
+ <div class="text-center">
+ <div class="mb-4" in:fade={{ duration: 300 }}>
+ <div class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-muted">
+ <Server class="h-8 w-8 animate-pulse text-muted-foreground" />
+ </div>
+
+ <h2 class="mb-2 text-xl font-semibold">Connecting to Server</h2>
+
+ <p class="text-sm text-muted-foreground">
+ {message}
+ </p>
+ </div>
+
+ <div class="mt-4">
+ <ServerStatus class="justify-center" />
+ </div>
+ </div>
+</div>
--- /dev/null
+<script lang="ts">
+ import { AlertTriangle, Server } from '@lucide/svelte';
+ import { Badge } from '$lib/components/ui/badge';
+ import { Button } from '$lib/components/ui/button';
+ import { serverProps, serverLoading, serverError, modelName } from '$lib/stores/server.svelte';
+
+ interface Props {
+ class?: string;
+ showActions?: boolean;
+ }
+
+ let { class: className = '', showActions = false }: Props = $props();
+
+ let error = $derived(serverError());
+ let loading = $derived(serverLoading());
+ let model = $derived(modelName());
+ let serverData = $derived(serverProps());
+
+ function getStatusColor() {
+ if (loading) return 'bg-yellow-500';
+ if (error) return 'bg-red-500';
+ if (serverData) return 'bg-green-500';
+
+ return 'bg-gray-500';
+ }
+
+ function getStatusText() {
+ if (loading) return 'Connecting...';
+ if (error) return 'Connection Error';
+ if (serverData) return 'Connected';
+
+ return 'Unknown';
+ }
+</script>
+
+<div class="flex items-center space-x-3 {className}">
+ <div class="flex items-center space-x-2">
+ <div class="h-2 w-2 rounded-full {getStatusColor()}"></div>
+
+ <span class="text-sm text-muted-foreground">{getStatusText()}</span>
+ </div>
+
+ {#if serverData && !error}
+ <Badge variant="outline" class="text-xs">
+ <Server class="mr-1 h-3 w-3" />
+
+ {model || 'Unknown Model'}
+ </Badge>
+
+ {#if serverData.default_generation_settings.n_ctx}
+ <Badge variant="secondary" class="text-xs">
+ ctx: {serverData.default_generation_settings.n_ctx.toLocaleString()}
+ </Badge>
+ {/if}
+ {/if}
+
+ {#if showActions && error}
+ <Button variant="outline" size="sm" class="text-destructive">
+ <AlertTriangle class="h-4 w-4" />
+
+ {error}
+ </Button>
+ {/if}
+</div>
--- /dev/null
+<script lang="ts">
+ import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
+ import { buttonVariants } from '$lib/components/ui/button/index.js';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: AlertDialogPrimitive.ActionProps = $props();
+</script>
+
+<AlertDialogPrimitive.Action
+ bind:ref
+ data-slot="alert-dialog-action"
+ class={cn(buttonVariants(), className)}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
+ import { buttonVariants } from '$lib/components/ui/button/index.js';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: AlertDialogPrimitive.CancelProps = $props();
+</script>
+
+<AlertDialogPrimitive.Cancel
+ bind:ref
+ data-slot="alert-dialog-cancel"
+ class={cn(buttonVariants({ variant: 'outline' }), className)}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
+ import AlertDialogOverlay from './alert-dialog-overlay.svelte';
+ import { cn, type WithoutChild, type WithoutChildrenOrChild } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ portalProps,
+ ...restProps
+ }: WithoutChild<AlertDialogPrimitive.ContentProps> & {
+ portalProps?: WithoutChildrenOrChild<AlertDialogPrimitive.PortalProps>;
+ } = $props();
+</script>
+
+<AlertDialogPrimitive.Portal {...portalProps}>
+ <AlertDialogOverlay />
+ <AlertDialogPrimitive.Content
+ bind:ref
+ data-slot="alert-dialog-content"
+ class={cn(
+ 'fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg',
+ className
+ )}
+ {...restProps}
+ />
+</AlertDialogPrimitive.Portal>
--- /dev/null
+<script lang="ts">
+ import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: AlertDialogPrimitive.DescriptionProps = $props();
+</script>
+
+<AlertDialogPrimitive.Description
+ bind:ref
+ data-slot="alert-dialog-description"
+ class={cn('text-sm text-muted-foreground', className)}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="alert-dialog-footer"
+ class={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import type { HTMLAttributes } from 'svelte/elements';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="alert-dialog-header"
+ class={cn('flex flex-col gap-2 text-center sm:text-left', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: AlertDialogPrimitive.OverlayProps = $props();
+</script>
+
+<AlertDialogPrimitive.Overlay
+ bind:ref
+ data-slot="alert-dialog-overlay"
+ class={cn(
+ 'fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0',
+ className
+ )}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: AlertDialogPrimitive.TitleProps = $props();
+</script>
+
+<AlertDialogPrimitive.Title
+ bind:ref
+ data-slot="alert-dialog-title"
+ class={cn('text-lg font-semibold', className)}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
+
+ let { ref = $bindable(null), ...restProps }: AlertDialogPrimitive.TriggerProps = $props();
+</script>
+
+<AlertDialogPrimitive.Trigger bind:ref data-slot="alert-dialog-trigger" {...restProps} />
--- /dev/null
+import { AlertDialog as AlertDialogPrimitive } from 'bits-ui';
+import Trigger from './alert-dialog-trigger.svelte';
+import Title from './alert-dialog-title.svelte';
+import Action from './alert-dialog-action.svelte';
+import Cancel from './alert-dialog-cancel.svelte';
+import Footer from './alert-dialog-footer.svelte';
+import Header from './alert-dialog-header.svelte';
+import Overlay from './alert-dialog-overlay.svelte';
+import Content from './alert-dialog-content.svelte';
+import Description from './alert-dialog-description.svelte';
+
+const Root = AlertDialogPrimitive.Root;
+const Portal = AlertDialogPrimitive.Portal;
+
+export {
+ Root,
+ Title,
+ Action,
+ Cancel,
+ Portal,
+ Footer,
+ Header,
+ Trigger,
+ Overlay,
+ Content,
+ Description,
+ //
+ Root as AlertDialog,
+ Title as AlertDialogTitle,
+ Action as AlertDialogAction,
+ Cancel as AlertDialogCancel,
+ Portal as AlertDialogPortal,
+ Footer as AlertDialogFooter,
+ Header as AlertDialogHeader,
+ Trigger as AlertDialogTrigger,
+ Overlay as AlertDialogOverlay,
+ Content as AlertDialogContent,
+ Description as AlertDialogDescription
+};
--- /dev/null
+<script lang="ts" module>
+ import { type VariantProps, tv } from 'tailwind-variants';
+
+ export const badgeVariants = tv({
+ base: 'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded-md border px-2 py-0.5 text-xs font-medium transition-[color,box-shadow] focus-visible:ring-[3px] [&>svg]:pointer-events-none [&>svg]:size-3',
+ variants: {
+ variant: {
+ default: 'bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent',
+ secondary:
+ 'bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent',
+ destructive:
+ 'bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-white',
+ outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground'
+ }
+ },
+ defaultVariants: {
+ variant: 'default'
+ }
+ });
+
+ export type BadgeVariant = VariantProps<typeof badgeVariants>['variant'];
+</script>
+
+<script lang="ts">
+ import type { HTMLAnchorAttributes } from 'svelte/elements';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils';
+
+ let {
+ ref = $bindable(null),
+ href,
+ class: className,
+ variant = 'default',
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAnchorAttributes> & {
+ variant?: BadgeVariant;
+ } = $props();
+</script>
+
+<svelte:element
+ this={href ? 'a' : 'span'}
+ bind:this={ref}
+ data-slot="badge"
+ {href}
+ class={cn(badgeVariants({ variant }), className)}
+ {...restProps}
+>
+ {@render children?.()}
+</svelte:element>
--- /dev/null
+export { default as Badge } from './badge.svelte';
+export { badgeVariants, type BadgeVariant } from './badge.svelte';
--- /dev/null
+<script lang="ts" module>
+ import { cn, type WithElementRef } from '$lib/components/ui/utils';
+ import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
+ import { type VariantProps, tv } from 'tailwind-variants';
+
+ export const buttonVariants = tv({
+ base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
+ variants: {
+ variant: {
+ default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
+ destructive:
+ 'bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white',
+ outline:
+ 'bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border',
+ secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
+ ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
+ link: 'text-primary underline-offset-4 hover:underline'
+ },
+ size: {
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
+ sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
+ icon: 'size-9'
+ }
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default'
+ }
+ });
+
+ export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
+ export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
+
+ export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
+ WithElementRef<HTMLAnchorAttributes> & {
+ variant?: ButtonVariant;
+ size?: ButtonSize;
+ };
+</script>
+
+<script lang="ts">
+ let {
+ class: className,
+ variant = 'default',
+ size = 'default',
+ ref = $bindable(null),
+ href = undefined,
+ type = 'button',
+ disabled,
+ children,
+ ...restProps
+ }: ButtonProps = $props();
+</script>
+
+{#if href}
+ <a
+ bind:this={ref}
+ data-slot="button"
+ class={cn(buttonVariants({ variant, size }), className)}
+ href={disabled ? undefined : href}
+ aria-disabled={disabled}
+ role={disabled ? 'link' : undefined}
+ tabindex={disabled ? -1 : undefined}
+ {...restProps}
+ >
+ {@render children?.()}
+ </a>
+{:else}
+ <button
+ bind:this={ref}
+ data-slot="button"
+ class={cn(buttonVariants({ variant, size }), className)}
+ {type}
+ {disabled}
+ {...restProps}
+ >
+ {@render children?.()}
+ </button>
+{/if}
+
+<style>
+ a,
+ button {
+ cursor: pointer;
+ }
+</style>
--- /dev/null
+import Root, {
+ type ButtonProps,
+ type ButtonSize,
+ type ButtonVariant,
+ buttonVariants
+} from './button.svelte';
+
+export {
+ Root,
+ type ButtonProps as Props,
+ //
+ Root as Button,
+ buttonVariants,
+ type ButtonProps,
+ type ButtonSize,
+ type ButtonVariant
+};
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="card-action"
+ class={cn('col-start-2 row-span-2 row-start-1 self-start justify-self-end', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import type { HTMLAttributes } from 'svelte/elements';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div bind:this={ref} data-slot="card-content" class={cn('px-6', className)} {...restProps}>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import type { HTMLAttributes } from 'svelte/elements';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
+</script>
+
+<p
+ bind:this={ref}
+ data-slot="card-description"
+ class={cn('text-sm text-muted-foreground', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</p>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="card-footer"
+ class={cn('flex items-center px-6 [.border-t]:pt-6', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="card-header"
+ class={cn(
+ '@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import type { HTMLAttributes } from 'svelte/elements';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="card-title"
+ class={cn('leading-none font-semibold', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import type { HTMLAttributes } from 'svelte/elements';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="card"
+ class={cn(
+ 'flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm',
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+import Root from './card.svelte';
+import Content from './card-content.svelte';
+import Description from './card-description.svelte';
+import Footer from './card-footer.svelte';
+import Header from './card-header.svelte';
+import Title from './card-title.svelte';
+import Action from './card-action.svelte';
+
+export {
+ Root,
+ Content,
+ Description,
+ Footer,
+ Header,
+ Title,
+ Action,
+ //
+ Root as Card,
+ Content as CardContent,
+ Description as CardDescription,
+ Footer as CardFooter,
+ Header as CardHeader,
+ Title as CardTitle,
+ Action as CardAction
+};
--- /dev/null
+<script lang="ts">
+ import { Checkbox as CheckboxPrimitive } from 'bits-ui';
+ import CheckIcon from '@lucide/svelte/icons/check';
+ import MinusIcon from '@lucide/svelte/icons/minus';
+ import { cn, type WithoutChildrenOrChild } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ checked = $bindable(false),
+ indeterminate = $bindable(false),
+ class: className,
+ ...restProps
+ }: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
+</script>
+
+<CheckboxPrimitive.Root
+ bind:ref
+ data-slot="checkbox"
+ class={cn(
+ 'peer flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input shadow-xs transition-shadow outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[state=checked]:bg-primary',
+ className
+ )}
+ bind:checked
+ bind:indeterminate
+ {...restProps}
+>
+ {#snippet children({ checked, indeterminate })}
+ <div data-slot="checkbox-indicator" class="text-current transition-none">
+ {#if checked}
+ <CheckIcon class="size-3.5" />
+ {:else if indeterminate}
+ <MinusIcon class="size-3.5" />
+ {/if}
+ </div>
+ {/snippet}
+</CheckboxPrimitive.Root>
--- /dev/null
+import Root from './checkbox.svelte';
+export {
+ Root,
+ //
+ Root as Checkbox
+};
--- /dev/null
+<script lang="ts">
+ import { Collapsible as CollapsiblePrimitive } from 'bits-ui';
+
+ let { ref = $bindable(null), ...restProps }: CollapsiblePrimitive.ContentProps = $props();
+</script>
+
+<CollapsiblePrimitive.Content bind:ref data-slot="collapsible-content" {...restProps} />
--- /dev/null
+<script lang="ts">
+ import { Collapsible as CollapsiblePrimitive } from 'bits-ui';
+
+ let { ref = $bindable(null), ...restProps }: CollapsiblePrimitive.TriggerProps = $props();
+</script>
+
+<CollapsiblePrimitive.Trigger bind:ref data-slot="collapsible-trigger" {...restProps} />
--- /dev/null
+<script lang="ts">
+ import { Collapsible as CollapsiblePrimitive } from 'bits-ui';
+
+ let {
+ ref = $bindable(null),
+ open = $bindable(false),
+ ...restProps
+ }: CollapsiblePrimitive.RootProps = $props();
+</script>
+
+<CollapsiblePrimitive.Root bind:ref bind:open data-slot="collapsible" {...restProps} />
--- /dev/null
+import Root from './collapsible.svelte';
+import Trigger from './collapsible-trigger.svelte';
+import Content from './collapsible-content.svelte';
+
+export {
+ Root,
+ Content,
+ Trigger,
+ //
+ Root as Collapsible,
+ Content as CollapsibleContent,
+ Trigger as CollapsibleTrigger
+};
--- /dev/null
+<script lang="ts">
+ import { Dialog as DialogPrimitive } from 'bits-ui';
+
+ let { ref = $bindable(null), ...restProps }: DialogPrimitive.CloseProps = $props();
+</script>
+
+<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />
--- /dev/null
+<script lang="ts">
+ import { Dialog as DialogPrimitive } from 'bits-ui';
+ import XIcon from '@lucide/svelte/icons/x';
+ import type { Snippet } from 'svelte';
+ import * as Dialog from './index.js';
+ import { cn, type WithoutChildrenOrChild } from '$lib/components/ui/utils';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ portalProps,
+ children,
+ showCloseButton = true,
+ ...restProps
+ }: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
+ portalProps?: DialogPrimitive.PortalProps;
+ children: Snippet;
+ showCloseButton?: boolean;
+ } = $props();
+</script>
+
+<Dialog.Portal {...portalProps}>
+ <Dialog.Overlay />
+ <DialogPrimitive.Content
+ bind:ref
+ data-slot="dialog-content"
+ class={cn(
+ 'fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border border-border/30 bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg',
+ className
+ )}
+ {...restProps}
+ >
+ {@render children?.()}
+ {#if showCloseButton}
+ <DialogPrimitive.Close
+ class="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
+ >
+ <XIcon />
+ <span class="sr-only">Close</span>
+ </DialogPrimitive.Close>
+ {/if}
+ </DialogPrimitive.Content>
+</Dialog.Portal>
--- /dev/null
+<script lang="ts">
+ import { Dialog as DialogPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: DialogPrimitive.DescriptionProps = $props();
+</script>
+
+<DialogPrimitive.Description
+ bind:ref
+ data-slot="dialog-description"
+ class={cn('text-sm text-muted-foreground', className)}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="dialog-footer"
+ class={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import type { HTMLAttributes } from 'svelte/elements';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="dialog-header"
+ class={cn('flex flex-col gap-2 text-center sm:text-left', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import { Dialog as DialogPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: DialogPrimitive.OverlayProps = $props();
+</script>
+
+<DialogPrimitive.Overlay
+ bind:ref
+ data-slot="dialog-overlay"
+ class={cn(
+ 'fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0',
+ className
+ )}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { Dialog as DialogPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: DialogPrimitive.TitleProps = $props();
+</script>
+
+<DialogPrimitive.Title
+ bind:ref
+ data-slot="dialog-title"
+ class={cn('text-lg leading-none font-semibold', className)}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { Dialog as DialogPrimitive } from 'bits-ui';
+
+ let { ref = $bindable(null), ...restProps }: DialogPrimitive.TriggerProps = $props();
+</script>
+
+<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} />
--- /dev/null
+import { Dialog as DialogPrimitive } from 'bits-ui';
+
+import Title from './dialog-title.svelte';
+import Footer from './dialog-footer.svelte';
+import Header from './dialog-header.svelte';
+import Overlay from './dialog-overlay.svelte';
+import Content from './dialog-content.svelte';
+import Description from './dialog-description.svelte';
+import Trigger from './dialog-trigger.svelte';
+import Close from './dialog-close.svelte';
+
+const Root = DialogPrimitive.Root;
+const Portal = DialogPrimitive.Portal;
+
+export {
+ Root,
+ Title,
+ Portal,
+ Footer,
+ Header,
+ Trigger,
+ Overlay,
+ Content,
+ Description,
+ Close,
+ //
+ Root as Dialog,
+ Title as DialogTitle,
+ Portal as DialogPortal,
+ Footer as DialogFooter,
+ Header as DialogHeader,
+ Trigger as DialogTrigger,
+ Overlay as DialogOverlay,
+ Content as DialogContent,
+ Description as DialogDescription,
+ Close as DialogClose
+};
--- /dev/null
+<script lang="ts">
+ import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
+ import CheckIcon from '@lucide/svelte/icons/check';
+ import MinusIcon from '@lucide/svelte/icons/minus';
+ import { cn, type WithoutChildrenOrChild } from '$lib/components/ui/utils.js';
+ import type { Snippet } from 'svelte';
+
+ let {
+ ref = $bindable(null),
+ checked = $bindable(false),
+ indeterminate = $bindable(false),
+ class: className,
+ children: childrenProp,
+ ...restProps
+ }: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & {
+ children?: Snippet;
+ } = $props();
+</script>
+
+<DropdownMenuPrimitive.CheckboxItem
+ bind:ref
+ bind:checked
+ bind:indeterminate
+ data-slot="dropdown-menu-checkbox-item"
+ class={cn(
+ "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+ className
+ )}
+ {...restProps}
+>
+ {#snippet children({ checked, indeterminate })}
+ <span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
+ {#if indeterminate}
+ <MinusIcon class="size-4" />
+ {:else}
+ <CheckIcon class={cn('size-4', !checked && 'text-transparent')} />
+ {/if}
+ </span>
+ {@render childrenProp?.()}
+ {/snippet}
+</DropdownMenuPrimitive.CheckboxItem>
--- /dev/null
+<script lang="ts">
+ import { cn } from '$lib/components/ui/utils.js';
+ import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
+
+ let {
+ ref = $bindable(null),
+ sideOffset = 4,
+ portalProps,
+ class: className,
+ ...restProps
+ }: DropdownMenuPrimitive.ContentProps & {
+ portalProps?: DropdownMenuPrimitive.PortalProps;
+ } = $props();
+</script>
+
+<DropdownMenuPrimitive.Portal {...portalProps}>
+ <DropdownMenuPrimitive.Content
+ bind:ref
+ data-slot="dropdown-menu-content"
+ {sideOffset}
+ class={cn(
+ 'z-50 max-h-(--bits-dropdown-menu-content-available-height) min-w-[8rem] origin-(--bits-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 dark:border-border/20',
+ className
+ )}
+ {...restProps}
+ />
+</DropdownMenuPrimitive.Portal>
--- /dev/null
+<script lang="ts">
+ import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils.js';
+ import type { ComponentProps } from 'svelte';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ inset,
+ ...restProps
+ }: ComponentProps<typeof DropdownMenuPrimitive.GroupHeading> & {
+ inset?: boolean;
+ } = $props();
+</script>
+
+<DropdownMenuPrimitive.GroupHeading
+ bind:ref
+ data-slot="dropdown-menu-group-heading"
+ data-inset={inset}
+ class={cn('px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8', className)}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
+
+ let { ref = $bindable(null), ...restProps }: DropdownMenuPrimitive.GroupProps = $props();
+</script>
+
+<DropdownMenuPrimitive.Group bind:ref data-slot="dropdown-menu-group" {...restProps} />
--- /dev/null
+<script lang="ts">
+ import { cn } from '$lib/components/ui/utils.js';
+ import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ inset,
+ variant = 'default',
+ ...restProps
+ }: DropdownMenuPrimitive.ItemProps & {
+ inset?: boolean;
+ variant?: 'default' | 'destructive';
+ } = $props();
+</script>
+
+<DropdownMenuPrimitive.Item
+ bind:ref
+ data-slot="dropdown-menu-item"
+ data-inset={inset}
+ data-variant={variant}
+ class={cn(
+ "relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/10 data-[variant=destructive]:data-highlighted:text-destructive dark:data-[variant=destructive]:data-highlighted:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:!text-destructive",
+ className
+ )}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ inset,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
+ inset?: boolean;
+ } = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="dropdown-menu-label"
+ data-inset={inset}
+ class={cn('px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
+
+ let {
+ ref = $bindable(null),
+ value = $bindable(),
+ ...restProps
+ }: DropdownMenuPrimitive.RadioGroupProps = $props();
+</script>
+
+<DropdownMenuPrimitive.RadioGroup
+ bind:ref
+ bind:value
+ data-slot="dropdown-menu-radio-group"
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
+ import CircleIcon from '@lucide/svelte/icons/circle';
+ import { cn, type WithoutChild } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children: childrenProp,
+ ...restProps
+ }: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props();
+</script>
+
+<DropdownMenuPrimitive.RadioItem
+ bind:ref
+ data-slot="dropdown-menu-radio-item"
+ class={cn(
+ "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+ className
+ )}
+ {...restProps}
+>
+ {#snippet children({ checked })}
+ <span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
+ {#if checked}
+ <CircleIcon class="size-2 fill-current" />
+ {/if}
+ </span>
+ {@render childrenProp?.({ checked })}
+ {/snippet}
+</DropdownMenuPrimitive.RadioItem>
--- /dev/null
+<script lang="ts">
+ import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: DropdownMenuPrimitive.SeparatorProps = $props();
+</script>
+
+<DropdownMenuPrimitive.Separator
+ bind:ref
+ data-slot="dropdown-menu-separator"
+ class={cn('-mx-1 my-1 h-px bg-border/20', className)}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import type { HTMLAttributes } from 'svelte/elements';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
+</script>
+
+<span
+ bind:this={ref}
+ data-slot="dropdown-menu-shortcut"
+ class={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</span>
--- /dev/null
+<script lang="ts">
+ import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: DropdownMenuPrimitive.SubContentProps = $props();
+</script>
+
+<DropdownMenuPrimitive.SubContent
+ bind:ref
+ data-slot="dropdown-menu-sub-content"
+ class={cn(
+ 'z-50 min-w-[8rem] origin-(--bits-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
+ className
+ )}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
+ import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ inset,
+ children,
+ ...restProps
+ }: DropdownMenuPrimitive.SubTriggerProps & {
+ inset?: boolean;
+ } = $props();
+</script>
+
+<DropdownMenuPrimitive.SubTrigger
+ bind:ref
+ data-slot="dropdown-menu-sub-trigger"
+ data-inset={inset}
+ class={cn(
+ "flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+ <ChevronRightIcon class="ml-auto size-4" />
+</DropdownMenuPrimitive.SubTrigger>
--- /dev/null
+<script lang="ts">
+ import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
+
+ let { ref = $bindable(null), ...restProps }: DropdownMenuPrimitive.TriggerProps = $props();
+</script>
+
+<DropdownMenuPrimitive.Trigger bind:ref data-slot="dropdown-menu-trigger" {...restProps} />
--- /dev/null
+import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
+import CheckboxItem from './dropdown-menu-checkbox-item.svelte';
+import Content from './dropdown-menu-content.svelte';
+import Group from './dropdown-menu-group.svelte';
+import Item from './dropdown-menu-item.svelte';
+import Label from './dropdown-menu-label.svelte';
+import RadioGroup from './dropdown-menu-radio-group.svelte';
+import RadioItem from './dropdown-menu-radio-item.svelte';
+import Separator from './dropdown-menu-separator.svelte';
+import Shortcut from './dropdown-menu-shortcut.svelte';
+import Trigger from './dropdown-menu-trigger.svelte';
+import SubContent from './dropdown-menu-sub-content.svelte';
+import SubTrigger from './dropdown-menu-sub-trigger.svelte';
+import GroupHeading from './dropdown-menu-group-heading.svelte';
+const Sub = DropdownMenuPrimitive.Sub;
+const Root = DropdownMenuPrimitive.Root;
+
+export {
+ CheckboxItem,
+ Content,
+ Root as DropdownMenu,
+ CheckboxItem as DropdownMenuCheckboxItem,
+ Content as DropdownMenuContent,
+ Group as DropdownMenuGroup,
+ Item as DropdownMenuItem,
+ Label as DropdownMenuLabel,
+ RadioGroup as DropdownMenuRadioGroup,
+ RadioItem as DropdownMenuRadioItem,
+ Separator as DropdownMenuSeparator,
+ Shortcut as DropdownMenuShortcut,
+ Sub as DropdownMenuSub,
+ SubContent as DropdownMenuSubContent,
+ SubTrigger as DropdownMenuSubTrigger,
+ Trigger as DropdownMenuTrigger,
+ GroupHeading as DropdownMenuGroupHeading,
+ Group,
+ GroupHeading,
+ Item,
+ Label,
+ RadioGroup,
+ RadioItem,
+ Root,
+ Separator,
+ Shortcut,
+ Sub,
+ SubContent,
+ SubTrigger,
+ Trigger
+};
--- /dev/null
+import Root from './input.svelte';
+
+export {
+ Root,
+ //
+ Root as Input
+};
--- /dev/null
+<script lang="ts">
+ import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils';
+
+ type InputType = Exclude<HTMLInputTypeAttribute, 'file'>;
+
+ type Props = WithElementRef<
+ Omit<HTMLInputAttributes, 'type'> &
+ ({ type: 'file'; files?: FileList } | { type?: InputType; files?: undefined })
+ >;
+
+ let {
+ ref = $bindable(null),
+ value = $bindable(),
+ type,
+ files = $bindable(),
+ class: className,
+ ...restProps
+ }: Props = $props();
+</script>
+
+{#if type === 'file'}
+ <input
+ bind:this={ref}
+ data-slot="input"
+ class={cn(
+ 'flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 pt-1.5 text-sm font-medium shadow-xs ring-offset-background transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30',
+ 'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
+ 'aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',
+ className
+ )}
+ type="file"
+ bind:files
+ bind:value
+ {...restProps}
+ />
+{:else}
+ <input
+ bind:this={ref}
+ data-slot="input"
+ class={cn(
+ 'flex h-9 w-full min-w-0 rounded-md border border-input bg-background px-3 py-1 text-base shadow-xs ring-offset-background transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30',
+ 'focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
+ 'aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',
+ className
+ )}
+ {type}
+ bind:value
+ {...restProps}
+ />
+{/if}
--- /dev/null
+import Root from './label.svelte';
+
+export {
+ Root,
+ //
+ Root as Label
+};
--- /dev/null
+<script lang="ts">
+ import { Label as LabelPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: LabelPrimitive.RootProps = $props();
+</script>
+
+<LabelPrimitive.Root
+ bind:ref
+ data-slot="label"
+ class={cn(
+ 'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
+ className
+ )}
+ {...restProps}
+/>
--- /dev/null
+import Scrollbar from './scroll-area-scrollbar.svelte';
+import Root from './scroll-area.svelte';
+
+export {
+ Root,
+ Scrollbar,
+ //,
+ Root as ScrollArea,
+ Scrollbar as ScrollAreaScrollbar
+};
--- /dev/null
+<script lang="ts">
+ import { ScrollArea as ScrollAreaPrimitive } from 'bits-ui';
+ import { cn, type WithoutChild } from '$lib/components/ui/utils';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ orientation = 'vertical',
+ children,
+ ...restProps
+ }: WithoutChild<ScrollAreaPrimitive.ScrollbarProps> = $props();
+</script>
+
+<ScrollAreaPrimitive.Scrollbar
+ bind:ref
+ data-slot="scroll-area-scrollbar"
+ {orientation}
+ class={cn(
+ 'flex touch-none p-px transition-colors select-none',
+ orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent',
+ orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent',
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+ <ScrollAreaPrimitive.Thumb
+ data-slot="scroll-area-thumb"
+ class="relative flex-1 rounded-full bg-border"
+ />
+</ScrollAreaPrimitive.Scrollbar>
--- /dev/null
+<script lang="ts">
+ import { ScrollArea as ScrollAreaPrimitive } from 'bits-ui';
+ import { Scrollbar } from './index.js';
+ import { cn, type WithoutChild } from '$lib/components/ui/utils';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ orientation = 'vertical',
+ scrollbarXClasses = '',
+ scrollbarYClasses = '',
+ children,
+ ...restProps
+ }: WithoutChild<ScrollAreaPrimitive.RootProps> & {
+ orientation?: 'vertical' | 'horizontal' | 'both' | undefined;
+ scrollbarXClasses?: string | undefined;
+ scrollbarYClasses?: string | undefined;
+ } = $props();
+</script>
+
+<ScrollAreaPrimitive.Root
+ bind:ref
+ data-slot="scroll-area"
+ class={cn('relative', className)}
+ {...restProps}
+>
+ <ScrollAreaPrimitive.Viewport
+ data-slot="scroll-area-viewport"
+ class="size-full rounded-[inherit] ring-ring/10 outline-ring/50 transition-[color,box-shadow] focus-visible:ring-4 focus-visible:outline-1 dark:ring-ring/20 dark:outline-ring/40"
+ >
+ {@render children?.()}
+ </ScrollAreaPrimitive.Viewport>
+ {#if orientation === 'vertical' || orientation === 'both'}
+ <Scrollbar orientation="vertical" class={scrollbarYClasses} />
+ {/if}
+ {#if orientation === 'horizontal' || orientation === 'both'}
+ <Scrollbar orientation="horizontal" class={scrollbarXClasses} />
+ {/if}
+ <ScrollAreaPrimitive.Corner />
+</ScrollAreaPrimitive.Root>
--- /dev/null
+import { Select as SelectPrimitive } from 'bits-ui';
+
+import Group from './select-group.svelte';
+import Label from './select-label.svelte';
+import Item from './select-item.svelte';
+import Content from './select-content.svelte';
+import Trigger from './select-trigger.svelte';
+import Separator from './select-separator.svelte';
+import ScrollDownButton from './select-scroll-down-button.svelte';
+import ScrollUpButton from './select-scroll-up-button.svelte';
+import GroupHeading from './select-group-heading.svelte';
+
+const Root = SelectPrimitive.Root;
+
+export {
+ Root,
+ Group,
+ Label,
+ Item,
+ Content,
+ Trigger,
+ Separator,
+ ScrollDownButton,
+ ScrollUpButton,
+ GroupHeading,
+ //
+ Root as Select,
+ Group as SelectGroup,
+ Label as SelectLabel,
+ Item as SelectItem,
+ Content as SelectContent,
+ Trigger as SelectTrigger,
+ Separator as SelectSeparator,
+ ScrollDownButton as SelectScrollDownButton,
+ ScrollUpButton as SelectScrollUpButton,
+ GroupHeading as SelectGroupHeading
+};
--- /dev/null
+<script lang="ts">
+ import { Select as SelectPrimitive } from 'bits-ui';
+ import SelectScrollUpButton from './select-scroll-up-button.svelte';
+ import SelectScrollDownButton from './select-scroll-down-button.svelte';
+ import { cn, type WithoutChild } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ sideOffset = 4,
+ portalProps,
+ children,
+ ...restProps
+ }: WithoutChild<SelectPrimitive.ContentProps> & {
+ portalProps?: SelectPrimitive.PortalProps;
+ } = $props();
+</script>
+
+<SelectPrimitive.Portal {...portalProps}>
+ <SelectPrimitive.Content
+ bind:ref
+ {sideOffset}
+ data-slot="select-content"
+ class={cn(
+ 'relative z-50 max-h-(--bits-select-content-available-height) min-w-[8rem] origin-(--bits-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:translate-y-1 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:-translate-x-1 data-[side=left]:slide-in-from-right-2 data-[side=right]:translate-x-1 data-[side=right]:slide-in-from-left-2 data-[side=top]:-translate-y-1 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
+ className
+ )}
+ {...restProps}
+ >
+ <SelectScrollUpButton />
+ <SelectPrimitive.Viewport
+ class={cn(
+ 'h-(--bits-select-anchor-height) w-full min-w-(--bits-select-anchor-width) scroll-my-1 p-1'
+ )}
+ >
+ {@render children?.()}
+ </SelectPrimitive.Viewport>
+ <SelectScrollDownButton />
+ </SelectPrimitive.Content>
+</SelectPrimitive.Portal>
--- /dev/null
+<script lang="ts">
+ import { Select as SelectPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils.js';
+ import type { ComponentProps } from 'svelte';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: ComponentProps<typeof SelectPrimitive.GroupHeading> = $props();
+</script>
+
+<SelectPrimitive.GroupHeading
+ bind:ref
+ data-slot="select-group-heading"
+ class={cn('px-2 py-1.5 text-xs text-muted-foreground', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</SelectPrimitive.GroupHeading>
--- /dev/null
+<script lang="ts">
+ import { Select as SelectPrimitive } from 'bits-ui';
+
+ let { ref = $bindable(null), ...restProps }: SelectPrimitive.GroupProps = $props();
+</script>
+
+<SelectPrimitive.Group data-slot="select-group" {...restProps} />
--- /dev/null
+<script lang="ts">
+ import CheckIcon from '@lucide/svelte/icons/check';
+ import { Select as SelectPrimitive } from 'bits-ui';
+ import { cn, type WithoutChild } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ value,
+ label,
+ children: childrenProp,
+ ...restProps
+ }: WithoutChild<SelectPrimitive.ItemProps> = $props();
+</script>
+
+<SelectPrimitive.Item
+ bind:ref
+ {value}
+ data-slot="select-item"
+ class={cn(
+ "relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
+ className
+ )}
+ {...restProps}
+>
+ {#snippet children({ selected, highlighted })}
+ <span class="absolute right-2 flex size-3.5 items-center justify-center">
+ {#if selected}
+ <CheckIcon class="size-4" />
+ {/if}
+ </span>
+ {#if childrenProp}
+ {@render childrenProp({ selected, highlighted })}
+ {:else}
+ {label || value}
+ {/if}
+ {/snippet}
+</SelectPrimitive.Item>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {} = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="select-label"
+ class={cn('px-2 py-1.5 text-xs text-muted-foreground', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
+ import { Select as SelectPrimitive } from 'bits-ui';
+ import { cn, type WithoutChildrenOrChild } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: WithoutChildrenOrChild<SelectPrimitive.ScrollDownButtonProps> = $props();
+</script>
+
+<SelectPrimitive.ScrollDownButton
+ bind:ref
+ data-slot="select-scroll-down-button"
+ class={cn('flex cursor-default items-center justify-center py-1', className)}
+ {...restProps}
+>
+ <ChevronDownIcon class="size-4" />
+</SelectPrimitive.ScrollDownButton>
--- /dev/null
+<script lang="ts">
+ import ChevronUpIcon from '@lucide/svelte/icons/chevron-up';
+ import { Select as SelectPrimitive } from 'bits-ui';
+ import { cn, type WithoutChildrenOrChild } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: WithoutChildrenOrChild<SelectPrimitive.ScrollUpButtonProps> = $props();
+</script>
+
+<SelectPrimitive.ScrollUpButton
+ bind:ref
+ data-slot="select-scroll-up-button"
+ class={cn('flex cursor-default items-center justify-center py-1', className)}
+ {...restProps}
+>
+ <ChevronUpIcon class="size-4" />
+</SelectPrimitive.ScrollUpButton>
--- /dev/null
+<script lang="ts">
+ import type { Separator as SeparatorPrimitive } from 'bits-ui';
+ import { Separator } from '$lib/components/ui/separator/index.js';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: SeparatorPrimitive.RootProps = $props();
+</script>
+
+<Separator
+ bind:ref
+ data-slot="select-separator"
+ class={cn('pointer-events-none -mx-1 my-1 h-px bg-border', className)}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { Select as SelectPrimitive } from 'bits-ui';
+ import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
+ import { cn, type WithoutChild } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ size = 'default',
+ ...restProps
+ }: WithoutChild<SelectPrimitive.TriggerProps> & {
+ size?: 'sm' | 'default';
+ } = $props();
+</script>
+
+<SelectPrimitive.Trigger
+ bind:ref
+ data-slot="select-trigger"
+ data-size={size}
+ class={cn(
+ "flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none select-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+ <ChevronDownIcon class="size-4 opacity-50" />
+</SelectPrimitive.Trigger>
--- /dev/null
+import Root from './separator.svelte';
+
+export {
+ Root,
+ //
+ Root as Separator
+};
--- /dev/null
+<script lang="ts">
+ import { Separator as SeparatorPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: SeparatorPrimitive.RootProps = $props();
+</script>
+
+<SeparatorPrimitive.Root
+ bind:ref
+ data-slot="separator"
+ class={cn(
+ 'shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
+ className
+ )}
+ {...restProps}
+/>
--- /dev/null
+import { Dialog as SheetPrimitive } from 'bits-ui';
+import Trigger from './sheet-trigger.svelte';
+import Close from './sheet-close.svelte';
+import Overlay from './sheet-overlay.svelte';
+import Content from './sheet-content.svelte';
+import Header from './sheet-header.svelte';
+import Footer from './sheet-footer.svelte';
+import Title from './sheet-title.svelte';
+import Description from './sheet-description.svelte';
+
+const Root = SheetPrimitive.Root;
+const Portal = SheetPrimitive.Portal;
+
+export {
+ Root,
+ Close,
+ Trigger,
+ Portal,
+ Overlay,
+ Content,
+ Header,
+ Footer,
+ Title,
+ Description,
+ //
+ Root as Sheet,
+ Close as SheetClose,
+ Trigger as SheetTrigger,
+ Portal as SheetPortal,
+ Overlay as SheetOverlay,
+ Content as SheetContent,
+ Header as SheetHeader,
+ Footer as SheetFooter,
+ Title as SheetTitle,
+ Description as SheetDescription
+};
--- /dev/null
+<script lang="ts">
+ import { Dialog as SheetPrimitive } from 'bits-ui';
+
+ let { ref = $bindable(null), ...restProps }: SheetPrimitive.CloseProps = $props();
+</script>
+
+<SheetPrimitive.Close bind:ref data-slot="sheet-close" {...restProps} />
--- /dev/null
+<script lang="ts" module>
+ import { tv, type VariantProps } from 'tailwind-variants';
+ export const sheetVariants = tv({
+ base: 'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
+ variants: {
+ side: {
+ top: 'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b',
+ bottom:
+ 'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t',
+ left: 'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',
+ right:
+ 'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm'
+ }
+ },
+ defaultVariants: {
+ side: 'right'
+ }
+ });
+
+ export type Side = VariantProps<typeof sheetVariants>['side'];
+</script>
+
+<script lang="ts">
+ import { Dialog as SheetPrimitive } from 'bits-ui';
+ import XIcon from '@lucide/svelte/icons/x';
+ import type { Snippet } from 'svelte';
+ import SheetOverlay from './sheet-overlay.svelte';
+ import { cn, type WithoutChildrenOrChild } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ side = 'right',
+ portalProps,
+ children,
+ ...restProps
+ }: WithoutChildrenOrChild<SheetPrimitive.ContentProps> & {
+ portalProps?: SheetPrimitive.PortalProps;
+ side?: Side;
+ children: Snippet;
+ } = $props();
+</script>
+
+<SheetPrimitive.Portal {...portalProps}>
+ <SheetOverlay />
+ <SheetPrimitive.Content
+ bind:ref
+ data-slot="sheet-content"
+ class={cn(sheetVariants({ side }), className)}
+ {...restProps}
+ >
+ {@render children?.()}
+ <SheetPrimitive.Close
+ class="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none"
+ >
+ <XIcon class="size-4" />
+ <span class="sr-only">Close</span>
+ </SheetPrimitive.Close>
+ </SheetPrimitive.Content>
+</SheetPrimitive.Portal>
--- /dev/null
+<script lang="ts">
+ import { Dialog as SheetPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: SheetPrimitive.DescriptionProps = $props();
+</script>
+
+<SheetPrimitive.Description
+ bind:ref
+ data-slot="sheet-description"
+ class={cn('text-sm text-muted-foreground', className)}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="sheet-footer"
+ class={cn('mt-auto flex flex-col gap-2 p-4', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import type { HTMLAttributes } from 'svelte/elements';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="sheet-header"
+ class={cn('flex flex-col gap-1.5 p-4', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import { Dialog as SheetPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: SheetPrimitive.OverlayProps = $props();
+</script>
+
+<SheetPrimitive.Overlay
+ bind:ref
+ data-slot="sheet-overlay"
+ class={cn(
+ 'fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0',
+ className
+ )}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { Dialog as SheetPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: SheetPrimitive.TitleProps = $props();
+</script>
+
+<SheetPrimitive.Title
+ bind:ref
+ data-slot="sheet-title"
+ class={cn('font-semibold text-foreground', className)}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { Dialog as SheetPrimitive } from 'bits-ui';
+
+ let { ref = $bindable(null), ...restProps }: SheetPrimitive.TriggerProps = $props();
+</script>
+
+<SheetPrimitive.Trigger bind:ref data-slot="sheet-trigger" {...restProps} />
--- /dev/null
+export const SIDEBAR_COOKIE_NAME = 'sidebar:state';
+export const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
+export const SIDEBAR_WIDTH = '18rem';
+export const SIDEBAR_WIDTH_MOBILE = '18rem';
+export const SIDEBAR_WIDTH_ICON = '3rem';
+export const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
--- /dev/null
+import { IsMobile } from '$lib/hooks/is-mobile.svelte.js';
+import { getContext, setContext } from 'svelte';
+import { SIDEBAR_KEYBOARD_SHORTCUT } from './constants.js';
+
+type Getter<T> = () => T;
+
+export type SidebarStateProps = {
+ /**
+ * A getter function that returns the current open state of the sidebar.
+ * We use a getter function here to support `bind:open` on the `Sidebar.Provider`
+ * component.
+ */
+ open: Getter<boolean>;
+
+ /**
+ * A function that sets the open state of the sidebar. To support `bind:open`, we need
+ * a source of truth for changing the open state to ensure it will be synced throughout
+ * the sub-components and any `bind:` references.
+ */
+ setOpen: (open: boolean) => void;
+};
+
+class SidebarState {
+ readonly props: SidebarStateProps;
+ open = $derived.by(() => this.props.open());
+ openMobile = $state(false);
+ setOpen: SidebarStateProps['setOpen'];
+ #isMobile: IsMobile;
+ state = $derived.by(() => (this.open ? 'expanded' : 'collapsed'));
+
+ constructor(props: SidebarStateProps) {
+ this.setOpen = props.setOpen;
+ this.#isMobile = new IsMobile();
+ this.props = props;
+ }
+
+ // Convenience getter for checking if the sidebar is mobile
+ // without this, we would need to use `sidebar.isMobile.current` everywhere
+ get isMobile() {
+ return this.#isMobile.current;
+ }
+
+ // Event handler to apply to the `<svelte:window>`
+ handleShortcutKeydown = (e: KeyboardEvent) => {
+ if (e.key === SIDEBAR_KEYBOARD_SHORTCUT && (e.metaKey || e.ctrlKey)) {
+ e.preventDefault();
+ this.toggle();
+ }
+ };
+
+ setOpenMobile = (value: boolean) => {
+ this.openMobile = value;
+ };
+
+ toggle = () => {
+ return this.#isMobile.current ? (this.openMobile = !this.openMobile) : this.setOpen(!this.open);
+ };
+}
+
+const SYMBOL_KEY = 'scn-sidebar';
+
+/**
+ * Instantiates a new `SidebarState` instance and sets it in the context.
+ *
+ * @param props The constructor props for the `SidebarState` class.
+ * @returns The `SidebarState` instance.
+ */
+export function setSidebar(props: SidebarStateProps): SidebarState {
+ return setContext(Symbol.for(SYMBOL_KEY), new SidebarState(props));
+}
+
+/**
+ * Retrieves the `SidebarState` instance from the context. This is a class instance,
+ * so you cannot destructure it.
+ * @returns The `SidebarState` instance.
+ */
+export function useSidebar(): SidebarState {
+ return getContext(Symbol.for(SYMBOL_KEY));
+}
--- /dev/null
+import { useSidebar } from './context.svelte.js';
+import Content from './sidebar-content.svelte';
+import Footer from './sidebar-footer.svelte';
+import GroupAction from './sidebar-group-action.svelte';
+import GroupContent from './sidebar-group-content.svelte';
+import GroupLabel from './sidebar-group-label.svelte';
+import Group from './sidebar-group.svelte';
+import Header from './sidebar-header.svelte';
+import Input from './sidebar-input.svelte';
+import Inset from './sidebar-inset.svelte';
+import MenuAction from './sidebar-menu-action.svelte';
+import MenuBadge from './sidebar-menu-badge.svelte';
+import MenuButton from './sidebar-menu-button.svelte';
+import MenuItem from './sidebar-menu-item.svelte';
+import MenuSkeleton from './sidebar-menu-skeleton.svelte';
+import MenuSubButton from './sidebar-menu-sub-button.svelte';
+import MenuSubItem from './sidebar-menu-sub-item.svelte';
+import MenuSub from './sidebar-menu-sub.svelte';
+import Menu from './sidebar-menu.svelte';
+import Provider from './sidebar-provider.svelte';
+import Rail from './sidebar-rail.svelte';
+import Separator from './sidebar-separator.svelte';
+import Trigger from './sidebar-trigger.svelte';
+import Root from './sidebar.svelte';
+
+export {
+ Content,
+ Footer,
+ Group,
+ GroupAction,
+ GroupContent,
+ GroupLabel,
+ Header,
+ Input,
+ Inset,
+ Menu,
+ MenuAction,
+ MenuBadge,
+ MenuButton,
+ MenuItem,
+ MenuSkeleton,
+ MenuSub,
+ MenuSubButton,
+ MenuSubItem,
+ Provider,
+ Rail,
+ Root,
+ Separator,
+ //
+ Root as Sidebar,
+ Content as SidebarContent,
+ Footer as SidebarFooter,
+ Group as SidebarGroup,
+ GroupAction as SidebarGroupAction,
+ GroupContent as SidebarGroupContent,
+ GroupLabel as SidebarGroupLabel,
+ Header as SidebarHeader,
+ Input as SidebarInput,
+ Inset as SidebarInset,
+ Menu as SidebarMenu,
+ MenuAction as SidebarMenuAction,
+ MenuBadge as SidebarMenuBadge,
+ MenuButton as SidebarMenuButton,
+ MenuItem as SidebarMenuItem,
+ MenuSkeleton as SidebarMenuSkeleton,
+ MenuSub as SidebarMenuSub,
+ MenuSubButton as SidebarMenuSubButton,
+ MenuSubItem as SidebarMenuSubItem,
+ Provider as SidebarProvider,
+ Rail as SidebarRail,
+ Separator as SidebarSeparator,
+ Trigger as SidebarTrigger,
+ Trigger,
+ useSidebar
+};
--- /dev/null
+<script lang="ts">
+ import type { HTMLAttributes } from 'svelte/elements';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="sidebar-content"
+ data-sidebar="content"
+ class={cn(
+ 'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import type { HTMLAttributes } from 'svelte/elements';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="sidebar-footer"
+ data-sidebar="footer"
+ class={cn('flex flex-col gap-2 p-2', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { Snippet } from 'svelte';
+ import type { HTMLButtonAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ child,
+ ...restProps
+ }: WithElementRef<HTMLButtonAttributes> & {
+ child?: Snippet<[{ props: Record<string, unknown> }]>;
+ } = $props();
+
+ const mergedProps = $derived({
+ class: cn(
+ 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground outline-hidden absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
+ // Increases the hit area of the button on mobile.
+ 'after:absolute after:-inset-2 md:after:hidden',
+ 'group-data-[collapsible=icon]:hidden',
+ className
+ ),
+ 'data-slot': 'sidebar-group-action',
+ 'data-sidebar': 'group-action',
+ ...restProps
+ });
+</script>
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+ <button bind:this={ref} {...mergedProps}>
+ {@render children?.()}
+ </button>
+{/if}
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="sidebar-group-content"
+ data-sidebar="group-content"
+ class={cn('w-full text-sm', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { Snippet } from 'svelte';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ children,
+ child,
+ class: className,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLElement>> & {
+ child?: Snippet<[{ props: Record<string, unknown> }]>;
+ } = $props();
+
+ const mergedProps = $derived({
+ class: cn(
+ 'text-sidebar-foreground/70 ring-sidebar-ring outline-hidden flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
+ 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
+ className
+ ),
+ 'data-slot': 'sidebar-group-label',
+ 'data-sidebar': 'group-label',
+ ...restProps
+ });
+</script>
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+ <div bind:this={ref} {...mergedProps}>
+ {@render children?.()}
+ </div>
+{/if}
--- /dev/null
+<script lang="ts">
+ import type { HTMLAttributes } from 'svelte/elements';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="sidebar-group"
+ data-sidebar="group"
+ class={cn('relative flex w-full min-w-0 flex-col p-2', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import type { HTMLAttributes } from 'svelte/elements';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="sidebar-header"
+ data-sidebar="header"
+ class={cn('flex flex-col gap-2 p-2', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import type { ComponentProps } from 'svelte';
+ import { Input } from '$lib/components/ui/input/index.js';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ value = $bindable(''),
+ class: className,
+ ...restProps
+ }: ComponentProps<typeof Input> = $props();
+</script>
+
+<Input
+ bind:ref
+ bind:value
+ data-slot="sidebar-input"
+ data-sidebar="input"
+ class={cn('h-8 w-full bg-background shadow-none', className)}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
+</script>
+
+<main
+ bind:this={ref}
+ data-slot="sidebar-inset"
+ class={cn(
+ 'relative flex w-full flex-1 flex-col',
+ 'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2',
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+</main>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { Snippet } from 'svelte';
+ import type { HTMLButtonAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ showOnHover = false,
+ children,
+ child,
+ ...restProps
+ }: WithElementRef<HTMLButtonAttributes> & {
+ child?: Snippet<[{ props: Record<string, unknown> }]>;
+ showOnHover?: boolean;
+ } = $props();
+
+ const mergedProps = $derived({
+ class: cn(
+ 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground outline-hidden absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
+ // Increases the hit area of the button on mobile.
+ 'after:absolute after:-inset-2 md:after:hidden',
+ 'peer-data-[size=sm]/menu-button:top-1',
+ 'peer-data-[size=default]/menu-button:top-1.5',
+ 'peer-data-[size=lg]/menu-button:top-2.5',
+ 'group-data-[collapsible=icon]:hidden',
+ showOnHover &&
+ 'peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0',
+ className
+ ),
+ 'data-slot': 'sidebar-menu-action',
+ 'data-sidebar': 'menu-action',
+ ...restProps
+ });
+</script>
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+ <button bind:this={ref} {...mergedProps}>
+ {@render children?.()}
+ </button>
+{/if}
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="sidebar-menu-badge"
+ data-sidebar="menu-badge"
+ class={cn(
+ 'pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none',
+ 'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
+ 'peer-data-[size=sm]/menu-button:top-1',
+ 'peer-data-[size=default]/menu-button:top-1.5',
+ 'peer-data-[size=lg]/menu-button:top-2.5',
+ 'group-data-[collapsible=icon]:hidden',
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts" module>
+ import { tv, type VariantProps } from 'tailwind-variants';
+
+ export const sidebarMenuButtonVariants = tv({
+ base: 'peer/menu-button outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground group-has-data-[sidebar=menu-action]/menu-item:pr-8 data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm transition-[width,height,padding] focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:font-medium [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
+ variants: {
+ variant: {
+ default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
+ outline:
+ 'bg-background hover:bg-sidebar-accent hover:text-sidebar-accent-foreground shadow-[0_0_0_1px_var(--sidebar-border)] hover:shadow-[0_0_0_1px_var(--sidebar-accent)]'
+ },
+ size: {
+ default: 'h-8 text-sm',
+ sm: 'h-7 text-xs',
+ lg: 'group-data-[collapsible=icon]:p-0! h-12 text-sm'
+ }
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default'
+ }
+ });
+
+ export type SidebarMenuButtonVariant = VariantProps<typeof sidebarMenuButtonVariants>['variant'];
+ export type SidebarMenuButtonSize = VariantProps<typeof sidebarMenuButtonVariants>['size'];
+</script>
+
+<script lang="ts">
+ import * as Tooltip from '$lib/components/ui/tooltip/index.js';
+ import {
+ cn,
+ type WithElementRef,
+ type WithoutChildrenOrChild
+ } from '$lib/components/ui/utils.js';
+ import { mergeProps } from 'bits-ui';
+ import type { ComponentProps, Snippet } from 'svelte';
+ import type { HTMLAttributes } from 'svelte/elements';
+ import { useSidebar } from './context.svelte.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ child,
+ variant = 'default',
+ size = 'default',
+ isActive = false,
+ tooltipContent,
+ tooltipContentProps,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement> & {
+ isActive?: boolean;
+ variant?: SidebarMenuButtonVariant;
+ size?: SidebarMenuButtonSize;
+ tooltipContent?: Snippet | string;
+ tooltipContentProps?: WithoutChildrenOrChild<ComponentProps<typeof Tooltip.Content>>;
+ child?: Snippet<[{ props: Record<string, unknown> }]>;
+ } = $props();
+
+ const sidebar = useSidebar();
+
+ const buttonProps = $derived({
+ class: cn(sidebarMenuButtonVariants({ variant, size }), className),
+ 'data-slot': 'sidebar-menu-button',
+ 'data-sidebar': 'menu-button',
+ 'data-size': size,
+ 'data-active': isActive,
+ ...restProps
+ });
+</script>
+
+{#snippet Button({ props }: { props?: Record<string, unknown> })}
+ {@const mergedProps = mergeProps(buttonProps, props)}
+ {#if child}
+ {@render child({ props: mergedProps })}
+ {:else}
+ <button bind:this={ref} {...mergedProps}>
+ {@render children?.()}
+ </button>
+ {/if}
+{/snippet}
+
+{#if !tooltipContent}
+ {@render Button({})}
+{:else}
+ <Tooltip.Root>
+ <Tooltip.Trigger>
+ {#snippet child({ props })}
+ {@render Button({ props })}
+ {/snippet}
+ </Tooltip.Trigger>
+
+ <Tooltip.Content
+ side="right"
+ align="center"
+ hidden={sidebar.state !== 'collapsed' || sidebar.isMobile}
+ {...tooltipContentProps}
+ >
+ {#if typeof tooltipContent === 'string'}
+ {tooltipContent}
+ {:else if tooltipContent}
+ {@render tooltipContent()}
+ {/if}
+ </Tooltip.Content>
+ </Tooltip.Root>
+{/if}
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLLIElement>, HTMLLIElement> = $props();
+</script>
+
+<li
+ bind:this={ref}
+ data-slot="sidebar-menu-item"
+ data-sidebar="menu-item"
+ class={cn('group/menu-item relative', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</li>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import { Skeleton } from '$lib/components/ui/skeleton/index.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ showIcon = false,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLElement>> & {
+ showIcon?: boolean;
+ } = $props();
+
+ // Random width between 50% and 90%
+ const width = `${Math.floor(Math.random() * 40) + 50}%`;
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="sidebar-menu-skeleton"
+ data-sidebar="menu-skeleton"
+ class={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}
+ {...restProps}
+>
+ {#if showIcon}
+ <Skeleton class="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />
+ {/if}
+ <Skeleton
+ class="h-4 max-w-(--skeleton-width) flex-1"
+ data-sidebar="menu-skeleton-text"
+ style="--skeleton-width: {width};"
+ />
+ {@render children?.()}
+</div>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { Snippet } from 'svelte';
+ import type { HTMLAnchorAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ children,
+ child,
+ class: className,
+ size = 'md',
+ isActive = false,
+ ...restProps
+ }: WithElementRef<HTMLAnchorAttributes> & {
+ child?: Snippet<[{ props: Record<string, unknown> }]>;
+ size?: 'sm' | 'md';
+ isActive?: boolean;
+ } = $props();
+
+ const mergedProps = $derived({
+ class: cn(
+ 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground outline-hidden flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
+ 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
+ size === 'sm' && 'text-xs',
+ size === 'md' && 'text-sm',
+ 'group-data-[collapsible=icon]:hidden',
+ className
+ ),
+ 'data-slot': 'sidebar-menu-sub-button',
+ 'data-sidebar': 'menu-sub-button',
+ 'data-size': size,
+ 'data-active': isActive,
+ ...restProps
+ });
+</script>
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+ <a bind:this={ref} {...mergedProps}>
+ {@render children?.()}
+ </a>
+{/if}
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ children,
+ class: className,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLLIElement>> = $props();
+</script>
+
+<li
+ bind:this={ref}
+ data-slot="sidebar-menu-sub-item"
+ data-sidebar="menu-sub-item"
+ class={cn('group/menu-sub-item relative', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</li>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLUListElement>> = $props();
+</script>
+
+<ul
+ bind:this={ref}
+ data-slot="sidebar-menu-sub"
+ data-sidebar="menu-sub"
+ class={cn(
+ 'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5',
+ 'group-data-[collapsible=icon]:hidden',
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+</ul>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLUListElement>, HTMLUListElement> = $props();
+</script>
+
+<ul
+ bind:this={ref}
+ data-slot="sidebar-menu"
+ data-sidebar="menu"
+ class={cn('flex w-full min-w-0 flex-col gap-1', className)}
+ {...restProps}
+>
+ {@render children?.()}
+</ul>
--- /dev/null
+<script lang="ts">
+ import * as Tooltip from '$lib/components/ui/tooltip/index.js';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+ import {
+ SIDEBAR_COOKIE_MAX_AGE,
+ SIDEBAR_COOKIE_NAME,
+ SIDEBAR_WIDTH,
+ SIDEBAR_WIDTH_ICON
+ } from './constants.js';
+ import { setSidebar } from './context.svelte.js';
+
+ let {
+ ref = $bindable(null),
+ open = $bindable(true),
+ onOpenChange = () => {},
+ class: className,
+ style,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
+ open?: boolean;
+ onOpenChange?: (open: boolean) => void;
+ } = $props();
+
+ const sidebar = setSidebar({
+ open: () => open,
+ setOpen: (value: boolean) => {
+ open = value;
+ onOpenChange(value);
+
+ // This sets the cookie to keep the sidebar state.
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
+ }
+ });
+</script>
+
+<svelte:window onkeydown={sidebar.handleShortcutKeydown} />
+
+<Tooltip.Provider delayDuration={0}>
+ <div
+ data-slot="sidebar-wrapper"
+ style="--sidebar-width: {SIDEBAR_WIDTH}; --sidebar-width-icon: {SIDEBAR_WIDTH_ICON}; {style}"
+ class={cn(
+ 'group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar',
+ className
+ )}
+ bind:this={ref}
+ {...restProps}
+ >
+ {@render children?.()}
+ </div>
+</Tooltip.Provider>
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+ import { useSidebar } from './context.svelte.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement> = $props();
+
+ const sidebar = useSidebar();
+</script>
+
+<button
+ bind:this={ref}
+ data-sidebar="rail"
+ data-slot="sidebar-rail"
+ aria-label="Toggle Sidebar"
+ tabIndex={-1}
+ onclick={sidebar.toggle}
+ title="Toggle Sidebar"
+ class={cn(
+ 'absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-[calc(1/2*100%-1px)] after:w-[2px] hover:after:bg-sidebar-border sm:flex',
+ 'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',
+ '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
+ 'group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar',
+ '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
+ '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+</button>
--- /dev/null
+<script lang="ts">
+ import { Separator } from '$lib/components/ui/separator/index.js';
+ import { cn } from '$lib/components/ui/utils.js';
+ import type { ComponentProps } from 'svelte';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: ComponentProps<typeof Separator> = $props();
+</script>
+
+<Separator
+ bind:ref
+ data-slot="sidebar-separator"
+ data-sidebar="separator"
+ class={cn('bg-sidebar-border', className)}
+ {...restProps}
+/>
--- /dev/null
+<script lang="ts">
+ import { Button } from '$lib/components/ui/button/index.js';
+ import { cn } from '$lib/components/ui/utils.js';
+ import PanelLeftIcon from '@lucide/svelte/icons/panel-left';
+ import type { ComponentProps } from 'svelte';
+ import { useSidebar } from './context.svelte.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ onclick,
+ ...restProps
+ }: ComponentProps<typeof Button> & {
+ onclick?: (e: MouseEvent) => void;
+ } = $props();
+
+ const sidebar = useSidebar();
+</script>
+
+<Button
+ data-sidebar="trigger"
+ data-slot="sidebar-trigger"
+ variant="ghost"
+ size="icon"
+ class={cn('size-7', className)}
+ type="button"
+ onclick={(e) => {
+ onclick?.(e);
+ sidebar.toggle();
+ }}
+ {...restProps}
+>
+ <PanelLeftIcon />
+ <span class="sr-only">Toggle Sidebar</span>
+</Button>
--- /dev/null
+<script lang="ts">
+ import * as Sheet from '$lib/components/ui/sheet/index.js';
+ import { cn, type WithElementRef } from '$lib/components/ui/utils.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+ import { SIDEBAR_WIDTH_MOBILE } from './constants.js';
+ import { useSidebar } from './context.svelte.js';
+
+ let {
+ ref = $bindable(null),
+ side = 'left',
+ variant = 'sidebar',
+ collapsible = 'offcanvas',
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
+ side?: 'left' | 'right';
+ variant?: 'sidebar' | 'floating' | 'inset';
+ collapsible?: 'offcanvas' | 'icon' | 'none';
+ } = $props();
+
+ const sidebar = useSidebar();
+</script>
+
+{#if collapsible === 'none'}
+ <div
+ class={cn(
+ 'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground',
+ className
+ )}
+ bind:this={ref}
+ {...restProps}
+ >
+ {@render children?.()}
+ </div>
+{:else if sidebar.isMobile}
+ <Sheet.Root bind:open={() => sidebar.openMobile, (v) => sidebar.setOpenMobile(v)} {...restProps}>
+ <Sheet.Content
+ data-sidebar="sidebar"
+ data-slot="sidebar"
+ data-mobile="true"
+ class="z-99999 w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground sm:z-99 [&>button]:hidden"
+ style="--sidebar-width: {SIDEBAR_WIDTH_MOBILE};"
+ {side}
+ >
+ <Sheet.Header class="sr-only">
+ <Sheet.Title>Sidebar</Sheet.Title>
+ <Sheet.Description>Displays the mobile sidebar.</Sheet.Description>
+ </Sheet.Header>
+ <div class="flex h-full w-full flex-col">
+ {@render children?.()}
+ </div>
+ </Sheet.Content>
+ </Sheet.Root>
+{:else}
+ <div
+ bind:this={ref}
+ class="group peer hidden text-sidebar-foreground md:block"
+ data-state={sidebar.state}
+ data-collapsible={sidebar.state === 'collapsed' ? collapsible : ''}
+ data-variant={variant}
+ data-side={side}
+ data-slot="sidebar"
+ >
+ <!-- This is what handles the sidebar gap on desktop -->
+ <div
+ data-slot="sidebar-gap"
+ class={cn(
+ 'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',
+ 'group-data-[collapsible=offcanvas]:w-0',
+ 'group-data-[side=right]:rotate-180',
+ variant === 'floating' || variant === 'inset'
+ ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
+ : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)'
+ )}
+ ></div>
+ <div
+ data-slot="sidebar-container"
+ class={cn(
+ 'fixed inset-y-0 z-999 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:z-0 md:flex',
+ side === 'left'
+ ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
+ : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
+ // Adjust the padding for floating and inset variants.
+ variant === 'floating' || variant === 'inset'
+ ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
+ : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
+ className
+ )}
+ {...restProps}
+ >
+ <div
+ data-sidebar="sidebar"
+ data-slot="sidebar-inner"
+ class="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow-sm"
+ >
+ {@render children?.()}
+ </div>
+ </div>
+ </div>
+{/if}
--- /dev/null
+import Root from './skeleton.svelte';
+
+export {
+ Root,
+ //
+ Root as Skeleton
+};
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef, type WithoutChildren } from '$lib/components/ui/utils.js';
+ import type { HTMLAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="skeleton"
+ class={cn('animate-pulse rounded-md bg-accent', className)}
+ {...restProps}
+></div>
--- /dev/null
+import Root from './textarea.svelte';
+
+export {
+ Root,
+ //
+ Root as Textarea
+};
--- /dev/null
+<script lang="ts">
+ import { cn, type WithElementRef, type WithoutChildren } from '$lib/components/ui/utils';
+ import type { HTMLTextareaAttributes } from 'svelte/elements';
+
+ let {
+ ref = $bindable(null),
+ value = $bindable(),
+ class: className,
+ ...restProps
+ }: WithoutChildren<WithElementRef<HTMLTextareaAttributes>> = $props();
+</script>
+
+<textarea
+ bind:this={ref}
+ data-slot="textarea"
+ class={cn(
+ 'flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40',
+ className
+ )}
+ bind:value
+ {...restProps}
+></textarea>
--- /dev/null
+import { Tooltip as TooltipPrimitive } from 'bits-ui';
+import Trigger from './tooltip-trigger.svelte';
+import Content from './tooltip-content.svelte';
+
+const Root = TooltipPrimitive.Root;
+const Provider = TooltipPrimitive.Provider;
+const Portal = TooltipPrimitive.Portal;
+
+export {
+ Root,
+ Trigger,
+ Content,
+ Provider,
+ Portal,
+ //
+ Root as Tooltip,
+ Content as TooltipContent,
+ Trigger as TooltipTrigger,
+ Provider as TooltipProvider,
+ Portal as TooltipPortal
+};
--- /dev/null
+<script lang="ts">
+ import { Tooltip as TooltipPrimitive } from 'bits-ui';
+ import { cn } from '$lib/components/ui/utils.js';
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ sideOffset = 0,
+ side = 'top',
+ children,
+ arrowClasses,
+ ...restProps
+ }: TooltipPrimitive.ContentProps & {
+ arrowClasses?: string;
+ } = $props();
+</script>
+
+<TooltipPrimitive.Portal>
+ <TooltipPrimitive.Content
+ bind:ref
+ data-slot="tooltip-content"
+ {sideOffset}
+ {side}
+ class={cn(
+ 'z-50 w-fit origin-(--bits-tooltip-content-transform-origin) animate-in rounded-md bg-primary px-3 py-1.5 text-xs text-balance text-primary-foreground fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
+ className
+ )}
+ {...restProps}
+ >
+ {@render children?.()}
+ <TooltipPrimitive.Arrow>
+ {#snippet child({ props })}
+ <div
+ class={cn(
+ 'z-50 size-2.5 rotate-45 rounded-[2px] bg-primary',
+ 'data-[side=top]:translate-x-1/2 data-[side=top]:translate-y-[calc(-50%_+_2px)]',
+ 'data-[side=bottom]:-translate-x-1/2 data-[side=bottom]:-translate-y-[calc(-50%_+_1px)]',
+ 'data-[side=right]:translate-x-[calc(50%_+_2px)] data-[side=right]:translate-y-1/2',
+ 'data-[side=left]:-translate-y-[calc(50%_-_3px)]',
+ arrowClasses
+ )}
+ {...props}
+ ></div>
+ {/snippet}
+ </TooltipPrimitive.Arrow>
+ </TooltipPrimitive.Content>
+</TooltipPrimitive.Portal>
--- /dev/null
+<script lang="ts">
+ import { Tooltip as TooltipPrimitive } from 'bits-ui';
+
+ let { ref = $bindable(null), ...restProps }: TooltipPrimitive.TriggerProps = $props();
+</script>
+
+<TooltipPrimitive.Trigger bind:ref data-slot="tooltip-trigger" {...restProps} />
--- /dev/null
+import { clsx, type ClassValue } from 'clsx';
+import { twMerge } from 'tailwind-merge';
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export type WithoutChild<T> = T extends { child?: any } ? Omit<T, 'child'> : T;
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, 'children'> : T;
+export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
+export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
--- /dev/null
+export const AUTO_SCROLL_INTERVAL = 100;
+export const INITIAL_SCROLL_DELAY = 50;
+export const AUTO_SCROLL_AT_BOTTOM_THRESHOLD = 10;
--- /dev/null
+export const SLOTS_DEBOUNCE_INTERVAL = 100;
--- /dev/null
+export const INPUT_CLASSES = `
+ bg-muted/70 dark:bg-muted/85
+ border border-border/30 focus-within:border-border dark:border-border/20 dark:focus-within:border-border
+ outline-none
+ text-foreground
+`;
--- /dev/null
+export const MAX_BUNDLE_SIZE = 2 * 1024 * 1024;
--- /dev/null
+export const PROCESSING_INFO_TIMEOUT = 2000;
--- /dev/null
+export const SETTING_CONFIG_DEFAULT: Record<string, string | number | boolean> = {
+ // 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: '',
+ theme: 'system',
+ showTokensPerSecond: false,
+ showThoughtInProgress: false,
+ keepStatsVisible: false,
+ askForTitleConfirmation: false,
+ pasteLongTextToFileLen: 2500,
+ pdfAsImage: false,
+ // make sure these default values are in sync with `common.h`
+ samplers: 'top_k;typ_p;top_p;min_p;temperature',
+ temperature: 0.8,
+ dynatemp_range: 0.0,
+ dynatemp_exponent: 1.0,
+ top_k: 40,
+ top_p: 0.95,
+ min_p: 0.05,
+ xtc_probability: 0.0,
+ xtc_threshold: 0.1,
+ typ_p: 1.0,
+ repeat_last_n: 64,
+ repeat_penalty: 1.0,
+ presence_penalty: 0.0,
+ frequency_penalty: 0.0,
+ dry_multiplier: 0.0,
+ dry_base: 1.75,
+ dry_allowed_length: 2,
+ dry_penalty_last_n: -1,
+ max_tokens: -1,
+ custom: '', // custom json-stringified object
+ // experimental features
+ pyInterpreterEnabled: false
+};
+
+export const SETTING_CONFIG_INFO: Record<string, string> = {
+ apiKey: 'Set the API Key if you are using --api-key option for the server.',
+ systemMessage: 'The starting message that defines how model should behave.',
+ theme:
+ 'Choose the color theme for the interface. You can choose between System (follows your device settings), Light, or Dark.',
+ pasteLongTextToFileLen:
+ 'On pasting long text, it will be converted to a file. You can control the file length by setting the value of this parameter. Value 0 means disable.',
+ samplers:
+ 'The order at which samplers are applied, in simplified way. Default is "top_k;typ_p;top_p;min_p;temperature": top_k->typ_p->top_p->min_p->temperature',
+ temperature:
+ 'Controls the randomness of the generated text by affecting the probability distribution of the output tokens. Higher = more random, lower = more focused.',
+ dynatemp_range:
+ 'Addon for the temperature sampler. The added value to the range of dynamic temperature, which adjusts probabilities by entropy of tokens.',
+ dynatemp_exponent:
+ 'Addon for the temperature sampler. Smoothes out the probability redistribution based on the most probable token.',
+ top_k: 'Keeps only k top tokens.',
+ top_p: 'Limits tokens to those that together have a cumulative probability of at least p',
+ min_p:
+ 'Limits tokens based on the minimum probability for a token to be considered, relative to the probability of the most likely token.',
+ xtc_probability:
+ 'XTC sampler cuts out top tokens; this parameter controls the chance of cutting tokens at all. 0 disables XTC.',
+ xtc_threshold:
+ 'XTC sampler cuts out top tokens; this parameter controls the token probability that is required to cut that token.',
+ typ_p: 'Sorts and limits tokens based on the difference between log-probability and entropy.',
+ repeat_last_n: 'Last n tokens to consider for penalizing repetition',
+ repeat_penalty: 'Controls the repetition of token sequences in the generated text',
+ presence_penalty: 'Limits tokens based on whether they appear in the output or not.',
+ frequency_penalty: 'Limits tokens based on how often they appear in the output.',
+ dry_multiplier:
+ 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the DRY sampling multiplier.',
+ dry_base:
+ 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the DRY sampling base value.',
+ dry_allowed_length:
+ 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the allowed length for DRY sampling.',
+ dry_penalty_last_n:
+ 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets DRY penalty for the last n tokens.',
+ 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.',
+ showTokensPerSecond: 'Display generation speed in tokens per second during streaming.',
+ showThoughtInProgress: 'Expand thought process by default when generating messages.',
+ keepStatsVisible: 'Keep processing statistics visible after generation finishes.',
+ askForTitleConfirmation:
+ 'Ask for confirmation before automatically changing conversation title when editing the first message.',
+ pdfAsImage: 'Parse PDF as image instead of text (requires vision-capable model).',
+ pyInterpreterEnabled:
+ 'Enable Python interpreter using Pyodide. Allows running Python code in markdown code blocks.'
+};
--- /dev/null
+/**
+ * Comprehensive dictionary of all supported file types in webui
+ * Organized by category with TypeScript enums for better type safety
+ */
+
+import {
+ FileExtensionAudio,
+ FileExtensionImage,
+ FileExtensionPdf,
+ FileExtensionText,
+ FileTypeAudio,
+ FileTypeImage,
+ FileTypePdf,
+ FileTypeText,
+ MimeTypeAudio,
+ MimeTypeImage,
+ MimeTypeApplication,
+ MimeTypeText
+} from '$lib/enums/files';
+
+// File type configuration using enums
+export const AUDIO_FILE_TYPES = {
+ [FileTypeAudio.MP3]: {
+ extensions: [FileExtensionAudio.MP3],
+ mimeTypes: [MimeTypeAudio.MP3_MPEG, MimeTypeAudio.MP3]
+ },
+ [FileTypeAudio.WAV]: {
+ extensions: [FileExtensionAudio.WAV],
+ mimeTypes: [MimeTypeAudio.WAV]
+ }
+} as const;
+
+export const IMAGE_FILE_TYPES = {
+ [FileTypeImage.JPEG]: {
+ extensions: [FileExtensionImage.JPG, FileExtensionImage.JPEG],
+ mimeTypes: [MimeTypeImage.JPEG]
+ },
+ [FileTypeImage.PNG]: {
+ extensions: [FileExtensionImage.PNG],
+ mimeTypes: [MimeTypeImage.PNG]
+ },
+ [FileTypeImage.GIF]: {
+ extensions: [FileExtensionImage.GIF],
+ mimeTypes: [MimeTypeImage.GIF]
+ },
+ [FileTypeImage.WEBP]: {
+ extensions: [FileExtensionImage.WEBP],
+ mimeTypes: [MimeTypeImage.WEBP]
+ },
+ [FileTypeImage.SVG]: {
+ extensions: [FileExtensionImage.SVG],
+ mimeTypes: [MimeTypeImage.SVG]
+ }
+} as const;
+
+export const PDF_FILE_TYPES = {
+ [FileTypePdf.PDF]: {
+ extensions: [FileExtensionPdf.PDF],
+ mimeTypes: [MimeTypeApplication.PDF]
+ }
+} as const;
+
+export const TEXT_FILE_TYPES = {
+ [FileTypeText.PLAIN_TEXT]: {
+ extensions: [FileExtensionText.TXT],
+ mimeTypes: [MimeTypeText.PLAIN]
+ },
+ [FileTypeText.MARKDOWN]: {
+ extensions: [FileExtensionText.MD],
+ mimeTypes: [MimeTypeText.MARKDOWN]
+ },
+ [FileTypeText.JAVASCRIPT]: {
+ extensions: [FileExtensionText.JS],
+ mimeTypes: [MimeTypeText.JAVASCRIPT, MimeTypeText.JAVASCRIPT_APP]
+ },
+ [FileTypeText.TYPESCRIPT]: {
+ extensions: [FileExtensionText.TS],
+ mimeTypes: [MimeTypeText.TYPESCRIPT]
+ },
+ [FileTypeText.JSX]: {
+ extensions: [FileExtensionText.JSX],
+ mimeTypes: [MimeTypeText.JSX]
+ },
+ [FileTypeText.TSX]: {
+ extensions: [FileExtensionText.TSX],
+ mimeTypes: [MimeTypeText.TSX]
+ },
+ [FileTypeText.CSS]: {
+ extensions: [FileExtensionText.CSS],
+ mimeTypes: [MimeTypeText.CSS]
+ },
+ [FileTypeText.HTML]: {
+ extensions: [FileExtensionText.HTML, FileExtensionText.HTM],
+ mimeTypes: [MimeTypeText.HTML]
+ },
+ [FileTypeText.JSON]: {
+ extensions: [FileExtensionText.JSON],
+ mimeTypes: [MimeTypeText.JSON]
+ },
+ [FileTypeText.XML]: {
+ extensions: [FileExtensionText.XML],
+ mimeTypes: [MimeTypeText.XML_TEXT, MimeTypeText.XML_APP]
+ },
+ [FileTypeText.YAML]: {
+ extensions: [FileExtensionText.YAML, FileExtensionText.YML],
+ mimeTypes: [MimeTypeText.YAML_TEXT, MimeTypeText.YAML_APP]
+ },
+ [FileTypeText.CSV]: {
+ extensions: [FileExtensionText.CSV],
+ mimeTypes: [MimeTypeText.CSV]
+ },
+ [FileTypeText.LOG]: {
+ extensions: [FileExtensionText.LOG],
+ mimeTypes: [MimeTypeText.PLAIN]
+ },
+ [FileTypeText.PYTHON]: {
+ extensions: [FileExtensionText.PY],
+ mimeTypes: [MimeTypeText.PYTHON]
+ },
+ [FileTypeText.JAVA]: {
+ extensions: [FileExtensionText.JAVA],
+ mimeTypes: [MimeTypeText.JAVA]
+ },
+ [FileTypeText.CPP]: {
+ extensions: [FileExtensionText.CPP, FileExtensionText.C, FileExtensionText.H],
+ mimeTypes: [MimeTypeText.CPP_SRC, MimeTypeText.C_SRC, MimeTypeText.C_HDR]
+ },
+ [FileTypeText.PHP]: {
+ extensions: [FileExtensionText.PHP],
+ mimeTypes: [MimeTypeText.PHP]
+ },
+ [FileTypeText.RUBY]: {
+ extensions: [FileExtensionText.RB],
+ mimeTypes: [MimeTypeText.RUBY]
+ },
+ [FileTypeText.GO]: {
+ extensions: [FileExtensionText.GO],
+ mimeTypes: [MimeTypeText.GO]
+ },
+ [FileTypeText.RUST]: {
+ extensions: [FileExtensionText.RS],
+ mimeTypes: [MimeTypeText.RUST]
+ },
+ [FileTypeText.SHELL]: {
+ extensions: [FileExtensionText.SH, FileExtensionText.BAT],
+ mimeTypes: [MimeTypeText.SHELL, MimeTypeText.BAT]
+ },
+ [FileTypeText.SQL]: {
+ extensions: [FileExtensionText.SQL],
+ mimeTypes: [MimeTypeText.SQL]
+ },
+ [FileTypeText.R]: {
+ extensions: [FileExtensionText.R],
+ mimeTypes: [MimeTypeText.R]
+ },
+ [FileTypeText.SCALA]: {
+ extensions: [FileExtensionText.SCALA],
+ mimeTypes: [MimeTypeText.SCALA]
+ },
+ [FileTypeText.KOTLIN]: {
+ extensions: [FileExtensionText.KT],
+ mimeTypes: [MimeTypeText.KOTLIN]
+ },
+ [FileTypeText.SWIFT]: {
+ extensions: [FileExtensionText.SWIFT],
+ mimeTypes: [MimeTypeText.SWIFT]
+ },
+ [FileTypeText.DART]: {
+ extensions: [FileExtensionText.DART],
+ mimeTypes: [MimeTypeText.DART]
+ },
+ [FileTypeText.VUE]: {
+ extensions: [FileExtensionText.VUE],
+ mimeTypes: [MimeTypeText.VUE]
+ },
+ [FileTypeText.SVELTE]: {
+ extensions: [FileExtensionText.SVELTE],
+ mimeTypes: [MimeTypeText.SVELTE]
+ }
+} as const;
--- /dev/null
+export const TOOLTIP_DELAY_DURATION = 100;
--- /dev/null
+export const DEFAULT_MOBILE_BREAKPOINT = 768;
--- /dev/null
+/**
+ * Comprehensive dictionary of all supported file types in webui
+ * Organized by category with TypeScript enums for better type safety
+ */
+
+// File type category enum
+export enum FileTypeCategory {
+ IMAGE = 'image',
+ AUDIO = 'audio',
+ PDF = 'pdf',
+ TEXT = 'text'
+}
+
+// Specific file type enums for each category
+export enum FileTypeImage {
+ JPEG = 'jpeg',
+ PNG = 'png',
+ GIF = 'gif',
+ WEBP = 'webp',
+ SVG = 'svg'
+}
+
+export enum FileTypeAudio {
+ MP3 = 'mp3',
+ WAV = 'wav',
+ WEBM = 'webm'
+}
+
+export enum FileTypePdf {
+ PDF = 'pdf'
+}
+
+export enum FileTypeText {
+ PLAIN_TEXT = 'plainText',
+ MARKDOWN = 'markdown',
+ JAVASCRIPT = 'javascript',
+ TYPESCRIPT = 'typescript',
+ JSX = 'jsx',
+ TSX = 'tsx',
+ CSS = 'css',
+ HTML = 'html',
+ JSON = 'json',
+ XML = 'xml',
+ YAML = 'yaml',
+ CSV = 'csv',
+ LOG = 'log',
+ PYTHON = 'python',
+ JAVA = 'java',
+ CPP = 'cpp',
+ PHP = 'php',
+ RUBY = 'ruby',
+ GO = 'go',
+ RUST = 'rust',
+ SHELL = 'shell',
+ SQL = 'sql',
+ R = 'r',
+ SCALA = 'scala',
+ KOTLIN = 'kotlin',
+ SWIFT = 'swift',
+ DART = 'dart',
+ VUE = 'vue',
+ SVELTE = 'svelte'
+}
+
+// File extension enums
+export enum FileExtensionImage {
+ JPG = '.jpg',
+ JPEG = '.jpeg',
+ PNG = '.png',
+ GIF = '.gif',
+ WEBP = '.webp',
+ SVG = '.svg'
+}
+
+export enum FileExtensionAudio {
+ MP3 = '.mp3',
+ WAV = '.wav'
+}
+
+export enum FileExtensionPdf {
+ PDF = '.pdf'
+}
+
+export enum FileExtensionText {
+ TXT = '.txt',
+ MD = '.md',
+ JS = '.js',
+ TS = '.ts',
+ JSX = '.jsx',
+ TSX = '.tsx',
+ CSS = '.css',
+ HTML = '.html',
+ HTM = '.htm',
+ JSON = '.json',
+ XML = '.xml',
+ YAML = '.yaml',
+ YML = '.yml',
+ CSV = '.csv',
+ LOG = '.log',
+ PY = '.py',
+ JAVA = '.java',
+ CPP = '.cpp',
+ C = '.c',
+ H = '.h',
+ PHP = '.php',
+ RB = '.rb',
+ GO = '.go',
+ RS = '.rs',
+ SH = '.sh',
+ BAT = '.bat',
+ SQL = '.sql',
+ R = '.r',
+ SCALA = '.scala',
+ KT = '.kt',
+ SWIFT = '.swift',
+ DART = '.dart',
+ VUE = '.vue',
+ SVELTE = '.svelte'
+}
+
+// MIME type enums
+export enum MimeTypeApplication {
+ PDF = 'application/pdf'
+}
+
+export enum MimeTypeAudio {
+ MP3_MPEG = 'audio/mpeg',
+ MP3 = 'audio/mp3',
+ MP4 = 'audio/mp4',
+ WAV = 'audio/wav',
+ WEBM = 'audio/webm',
+ WEBM_OPUS = 'audio/webm;codecs=opus'
+}
+
+export enum MimeTypeImage {
+ JPEG = 'image/jpeg',
+ PNG = 'image/png',
+ GIF = 'image/gif',
+ WEBP = 'image/webp',
+ SVG = 'image/svg+xml'
+}
+
+export enum MimeTypeText {
+ PLAIN = 'text/plain',
+ MARKDOWN = 'text/markdown',
+ JAVASCRIPT = 'text/javascript',
+ JAVASCRIPT_APP = 'application/javascript',
+ TYPESCRIPT = 'text/typescript',
+ JSX = 'text/jsx',
+ TSX = 'text/tsx',
+ CSS = 'text/css',
+ HTML = 'text/html',
+ JSON = 'application/json',
+ XML_TEXT = 'text/xml',
+ XML_APP = 'application/xml',
+ YAML_TEXT = 'text/yaml',
+ YAML_APP = 'application/yaml',
+ CSV = 'text/csv',
+ PYTHON = 'text/x-python',
+ JAVA = 'text/x-java-source',
+ CPP_SRC = 'text/x-c++src',
+ C_SRC = 'text/x-csrc',
+ C_HDR = 'text/x-chdr',
+ PHP = 'text/x-php',
+ RUBY = 'text/x-ruby',
+ GO = 'text/x-go',
+ RUST = 'text/x-rust',
+ SHELL = 'text/x-shellscript',
+ BAT = 'application/x-bat',
+ SQL = 'text/x-sql',
+ R = 'text/x-r',
+ SCALA = 'text/x-scala',
+ KOTLIN = 'text/x-kotlin',
+ SWIFT = 'text/x-swift',
+ DART = 'text/x-dart',
+ VUE = 'text/x-vue',
+ SVELTE = 'text/x-svelte'
+}
--- /dev/null
+import { DEFAULT_MOBILE_BREAKPOINT } from '$lib/constants/viewport';
+import { MediaQuery } from 'svelte/reactivity';
+
+export class IsMobile extends MediaQuery {
+ constructor(breakpoint: number = DEFAULT_MOBILE_BREAKPOINT) {
+ super(`max-width: ${breakpoint - 1}px`);
+ }
+}
--- /dev/null
+import { slotsService } from '$lib/services';
+import { config } from '$lib/stores/settings.svelte';
+
+export interface UseProcessingStateReturn {
+ readonly processingState: ApiProcessingState | null;
+ getProcessingDetails(): string[];
+ getProcessingMessage(): string;
+ shouldShowDetails(): boolean;
+ startMonitoring(): Promise<void>;
+ stopMonitoring(): void;
+}
+
+/**
+ * useProcessingState - Reactive processing state hook
+ *
+ * This hook provides reactive access to the processing state of the server.
+ * It subscribes to timing data updates from the slots service and provides
+ * formatted processing details for UI display.
+ *
+ * **Features:**
+ * - Real-time processing state monitoring
+ * - Context and output token tracking
+ * - Tokens per second calculation
+ * - Graceful degradation when slots endpoint unavailable
+ * - Automatic cleanup on component unmount
+ *
+ * @returns Hook interface with processing state and control methods
+ */
+export function useProcessingState(): UseProcessingStateReturn {
+ let isMonitoring = $state(false);
+ let processingState = $state<ApiProcessingState | null>(null);
+ let lastKnownState = $state<ApiProcessingState | null>(null);
+ let unsubscribe: (() => void) | null = null;
+
+ async function startMonitoring(): Promise<void> {
+ if (isMonitoring) return;
+
+ isMonitoring = true;
+
+ unsubscribe = slotsService.subscribe((state) => {
+ processingState = state;
+ if (state) {
+ lastKnownState = state;
+ } else {
+ lastKnownState = null;
+ }
+ });
+
+ try {
+ const currentState = await slotsService.getCurrentState();
+
+ if (currentState) {
+ processingState = currentState;
+ lastKnownState = currentState;
+ }
+
+ if (slotsService.isStreaming()) {
+ slotsService.startStreaming();
+ }
+ } catch (error) {
+ console.warn('Failed to start slots monitoring:', error);
+ // Continue without slots monitoring - graceful degradation
+ }
+ }
+
+ function stopMonitoring(): void {
+ if (!isMonitoring) return;
+
+ isMonitoring = false;
+
+ // Only clear processing state if keepStatsVisible is disabled
+ // This preserves the last known state for display when stats should remain visible
+ const currentConfig = config();
+ if (!currentConfig.keepStatsVisible) {
+ processingState = null;
+ } else if (lastKnownState) {
+ // Keep the last known state visible when keepStatsVisible is enabled
+ processingState = lastKnownState;
+ }
+
+ if (unsubscribe) {
+ unsubscribe();
+ unsubscribe = null;
+ }
+ }
+
+ function getProcessingMessage(): string {
+ if (!processingState) {
+ return 'Processing...';
+ }
+
+ switch (processingState.status) {
+ case 'initializing':
+ return 'Initializing...';
+ case 'preparing':
+ if (processingState.progressPercent !== undefined) {
+ return `Processing (${processingState.progressPercent}%)`;
+ }
+ return 'Preparing response...';
+ case 'generating':
+ if (processingState.tokensDecoded > 0) {
+ return `Generating... (${processingState.tokensDecoded} tokens)`;
+ }
+ return 'Generating...';
+ default:
+ return 'Processing...';
+ }
+ }
+
+ function getProcessingDetails(): string[] {
+ // Use current processing state or fall back to last known state
+ const stateToUse = processingState || lastKnownState;
+ if (!stateToUse) {
+ return [];
+ }
+
+ const details: string[] = [];
+ const currentConfig = config(); // Get fresh config each time
+
+ // Always show context info when we have valid data
+ if (stateToUse.contextUsed >= 0 && stateToUse.contextTotal > 0) {
+ const contextPercent = Math.round((stateToUse.contextUsed / stateToUse.contextTotal) * 100);
+
+ details.push(
+ `Context: ${stateToUse.contextUsed}/${stateToUse.contextTotal} (${contextPercent}%)`
+ );
+ }
+
+ if (stateToUse.outputTokensUsed > 0) {
+ // Handle infinite max_tokens (-1) case
+ if (stateToUse.outputTokensMax <= 0) {
+ details.push(`Output: ${stateToUse.outputTokensUsed}/∞`);
+ } else {
+ const outputPercent = Math.round(
+ (stateToUse.outputTokensUsed / stateToUse.outputTokensMax) * 100
+ );
+
+ details.push(
+ `Output: ${stateToUse.outputTokensUsed}/${stateToUse.outputTokensMax} (${outputPercent}%)`
+ );
+ }
+ }
+
+ if (
+ currentConfig.showTokensPerSecond &&
+ stateToUse.tokensPerSecond &&
+ stateToUse.tokensPerSecond > 0
+ ) {
+ details.push(`${stateToUse.tokensPerSecond.toFixed(1)} tokens/sec`);
+ }
+
+ if (stateToUse.speculative) {
+ details.push('Speculative decoding enabled');
+ }
+
+ return details;
+ }
+
+ function shouldShowDetails(): boolean {
+ return processingState !== null && processingState.status !== 'idle';
+ }
+
+ return {
+ get processingState() {
+ return processingState;
+ },
+ getProcessingDetails,
+ getProcessingMessage,
+ shouldShowDetails,
+ startMonitoring,
+ stopMonitoring
+ };
+}
--- /dev/null
+import { config } from '$lib/stores/settings.svelte';
+import { slotsService } from './slots';
+/**
+ * ChatService - Low-level API communication layer for llama.cpp server interactions
+ *
+ * This service handles direct communication with the llama.cpp server's chat completion API.
+ * It provides the network layer abstraction for AI model interactions while remaining
+ * stateless and focused purely on API communication.
+ *
+ * **Architecture & Relationship with ChatStore:**
+ * - **ChatService** (this class): Stateless API communication layer
+ * - Handles HTTP requests/responses with llama.cpp server
+ * - Manages streaming and non-streaming response parsing
+ * - Provides request abortion capabilities
+ * - Converts database messages to API format
+ * - Handles error translation and context detection
+ *
+ * - **ChatStore**: Stateful orchestration and UI state management
+ * - Uses ChatService for all AI model communication
+ * - Manages conversation state, message history, and UI reactivity
+ * - Coordinates with DatabaseStore for persistence
+ * - Handles complex workflows like branching and regeneration
+ *
+ * **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)
+ * - Context error detection and reporting
+ * - Request lifecycle management (abort, cleanup)
+ */
+export class ChatService {
+ private abortController: AbortController | null = null;
+
+ /**
+ * Sends a chat completion request to the llama.cpp server.
+ * Supports both streaming and non-streaming responses with comprehensive parameter configuration.
+ * Automatically converts database messages with attachments to the appropriate API format.
+ *
+ * @param messages - Array of chat messages to send to the API (supports both ApiChatMessageData and DatabaseMessage with attachments)
+ * @param options - Configuration options for the chat completion request. See `SettingsChatServiceOptions` type for details.
+ * @returns {Promise<string | void>} that resolves to the complete response string (non-streaming) or void (streaming)
+ * @throws {Error} if the request fails or is aborted
+ */
+ async sendMessage(
+ messages: ApiChatMessageData[] | (DatabaseMessage & { extra?: DatabaseMessageExtra[] })[],
+ options: SettingsChatServiceOptions = {}
+ ): Promise<string | void> {
+ const {
+ stream,
+ onChunk,
+ onComplete,
+ onError,
+ // Generation parameters
+ temperature,
+ max_tokens,
+ // Sampling parameters
+ dynatemp_range,
+ dynatemp_exponent,
+ top_k,
+ top_p,
+ min_p,
+ xtc_probability,
+ xtc_threshold,
+ typ_p,
+ // Penalty parameters
+ repeat_last_n,
+ repeat_penalty,
+ presence_penalty,
+ frequency_penalty,
+ dry_multiplier,
+ dry_base,
+ dry_allowed_length,
+ dry_penalty_last_n,
+ // Other parameters
+ samplers,
+ custom,
+ timings_per_token
+ } = options;
+
+ // Cancel any ongoing request and create a new abort controller
+ this.abort();
+ this.abortController = new AbortController();
+
+ // Convert database messages with attachments to API format if needed
+ const normalizedMessages: ApiChatMessageData[] = messages
+ .map((msg) => {
+ // Check if this is a DatabaseMessage by checking for DatabaseMessage-specific fields
+ if ('id' in msg && 'convId' in msg && 'timestamp' in msg) {
+ // This is a DatabaseMessage, convert it
+ const dbMsg = msg as DatabaseMessage & { extra?: DatabaseMessageExtra[] };
+ return ChatService.convertMessageToChatServiceData(dbMsg);
+ } else {
+ // This is already an ApiChatMessageData object
+ return msg as ApiChatMessageData;
+ }
+ })
+ .filter((msg) => {
+ // Filter out empty system messages
+ if (msg.role === 'system') {
+ const content = typeof msg.content === 'string' ? msg.content : '';
+
+ return content.trim().length > 0;
+ }
+
+ return true;
+ });
+
+ // Build base request body with system message injection
+ const processedMessages = this.injectSystemMessage(normalizedMessages);
+
+ const requestBody: ApiChatCompletionRequest = {
+ messages: processedMessages.map((msg: ApiChatMessageData) => ({
+ role: msg.role,
+ content: msg.content
+ })),
+ stream
+ };
+
+ requestBody.reasoning_format = 'auto';
+
+ if (temperature !== undefined) requestBody.temperature = temperature;
+ // Set max_tokens to -1 (infinite) if not provided or empty
+ requestBody.max_tokens =
+ max_tokens !== undefined && max_tokens !== null && max_tokens !== 0 ? max_tokens : -1;
+
+ if (dynatemp_range !== undefined) requestBody.dynatemp_range = dynatemp_range;
+ if (dynatemp_exponent !== undefined) requestBody.dynatemp_exponent = dynatemp_exponent;
+ if (top_k !== undefined) requestBody.top_k = top_k;
+ if (top_p !== undefined) requestBody.top_p = top_p;
+ if (min_p !== undefined) requestBody.min_p = min_p;
+ if (xtc_probability !== undefined) requestBody.xtc_probability = xtc_probability;
+ if (xtc_threshold !== undefined) requestBody.xtc_threshold = xtc_threshold;
+ if (typ_p !== undefined) requestBody.typ_p = typ_p;
+
+ if (repeat_last_n !== undefined) requestBody.repeat_last_n = repeat_last_n;
+ if (repeat_penalty !== undefined) requestBody.repeat_penalty = repeat_penalty;
+ if (presence_penalty !== undefined) requestBody.presence_penalty = presence_penalty;
+ if (frequency_penalty !== undefined) requestBody.frequency_penalty = frequency_penalty;
+ if (dry_multiplier !== undefined) requestBody.dry_multiplier = dry_multiplier;
+ if (dry_base !== undefined) requestBody.dry_base = dry_base;
+ if (dry_allowed_length !== undefined) requestBody.dry_allowed_length = dry_allowed_length;
+ if (dry_penalty_last_n !== undefined) requestBody.dry_penalty_last_n = dry_penalty_last_n;
+
+ if (samplers !== undefined) {
+ requestBody.samplers =
+ typeof samplers === 'string'
+ ? samplers.split(';').filter((s: string) => s.trim())
+ : samplers;
+ }
+
+ if (timings_per_token !== undefined) requestBody.timings_per_token = timings_per_token;
+
+ if (custom) {
+ try {
+ const customParams = typeof custom === 'string' ? JSON.parse(custom) : custom;
+ Object.assign(requestBody, customParams);
+ } catch (error) {
+ console.warn('Failed to parse custom parameters:', error);
+ }
+ }
+
+ try {
+ const currentConfig = config();
+ const apiKey = currentConfig.apiKey?.toString().trim();
+
+ const response = await fetch(`/v1/chat/completions`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {})
+ },
+ body: JSON.stringify(requestBody),
+ signal: this.abortController.signal
+ });
+
+ if (!response.ok) {
+ // Use the new parseErrorResponse method to handle structured errors
+ const error = await this.parseErrorResponse(response);
+ if (onError) {
+ onError(error);
+ }
+ throw error;
+ }
+
+ if (stream) {
+ return this.handleStreamResponse(
+ response,
+ onChunk,
+ onComplete,
+ onError,
+ options.onReasoningChunk
+ );
+ } else {
+ return this.handleNonStreamResponse(response, onComplete, onError);
+ }
+ } catch (error) {
+ if (error instanceof Error && error.name === 'AbortError') {
+ console.log('Chat completion request was aborted');
+ return;
+ }
+
+ let userFriendlyError: Error;
+
+ if (error instanceof Error) {
+ if (error.name === 'TypeError' && error.message.includes('fetch')) {
+ userFriendlyError = new Error(
+ 'Unable to connect to server - please check if the server is running'
+ );
+ } else if (error.message.includes('ECONNREFUSED')) {
+ userFriendlyError = new Error('Connection refused - server may be offline');
+ } else if (error.message.includes('ETIMEDOUT')) {
+ userFriendlyError = new Error('Request timeout - server may be overloaded');
+ } else {
+ userFriendlyError = error;
+ }
+ } else {
+ userFriendlyError = new Error('Unknown error occurred while sending message');
+ }
+
+ console.error('Error in sendMessage:', error);
+ if (onError) {
+ onError(userFriendlyError);
+ }
+ throw userFriendlyError;
+ }
+ }
+
+ /**
+ * Handles streaming response from the chat completion API.
+ * Processes server-sent events and extracts content chunks from the stream.
+ *
+ * @param response - The fetch Response object containing the streaming data
+ * @param onChunk - Optional callback invoked for each content chunk received
+ * @param onComplete - Optional callback invoked when the stream is complete with full response
+ * @param onError - Optional callback invoked if an error occurs during streaming
+ * @param onReasoningChunk - Optional callback invoked for each reasoning content chunk
+ * @returns {Promise<void>} Promise that resolves when streaming is complete
+ * @throws {Error} if the stream cannot be read or parsed
+ */
+ private async handleStreamResponse(
+ response: Response,
+ onChunk?: (chunk: string) => void,
+ onComplete?: (
+ response: string,
+ reasoningContent?: string,
+ timings?: ChatMessageTimings
+ ) => void,
+ onError?: (error: Error) => void,
+ onReasoningChunk?: (chunk: string) => void
+ ): Promise<void> {
+ const reader = response.body?.getReader();
+
+ if (!reader) {
+ throw new Error('No response body');
+ }
+
+ const decoder = new TextDecoder();
+ let fullResponse = '';
+ let fullReasoningContent = '';
+ let regularContent = '';
+ let insideThinkTag = false;
+ let hasReceivedData = false;
+ let lastTimings: ChatMessageTimings | undefined;
+
+ try {
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ const chunk = decoder.decode(value, { stream: true });
+ const lines = chunk.split('\n');
+
+ for (const line of lines) {
+ if (line.startsWith('data: ')) {
+ const data = line.slice(6);
+ if (data === '[DONE]') {
+ if (!hasReceivedData && fullResponse.length === 0) {
+ const contextError = new Error(
+ 'The request exceeds the available context size. Try increasing the context size or enable context shift.'
+ );
+ contextError.name = 'ContextError';
+ onError?.(contextError);
+ return;
+ }
+
+ onComplete?.(regularContent, fullReasoningContent || undefined, lastTimings);
+
+ return;
+ }
+
+ try {
+ const parsed: ApiChatCompletionStreamChunk = JSON.parse(data);
+
+ const content = parsed.choices[0]?.delta?.content;
+ const reasoningContent = parsed.choices[0]?.delta?.reasoning_content;
+ const timings = parsed.timings;
+ const promptProgress = parsed.prompt_progress;
+
+ if (timings || promptProgress) {
+ this.updateProcessingState(timings, promptProgress);
+
+ // Store the latest timing data
+ if (timings) {
+ lastTimings = timings;
+ }
+ }
+
+ if (content) {
+ hasReceivedData = true;
+ fullResponse += content;
+
+ // Track the regular content before processing this chunk
+ const regularContentBefore = regularContent;
+
+ // Process content character by character to handle think tags
+ insideThinkTag = this.processContentForThinkTags(
+ content,
+ insideThinkTag,
+ () => {
+ // Think content is ignored - we don't include it in API requests
+ },
+ (regularChunk) => {
+ regularContent += regularChunk;
+ }
+ );
+
+ const newRegularContent = regularContent.slice(regularContentBefore.length);
+ if (newRegularContent) {
+ onChunk?.(newRegularContent);
+ }
+ }
+
+ if (reasoningContent) {
+ hasReceivedData = true;
+ fullReasoningContent += reasoningContent;
+ onReasoningChunk?.(reasoningContent);
+ }
+ } catch (e) {
+ console.error('Error parsing JSON chunk:', e);
+ }
+ }
+ }
+ }
+
+ if (!hasReceivedData && fullResponse.length === 0) {
+ const contextError = new Error(
+ 'The request exceeds the available context size. Try increasing the context size or enable context shift.'
+ );
+ contextError.name = 'ContextError';
+ onError?.(contextError);
+ return;
+ }
+ } catch (error) {
+ const err = error instanceof Error ? error : new Error('Stream error');
+
+ onError?.(err);
+
+ throw err;
+ } finally {
+ reader.releaseLock();
+ }
+ }
+
+ /**
+ * Handles non-streaming response from the chat completion API.
+ * Parses the JSON response and extracts the generated content.
+ *
+ * @param response - The fetch Response object containing the JSON data
+ * @param onComplete - Optional callback invoked when response is successfully parsed
+ * @param onError - Optional callback invoked if an error occurs during parsing
+ * @returns {Promise<string>} Promise that resolves to the generated content string
+ * @throws {Error} if the response cannot be parsed or is malformed
+ */
+ private async handleNonStreamResponse(
+ response: Response,
+ onComplete?: (
+ response: string,
+ reasoningContent?: string,
+ timings?: ChatMessageTimings
+ ) => void,
+ onError?: (error: Error) => void
+ ): Promise<string> {
+ try {
+ const responseText = await response.text();
+
+ if (!responseText.trim()) {
+ const contextError = new Error(
+ 'The request exceeds the available context size. Try increasing the context size or enable context shift.'
+ );
+ contextError.name = 'ContextError';
+ onError?.(contextError);
+ throw contextError;
+ }
+
+ const data: ApiChatCompletionResponse = JSON.parse(responseText);
+ const content = data.choices[0]?.message?.content || '';
+ const reasoningContent = data.choices[0]?.message?.reasoning_content;
+
+ if (reasoningContent) {
+ console.log('Full reasoning content:', reasoningContent);
+ }
+
+ if (!content.trim()) {
+ const contextError = new Error(
+ 'The request exceeds the available context size. Try increasing the context size or enable context shift.'
+ );
+ contextError.name = 'ContextError';
+ onError?.(contextError);
+ throw contextError;
+ }
+
+ onComplete?.(content, reasoningContent);
+
+ return content;
+ } catch (error) {
+ if (error instanceof Error && error.name === 'ContextError') {
+ throw error;
+ }
+
+ const err = error instanceof Error ? error : new Error('Parse error');
+
+ onError?.(err);
+
+ throw err;
+ }
+ }
+
+ /**
+ * Converts a database message with attachments to API chat message format.
+ * Processes various attachment types (images, text files, PDFs) and formats them
+ * as content parts suitable for the chat completion API.
+ *
+ * @param message - Database message object with optional extra attachments
+ * @param message.content - The text content of the message
+ * @param message.role - The role of the message sender (user, assistant, system)
+ * @param message.extra - Optional array of message attachments (images, files, etc.)
+ * @returns {ApiChatMessageData} object formatted for the chat completion API
+ * @static
+ */
+ static convertMessageToChatServiceData(
+ message: DatabaseMessage & { extra?: DatabaseMessageExtra[] }
+ ): ApiChatMessageData {
+ if (!message.extra || message.extra.length === 0) {
+ return {
+ role: message.role as 'user' | 'assistant' | 'system',
+ content: message.content
+ };
+ }
+
+ const contentParts: ApiChatMessageContentPart[] = [];
+
+ if (message.content) {
+ contentParts.push({
+ type: 'text',
+ text: message.content
+ });
+ }
+
+ const imageFiles = message.extra.filter(
+ (extra: DatabaseMessageExtra): extra is DatabaseMessageExtraImageFile =>
+ extra.type === 'imageFile'
+ );
+
+ for (const image of imageFiles) {
+ contentParts.push({
+ type: 'image_url',
+ image_url: { url: image.base64Url }
+ });
+ }
+
+ const textFiles = message.extra.filter(
+ (extra: DatabaseMessageExtra): extra is DatabaseMessageExtraTextFile =>
+ extra.type === 'textFile'
+ );
+
+ for (const textFile of textFiles) {
+ contentParts.push({
+ type: 'text',
+ text: `\n\n--- File: ${textFile.name} ---\n${textFile.content}`
+ });
+ }
+
+ const audioFiles = message.extra.filter(
+ (extra: DatabaseMessageExtra): extra is DatabaseMessageExtraAudioFile =>
+ extra.type === 'audioFile'
+ );
+
+ for (const audio of audioFiles) {
+ contentParts.push({
+ type: 'input_audio',
+ input_audio: {
+ data: audio.base64Data,
+ format: audio.mimeType.includes('wav') ? 'wav' : 'mp3'
+ }
+ });
+ }
+
+ const pdfFiles = message.extra.filter(
+ (extra: DatabaseMessageExtra): extra is DatabaseMessageExtraPdfFile =>
+ extra.type === 'pdfFile'
+ );
+
+ for (const pdfFile of pdfFiles) {
+ if (pdfFile.processedAsImages && pdfFile.images) {
+ for (let i = 0; i < pdfFile.images.length; i++) {
+ contentParts.push({
+ type: 'image_url',
+ image_url: { url: pdfFile.images[i] }
+ });
+ }
+ } else {
+ contentParts.push({
+ type: 'text',
+ text: `\n\n--- PDF File: ${pdfFile.name} ---\n${pdfFile.content}`
+ });
+ }
+ }
+
+ return {
+ role: message.role as 'user' | 'assistant' | 'system',
+ content: contentParts
+ };
+ }
+
+ /**
+ * Get server properties - static method for API compatibility
+ */
+ static async getServerProps(): Promise<ApiLlamaCppServerProps> {
+ try {
+ const currentConfig = config();
+ const apiKey = currentConfig.apiKey?.toString().trim();
+
+ const response = await fetch(`/props`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {})
+ }
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to fetch server props: ${response.status}`);
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error('Error fetching server props:', error);
+ throw error;
+ }
+ }
+
+ /**
+ * Processes content to separate thinking tags from regular content.
+ * Parses <think> and </think> tags to route content to appropriate handlers.
+ *
+ * @param content - The content string to process
+ * @param currentInsideThinkTag - Current state of whether we're inside a think tag
+ * @param addThinkContent - Callback to handle content inside think tags
+ * @param addRegularContent - Callback to handle regular content outside think tags
+ * @returns Boolean indicating if we're still inside a think tag after processing
+ * @private
+ */
+ private processContentForThinkTags(
+ content: string,
+ currentInsideThinkTag: boolean,
+ addThinkContent: (chunk: string) => void,
+ addRegularContent: (chunk: string) => void
+ ): boolean {
+ let i = 0;
+ let insideThinkTag = currentInsideThinkTag;
+
+ while (i < content.length) {
+ if (!insideThinkTag && content.substring(i, i + 7) === '<think>') {
+ insideThinkTag = true;
+ i += 7; // Skip the <think> tag
+ continue;
+ }
+
+ if (insideThinkTag && content.substring(i, i + 8) === '</think>') {
+ insideThinkTag = false;
+ i += 8; // Skip the </think> tag
+ continue;
+ }
+
+ if (insideThinkTag) {
+ addThinkContent(content[i]);
+ } else {
+ addRegularContent(content[i]);
+ }
+
+ i++;
+ }
+
+ return insideThinkTag;
+ }
+
+ /**
+ * Aborts any ongoing chat completion request.
+ * Cancels the current request and cleans up the abort controller.
+ *
+ * @public
+ */
+ public abort(): void {
+ if (this.abortController) {
+ this.abortController.abort();
+ this.abortController = null;
+ }
+ }
+
+ /**
+ * Injects a system message at the beginning of the conversation if configured in settings.
+ * Checks for existing system messages to avoid duplication and retrieves the system message
+ * from the current configuration settings.
+ *
+ * @param messages - Array of chat messages to process
+ * @returns Array of messages with system message injected at the beginning if configured
+ * @private
+ */
+ private injectSystemMessage(messages: ApiChatMessageData[]): ApiChatMessageData[] {
+ const currentConfig = config();
+ const systemMessage = currentConfig.systemMessage?.toString().trim();
+
+ if (!systemMessage) {
+ return messages;
+ }
+
+ if (messages.length > 0 && messages[0].role === 'system') {
+ if (messages[0].content !== systemMessage) {
+ const updatedMessages = [...messages];
+ updatedMessages[0] = {
+ role: 'system',
+ content: systemMessage
+ };
+ return updatedMessages;
+ }
+
+ return messages;
+ }
+
+ const systemMsg: ApiChatMessageData = {
+ role: 'system',
+ content: systemMessage
+ };
+
+ return [systemMsg, ...messages];
+ }
+
+ /**
+ * Parses error response and creates appropriate error with context information
+ * @param response - HTTP response object
+ * @returns Promise<Error> - Parsed error with context info if available
+ */
+ private async parseErrorResponse(response: Response): Promise<Error> {
+ try {
+ const errorText = await response.text();
+ const errorData: ApiErrorResponse = JSON.parse(errorText);
+
+ if (errorData.error?.type === 'exceed_context_size_error') {
+ const contextError = errorData.error as ApiContextSizeError;
+ const error = new Error(contextError.message);
+ error.name = 'ContextError';
+ // Attach structured context information
+ (
+ error as Error & {
+ contextInfo?: { promptTokens: number; maxContext: number; estimatedTokens: number };
+ }
+ ).contextInfo = {
+ promptTokens: contextError.n_prompt_tokens,
+ maxContext: contextError.n_ctx,
+ estimatedTokens: contextError.n_prompt_tokens
+ };
+ return error;
+ }
+
+ // Fallback for other error types
+ const message = errorData.error?.message || 'Unknown server error';
+ return new Error(message);
+ } catch {
+ // If we can't parse the error response, return a generic error
+ return new Error(`Server error (${response.status}): ${response.statusText}`);
+ }
+ }
+
+ /**
+ * Updates the processing state with timing information from the server response
+ * @param timings - Timing data from the API response
+ * @param promptProgress - Progress data from the API response
+ */
+ private updateProcessingState(
+ timings?: ChatMessageTimings,
+ promptProgress?: ChatMessagePromptProgress
+ ): void {
+ // Calculate tokens per second from timing data
+ const tokensPerSecond =
+ timings?.predicted_ms && timings?.predicted_n
+ ? (timings.predicted_n / timings.predicted_ms) * 1000
+ : 0;
+
+ // Update slots service with timing data (async but don't wait)
+ slotsService
+ .updateFromTimingData({
+ prompt_n: timings?.prompt_n || 0,
+ predicted_n: timings?.predicted_n || 0,
+ predicted_per_second: tokensPerSecond,
+ cache_n: timings?.cache_n || 0,
+ prompt_progress: promptProgress
+ })
+ .catch((error) => {
+ console.warn('Failed to update processing state:', error);
+ });
+ }
+}
+
+export const chatService = new ChatService();
--- /dev/null
+import { slotsService } from './slots';
+
+export interface ContextCheckResult {
+ wouldExceed: boolean;
+ currentUsage: number;
+ maxContext: number;
+ availableTokens: number;
+ reservedTokens: number;
+}
+
+/**
+ * ContextService - Context window management and limit checking
+ *
+ * This service provides context window monitoring and limit checking using real-time
+ * server data from the slots service. It helps prevent context overflow by tracking
+ * current usage and calculating available space for new content.
+ *
+ * **Architecture & Relationships:**
+ * - **ContextService** (this class): Context limit monitoring
+ * - Uses SlotsService for real-time context usage data
+ * - Calculates available tokens with configurable reserves
+ * - Provides context limit checking and error messaging
+ * - Helps prevent context window overflow
+ *
+ * - **SlotsService**: Provides current context usage from server slots
+ * - **ChatStore**: Uses context checking before sending messages
+ * - **UI Components**: Display context usage warnings and limits
+ *
+ * **Key Features:**
+ * - **Real-time Context Checking**: Uses live server data for accuracy
+ * - **Token Reservation**: Reserves tokens for response generation
+ * - **Limit Detection**: Prevents context window overflow
+ * - **Usage Reporting**: Detailed context usage statistics
+ * - **Error Messaging**: User-friendly context limit messages
+ * - **Configurable Reserves**: Adjustable token reservation for responses
+ *
+ * **Context Management:**
+ * - Monitors current context usage from active slots
+ * - Calculates available space considering reserved tokens
+ * - Provides early warning before context limits are reached
+ * - Helps optimize conversation length and content
+ */
+export class ContextService {
+ private reserveTokens: number;
+
+ constructor(reserveTokens = 512) {
+ this.reserveTokens = reserveTokens;
+ }
+
+ /**
+ * Checks if the context limit would be exceeded
+ *
+ * @returns {Promise<ContextCheckResult | null>} Promise that resolves to the context check result or null if an error occurs
+ */
+ async checkContextLimit(): Promise<ContextCheckResult | null> {
+ try {
+ const currentState = await slotsService.getCurrentState();
+
+ if (!currentState) {
+ return null;
+ }
+
+ const maxContext = currentState.contextTotal;
+ const currentUsage = currentState.contextUsed;
+ const availableTokens = maxContext - currentUsage - this.reserveTokens;
+ const wouldExceed = availableTokens <= 0;
+
+ return {
+ wouldExceed,
+ currentUsage,
+ maxContext,
+ availableTokens: Math.max(0, availableTokens),
+ reservedTokens: this.reserveTokens
+ };
+ } catch (error) {
+ console.warn('Error checking context limit:', error);
+ return null;
+ }
+ }
+
+ /**
+ * Returns a formatted error message for context limit exceeded
+ *
+ * @param {ContextCheckResult} result - Context check result
+ * @returns {string} Formatted error message
+ */
+ getContextErrorMessage(result: ContextCheckResult): string {
+ const usagePercent = Math.round((result.currentUsage / result.maxContext) * 100);
+ return `Context window is nearly full. Current usage: ${result.currentUsage.toLocaleString()}/${result.maxContext.toLocaleString()} tokens (${usagePercent}%). Available space: ${result.availableTokens.toLocaleString()} tokens (${result.reservedTokens} reserved for response).`;
+ }
+
+ /**
+ * Sets the number of tokens to reserve for response generation
+ *
+ * @param {number} tokens - Number of tokens to reserve
+ */
+ setReserveTokens(tokens: number): void {
+ this.reserveTokens = tokens;
+ }
+}
+
+export const contextService = new ContextService();
--- /dev/null
+export { chatService } from './chat';
+export { contextService } from './context';
+export { slotsService } from './slots';
--- /dev/null
+import { config } from '$lib/stores/settings.svelte';
+
+/**
+ * SlotsService - Real-time processing state monitoring and token rate calculation
+ *
+ * This service provides real-time information about generation progress, token rates,
+ * and context usage based on timing data from ChatService streaming responses.
+ * It manages streaming session tracking and provides accurate processing state updates.
+ *
+ * **Architecture & Relationships:**
+ * - **SlotsService** (this class): Processing state monitoring
+ * - Receives timing data from ChatService streaming responses
+ * - Calculates token generation rates and context usage
+ * - Manages streaming session lifecycle
+ * - Provides real-time updates to UI components
+ *
+ * - **ChatService**: Provides timing data from `/chat/completions` streaming
+ * - **UI Components**: Subscribe to processing state for progress indicators
+ *
+ * **Key Features:**
+ * - **Real-time Monitoring**: Live processing state during generation
+ * - **Token Rate Calculation**: Accurate tokens/second from timing data
+ * - **Context Tracking**: Current context usage and remaining capacity
+ * - **Streaming Lifecycle**: Start/stop tracking for streaming sessions
+ * - **Timing Data Processing**: Converts streaming timing data to structured state
+ * - **Error Handling**: Graceful handling when timing data is unavailable
+ *
+ * **Processing States:**
+ * - `idle`: No active processing
+ * - `generating`: Actively generating tokens
+ *
+ * **Token Rate Calculation:**
+ * Uses timing data from `/chat/completions` streaming response for accurate
+ * real-time token generation rate measurement.
+ */
+export class SlotsService {
+ private callbacks: Set<(state: ApiProcessingState | null) => void> = new Set();
+ private isStreamingActive: boolean = false;
+ private lastKnownState: ApiProcessingState | null = null;
+
+ /**
+ * Start streaming session tracking
+ */
+ startStreaming(): void {
+ this.isStreamingActive = true;
+ }
+
+ /**
+ * Stop streaming session tracking
+ */
+ stopStreaming(): void {
+ this.isStreamingActive = false;
+ }
+
+ /**
+ * Clear the current processing state
+ * Used when switching to a conversation without timing data
+ */
+ clearState(): void {
+ this.lastKnownState = null;
+
+ for (const callback of this.callbacks) {
+ try {
+ callback(null);
+ } catch (error) {
+ console.error('Error in clearState callback:', error);
+ }
+ }
+ }
+
+ /**
+ * Check if currently in a streaming session
+ */
+ isStreaming(): boolean {
+ return this.isStreamingActive;
+ }
+
+ /**
+ * @deprecated Polling is no longer used - timing data comes from ChatService streaming response
+ * This method logs a warning if called to help identify outdated usage
+ */
+ fetchAndNotify(): void {
+ console.warn(
+ 'SlotsService.fetchAndNotify() is deprecated - use timing data from ChatService instead'
+ );
+ }
+
+ subscribe(callback: (state: ApiProcessingState | null) => void): () => void {
+ this.callbacks.add(callback);
+
+ if (this.lastKnownState) {
+ callback(this.lastKnownState);
+ }
+
+ return () => {
+ this.callbacks.delete(callback);
+ };
+ }
+
+ /**
+ * Updates processing state with timing data from ChatService streaming response
+ */
+ async updateFromTimingData(timingData: {
+ prompt_n: number;
+ predicted_n: number;
+ predicted_per_second: number;
+ cache_n: number;
+ prompt_progress?: ChatMessagePromptProgress;
+ }): Promise<void> {
+ const processingState = await this.parseCompletionTimingData(timingData);
+
+ // Only update if we successfully parsed the state
+ if (processingState === null) {
+ console.warn('Failed to parse timing data - skipping update');
+ return;
+ }
+
+ this.lastKnownState = processingState;
+
+ for (const callback of this.callbacks) {
+ try {
+ callback(processingState);
+ } catch (error) {
+ console.error('Error in timing callback:', error);
+ }
+ }
+ }
+
+ /**
+ * Gets context total from last known slots data or fetches from server
+ */
+ private async getContextTotal(): Promise<number | null> {
+ if (this.lastKnownState && this.lastKnownState.contextTotal > 0) {
+ return this.lastKnownState.contextTotal;
+ }
+
+ try {
+ const currentConfig = config();
+ const apiKey = currentConfig.apiKey?.toString().trim();
+
+ const response = await fetch('/slots', {
+ headers: {
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {})
+ }
+ });
+ if (response.ok) {
+ const slotsData = await response.json();
+ if (Array.isArray(slotsData) && slotsData.length > 0) {
+ const slot = slotsData[0];
+ if (slot.n_ctx && slot.n_ctx > 0) {
+ return slot.n_ctx;
+ }
+ }
+ }
+ } catch (error) {
+ console.warn('Failed to fetch context total from /slots:', error);
+ }
+
+ return 4096;
+ }
+
+ private async parseCompletionTimingData(
+ timingData: Record<string, unknown>
+ ): Promise<ApiProcessingState | null> {
+ const promptTokens = (timingData.prompt_n as number) || 0;
+ 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;
+
+ const contextTotal = await this.getContextTotal();
+
+ if (contextTotal === null) {
+ console.warn('No context total available - cannot calculate processing state');
+ return null;
+ }
+
+ const currentConfig = config();
+ const outputTokensMax = currentConfig.max_tokens || -1;
+
+ const contextUsed = promptTokens + cacheTokens + predictedTokens;
+ const outputTokensUsed = predictedTokens;
+
+ const progressPercent = promptProgress
+ ? Math.round((promptProgress.processed / promptProgress.total) * 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,
+ promptTokens,
+ cacheTokens
+ };
+ }
+
+ /**
+ * Get current processing state
+ * Returns the last known state from timing data, or null if no data available
+ */
+ async getCurrentState(): Promise<ApiProcessingState | null> {
+ if (this.lastKnownState) {
+ return this.lastKnownState;
+ }
+ try {
+ // Import dynamically to avoid circular dependency
+ const { chatStore } = await import('$lib/stores/chat.svelte');
+ const messages = chatStore.activeMessages;
+
+ for (let i = messages.length - 1; i >= 0; i--) {
+ const message = messages[i];
+ if (message.role === 'assistant' && message.timings) {
+ const restoredState = await this.parseCompletionTimingData({
+ prompt_n: message.timings.prompt_n || 0,
+ 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.lastKnownState = restoredState;
+ return restoredState;
+ }
+ }
+ }
+ } catch (error) {
+ console.warn('Failed to restore timing data from messages:', error);
+ }
+
+ return null;
+ }
+}
+
+export const slotsService = new SlotsService();
--- /dev/null
+import { DatabaseStore } from '$lib/stores/database';
+import { chatService, slotsService } from '$lib/services';
+import { serverStore } from '$lib/stores/server.svelte';
+import { config } from '$lib/stores/settings.svelte';
+import { filterByLeafNodeId, findLeafNode, findDescendantMessages } from '$lib/utils/branching';
+import { browser } from '$app/environment';
+import { goto } from '$app/navigation';
+import { extractPartialThinking } from '$lib/utils/thinking';
+
+/**
+ * ChatStore - Central state management for chat conversations and AI interactions
+ *
+ * This store manages the complete chat experience including:
+ * - Conversation lifecycle (create, load, delete, update)
+ * - Message management with branching support for conversation trees
+ * - Real-time AI response streaming with reasoning content support
+ * - File attachment handling and processing
+ * - Context error management and recovery
+ * - Database persistence through DatabaseStore integration
+ *
+ * **Architecture & Relationships:**
+ * - **ChatService**: Handles low-level API communication with AI models
+ * - ChatStore orchestrates ChatService for streaming responses
+ * - ChatService provides abort capabilities and error handling
+ * - ChatStore manages the UI state while ChatService handles network layer
+ *
+ * - **DatabaseStore**: Provides persistent storage for conversations and messages
+ * - ChatStore uses DatabaseStore for all CRUD operations
+ * - Maintains referential integrity for conversation trees
+ * - Handles message branching and parent-child relationships
+ *
+ * - **SlotsService**: Monitors server resource usage during AI generation
+ * - ChatStore coordinates slots polling during streaming
+ * - Provides real-time feedback on server capacity
+ *
+ * **Key Features:**
+ * - Reactive state management using Svelte 5 runes ($state)
+ * - Conversation branching for exploring different response paths
+ * - Streaming AI responses with real-time content updates
+ * - File attachment support (images, PDFs, text files, audio)
+ * - Context window management with error recovery
+ * - Partial response saving when generation is interrupted
+ * - Message editing with automatic response regeneration
+ */
+class ChatStore {
+ activeConversation = $state<DatabaseConversation | null>(null);
+ activeMessages = $state<DatabaseMessage[]>([]);
+ conversations = $state<DatabaseConversation[]>([]);
+ currentResponse = $state('');
+ isInitialized = $state(false);
+ isLoading = $state(false);
+ maxContextError = $state<{ message: string; estimatedTokens: number; maxContext: number } | null>(
+ null
+ );
+ titleUpdateConfirmationCallback?: (currentTitle: string, newTitle: string) => Promise<boolean>;
+
+ constructor() {
+ if (browser) {
+ this.initialize();
+ }
+ }
+
+ /**
+ * Initializes the chat store by loading conversations from the database
+ * Sets up the initial state and loads existing conversations
+ */
+ async initialize(): Promise<void> {
+ try {
+ await this.loadConversations();
+
+ this.maxContextError = null;
+
+ this.isInitialized = true;
+ } catch (error) {
+ console.error('Failed to initialize chat store:', error);
+ }
+ }
+
+ /**
+ * Loads all conversations from the database
+ * Refreshes the conversations list from persistent storage
+ */
+ async loadConversations(): Promise<void> {
+ this.conversations = await DatabaseStore.getAllConversations();
+ }
+
+ /**
+ * Creates a new conversation and navigates to it
+ * @param name - Optional name for the conversation, defaults to timestamped name
+ * @returns The ID of the created conversation
+ */
+ async createConversation(name?: string): Promise<string> {
+ const conversationName = name || `Chat ${new Date().toLocaleString()}`;
+ const conversation = await DatabaseStore.createConversation(conversationName);
+
+ this.conversations.unshift(conversation);
+
+ this.activeConversation = conversation;
+ this.activeMessages = [];
+
+ this.maxContextError = null;
+
+ await goto(`/chat/${conversation.id}`);
+
+ return conversation.id;
+ }
+
+ /**
+ * Loads a specific conversation and its messages
+ * @param convId - The conversation ID to load
+ * @returns True if conversation was loaded successfully, false otherwise
+ */
+ async loadConversation(convId: string): Promise<boolean> {
+ try {
+ const conversation = await DatabaseStore.getConversation(convId);
+
+ if (!conversation) {
+ return false;
+ }
+
+ this.activeConversation = conversation;
+
+ if (conversation.currNode) {
+ const allMessages = await DatabaseStore.getConversationMessages(convId);
+ this.activeMessages = filterByLeafNodeId(
+ allMessages,
+ conversation.currNode,
+ false
+ ) as DatabaseMessage[];
+ } else {
+ // Load all messages for conversations without currNode (backward compatibility)
+ this.activeMessages = await DatabaseStore.getConversationMessages(convId);
+ }
+
+ this.maxContextError = null;
+
+ return true;
+ } catch (error) {
+ console.error('Failed to load conversation:', error);
+
+ return false;
+ }
+ }
+
+ /**
+ * Adds a new message to the active conversation
+ * @param role - The role of the message sender (user/assistant)
+ * @param content - The message content
+ * @param type - The message type, defaults to 'text'
+ * @param parent - Parent message ID, defaults to '-1' for auto-detection
+ * @param extras - Optional extra data (files, attachments, etc.)
+ * @returns The created message or null if failed
+ */
+ async addMessage(
+ role: ChatRole,
+ content: string,
+ type: ChatMessageType = 'text',
+ parent: string = '-1',
+ extras?: DatabaseMessageExtra[]
+ ): Promise<DatabaseMessage | null> {
+ if (!this.activeConversation) {
+ console.error('No active conversation when trying to add message');
+ return null;
+ }
+
+ try {
+ let parentId: string | null = null;
+
+ if (parent === '-1') {
+ if (this.activeMessages.length > 0) {
+ parentId = this.activeMessages[this.activeMessages.length - 1].id;
+ } else {
+ const allMessages = await DatabaseStore.getConversationMessages(
+ this.activeConversation.id
+ );
+ const rootMessage = allMessages.find((m) => m.parent === null && m.type === 'root');
+
+ if (!rootMessage) {
+ const rootId = await DatabaseStore.createRootMessage(this.activeConversation.id);
+ parentId = rootId;
+ } else {
+ parentId = rootMessage.id;
+ }
+ }
+ } else {
+ parentId = parent;
+ }
+
+ const message = await DatabaseStore.createMessageBranch(
+ {
+ convId: this.activeConversation.id,
+ role,
+ content,
+ type,
+ timestamp: Date.now(),
+ thinking: '',
+ children: [],
+ extra: extras
+ },
+ parentId
+ );
+
+ this.activeMessages.push(message);
+
+ await DatabaseStore.updateCurrentNode(this.activeConversation.id, message.id);
+ this.activeConversation.currNode = message.id;
+
+ this.updateConversationTimestamp();
+
+ return message;
+ } catch (error) {
+ console.error('Failed to add message:', error);
+ return null;
+ }
+ }
+
+ /**
+ * Gets API options from current configuration settings
+ * Converts settings store values to API-compatible format
+ * @returns API options object for chat completion requests
+ */
+ private getApiOptions(): Record<string, unknown> {
+ const currentConfig = config();
+ const apiOptions: Record<string, unknown> = {
+ stream: true,
+ timings_per_token: true
+ };
+
+ if (currentConfig.temperature !== undefined && currentConfig.temperature !== null) {
+ apiOptions.temperature = Number(currentConfig.temperature);
+ }
+ if (currentConfig.max_tokens !== undefined && currentConfig.max_tokens !== null) {
+ apiOptions.max_tokens = Number(currentConfig.max_tokens);
+ }
+ if (currentConfig.dynatemp_range !== undefined && currentConfig.dynatemp_range !== null) {
+ apiOptions.dynatemp_range = Number(currentConfig.dynatemp_range);
+ }
+ if (currentConfig.dynatemp_exponent !== undefined && currentConfig.dynatemp_exponent !== null) {
+ apiOptions.dynatemp_exponent = Number(currentConfig.dynatemp_exponent);
+ }
+ if (currentConfig.top_k !== undefined && currentConfig.top_k !== null) {
+ apiOptions.top_k = Number(currentConfig.top_k);
+ }
+ if (currentConfig.top_p !== undefined && currentConfig.top_p !== null) {
+ apiOptions.top_p = Number(currentConfig.top_p);
+ }
+ if (currentConfig.min_p !== undefined && currentConfig.min_p !== null) {
+ apiOptions.min_p = Number(currentConfig.min_p);
+ }
+ if (currentConfig.xtc_probability !== undefined && currentConfig.xtc_probability !== null) {
+ apiOptions.xtc_probability = Number(currentConfig.xtc_probability);
+ }
+ if (currentConfig.xtc_threshold !== undefined && currentConfig.xtc_threshold !== null) {
+ apiOptions.xtc_threshold = Number(currentConfig.xtc_threshold);
+ }
+ if (currentConfig.typ_p !== undefined && currentConfig.typ_p !== null) {
+ apiOptions.typ_p = Number(currentConfig.typ_p);
+ }
+ if (currentConfig.repeat_last_n !== undefined && currentConfig.repeat_last_n !== null) {
+ apiOptions.repeat_last_n = Number(currentConfig.repeat_last_n);
+ }
+ if (currentConfig.repeat_penalty !== undefined && currentConfig.repeat_penalty !== null) {
+ apiOptions.repeat_penalty = Number(currentConfig.repeat_penalty);
+ }
+ if (currentConfig.presence_penalty !== undefined && currentConfig.presence_penalty !== null) {
+ apiOptions.presence_penalty = Number(currentConfig.presence_penalty);
+ }
+ if (currentConfig.frequency_penalty !== undefined && currentConfig.frequency_penalty !== null) {
+ apiOptions.frequency_penalty = Number(currentConfig.frequency_penalty);
+ }
+ if (currentConfig.dry_multiplier !== undefined && currentConfig.dry_multiplier !== null) {
+ apiOptions.dry_multiplier = Number(currentConfig.dry_multiplier);
+ }
+ if (currentConfig.dry_base !== undefined && currentConfig.dry_base !== null) {
+ apiOptions.dry_base = Number(currentConfig.dry_base);
+ }
+ if (
+ currentConfig.dry_allowed_length !== undefined &&
+ currentConfig.dry_allowed_length !== null
+ ) {
+ apiOptions.dry_allowed_length = Number(currentConfig.dry_allowed_length);
+ }
+ if (
+ currentConfig.dry_penalty_last_n !== undefined &&
+ currentConfig.dry_penalty_last_n !== null
+ ) {
+ apiOptions.dry_penalty_last_n = Number(currentConfig.dry_penalty_last_n);
+ }
+ if (currentConfig.samplers) {
+ apiOptions.samplers = currentConfig.samplers;
+ }
+ if (currentConfig.custom) {
+ apiOptions.custom = currentConfig.custom;
+ }
+
+ return apiOptions;
+ }
+
+ /**
+ * Handles streaming chat completion with the AI model
+ * @param allMessages - All messages in the conversation
+ * @param assistantMessage - The assistant message to stream content into
+ * @param onComplete - Optional callback when streaming completes
+ * @param onError - Optional callback when an error occurs
+ */
+ private async streamChatCompletion(
+ allMessages: DatabaseMessage[],
+ assistantMessage: DatabaseMessage,
+ onComplete?: (content: string) => Promise<void>,
+ onError?: (error: Error) => void
+ ): Promise<void> {
+ let streamedContent = '';
+
+ let streamedReasoningContent = '';
+
+ slotsService.startStreaming();
+
+ await chatService.sendMessage(allMessages, {
+ ...this.getApiOptions(),
+
+ onChunk: (chunk: string) => {
+ streamedContent += chunk;
+ this.currentResponse = streamedContent;
+
+ const partialThinking = extractPartialThinking(streamedContent);
+ const messageIndex = this.findMessageIndex(assistantMessage.id);
+ this.updateMessageAtIndex(messageIndex, {
+ content: partialThinking.remainingContent || streamedContent
+ });
+ },
+
+ onReasoningChunk: (reasoningChunk: string) => {
+ streamedReasoningContent += reasoningChunk;
+ const messageIndex = this.findMessageIndex(assistantMessage.id);
+ this.updateMessageAtIndex(messageIndex, { thinking: streamedReasoningContent });
+ },
+
+ onComplete: async (
+ finalContent?: string,
+ reasoningContent?: string,
+ timings?: ChatMessageTimings
+ ) => {
+ slotsService.stopStreaming();
+
+ await DatabaseStore.updateMessage(assistantMessage.id, {
+ content: finalContent || streamedContent,
+ thinking: reasoningContent || streamedReasoningContent,
+ timings: timings
+ });
+
+ const messageIndex = this.findMessageIndex(assistantMessage.id);
+
+ this.updateMessageAtIndex(messageIndex, {
+ timings: timings
+ });
+
+ await DatabaseStore.updateCurrentNode(this.activeConversation!.id, assistantMessage.id);
+ this.activeConversation!.currNode = assistantMessage.id;
+
+ await this.refreshActiveMessages();
+
+ if (onComplete) {
+ await onComplete(streamedContent);
+ }
+
+ this.isLoading = false;
+ this.currentResponse = '';
+ },
+
+ onError: (error: Error) => {
+ slotsService.stopStreaming();
+
+ if (error.name === 'AbortError' || error instanceof DOMException) {
+ this.isLoading = false;
+ this.currentResponse = '';
+ return;
+ }
+
+ if (error.name === 'ContextError') {
+ console.warn('Context error detected:', error.message);
+ this.isLoading = false;
+ this.currentResponse = '';
+
+ const messageIndex = this.activeMessages.findIndex(
+ (m: DatabaseMessage) => m.id === assistantMessage.id
+ );
+
+ if (messageIndex !== -1) {
+ this.activeMessages.splice(messageIndex, 1);
+ DatabaseStore.deleteMessage(assistantMessage.id).catch(console.error);
+ }
+
+ // Use structured context info from new exceed_context_size_error format if available
+ const contextInfo = (
+ error as Error & {
+ contextInfo?: { promptTokens: number; maxContext: number; estimatedTokens: number };
+ }
+ ).contextInfo;
+ let estimatedTokens = 0;
+ let maxContext = serverStore.serverProps?.default_generation_settings.n_ctx || 8192;
+
+ if (contextInfo) {
+ // Use precise token counts from server response
+ estimatedTokens = contextInfo.promptTokens;
+ maxContext = contextInfo.maxContext;
+ } else {
+ // Fallback to estimation for older error format
+ try {
+ // Rough estimation: ~4 characters per token
+ const messageContent = JSON.stringify(messages);
+ estimatedTokens = Math.ceil(messageContent.length / 4);
+ } catch {
+ estimatedTokens = 0;
+ }
+ }
+
+ this.maxContextError = {
+ message: error.message,
+ estimatedTokens,
+ maxContext
+ };
+
+ if (onError) {
+ onError(error);
+ }
+ return;
+ }
+
+ console.error('Streaming error:', error);
+ this.isLoading = false;
+ this.currentResponse = '';
+
+ const messageIndex = this.activeMessages.findIndex(
+ (m: DatabaseMessage) => m.id === assistantMessage.id
+ );
+
+ if (messageIndex !== -1) {
+ this.activeMessages[messageIndex].content = `Error: ${error.message}`;
+ }
+
+ if (onError) {
+ onError(error);
+ }
+ }
+ });
+ }
+
+ /**
+ * Checks if an error is an abort error (user cancelled operation)
+ * @param error - The error to check
+ * @returns True if the error is an abort error
+ */
+ private isAbortError(error: unknown): boolean {
+ return error instanceof Error && (error.name === 'AbortError' || error instanceof DOMException);
+ }
+
+ /**
+ * Finds the index of a message in the active messages array
+ * @param messageId - The message ID to find
+ * @returns The index of the message, or -1 if not found
+ */
+ private findMessageIndex(messageId: string): number {
+ return this.activeMessages.findIndex((m) => m.id === messageId);
+ }
+
+ /**
+ * Updates a message at a specific index with partial data
+ * @param index - The index of the message to update
+ * @param updates - Partial message data to update
+ */
+ private updateMessageAtIndex(index: number, updates: Partial<DatabaseMessage>): void {
+ if (index !== -1) {
+ Object.assign(this.activeMessages[index], updates);
+ }
+ }
+
+ /**
+ * Creates a new assistant message in the database
+ * @param parentId - Optional parent message ID, defaults to '-1'
+ * @returns The created assistant message or null if failed
+ */
+ private async createAssistantMessage(parentId?: string): Promise<DatabaseMessage | null> {
+ if (!this.activeConversation) return null;
+
+ return await DatabaseStore.createMessageBranch(
+ {
+ convId: this.activeConversation.id,
+ type: 'text',
+ role: 'assistant',
+ content: '',
+ timestamp: Date.now(),
+ thinking: '',
+ children: []
+ },
+ parentId || null
+ );
+ }
+
+ /**
+ * Updates conversation lastModified timestamp and moves it to top of list
+ * Ensures recently active conversations appear first in the sidebar
+ */
+ private updateConversationTimestamp(): void {
+ if (!this.activeConversation) return;
+
+ const chatIndex = this.conversations.findIndex((c) => c.id === this.activeConversation!.id);
+
+ if (chatIndex !== -1) {
+ this.conversations[chatIndex].lastModified = Date.now();
+ const updatedConv = this.conversations.splice(chatIndex, 1)[0];
+ this.conversations.unshift(updatedConv);
+ }
+ }
+
+ /**
+ * Sends a new message and generates AI response
+ * @param content - The message content to send
+ * @param extras - Optional extra data (files, attachments, etc.)
+ */
+ async sendMessage(content: string, extras?: DatabaseMessageExtra[]): Promise<void> {
+ if ((!content.trim() && (!extras || extras.length === 0)) || this.isLoading) return;
+
+ let isNewConversation = false;
+
+ if (!this.activeConversation) {
+ await this.createConversation();
+ isNewConversation = true;
+ }
+
+ if (!this.activeConversation) {
+ console.error('No active conversation available for sending message');
+ return;
+ }
+
+ this.isLoading = true;
+ this.currentResponse = '';
+
+ let userMessage: DatabaseMessage | null = null;
+
+ try {
+ userMessage = await this.addMessage('user', content, 'text', '-1', extras);
+
+ if (!userMessage) {
+ throw new Error('Failed to add user message');
+ }
+
+ // If this is a new conversation, update the title with the first user prompt
+ if (isNewConversation && content) {
+ const title = content.trim();
+ await this.updateConversationName(this.activeConversation.id, title);
+ }
+
+ const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id);
+ const assistantMessage = await this.createAssistantMessage(userMessage.id);
+
+ if (!assistantMessage) {
+ throw new Error('Failed to create assistant message');
+ }
+
+ this.activeMessages.push(assistantMessage);
+ // Don't update currNode until after streaming completes to maintain proper conversation path
+
+ await this.streamChatCompletion(allMessages, assistantMessage, undefined, (error: Error) => {
+ if (error.name === 'ContextError' && userMessage) {
+ const userMessageIndex = this.findMessageIndex(userMessage.id);
+ if (userMessageIndex !== -1) {
+ this.activeMessages.splice(userMessageIndex, 1);
+ DatabaseStore.deleteMessage(userMessage.id).catch(console.error);
+ }
+ }
+ });
+ } catch (error) {
+ if (this.isAbortError(error)) {
+ this.isLoading = false;
+ return;
+ }
+
+ if (error instanceof Error && error.name === 'ContextError' && userMessage) {
+ const userMessageIndex = this.findMessageIndex(userMessage.id);
+ if (userMessageIndex !== -1) {
+ this.activeMessages.splice(userMessageIndex, 1);
+ DatabaseStore.deleteMessage(userMessage.id).catch(console.error);
+ }
+ }
+
+ console.error('Failed to send message:', error);
+ this.isLoading = false;
+ }
+ }
+
+ /**
+ * Stops the current message generation
+ * Aborts ongoing requests and saves partial response if available
+ */
+ stopGeneration(): void {
+ slotsService.stopStreaming();
+ chatService.abort();
+ this.savePartialResponseIfNeeded();
+ this.isLoading = false;
+ this.currentResponse = '';
+ }
+
+ /**
+ * Gracefully stops generation and saves partial response
+ */
+ async gracefulStop(): Promise<void> {
+ if (!this.isLoading) return;
+
+ slotsService.stopStreaming();
+ chatService.abort();
+ await this.savePartialResponseIfNeeded();
+ this.isLoading = false;
+ this.currentResponse = '';
+ }
+
+ /**
+ * Clears the max context error state
+ * Removes any displayed context limit warnings
+ */
+ clearMaxContextError(): void {
+ this.maxContextError = null;
+ }
+
+ /**
+ * Sets the max context error state
+ * @param error - The context error details or null to clear
+ */
+ setMaxContextError(
+ error: { message: string; estimatedTokens: number; maxContext: number } | null
+ ): void {
+ this.maxContextError = error;
+ }
+
+ /**
+ * Saves partial response if generation was interrupted
+ * Preserves user's partial content and timing data when generation is stopped early
+ */
+ private async savePartialResponseIfNeeded(): Promise<void> {
+ if (!this.currentResponse.trim() || !this.activeMessages.length) {
+ return;
+ }
+
+ const lastMessage = this.activeMessages[this.activeMessages.length - 1];
+
+ if (lastMessage && lastMessage.role === 'assistant') {
+ try {
+ const partialThinking = extractPartialThinking(this.currentResponse);
+
+ const updateData: {
+ content: string;
+ thinking?: string;
+ timings?: ChatMessageTimings;
+ } = {
+ content: partialThinking.remainingContent || this.currentResponse
+ };
+
+ if (partialThinking.thinking) {
+ updateData.thinking = partialThinking.thinking;
+ }
+
+ const lastKnownState = await slotsService.getCurrentState();
+
+ if (lastKnownState) {
+ updateData.timings = {
+ prompt_n: lastKnownState.promptTokens || 0,
+ predicted_n: lastKnownState.tokensDecoded || 0,
+ cache_n: lastKnownState.cacheTokens || 0,
+ // We don't have ms data from the state, but we can estimate
+ predicted_ms:
+ lastKnownState.tokensPerSecond && lastKnownState.tokensDecoded
+ ? (lastKnownState.tokensDecoded / lastKnownState.tokensPerSecond) * 1000
+ : undefined
+ };
+ }
+
+ await DatabaseStore.updateMessage(lastMessage.id, updateData);
+
+ lastMessage.content = partialThinking.remainingContent || this.currentResponse;
+ if (updateData.timings) {
+ lastMessage.timings = updateData.timings;
+ }
+ } catch (error) {
+ lastMessage.content = this.currentResponse;
+ console.error('Failed to save partial response:', error);
+ }
+ } else {
+ console.error('Last message is not an assistant message');
+ }
+ }
+
+ /**
+ * Updates a user message and regenerates the assistant response
+ * @param messageId - The ID of the message to update
+ * @param newContent - The new content for the message
+ */
+ async updateMessage(messageId: string, newContent: string): Promise<void> {
+ if (!this.activeConversation) return;
+
+ if (this.isLoading) {
+ this.stopGeneration();
+ }
+
+ try {
+ const messageIndex = this.findMessageIndex(messageId);
+ if (messageIndex === -1) {
+ console.error('Message not found for update');
+ return;
+ }
+
+ const messageToUpdate = this.activeMessages[messageIndex];
+ const originalContent = messageToUpdate.content;
+
+ if (messageToUpdate.role !== 'user') {
+ console.error('Only user messages can be edited');
+ return;
+ }
+
+ const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id);
+ const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
+ const isFirstUserMessage =
+ rootMessage && messageToUpdate.parent === rootMessage.id && messageToUpdate.role === 'user';
+
+ this.updateMessageAtIndex(messageIndex, { content: newContent });
+ await DatabaseStore.updateMessage(messageId, { content: newContent });
+
+ // If this is the first user message, update the conversation title with confirmation if needed
+ if (isFirstUserMessage && newContent.trim()) {
+ await this.updateConversationTitleWithConfirmation(
+ this.activeConversation.id,
+ newContent.trim(),
+ this.titleUpdateConfirmationCallback
+ );
+ }
+
+ const messagesToRemove = this.activeMessages.slice(messageIndex + 1);
+ for (const message of messagesToRemove) {
+ await DatabaseStore.deleteMessage(message.id);
+ }
+
+ this.activeMessages = this.activeMessages.slice(0, messageIndex + 1);
+ this.updateConversationTimestamp();
+
+ this.isLoading = true;
+ this.currentResponse = '';
+
+ try {
+ const assistantMessage = await this.createAssistantMessage();
+ if (!assistantMessage) {
+ throw new Error('Failed to create assistant message');
+ }
+
+ this.activeMessages.push(assistantMessage);
+ await DatabaseStore.updateCurrentNode(this.activeConversation.id, assistantMessage.id);
+ this.activeConversation.currNode = assistantMessage.id;
+
+ await this.streamChatCompletion(
+ this.activeMessages.slice(0, -1),
+ assistantMessage,
+ undefined,
+ () => {
+ const editedMessageIndex = this.findMessageIndex(messageId);
+ this.updateMessageAtIndex(editedMessageIndex, { content: originalContent });
+ }
+ );
+ } catch (regenerateError) {
+ console.error('Failed to regenerate response:', regenerateError);
+ this.isLoading = false;
+
+ const messageIndex = this.findMessageIndex(messageId);
+ this.updateMessageAtIndex(messageIndex, { content: originalContent });
+ }
+ } catch (error) {
+ if (this.isAbortError(error)) {
+ return;
+ }
+
+ console.error('Failed to update message:', error);
+ }
+ }
+
+ /**
+ * Regenerates an assistant message with a new response
+ * @param messageId - The ID of the assistant message to regenerate
+ */
+ async regenerateMessage(messageId: string): Promise<void> {
+ if (!this.activeConversation || this.isLoading) return;
+
+ try {
+ const messageIndex = this.findMessageIndex(messageId);
+ if (messageIndex === -1) {
+ console.error('Message not found for regeneration');
+ return;
+ }
+
+ const messageToRegenerate = this.activeMessages[messageIndex];
+ if (messageToRegenerate.role !== 'assistant') {
+ console.error('Only assistant messages can be regenerated');
+ return;
+ }
+
+ const messagesToRemove = this.activeMessages.slice(messageIndex);
+ for (const message of messagesToRemove) {
+ await DatabaseStore.deleteMessage(message.id);
+ }
+
+ this.activeMessages = this.activeMessages.slice(0, messageIndex);
+ this.updateConversationTimestamp();
+
+ this.isLoading = true;
+ this.currentResponse = '';
+
+ try {
+ const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id);
+ const assistantMessage = await this.createAssistantMessage();
+
+ if (!assistantMessage) {
+ throw new Error('Failed to create assistant message');
+ }
+
+ this.activeMessages.push(assistantMessage);
+ await DatabaseStore.updateCurrentNode(this.activeConversation.id, assistantMessage.id);
+ this.activeConversation.currNode = assistantMessage.id;
+
+ await this.streamChatCompletion(allMessages, assistantMessage);
+ } catch (regenerateError) {
+ console.error('Failed to regenerate response:', regenerateError);
+ this.isLoading = false;
+ }
+ } catch (error) {
+ if (this.isAbortError(error)) return;
+ console.error('Failed to regenerate message:', error);
+ }
+ }
+
+ /**
+ * Updates the name of a conversation
+ * @param convId - The conversation ID to update
+ * @param name - The new name for the conversation
+ */
+ async updateConversationName(convId: string, name: string): Promise<void> {
+ try {
+ await DatabaseStore.updateConversation(convId, { name });
+
+ const convIndex = this.conversations.findIndex((c) => c.id === convId);
+
+ if (convIndex !== -1) {
+ this.conversations[convIndex].name = name;
+ }
+
+ if (this.activeConversation?.id === convId) {
+ this.activeConversation.name = name;
+ }
+ } catch (error) {
+ console.error('Failed to update conversation name:', error);
+ }
+ }
+
+ /**
+ * Sets the callback function for title update confirmations
+ * @param callback - Function to call when confirmation is needed
+ */
+ setTitleUpdateConfirmationCallback(
+ callback: (currentTitle: string, newTitle: string) => Promise<boolean>
+ ): void {
+ this.titleUpdateConfirmationCallback = callback;
+ }
+
+ /**
+ * 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 Promise<boolean> - True if title was updated, false if cancelled
+ */
+ async updateConversationTitleWithConfirmation(
+ convId: string,
+ newTitle: string,
+ onConfirmationNeeded?: (currentTitle: string, newTitle: string) => Promise<boolean>
+ ): Promise<boolean> {
+ try {
+ const currentConfig = config();
+
+ // Only ask for confirmation if the setting is enabled and callback is provided
+ if (currentConfig.askForTitleConfirmation && onConfirmationNeeded) {
+ const conversation = await DatabaseStore.getConversation(convId);
+ if (!conversation) return false;
+
+ const shouldUpdate = await onConfirmationNeeded(conversation.name, newTitle);
+ if (!shouldUpdate) return false;
+ }
+
+ await this.updateConversationName(convId, newTitle);
+ return true;
+ } catch (error) {
+ console.error('Failed to update conversation title with confirmation:', error);
+ return false;
+ }
+ }
+
+ /**
+ * Deletes a conversation and all its messages
+ * @param convId - The conversation ID to delete
+ */
+ async deleteConversation(convId: string): Promise<void> {
+ try {
+ await DatabaseStore.deleteConversation(convId);
+
+ this.conversations = this.conversations.filter((c) => c.id !== convId);
+
+ if (this.activeConversation?.id === convId) {
+ this.activeConversation = null;
+ this.activeMessages = [];
+ await goto('/?new_chat=true');
+ }
+ } catch (error) {
+ console.error('Failed to delete conversation:', error);
+ }
+ }
+
+ /**
+ * Gets information about what messages will be deleted when deleting a specific message
+ * @param messageId - The ID of the message to be deleted
+ * @returns Object with deletion info including count and types of messages
+ */
+ async getDeletionInfo(messageId: string): Promise<{
+ totalCount: number;
+ userMessages: number;
+ assistantMessages: number;
+ messageTypes: string[];
+ }> {
+ if (!this.activeConversation) {
+ return { totalCount: 0, userMessages: 0, assistantMessages: 0, messageTypes: [] };
+ }
+
+ const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id);
+ const descendants = findDescendantMessages(allMessages, messageId);
+ const allToDelete = [messageId, ...descendants];
+
+ const messagesToDelete = allMessages.filter((m) => allToDelete.includes(m.id));
+
+ let userMessages = 0;
+ let assistantMessages = 0;
+ const messageTypes: string[] = [];
+
+ for (const msg of messagesToDelete) {
+ if (msg.role === 'user') {
+ userMessages++;
+ if (!messageTypes.includes('user message')) messageTypes.push('user message');
+ } else if (msg.role === 'assistant') {
+ assistantMessages++;
+ if (!messageTypes.includes('assistant response')) messageTypes.push('assistant response');
+ }
+ }
+
+ return {
+ totalCount: allToDelete.length,
+ userMessages,
+ assistantMessages,
+ messageTypes
+ };
+ }
+
+ /**
+ * Deletes a message and all its descendants, updating conversation path if needed
+ * @param messageId - The ID of the message to delete
+ */
+ async deleteMessage(messageId: string): Promise<void> {
+ try {
+ if (!this.activeConversation) return;
+
+ // Get all messages to find siblings before deletion
+ const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id);
+ const messageToDelete = allMessages.find((m) => m.id === messageId);
+
+ if (!messageToDelete) {
+ console.error('Message to delete not found');
+ return;
+ }
+
+ // Check if the deleted message is in the current conversation path
+ const currentPath = filterByLeafNodeId(
+ allMessages,
+ this.activeConversation.currNode || '',
+ false
+ );
+ const isInCurrentPath = currentPath.some((m) => m.id === messageId);
+
+ // If the deleted message is in the current path, we need to update currNode
+ if (isInCurrentPath && messageToDelete.parent) {
+ // Find all siblings (messages with same parent)
+ const siblings = allMessages.filter(
+ (m) => m.parent === messageToDelete.parent && m.id !== messageId
+ );
+
+ if (siblings.length > 0) {
+ // Find the latest sibling (highest timestamp)
+ const latestSibling = siblings.reduce((latest, sibling) =>
+ sibling.timestamp > latest.timestamp ? sibling : latest
+ );
+
+ // Find the leaf node for this sibling branch to get the complete conversation path
+ const leafNodeId = findLeafNode(allMessages, latestSibling.id);
+
+ // Update conversation to use the leaf node of the latest remaining sibling
+ await DatabaseStore.updateCurrentNode(this.activeConversation.id, leafNodeId);
+ this.activeConversation.currNode = leafNodeId;
+ } else {
+ // No siblings left, navigate to parent if it exists
+ if (messageToDelete.parent) {
+ const parentLeafId = findLeafNode(allMessages, messageToDelete.parent);
+ await DatabaseStore.updateCurrentNode(this.activeConversation.id, parentLeafId);
+ this.activeConversation.currNode = parentLeafId;
+ }
+ }
+ }
+
+ // Use cascading deletion to remove the message and all its descendants
+ await DatabaseStore.deleteMessageCascading(this.activeConversation.id, messageId);
+
+ // Refresh active messages to show the updated branch
+ await this.refreshActiveMessages();
+
+ // Update conversation timestamp
+ this.updateConversationTimestamp();
+ } catch (error) {
+ console.error('Failed to delete message:', error);
+ }
+ }
+
+ /**
+ * Clears the active conversation and resets state
+ * Used when navigating away from chat or starting fresh
+ */
+ clearActiveConversation(): void {
+ this.activeConversation = null;
+ this.activeMessages = [];
+ this.currentResponse = '';
+ this.isLoading = false;
+ this.maxContextError = null;
+ }
+
+ /** Refreshes active messages based on currNode after branch navigation */
+ async refreshActiveMessages(): Promise<void> {
+ if (!this.activeConversation) return;
+
+ const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id);
+ if (allMessages.length === 0) {
+ this.activeMessages = [];
+ return;
+ }
+
+ const leafNodeId =
+ this.activeConversation.currNode ||
+ 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);
+ }
+
+ /**
+ * 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<void> {
+ if (!this.activeConversation) return;
+
+ // Get the current first user message before navigation
+ const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id);
+ const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
+ const currentFirstUserMessage = this.activeMessages.find(
+ (m) => m.role === 'user' && m.parent === rootMessage?.id
+ );
+
+ await DatabaseStore.updateCurrentNode(this.activeConversation.id, siblingId);
+ this.activeConversation.currNode = siblingId;
+ await this.refreshActiveMessages();
+
+ // Only show title dialog if we're navigating between different first user message siblings
+ if (rootMessage && this.activeMessages.length > 0) {
+ // Find the first user message in the new active path
+ const newFirstUserMessage = this.activeMessages.find(
+ (m) => m.role === 'user' && m.parent === rootMessage.id
+ );
+
+ // Only show dialog if:
+ // 1. We have a new first user message
+ // 2. It's different from the previous one (different ID or content)
+ // 3. The new message has content
+ if (
+ newFirstUserMessage &&
+ newFirstUserMessage.content.trim() &&
+ (!currentFirstUserMessage ||
+ newFirstUserMessage.id !== currentFirstUserMessage.id ||
+ newFirstUserMessage.content.trim() !== currentFirstUserMessage.content.trim())
+ ) {
+ await this.updateConversationTitleWithConfirmation(
+ this.activeConversation.id,
+ newFirstUserMessage.content.trim(),
+ this.titleUpdateConfirmationCallback
+ );
+ }
+ }
+ }
+
+ /**
+ * Edits an assistant message with optional branching
+ * @param messageId - The ID of the assistant message to edit
+ * @param newContent - The new content for the message
+ * @param shouldBranch - Whether to create a branch or replace in-place
+ */
+ async editAssistantMessage(
+ messageId: string,
+ newContent: string,
+ shouldBranch: boolean
+ ): Promise<void> {
+ if (!this.activeConversation || this.isLoading) return;
+
+ try {
+ const messageIndex = this.findMessageIndex(messageId);
+
+ if (messageIndex === -1) {
+ console.error('Message not found for editing');
+ return;
+ }
+
+ const messageToEdit = this.activeMessages[messageIndex];
+
+ if (messageToEdit.role !== 'assistant') {
+ console.error('Only assistant messages can be edited with this method');
+ return;
+ }
+
+ if (shouldBranch) {
+ const newMessage = await DatabaseStore.createMessageBranch(
+ {
+ convId: messageToEdit.convId,
+ type: messageToEdit.type,
+ timestamp: Date.now(),
+ role: messageToEdit.role,
+ content: newContent,
+ thinking: messageToEdit.thinking || '',
+ children: []
+ },
+ messageToEdit.parent!
+ );
+
+ await DatabaseStore.updateCurrentNode(this.activeConversation.id, newMessage.id);
+ this.activeConversation.currNode = newMessage.id;
+ } else {
+ await DatabaseStore.updateMessage(messageToEdit.id, {
+ content: newContent,
+ timestamp: Date.now()
+ });
+
+ this.updateMessageAtIndex(messageIndex, {
+ content: newContent,
+ timestamp: Date.now()
+ });
+ }
+
+ this.updateConversationTimestamp();
+ await this.refreshActiveMessages();
+ } catch (error) {
+ console.error('Failed to edit assistant message:', error);
+ }
+ }
+
+ /**
+ * Edits a message by creating a new branch with the edited content
+ * @param messageId - The ID of the message to edit
+ * @param newContent - The new content for the message
+ */
+ async editMessageWithBranching(messageId: string, newContent: string): Promise<void> {
+ if (!this.activeConversation || this.isLoading) return;
+
+ try {
+ const messageIndex = this.findMessageIndex(messageId);
+ if (messageIndex === -1) {
+ console.error('Message not found for editing');
+ return;
+ }
+
+ const messageToEdit = this.activeMessages[messageIndex];
+ if (messageToEdit.role !== 'user') {
+ console.error('Only user messages can be edited');
+ return;
+ }
+
+ // Check if this is the first user message in the conversation
+ // First user message is one that has the root message as its parent
+ const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id);
+ const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
+ const isFirstUserMessage =
+ rootMessage && messageToEdit.parent === rootMessage.id && messageToEdit.role === 'user';
+
+ let parentId = messageToEdit.parent;
+
+ if (parentId === undefined || parentId === null) {
+ const rootMessage = allMessages.find((m) => m.type === 'root' && m.parent === null);
+ if (rootMessage) {
+ parentId = rootMessage.id;
+ } else {
+ console.error('No root message found for editing');
+ return;
+ }
+ }
+
+ const newMessage = await DatabaseStore.createMessageBranch(
+ {
+ convId: messageToEdit.convId,
+ type: messageToEdit.type,
+ timestamp: Date.now(),
+ role: messageToEdit.role,
+ content: newContent,
+ thinking: messageToEdit.thinking || '',
+ children: [],
+ extra: messageToEdit.extra ? JSON.parse(JSON.stringify(messageToEdit.extra)) : undefined
+ },
+ parentId
+ );
+
+ await DatabaseStore.updateCurrentNode(this.activeConversation.id, newMessage.id);
+ this.activeConversation.currNode = newMessage.id;
+ this.updateConversationTimestamp();
+
+ // If this is the first user message, update the conversation title with confirmation if needed
+ if (isFirstUserMessage && newContent.trim()) {
+ await this.updateConversationTitleWithConfirmation(
+ this.activeConversation.id,
+ newContent.trim(),
+ this.titleUpdateConfirmationCallback
+ );
+ }
+
+ await this.refreshActiveMessages();
+
+ if (messageToEdit.role === 'user') {
+ await this.generateResponseForMessage(newMessage.id);
+ }
+ } catch (error) {
+ console.error('Failed to edit message with branching:', error);
+ }
+ }
+
+ /**
+ * Regenerates an assistant message by creating a new branch with a new response
+ * @param messageId - The ID of the assistant message to regenerate
+ */
+ async regenerateMessageWithBranching(messageId: string): Promise<void> {
+ if (!this.activeConversation || this.isLoading) return;
+
+ try {
+ const messageIndex = this.findMessageIndex(messageId);
+ if (messageIndex === -1) {
+ console.error('Message not found for regeneration');
+ return;
+ }
+
+ const messageToRegenerate = this.activeMessages[messageIndex];
+ if (messageToRegenerate.role !== 'assistant') {
+ console.error('Only assistant messages can be regenerated');
+ return;
+ }
+
+ // Find parent message in all conversation messages, not just active path
+ const conversationMessages = await DatabaseStore.getConversationMessages(
+ this.activeConversation.id
+ );
+ const parentMessage = conversationMessages.find((m) => m.id === messageToRegenerate.parent);
+ if (!parentMessage) {
+ console.error('Parent message not found for regeneration');
+ return;
+ }
+
+ this.isLoading = true;
+ this.currentResponse = '';
+
+ const newAssistantMessage = await DatabaseStore.createMessageBranch(
+ {
+ convId: this.activeConversation.id,
+ type: 'text',
+ timestamp: Date.now(),
+ role: 'assistant',
+ content: '',
+ thinking: '',
+ children: []
+ },
+ parentMessage.id
+ );
+
+ await DatabaseStore.updateCurrentNode(this.activeConversation.id, newAssistantMessage.id);
+ this.activeConversation.currNode = newAssistantMessage.id;
+ this.updateConversationTimestamp();
+ await this.refreshActiveMessages();
+
+ const allConversationMessages = await DatabaseStore.getConversationMessages(
+ this.activeConversation.id
+ );
+ const conversationPath = filterByLeafNodeId(
+ allConversationMessages,
+ parentMessage.id,
+ false
+ ) as DatabaseMessage[];
+
+ await this.streamChatCompletion(conversationPath, newAssistantMessage);
+ } catch (error) {
+ if (this.isAbortError(error)) return;
+
+ console.error('Failed to regenerate message with branching:', error);
+ this.isLoading = false;
+ }
+ }
+
+ /**
+ * Generates a new assistant response for a given user message
+ * @param userMessageId - ID of user message to respond to
+ */
+ private async generateResponseForMessage(userMessageId: string): Promise<void> {
+ if (!this.activeConversation) return;
+
+ this.isLoading = true;
+ this.currentResponse = '';
+
+ try {
+ // Get conversation path up to the user message
+ const allMessages = await DatabaseStore.getConversationMessages(this.activeConversation.id);
+ const conversationPath = filterByLeafNodeId(
+ allMessages,
+ userMessageId,
+ false
+ ) as DatabaseMessage[];
+
+ // Create new assistant message branch
+ const assistantMessage = await DatabaseStore.createMessageBranch(
+ {
+ convId: this.activeConversation.id,
+ type: 'text',
+ timestamp: Date.now(),
+ role: 'assistant',
+ content: '',
+ thinking: '',
+ children: []
+ },
+ userMessageId
+ );
+
+ // Add assistant message to active messages immediately for UI reactivity
+ this.activeMessages.push(assistantMessage);
+
+ // Stream response to new assistant message
+ await this.streamChatCompletion(conversationPath, assistantMessage);
+ } catch (error) {
+ console.error('Failed to generate response:', error);
+ this.isLoading = false;
+ }
+ }
+}
+
+export const chatStore = new ChatStore();
+
+export const conversations = () => chatStore.conversations;
+export const activeConversation = () => chatStore.activeConversation;
+export const activeMessages = () => chatStore.activeMessages;
+export const isLoading = () => chatStore.isLoading;
+export const currentResponse = () => chatStore.currentResponse;
+export const isInitialized = () => chatStore.isInitialized;
+export const maxContextError = () => chatStore.maxContextError;
+
+export const createConversation = chatStore.createConversation.bind(chatStore);
+export const deleteConversation = chatStore.deleteConversation.bind(chatStore);
+export const sendMessage = chatStore.sendMessage.bind(chatStore);
+export const gracefulStop = chatStore.gracefulStop.bind(chatStore);
+export const clearMaxContextError = chatStore.clearMaxContextError.bind(chatStore);
+export const setMaxContextError = chatStore.setMaxContextError.bind(chatStore);
+
+// Branching operations
+export const refreshActiveMessages = chatStore.refreshActiveMessages.bind(chatStore);
+export const navigateToSibling = chatStore.navigateToSibling.bind(chatStore);
+export const editAssistantMessage = chatStore.editAssistantMessage.bind(chatStore);
+export const editMessageWithBranching = chatStore.editMessageWithBranching.bind(chatStore);
+export const regenerateMessageWithBranching =
+ chatStore.regenerateMessageWithBranching.bind(chatStore);
+export const deleteMessage = chatStore.deleteMessage.bind(chatStore);
+export const getDeletionInfo = chatStore.getDeletionInfo.bind(chatStore);
+export const updateConversationName = chatStore.updateConversationName.bind(chatStore);
+export const setTitleUpdateConfirmationCallback =
+ chatStore.setTitleUpdateConfirmationCallback.bind(chatStore);
+
+export function stopGeneration() {
+ chatStore.stopGeneration();
+}
+export const messages = () => chatStore.activeMessages;
--- /dev/null
+import Dexie, { type EntityTable } from 'dexie';
+import { filterByLeafNodeId, findDescendantMessages } from '$lib/utils/branching';
+
+class LlamacppDatabase extends Dexie {
+ conversations!: EntityTable<DatabaseConversation, string>;
+ messages!: EntityTable<DatabaseMessage, string>;
+
+ constructor() {
+ super('LlamacppWebui');
+
+ this.version(1).stores({
+ conversations: 'id, lastModified, currNode, name',
+ messages: 'id, convId, type, role, timestamp, parent, children'
+ });
+ }
+}
+
+const db = new LlamacppDatabase();
+
+/**
+ * DatabaseStore - Persistent data layer for conversation and message management
+ *
+ * This service provides a comprehensive data access layer built on IndexedDB using Dexie.
+ * It handles all persistent storage operations for conversations, messages, and application settings
+ * with support for complex conversation branching and message threading.
+ *
+ * **Architecture & Relationships:**
+ * - **DatabaseStore** (this class): Stateless data persistence layer
+ * - Manages IndexedDB operations through Dexie ORM
+ * - Handles conversation and message CRUD operations
+ * - Supports complex branching with parent-child relationships
+ * - Provides transaction safety for multi-table operations
+ *
+ * - **ChatStore**: Primary consumer for conversation state management
+ * - Uses DatabaseStore for all persistence operations
+ * - Coordinates UI state with database state
+ * - Handles conversation lifecycle and message branching
+ *
+ * **Key Features:**
+ * - **Conversation Management**: Create, read, update, delete conversations
+ * - **Message Branching**: Support for tree-like conversation structures
+ * - **Transaction Safety**: Atomic operations for data consistency
+ * - **Path Resolution**: Navigate conversation branches and find leaf nodes
+ * - **Cascading Deletion**: Remove entire conversation branches
+ *
+ * **Database Schema:**
+ * - `conversations`: Conversation metadata with current node tracking
+ * - `messages`: Individual messages with parent-child relationships
+ *
+ * **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.
+ */
+import { v4 as uuid } from 'uuid';
+
+export class DatabaseStore {
+ /**
+ * Adds a new message to the database.
+ *
+ * @param message - Message to add (without id)
+ * @returns The created message
+ */
+ static async addMessage(message: Omit<DatabaseMessage, 'id'>): Promise<DatabaseMessage> {
+ const newMessage: DatabaseMessage = {
+ ...message,
+ id: uuid()
+ };
+
+ await db.messages.add(newMessage);
+ return newMessage;
+ }
+
+ /**
+ * Creates a new conversation.
+ *
+ * @param name - Name of the conversation
+ * @returns The created conversation
+ */
+ static async createConversation(name: string): Promise<DatabaseConversation> {
+ const conversation: DatabaseConversation = {
+ id: uuid(),
+ name,
+ lastModified: Date.now(),
+ currNode: ''
+ };
+
+ await db.conversations.add(conversation);
+ return conversation;
+ }
+
+ /**
+ * Creates a new message branch by adding a message and updating parent/child relationships.
+ * Also updates the conversation's currNode to point to the new message.
+ *
+ * @param message - Message to add (without id)
+ * @param parentId - Parent message ID to attach to
+ * @returns The created message
+ */
+ static async createMessageBranch(
+ message: Omit<DatabaseMessage, 'id'>,
+ parentId: string | null
+ ): Promise<DatabaseMessage> {
+ return await db.transaction('rw', [db.conversations, db.messages], async () => {
+ // Handle null parent (root message case)
+ if (parentId !== null) {
+ const parentMessage = await db.messages.get(parentId);
+ if (!parentMessage) {
+ throw new Error(`Parent message ${parentId} not found`);
+ }
+ }
+
+ const newMessage: DatabaseMessage = {
+ ...message,
+ id: uuid(),
+ parent: parentId,
+ children: []
+ };
+
+ await db.messages.add(newMessage);
+
+ // Update parent's children array if parent exists
+ if (parentId !== null) {
+ const parentMessage = await db.messages.get(parentId);
+ if (parentMessage) {
+ await db.messages.update(parentId, {
+ children: [...parentMessage.children, newMessage.id]
+ });
+ }
+ }
+
+ await this.updateConversation(message.convId, {
+ currNode: newMessage.id
+ });
+
+ return newMessage;
+ });
+ }
+
+ /**
+ * Creates a root message for a new conversation.
+ * Root messages are not displayed but serve as the tree root for branching.
+ *
+ * @param convId - Conversation ID
+ * @returns The created root message
+ */
+ static async createRootMessage(convId: string): Promise<string> {
+ const rootMessage: DatabaseMessage = {
+ id: uuid(),
+ convId,
+ type: 'root',
+ timestamp: Date.now(),
+ role: 'system',
+ content: '',
+ parent: null,
+ thinking: '',
+ children: []
+ };
+
+ await db.messages.add(rootMessage);
+ return rootMessage.id;
+ }
+
+ /**
+ * Deletes a conversation and all its messages.
+ *
+ * @param id - Conversation ID
+ */
+ static async deleteConversation(id: string): Promise<void> {
+ await db.transaction('rw', [db.conversations, db.messages], async () => {
+ await db.conversations.delete(id);
+ await db.messages.where('convId').equals(id).delete();
+ });
+ }
+
+ /**
+ * Deletes a message and removes it from its parent's children array.
+ *
+ * @param messageId - ID of the message to delete
+ */
+ static async deleteMessage(messageId: string): Promise<void> {
+ await db.transaction('rw', db.messages, async () => {
+ const message = await db.messages.get(messageId);
+ if (!message) return;
+
+ // Remove this message from its parent's children array
+ if (message.parent) {
+ const parent = await db.messages.get(message.parent);
+ if (parent) {
+ parent.children = parent.children.filter((childId: string) => childId !== messageId);
+ await db.messages.put(parent);
+ }
+ }
+
+ // Delete the message
+ await db.messages.delete(messageId);
+ });
+ }
+
+ /**
+ * Deletes a message and all its descendant messages (cascading deletion).
+ * This removes the entire branch starting from the specified message.
+ *
+ * @param conversationId - ID of the conversation containing the message
+ * @param messageId - ID of the root message to delete (along with all descendants)
+ * @returns Array of all deleted message IDs
+ */
+ static async deleteMessageCascading(
+ conversationId: string,
+ messageId: string
+ ): Promise<string[]> {
+ return await db.transaction('rw', db.messages, async () => {
+ // Get all messages in the conversation to find descendants
+ const allMessages = await db.messages.where('convId').equals(conversationId).toArray();
+
+ // Find all descendant messages
+ const descendants = findDescendantMessages(allMessages, messageId);
+ const allToDelete = [messageId, ...descendants];
+
+ // Get the message to delete for parent cleanup
+ const message = await db.messages.get(messageId);
+ if (message && message.parent) {
+ const parent = await db.messages.get(message.parent);
+ if (parent) {
+ parent.children = parent.children.filter((childId: string) => childId !== messageId);
+ await db.messages.put(parent);
+ }
+ }
+
+ // Delete all messages in the branch
+ await db.messages.bulkDelete(allToDelete);
+
+ return allToDelete;
+ });
+ }
+
+ /**
+ * Gets all conversations, sorted by last modified time (newest first).
+ *
+ * @returns Array of conversations
+ */
+ static async getAllConversations(): Promise<DatabaseConversation[]> {
+ return await db.conversations.orderBy('lastModified').reverse().toArray();
+ }
+
+ /**
+ * Gets a conversation by ID.
+ *
+ * @param id - Conversation ID
+ * @returns The conversation if found, otherwise undefined
+ */
+ static async getConversation(id: string): Promise<DatabaseConversation | undefined> {
+ return await db.conversations.get(id);
+ }
+
+ /**
+ * Gets all leaf nodes (messages with no children) in a conversation.
+ * Useful for finding all possible conversation endpoints.
+ *
+ * @param convId - Conversation ID
+ * @returns Array of leaf node message IDs
+ */
+ static async getConversationLeafNodes(convId: string): Promise<string[]> {
+ const allMessages = await this.getConversationMessages(convId);
+ return allMessages.filter((msg) => msg.children.length === 0).map((msg) => msg.id);
+ }
+
+ /**
+ * Gets all messages in a conversation, sorted by timestamp (oldest first).
+ *
+ * @param convId - Conversation ID
+ * @returns Array of messages in the conversation
+ */
+ static async getConversationMessages(convId: string): Promise<DatabaseMessage[]> {
+ return await db.messages.where('convId').equals(convId).sortBy('timestamp');
+ }
+
+ /**
+ * Gets the conversation path from root to the current leaf node.
+ * Uses the conversation's currNode to determine the active branch.
+ *
+ * @param convId - Conversation ID
+ * @returns Array of messages in the current conversation path
+ */
+ static async getConversationPath(convId: string): Promise<DatabaseMessage[]> {
+ const conversation = await this.getConversation(convId);
+
+ if (!conversation) {
+ return [];
+ }
+
+ const allMessages = await this.getConversationMessages(convId);
+
+ if (allMessages.length === 0) {
+ return [];
+ }
+
+ // If no currNode is set, use the latest message as leaf
+ const leafNodeId =
+ conversation.currNode ||
+ allMessages.reduce((latest, msg) => (msg.timestamp > latest.timestamp ? msg : latest)).id;
+
+ return filterByLeafNodeId(allMessages, leafNodeId, false) as DatabaseMessage[];
+ }
+
+ /**
+ * Updates a conversation.
+ *
+ * @param id - Conversation ID
+ * @param updates - Partial updates to apply
+ * @returns Promise that resolves when the conversation is updated
+ */
+ static async updateConversation(
+ id: string,
+ updates: Partial<Omit<DatabaseConversation, 'id'>>
+ ): Promise<void> {
+ await db.conversations.update(id, {
+ ...updates,
+ lastModified: Date.now()
+ });
+ }
+
+ /**
+ * Updates the conversation's current node (active branch).
+ * This determines which conversation path is currently being viewed.
+ *
+ * @param convId - Conversation ID
+ * @param nodeId - Message ID to set as current node
+ */
+ static async updateCurrentNode(convId: string, nodeId: string): Promise<void> {
+ await this.updateConversation(convId, {
+ currNode: nodeId
+ });
+ }
+
+ /**
+ * Updates a message.
+ *
+ * @param id - Message ID
+ * @param updates - Partial updates to apply
+ * @returns Promise that resolves when the message is updated
+ */
+ static async updateMessage(
+ id: string,
+ updates: Partial<Omit<DatabaseMessage, 'id'>>
+ ): Promise<void> {
+ await db.messages.update(id, updates);
+ }
+}
--- /dev/null
+import { ChatService } from '$lib/services/chat';
+import { config } from '$lib/stores/settings.svelte';
+
+/**
+ * ServerStore - Server state management and capability detection
+ *
+ * This store manages communication with the llama.cpp server to retrieve and maintain
+ * server properties, model information, and capability detection. It provides reactive
+ * state for server connectivity, model capabilities, and endpoint availability.
+ *
+ * **Architecture & Relationships:**
+ * - **ServerStore** (this class): Server state and capability management
+ * - Fetches and caches server properties from `/props` endpoint
+ * - Detects model capabilities (vision, audio support)
+ * - Tests endpoint availability (slots endpoint)
+ * - Provides reactive server state for UI components
+ *
+ * - **ChatService**: Uses server properties for request validation
+ * - **SlotsService**: Depends on slots endpoint availability detection
+ * - **UI Components**: Subscribe to server state for capability-based rendering
+ *
+ * **Key Features:**
+ * - **Server Properties**: Model path, context size, build information
+ * - **Capability Detection**: Vision and audio modality support
+ * - **Endpoint Testing**: Slots endpoint availability checking
+ * - **Error Handling**: User-friendly error messages for connection issues
+ * - **Reactive State**: Svelte 5 runes for automatic UI updates
+ * - **State Management**: Loading states and error recovery
+ *
+ * **Server Capabilities Detected:**
+ * - Model name extraction from file path
+ * - Vision support (multimodal image processing)
+ * - Audio support (speech processing)
+ * - Slots endpoint availability (for processing state monitoring)
+ * - Context window size and token limits
+ */
+class ServerStore {
+ private _serverProps = $state<ApiLlamaCppServerProps | null>(null);
+ private _loading = $state(false);
+ private _error = $state<string | null>(null);
+ private _slotsEndpointAvailable = $state<boolean | null>(null);
+
+ get serverProps(): ApiLlamaCppServerProps | null {
+ return this._serverProps;
+ }
+
+ get loading(): boolean {
+ return this._loading;
+ }
+
+ get error(): string | null {
+ return this._error;
+ }
+
+ get modelName(): string | null {
+ if (!this._serverProps?.model_path) return null;
+ return this._serverProps.model_path.split(/(\\|\/)/).pop() || null;
+ }
+
+ get supportedModalities(): string[] {
+ const modalities: string[] = [];
+ if (this._serverProps?.modalities?.audio) {
+ modalities.push('audio');
+ }
+ if (this._serverProps?.modalities?.vision) {
+ modalities.push('vision');
+ }
+ return modalities;
+ }
+
+ get supportsVision(): boolean {
+ return this._serverProps?.modalities?.vision ?? false;
+ }
+
+ get supportsAudio(): boolean {
+ return this._serverProps?.modalities?.audio ?? false;
+ }
+
+ get slotsEndpointAvailable(): boolean | null {
+ return this._slotsEndpointAvailable;
+ }
+
+ /**
+ * Check if slots endpoint is available based on server properties and endpoint support
+ */
+ private async checkSlotsEndpointAvailability(): Promise<void> {
+ if (!this._serverProps) {
+ this._slotsEndpointAvailable = false;
+ return;
+ }
+
+ if (this._serverProps.total_slots <= 0) {
+ this._slotsEndpointAvailable = false;
+ return;
+ }
+
+ try {
+ const currentConfig = config();
+ const apiKey = currentConfig.apiKey?.toString().trim();
+
+ const response = await fetch('/slots', {
+ headers: {
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {})
+ }
+ });
+
+ if (response.status === 501) {
+ console.info('Slots endpoint not implemented - server started without --slots flag');
+ this._slotsEndpointAvailable = false;
+ return;
+ }
+
+ this._slotsEndpointAvailable = true;
+ } catch (error) {
+ console.warn('Unable to test slots endpoint availability:', error);
+ this._slotsEndpointAvailable = false;
+ }
+ }
+
+ /**
+ * Fetches server properties from the server
+ */
+ async fetchServerProps(): Promise<void> {
+ this._loading = true;
+ this._error = null;
+
+ try {
+ console.log('Fetching server properties...');
+ const props = await ChatService.getServerProps();
+ this._serverProps = props;
+ console.log('Server properties loaded:', props);
+
+ // Check slots endpoint availability after server props are loaded
+ await this.checkSlotsEndpointAvailability();
+ } catch (error) {
+ let errorMessage = 'Failed to connect to server';
+
+ if (error instanceof Error) {
+ // Handle specific error types with user-friendly messages
+ if (error.name === 'TypeError' && error.message.includes('fetch')) {
+ errorMessage = 'Server is not running or unreachable';
+ } else if (error.message.includes('ECONNREFUSED')) {
+ errorMessage = 'Connection refused - server may be offline';
+ } else if (error.message.includes('ENOTFOUND')) {
+ errorMessage = 'Server not found - check server address';
+ } else if (error.message.includes('ETIMEDOUT')) {
+ errorMessage = 'Connection timeout - server may be overloaded';
+ } else if (error.message.includes('500')) {
+ errorMessage = 'Server error - check server logs';
+ } else if (error.message.includes('404')) {
+ errorMessage = 'Server endpoint not found';
+ } else if (error.message.includes('403') || error.message.includes('401')) {
+ errorMessage = 'Access denied';
+ }
+ }
+
+ this._error = errorMessage;
+ console.error('Error fetching server properties:', error);
+ } finally {
+ this._loading = false;
+ }
+ }
+
+ /**
+ * Clears the server state
+ */
+ clear(): void {
+ this._serverProps = null;
+ this._error = null;
+ this._loading = false;
+ this._slotsEndpointAvailable = null;
+ }
+}
+
+export const serverStore = new ServerStore();
+
+export const serverProps = () => serverStore.serverProps;
+export const serverLoading = () => serverStore.loading;
+export const serverError = () => serverStore.error;
+export const modelName = () => serverStore.modelName;
+export const supportedModalities = () => serverStore.supportedModalities;
+export const supportsVision = () => serverStore.supportsVision;
+export const supportsAudio = () => serverStore.supportsAudio;
+export const slotsEndpointAvailable = () => serverStore.slotsEndpointAvailable;
--- /dev/null
+/**
+ * SettingsStore - Application configuration and theme management
+ *
+ * This store manages all application settings including AI model parameters, UI preferences,
+ * and theme configuration. It provides persistent storage through localStorage with reactive
+ * state management using Svelte 5 runes.
+ *
+ * **Architecture & Relationships:**
+ * - **SettingsStore** (this class): Configuration state management
+ * - Manages AI model parameters (temperature, max tokens, etc.)
+ * - Handles theme switching and persistence
+ * - Provides localStorage synchronization
+ * - Offers reactive configuration access
+ *
+ * - **ChatService**: Reads model parameters for API requests
+ * - **UI Components**: Subscribe to theme and configuration changes
+ *
+ * **Key Features:**
+ * - **Model Parameters**: Temperature, max tokens, top-p, top-k, repeat penalty
+ * - **Theme Management**: Auto, light, dark theme switching
+ * - **Persistence**: Automatic localStorage synchronization
+ * - **Reactive State**: Svelte 5 runes for automatic UI updates
+ * - **Default Handling**: Graceful fallback to defaults for missing settings
+ * - **Batch Updates**: Efficient multi-setting updates
+ * - **Reset Functionality**: Restore defaults for individual or all settings
+ *
+ * **Configuration Categories:**
+ * - Generation parameters (temperature, tokens, sampling)
+ * - UI preferences (theme, display options)
+ * - System settings (model selection, prompts)
+ * - Advanced options (seed, penalties, context handling)
+ */
+
+import { browser } from '$app/environment';
+import { SETTING_CONFIG_DEFAULT } from '$lib/constants/settings-config';
+
+class SettingsStore {
+ config = $state<SettingsConfigType>({ ...SETTING_CONFIG_DEFAULT });
+ theme = $state<string>('auto');
+ isInitialized = $state(false);
+
+ constructor() {
+ if (browser) {
+ this.initialize();
+ }
+ }
+
+ /**
+ * Initialize the settings store by loading from localStorage
+ */
+ initialize() {
+ try {
+ this.loadConfig();
+ this.loadTheme();
+ this.isInitialized = true;
+ } catch (error) {
+ console.error('Failed to initialize settings store:', error);
+ }
+ }
+
+ /**
+ * Load configuration from localStorage
+ * Returns default values for missing keys to prevent breaking changes
+ */
+ private loadConfig() {
+ if (!browser) return;
+
+ try {
+ const savedVal = JSON.parse(localStorage.getItem('config') || '{}');
+ // Merge with defaults to prevent breaking changes
+ this.config = {
+ ...SETTING_CONFIG_DEFAULT,
+ ...savedVal
+ };
+ } catch (error) {
+ console.warn('Failed to parse config from localStorage, using defaults:', error);
+ this.config = { ...SETTING_CONFIG_DEFAULT };
+ }
+ }
+
+ /**
+ * Load theme from localStorage
+ */
+ private loadTheme() {
+ if (!browser) return;
+
+ this.theme = localStorage.getItem('theme') || 'auto';
+ }
+
+ /**
+ * Update a specific configuration setting
+ * @param key - The configuration key to update
+ * @param value - The new value for the configuration key
+ */
+ updateConfig<K extends keyof SettingsConfigType>(key: K, value: SettingsConfigType[K]) {
+ this.config[key] = value;
+ this.saveConfig();
+ }
+
+ /**
+ * Update multiple configuration settings at once
+ * @param updates - Object containing the configuration updates
+ */
+ updateMultipleConfig(updates: Partial<SettingsConfigType>) {
+ Object.assign(this.config, updates);
+ this.saveConfig();
+ }
+
+ /**
+ * Save the current configuration to localStorage
+ */
+ private saveConfig() {
+ if (!browser) return;
+
+ try {
+ localStorage.setItem('config', JSON.stringify(this.config));
+ } catch (error) {
+ console.error('Failed to save config to localStorage:', error);
+ }
+ }
+
+ /**
+ * Update the theme setting
+ * @param newTheme - The new theme value
+ */
+ updateTheme(newTheme: string) {
+ this.theme = newTheme;
+ this.saveTheme();
+ }
+
+ /**
+ * Save the current theme to localStorage
+ */
+ private saveTheme() {
+ if (!browser) return;
+
+ try {
+ if (this.theme === 'auto') {
+ localStorage.removeItem('theme');
+ } else {
+ localStorage.setItem('theme', this.theme);
+ }
+ } catch (error) {
+ console.error('Failed to save theme to localStorage:', error);
+ }
+ }
+
+ /**
+ * Reset configuration to defaults
+ */
+ resetConfig() {
+ this.config = { ...SETTING_CONFIG_DEFAULT };
+ this.saveConfig();
+ }
+
+ /**
+ * Reset theme to auto
+ */
+ resetTheme() {
+ this.theme = 'auto';
+ this.saveTheme();
+ }
+
+ /**
+ * Reset all settings to defaults
+ */
+ resetAll() {
+ this.resetConfig();
+ this.resetTheme();
+ }
+
+ /**
+ * Get a specific configuration value
+ * @param key - The configuration key to get
+ * @returns The configuration value
+ */
+ getConfig<K extends keyof SettingsConfigType>(key: K): SettingsConfigType[K] {
+ return this.config[key];
+ }
+
+ /**
+ * Get the entire configuration object
+ * @returns The complete configuration object
+ */
+ getAllConfig(): SettingsConfigType {
+ return { ...this.config };
+ }
+}
+
+// Create and export the settings store instance
+export const settingsStore = new SettingsStore();
+
+// Export reactive getters for easy access in components
+export const config = () => settingsStore.config;
+export const theme = () => settingsStore.theme;
+export const isInitialized = () => settingsStore.isInitialized;
+
+// Export bound methods for easy access
+export const updateConfig = settingsStore.updateConfig.bind(settingsStore);
+export const updateMultipleConfig = settingsStore.updateMultipleConfig.bind(settingsStore);
+export const updateTheme = settingsStore.updateTheme.bind(settingsStore);
+export const resetConfig = settingsStore.resetConfig.bind(settingsStore);
+export const resetTheme = settingsStore.resetTheme.bind(settingsStore);
+export const resetAll = settingsStore.resetAll.bind(settingsStore);
+export const getConfig = settingsStore.getConfig.bind(settingsStore);
+export const getAllConfig = settingsStore.getAllConfig.bind(settingsStore);
--- /dev/null
+import type { ChatMessagePromptProgress } from './chat';
+
+export interface ApiChatMessageContentPart {
+ type: 'text' | 'image_url' | 'input_audio';
+ text?: string;
+ image_url?: {
+ url: string;
+ };
+ input_audio?: {
+ data: string;
+ format: 'wav' | 'mp3';
+ };
+}
+
+export interface ApiContextSizeError {
+ code: number;
+ message: string;
+ type: 'exceed_context_size_error';
+ n_prompt_tokens: number;
+ n_ctx: number;
+}
+
+export interface ApiErrorResponse {
+ error:
+ | ApiContextSizeError
+ | {
+ code: number;
+ message: string;
+ type?: string;
+ };
+}
+
+export interface ApiChatMessageData {
+ role: ChatRole;
+ content: string | ApiChatMessageContentPart[];
+ timestamp?: number;
+}
+
+export interface ApiLlamaCppServerProps {
+ default_generation_settings: {
+ id: number;
+ id_task: number;
+ n_ctx: number;
+ speculative: boolean;
+ is_processing: boolean;
+ params: {
+ n_predict: number;
+ seed: number;
+ temperature: number;
+ dynatemp_range: number;
+ dynatemp_exponent: number;
+ top_k: number;
+ top_p: number;
+ min_p: number;
+ top_n_sigma: number;
+ xtc_probability: number;
+ xtc_threshold: number;
+ typ_p: number;
+ repeat_last_n: number;
+ repeat_penalty: number;
+ presence_penalty: number;
+ frequency_penalty: number;
+ dry_multiplier: number;
+ dry_base: number;
+ dry_allowed_length: number;
+ dry_penalty_last_n: number;
+ dry_sequence_breakers: string[];
+ mirostat: number;
+ mirostat_tau: number;
+ mirostat_eta: number;
+ stop: string[];
+ max_tokens: number;
+ n_keep: number;
+ n_discard: number;
+ ignore_eos: boolean;
+ stream: boolean;
+ logit_bias: Array<[number, number]>;
+ n_probs: number;
+ min_keep: number;
+ grammar: string;
+ grammar_lazy: boolean;
+ grammar_triggers: string[];
+ preserved_tokens: number[];
+ chat_format: string;
+ reasoning_format: string;
+ reasoning_in_content: boolean;
+ thinking_forced_open: boolean;
+ samplers: string[];
+ 'speculative.n_max': number;
+ 'speculative.n_min': number;
+ 'speculative.p_min': number;
+ timings_per_token: boolean;
+ post_sampling_probs: boolean;
+ lora: Array<{ name: string; scale: number }>;
+ };
+ prompt: string;
+ next_token: {
+ has_next_token: boolean;
+ has_new_line: boolean;
+ n_remain: number;
+ n_decoded: number;
+ stopping_word: string;
+ };
+ };
+ total_slots: number;
+ model_path: string;
+ modalities: {
+ vision: boolean;
+ audio: boolean;
+ };
+ chat_template: string;
+ bos_token: string;
+ eos_token: string;
+ build_info: string;
+}
+
+export interface ApiChatCompletionRequest {
+ messages: Array<{
+ role: ChatRole;
+ content: string | ApiChatMessageContentPart[];
+ }>;
+ stream?: boolean;
+ // Reasoning parameters
+ reasoning_format?: string;
+ // Generation parameters
+ temperature?: number;
+ max_tokens?: number;
+ // Sampling parameters
+ dynatemp_range?: number;
+ dynatemp_exponent?: number;
+ top_k?: number;
+ top_p?: number;
+ min_p?: number;
+ xtc_probability?: number;
+ xtc_threshold?: number;
+ typ_p?: number;
+ // Penalty parameters
+ repeat_last_n?: number;
+ repeat_penalty?: number;
+ presence_penalty?: number;
+ frequency_penalty?: number;
+ dry_multiplier?: number;
+ dry_base?: number;
+ dry_allowed_length?: number;
+ dry_penalty_last_n?: number;
+ // Sampler configuration
+ samplers?: string[];
+ // Custom parameters (JSON string)
+ custom?: Record<string, unknown>;
+}
+
+export interface ApiChatCompletionStreamChunk {
+ choices: Array<{
+ delta: {
+ content?: string;
+ reasoning_content?: string;
+ };
+ }>;
+ timings?: {
+ prompt_n?: number;
+ prompt_ms?: number;
+ predicted_n?: number;
+ predicted_ms?: number;
+ cache_n?: number;
+ };
+ prompt_progress?: ChatMessagePromptProgress;
+}
+
+export interface ApiChatCompletionResponse {
+ choices: Array<{
+ message: {
+ content: string;
+ reasoning_content?: string;
+ };
+ }>;
+}
+
+export interface ApiSlotData {
+ id: number;
+ id_task: number;
+ n_ctx: number;
+ speculative: boolean;
+ is_processing: boolean;
+ params: {
+ n_predict: number;
+ seed: number;
+ temperature: number;
+ dynatemp_range: number;
+ dynatemp_exponent: number;
+ top_k: number;
+ top_p: number;
+ min_p: number;
+ top_n_sigma: number;
+ xtc_probability: number;
+ xtc_threshold: number;
+ typical_p: number;
+ repeat_last_n: number;
+ repeat_penalty: number;
+ presence_penalty: number;
+ frequency_penalty: number;
+ dry_multiplier: number;
+ dry_base: number;
+ dry_allowed_length: number;
+ dry_penalty_last_n: number;
+ mirostat: number;
+ mirostat_tau: number;
+ mirostat_eta: number;
+ max_tokens: number;
+ n_keep: number;
+ n_discard: number;
+ ignore_eos: boolean;
+ stream: boolean;
+ n_probs: number;
+ min_keep: number;
+ chat_format: string;
+ reasoning_format: string;
+ reasoning_in_content: boolean;
+ thinking_forced_open: boolean;
+ samplers: string[];
+ 'speculative.n_max': number;
+ 'speculative.n_min': number;
+ 'speculative.p_min': number;
+ timings_per_token: boolean;
+ post_sampling_probs: boolean;
+ lora: Array<{ name: string; scale: number }>;
+ };
+ next_token: {
+ has_next_token: boolean;
+ has_new_line: boolean;
+ n_remain: number;
+ n_decoded: number;
+ };
+}
+
+export interface ApiProcessingState {
+ status: 'initializing' | 'generating' | 'preparing' | 'idle';
+ tokensDecoded: number;
+ tokensRemaining: number;
+ contextUsed: number;
+ contextTotal: number;
+ outputTokensUsed: number; // Total output tokens (thinking + regular content)
+ outputTokensMax: number; // Max output tokens allowed
+ temperature: number;
+ topP: number;
+ speculative: boolean;
+ hasNextToken: boolean;
+ tokensPerSecond?: number;
+ // Progress information from prompt_progress
+ progressPercent?: number;
+ promptTokens?: number;
+ cacheTokens?: number;
+}
--- /dev/null
+export type ChatMessageType = 'root' | 'text' | 'think';
+export type ChatRole = 'user' | 'assistant' | 'system';
+
+export interface ChatUploadedFile {
+ id: string;
+ name: string;
+ size: number;
+ type: string;
+ file: File;
+ preview?: string;
+ textContent?: string;
+}
+
+export interface ChatMessageSiblingInfo {
+ message: DatabaseMessage;
+ siblingIds: string[];
+ currentIndex: number;
+ totalSiblings: number;
+}
+
+export interface ChatMessagePromptProgress {
+ cache: number;
+ processed: number;
+ time_ms: number;
+ total: number;
+}
+
+export interface ChatMessageTimings {
+ cache_n?: number;
+ predicted_ms?: number;
+ predicted_n?: number;
+ prompt_ms?: number;
+ prompt_n?: number;
+}
--- /dev/null
+import type { ChatMessageTimings } from './chat';
+
+export interface DatabaseConversation {
+ currNode: string | null;
+ id: string;
+ lastModified: number;
+ name: string;
+}
+
+export interface DatabaseMessageExtraAudioFile {
+ type: 'audioFile';
+ name: string;
+ base64Data: string;
+ mimeType: string;
+}
+
+export interface DatabaseMessageExtraImageFile {
+ type: 'imageFile';
+ name: string;
+ base64Url: string;
+}
+
+export interface DatabaseMessageExtraTextFile {
+ type: 'textFile';
+ name: string;
+ content: string;
+}
+
+export interface DatabaseMessageExtraPdfFile {
+ type: 'pdfFile';
+ 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
+}
+
+export type DatabaseMessageExtra =
+ | DatabaseMessageExtraImageFile
+ | DatabaseMessageExtraTextFile
+ | DatabaseMessageExtraAudioFile
+ | DatabaseMessageExtraPdfFile;
+
+export interface DatabaseMessage {
+ id: string;
+ convId: string;
+ type: ChatMessageType;
+ timestamp: number;
+ role: ChatRole;
+ content: string;
+ parent: string;
+ thinking: string;
+ children: string[];
+ extra?: DatabaseMessageExtra[];
+ timings?: ChatMessageTimings;
+}
--- /dev/null
+import type { SETTING_CONFIG_DEFAULT } from '$lib/constants/settings-config';
+import type { ChatMessageTimings } from './chat';
+
+export type SettingsConfigValue = string | number | boolean;
+
+export interface SettingsFieldConfig {
+ key: string;
+ label: string;
+ type: 'input' | 'textarea' | 'checkbox' | 'select';
+ help?: string;
+ options?: Array<{ value: string; label: string; icon?: typeof import('@lucide/svelte').Icon }>;
+}
+
+export interface SettingsChatServiceOptions {
+ stream?: boolean;
+ // Generation parameters
+ temperature?: number;
+ max_tokens?: number;
+ // Sampling parameters
+ dynatemp_range?: number;
+ dynatemp_exponent?: number;
+ top_k?: number;
+ top_p?: number;
+ min_p?: number;
+ xtc_probability?: number;
+ xtc_threshold?: number;
+ typ_p?: number;
+ // Penalty parameters
+ repeat_last_n?: number;
+ repeat_penalty?: number;
+ presence_penalty?: number;
+ frequency_penalty?: number;
+ dry_multiplier?: number;
+ dry_base?: number;
+ dry_allowed_length?: number;
+ dry_penalty_last_n?: number;
+ // Sampler configuration
+ samplers?: string | string[];
+ // Custom parameters
+ custom?: string;
+ // Callbacks
+ onChunk?: (chunk: string) => void;
+ onReasoningChunk?: (chunk: string) => void;
+ onComplete?: (response: string, reasoningContent?: string, timings?: ChatMessageTimings) => void;
+ onError?: (error: Error) => void;
+}
+
+export type SettingsConfigType = typeof SETTING_CONFIG_DEFAULT & {
+ [key: string]: SettingsConfigValue;
+};
--- /dev/null
+import { error } from '@sveltejs/kit';
+import { browser } from '$app/environment';
+import { config } from '$lib/stores/settings.svelte';
+
+/**
+ * Validates API key by making a request to the server props endpoint
+ * Throws SvelteKit errors for authentication failures or server issues
+ */
+export async function validateApiKey(fetch: typeof globalThis.fetch): Promise<void> {
+ if (!browser) {
+ return;
+ }
+
+ try {
+ const apiKey = config().apiKey;
+
+ const headers: Record<string, string> = {
+ 'Content-Type': 'application/json'
+ };
+
+ if (apiKey) {
+ headers.Authorization = `Bearer ${apiKey}`;
+ }
+
+ const response = await fetch('/props', { headers });
+
+ if (!response.ok) {
+ if (response.status === 401 || response.status === 403) {
+ throw error(401, 'Access denied');
+ } else if (response.status >= 500) {
+ throw error(response.status, 'Server error - check if llama.cpp server is running');
+ } else {
+ throw error(response.status, `Server responded with status ${response.status}`);
+ }
+ }
+ } catch (err) {
+ // If it's already a SvelteKit error, re-throw it
+ if (err && typeof err === 'object' && 'status' in err) {
+ throw err;
+ }
+
+ // Network or other errors
+ throw error(503, 'Cannot connect to server - check if llama.cpp server is running');
+ }
+}
--- /dev/null
+import { MimeTypeAudio } from '$lib/enums/files';
+
+/**
+ * AudioRecorder - Browser-based audio recording with MediaRecorder API
+ *
+ * This class provides a complete audio recording solution using the browser's MediaRecorder API.
+ * It handles microphone access, recording state management, and audio format optimization.
+ *
+ * **Features:**
+ * - Automatic microphone permission handling
+ * - Audio enhancement (echo cancellation, noise suppression, auto gain)
+ * - Multiple format support with fallback (WAV, WebM, MP4, AAC)
+ * - Real-time recording state tracking
+ * - Proper cleanup and resource management
+ */
+export class AudioRecorder {
+ private mediaRecorder: MediaRecorder | null = null;
+ private audioChunks: Blob[] = [];
+ private stream: MediaStream | null = null;
+ private recordingState: boolean = false;
+
+ async startRecording(): Promise<void> {
+ try {
+ this.stream = await navigator.mediaDevices.getUserMedia({
+ audio: {
+ echoCancellation: true,
+ noiseSuppression: true,
+ autoGainControl: true
+ }
+ });
+
+ this.initializeRecorder(this.stream);
+
+ this.audioChunks = [];
+ // Start recording with a small timeslice to ensure we get data
+ this.mediaRecorder!.start(100);
+ this.recordingState = true;
+ } catch (error) {
+ console.error('Failed to start recording:', error);
+ throw new Error('Failed to access microphone. Please check permissions.');
+ }
+ }
+
+ async stopRecording(): Promise<Blob> {
+ return new Promise((resolve, reject) => {
+ if (!this.mediaRecorder || this.mediaRecorder.state === 'inactive') {
+ reject(new Error('No active recording to stop'));
+ return;
+ }
+
+ this.mediaRecorder.onstop = () => {
+ const mimeType = this.mediaRecorder?.mimeType || MimeTypeAudio.WAV;
+ const audioBlob = new Blob(this.audioChunks, { type: mimeType });
+
+ this.cleanup();
+
+ resolve(audioBlob);
+ };
+
+ this.mediaRecorder.onerror = (event) => {
+ console.error('Recording error:', event);
+ this.cleanup();
+ reject(new Error('Recording failed'));
+ };
+
+ this.mediaRecorder.stop();
+ });
+ }
+
+ isRecording(): boolean {
+ return this.recordingState;
+ }
+
+ cancelRecording(): void {
+ if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
+ this.mediaRecorder.stop();
+ }
+ this.cleanup();
+ }
+
+ private initializeRecorder(stream: MediaStream): void {
+ const options: MediaRecorderOptions = {};
+
+ if (MediaRecorder.isTypeSupported(MimeTypeAudio.WAV)) {
+ options.mimeType = MimeTypeAudio.WAV;
+ } else if (MediaRecorder.isTypeSupported(MimeTypeAudio.WEBM_OPUS)) {
+ options.mimeType = MimeTypeAudio.WEBM_OPUS;
+ } else if (MediaRecorder.isTypeSupported(MimeTypeAudio.WEBM)) {
+ options.mimeType = MimeTypeAudio.WEBM;
+ } else if (MediaRecorder.isTypeSupported(MimeTypeAudio.MP4)) {
+ options.mimeType = MimeTypeAudio.MP4;
+ } else {
+ console.warn('No preferred audio format supported, using default');
+ }
+
+ this.mediaRecorder = new MediaRecorder(stream, options);
+
+ this.mediaRecorder.ondataavailable = (event) => {
+ if (event.data.size > 0) {
+ this.audioChunks.push(event.data);
+ }
+ };
+
+ this.mediaRecorder.onstop = () => {
+ this.recordingState = false;
+ };
+
+ this.mediaRecorder.onerror = (event) => {
+ console.error('MediaRecorder error:', event);
+ this.recordingState = false;
+ };
+ }
+
+ private cleanup(): void {
+ if (this.stream) {
+ for (const track of this.stream.getTracks()) {
+ track.stop();
+ }
+
+ this.stream = null;
+ }
+ this.mediaRecorder = null;
+ this.audioChunks = [];
+ this.recordingState = false;
+ }
+}
+
+export async function convertToWav(audioBlob: Blob): Promise<Blob> {
+ try {
+ if (audioBlob.type.includes('wav')) {
+ return audioBlob;
+ }
+
+ const arrayBuffer = await audioBlob.arrayBuffer();
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
+
+ const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
+
+ const wavBlob = audioBufferToWav(audioBuffer);
+
+ audioContext.close();
+
+ return wavBlob;
+ } catch (error) {
+ console.error('Failed to convert audio to WAV:', error);
+ return audioBlob;
+ }
+}
+
+function audioBufferToWav(buffer: AudioBuffer): Blob {
+ const length = buffer.length;
+ const numberOfChannels = buffer.numberOfChannels;
+ const sampleRate = buffer.sampleRate;
+ const bytesPerSample = 2; // 16-bit
+ const blockAlign = numberOfChannels * bytesPerSample;
+ const byteRate = sampleRate * blockAlign;
+ const dataSize = length * blockAlign;
+ const bufferSize = 44 + dataSize;
+
+ const arrayBuffer = new ArrayBuffer(bufferSize);
+ const view = new DataView(arrayBuffer);
+
+ const writeString = (offset: number, string: string) => {
+ for (let i = 0; i < string.length; i++) {
+ view.setUint8(offset + i, string.charCodeAt(i));
+ }
+ };
+
+ writeString(0, 'RIFF'); // ChunkID
+ view.setUint32(4, bufferSize - 8, true); // ChunkSize
+ writeString(8, 'WAVE'); // Format
+ writeString(12, 'fmt '); // Subchunk1ID
+ view.setUint32(16, 16, true); // Subchunk1Size
+ view.setUint16(20, 1, true); // AudioFormat (PCM)
+ view.setUint16(22, numberOfChannels, true); // NumChannels
+ view.setUint32(24, sampleRate, true); // SampleRate
+ view.setUint32(28, byteRate, true); // ByteRate
+ view.setUint16(32, blockAlign, true); // BlockAlign
+ view.setUint16(34, 16, true); // BitsPerSample
+ writeString(36, 'data'); // Subchunk2ID
+ view.setUint32(40, dataSize, true); // Subchunk2Size
+
+ let offset = 44;
+ for (let i = 0; i < length; i++) {
+ for (let channel = 0; channel < numberOfChannels; channel++) {
+ const sample = Math.max(-1, Math.min(1, buffer.getChannelData(channel)[i]));
+ view.setInt16(offset, sample * 0x7fff, true);
+ offset += 2;
+ }
+ }
+
+ return new Blob([arrayBuffer], { type: MimeTypeAudio.WAV });
+}
+
+/**
+ * Create a File object from audio blob with timestamp-based naming
+ * @param audioBlob - The audio blob to wrap
+ * @param filename - Optional custom filename
+ * @returns File object with appropriate name and metadata
+ */
+export function createAudioFile(audioBlob: Blob, filename?: string): File {
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
+ const extension = audioBlob.type.includes('wav') ? 'wav' : 'mp3';
+ const defaultFilename = `recording-${timestamp}.${extension}`;
+
+ return new File([audioBlob], filename || defaultFilename, {
+ type: audioBlob.type,
+ lastModified: Date.now()
+ });
+}
+
+/**
+ * Check if audio recording is supported in the current browser
+ * @returns True if MediaRecorder and getUserMedia are available
+ */
+export function isAudioRecordingSupported(): boolean {
+ return !!(
+ typeof navigator !== 'undefined' &&
+ navigator.mediaDevices &&
+ typeof navigator.mediaDevices.getUserMedia === 'function' &&
+ typeof window !== 'undefined' &&
+ window.MediaRecorder
+ );
+}
--- /dev/null
+/**
+ * Automatically resizes a textarea element to fit its content
+ * @param textareaElement - The textarea element to resize
+ */
+export default function autoResizeTextarea(textareaElement: HTMLTextAreaElement | null): void {
+ if (textareaElement) {
+ textareaElement.style.height = '1rem';
+ textareaElement.style.height = textareaElement.scrollHeight + 'px';
+ }
+}
--- /dev/null
+/**
+ * Message branching utilities for conversation tree navigation.
+ *
+ * Conversation branching allows users to edit messages and create alternate paths
+ * while preserving the original conversation flow. Each message has parent/children
+ * relationships forming a tree structure.
+ *
+ * Example tree:
+ * root
+ * ├── message 1 (user)
+ * │ └── message 2 (assistant)
+ * │ ├── message 3 (user)
+ * │ └── message 6 (user) ← new branch
+ * └── message 4 (user)
+ * └── message 5 (assistant)
+ */
+
+/**
+ * Filters messages to get the conversation path from root to a specific leaf node.
+ * If the leafNodeId doesn't exist, returns the path with the latest timestamp.
+ *
+ * @param messages - All messages in the conversation
+ * @param leafNodeId - The target leaf node ID to trace back from
+ * @param includeRoot - Whether to include root messages in the result
+ * @returns Array of messages from root to leaf, sorted by timestamp
+ */
+export function filterByLeafNodeId(
+ messages: readonly DatabaseMessage[],
+ leafNodeId: string,
+ includeRoot: boolean = false
+): readonly DatabaseMessage[] {
+ const result: DatabaseMessage[] = [];
+ const nodeMap = new Map<string, DatabaseMessage>();
+
+ // Build node map for quick lookups
+ for (const msg of messages) {
+ nodeMap.set(msg.id, msg);
+ }
+
+ // Find the starting node (leaf node or latest if not found)
+ let startNode: DatabaseMessage | undefined = nodeMap.get(leafNodeId);
+ if (!startNode) {
+ // If leaf node not found, use the message with latest timestamp
+ let latestTime = -1;
+ for (const msg of messages) {
+ if (msg.timestamp > latestTime) {
+ startNode = msg;
+ latestTime = msg.timestamp;
+ }
+ }
+ }
+
+ // Traverse from leaf to root, collecting messages
+ let currentNode: DatabaseMessage | undefined = startNode;
+ while (currentNode) {
+ // Include message if it's not root, or if we want to include root
+ if (currentNode.type !== 'root' || includeRoot) {
+ result.push(currentNode);
+ }
+
+ // Stop traversal if parent is null (reached root)
+ if (currentNode.parent === null) {
+ break;
+ }
+ currentNode = nodeMap.get(currentNode.parent);
+ }
+
+ // Sort by timestamp to get chronological order (root to leaf)
+ result.sort((a, b) => a.timestamp - b.timestamp);
+ return result;
+}
+
+/**
+ * Finds the leaf node (message with no children) for a given message branch.
+ * Traverses down the tree following the last child until reaching a leaf.
+ *
+ * @param messages - All messages in the conversation
+ * @param messageId - Starting message ID to find leaf for
+ * @returns The leaf node ID, or the original messageId if no children
+ */
+export function findLeafNode(messages: readonly DatabaseMessage[], messageId: string): string {
+ const nodeMap = new Map<string, DatabaseMessage>();
+
+ // Build node map for quick lookups
+ for (const msg of messages) {
+ nodeMap.set(msg.id, msg);
+ }
+
+ let currentNode: DatabaseMessage | undefined = nodeMap.get(messageId);
+ while (currentNode && currentNode.children.length > 0) {
+ // Follow the last child (most recent branch)
+ const lastChildId = currentNode.children[currentNode.children.length - 1];
+ currentNode = nodeMap.get(lastChildId);
+ }
+
+ return currentNode?.id ?? messageId;
+}
+
+/**
+ * Finds all descendant messages (children, grandchildren, etc.) of a given message.
+ * This is used for cascading deletion to remove all messages in a branch.
+ *
+ * @param messages - All messages in the conversation
+ * @param messageId - The root message ID to find descendants for
+ * @returns Array of all descendant message IDs
+ */
+export function findDescendantMessages(
+ messages: readonly DatabaseMessage[],
+ messageId: string
+): string[] {
+ const nodeMap = new Map<string, DatabaseMessage>();
+
+ // Build node map for quick lookups
+ for (const msg of messages) {
+ nodeMap.set(msg.id, msg);
+ }
+
+ const descendants: string[] = [];
+ const queue: string[] = [messageId];
+
+ while (queue.length > 0) {
+ const currentId = queue.shift()!;
+ const currentNode = nodeMap.get(currentId);
+
+ if (currentNode) {
+ // Add all children to the queue and descendants list
+ for (const childId of currentNode.children) {
+ descendants.push(childId);
+ queue.push(childId);
+ }
+ }
+ }
+
+ return descendants;
+}
+
+/**
+ * Gets sibling information for a message, including all sibling IDs and current position.
+ * Siblings are messages that share the same parent.
+ *
+ * @param messages - All messages in the conversation
+ * @param messageId - The message to get sibling info for
+ * @returns Sibling information including leaf node IDs for navigation
+ */
+export function getMessageSiblings(
+ messages: readonly DatabaseMessage[],
+ messageId: string
+): ChatMessageSiblingInfo | null {
+ const nodeMap = new Map<string, DatabaseMessage>();
+
+ // Build node map for quick lookups
+ for (const msg of messages) {
+ nodeMap.set(msg.id, msg);
+ }
+
+ const message = nodeMap.get(messageId);
+ if (!message) {
+ return null;
+ }
+
+ // Handle null parent (root message) case
+ if (message.parent === null) {
+ // No parent means this is likely a root node with no siblings
+ return {
+ message,
+ siblingIds: [messageId],
+ currentIndex: 0,
+ totalSiblings: 1
+ };
+ }
+
+ const parentNode = nodeMap.get(message.parent);
+ if (!parentNode) {
+ // Parent not found - treat as single message
+ return {
+ message,
+ siblingIds: [messageId],
+ currentIndex: 0,
+ totalSiblings: 1
+ };
+ }
+
+ // Get all sibling IDs (including self)
+ const siblingIds = parentNode.children;
+
+ // Convert sibling message IDs to their corresponding leaf node IDs
+ // This allows navigation between different conversation branches
+ const siblingLeafIds = siblingIds.map((siblingId: string) => findLeafNode(messages, siblingId));
+
+ // Find current message's position among siblings
+ const currentIndex = siblingIds.indexOf(messageId);
+
+ return {
+ message,
+ siblingIds: siblingLeafIds,
+ currentIndex,
+ totalSiblings: siblingIds.length
+ };
+}
+
+/**
+ * Creates a display-ready list of messages with sibling information for UI rendering.
+ * This is the main function used by chat components to render conversation branches.
+ *
+ * @param messages - All messages in the conversation
+ * @param leafNodeId - Current leaf node being viewed
+ * @returns Array of messages with sibling navigation info
+ */
+export function getMessageDisplayList(
+ messages: readonly DatabaseMessage[],
+ leafNodeId: string
+): ChatMessageSiblingInfo[] {
+ // Get the current conversation path
+ const currentPath = filterByLeafNodeId(messages, leafNodeId, true);
+ const result: ChatMessageSiblingInfo[] = [];
+
+ // Add sibling info for each message in the current path
+ for (const message of currentPath) {
+ if (message.type === 'root') {
+ continue; // Skip root messages in display
+ }
+
+ const siblingInfo = getMessageSiblings(messages, message.id);
+ if (siblingInfo) {
+ result.push(siblingInfo);
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Checks if a message has multiple siblings (indicating branching at that point).
+ *
+ * @param messages - All messages in the conversation
+ * @param messageId - The message to check
+ * @returns True if the message has siblings
+ */
+export function hasMessageSiblings(
+ messages: readonly DatabaseMessage[],
+ messageId: string
+): boolean {
+ const siblingInfo = getMessageSiblings(messages, messageId);
+ return siblingInfo ? siblingInfo.totalSiblings > 1 : false;
+}
+
+/**
+ * Gets the next sibling message ID for navigation.
+ *
+ * @param messages - All messages in the conversation
+ * @param messageId - Current message ID
+ * @returns Next sibling's leaf node ID, or null if at the end
+ */
+export function getNextSibling(
+ messages: readonly DatabaseMessage[],
+ messageId: string
+): string | null {
+ const siblingInfo = getMessageSiblings(messages, messageId);
+ if (!siblingInfo || siblingInfo.currentIndex >= siblingInfo.totalSiblings - 1) {
+ return null;
+ }
+
+ return siblingInfo.siblingIds[siblingInfo.currentIndex + 1];
+}
+
+/**
+ * Gets the previous sibling message ID for navigation.
+ *
+ * @param messages - All messages in the conversation
+ * @param messageId - Current message ID
+ * @returns Previous sibling's leaf node ID, or null if at the beginning
+ */
+export function getPreviousSibling(
+ messages: readonly DatabaseMessage[],
+ messageId: string
+): string | null {
+ const siblingInfo = getMessageSiblings(messages, messageId);
+ if (!siblingInfo || siblingInfo.currentIndex <= 0) {
+ return null;
+ }
+
+ return siblingInfo.siblingIds[siblingInfo.currentIndex - 1];
+}
--- /dev/null
+import { convertPDFToImage, convertPDFToText } from './pdf-processing';
+import { isSvgMimeType, svgBase64UrlToPngDataURL } from './svg-to-png';
+import { isWebpMimeType, webpBase64UrlToPngDataURL } from './webp-to-png';
+import { FileTypeCategory } from '$lib/enums/files';
+import { config, settingsStore } from '$lib/stores/settings.svelte';
+import { supportsVision } from '$lib/stores/server.svelte';
+import { getFileTypeCategory } from '$lib/utils/file-type';
+import { readFileAsText, isLikelyTextFile } from './text-files';
+import { toast } from 'svelte-sonner';
+
+function readFileAsBase64(file: File): Promise<string> {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+
+ reader.onload = () => {
+ // Extract base64 data without the data URL prefix
+ const dataUrl = reader.result as string;
+ const base64 = dataUrl.split(',')[1];
+ resolve(base64);
+ };
+
+ reader.onerror = () => reject(reader.error);
+
+ reader.readAsDataURL(file);
+ });
+}
+
+export interface FileProcessingResult {
+ extras: DatabaseMessageExtra[];
+ emptyFiles: string[];
+}
+
+export async function parseFilesToMessageExtras(
+ files: ChatUploadedFile[]
+): Promise<FileProcessingResult> {
+ const extras: DatabaseMessageExtra[] = [];
+ const emptyFiles: string[] = [];
+
+ for (const file of files) {
+ if (getFileTypeCategory(file.type) === FileTypeCategory.IMAGE) {
+ if (file.preview) {
+ let base64Url = file.preview;
+
+ if (isSvgMimeType(file.type)) {
+ try {
+ base64Url = await svgBase64UrlToPngDataURL(base64Url);
+ } catch (error) {
+ console.error('Failed to convert SVG to PNG for database storage:', error);
+ }
+ } else if (isWebpMimeType(file.type)) {
+ try {
+ base64Url = await webpBase64UrlToPngDataURL(base64Url);
+ } catch (error) {
+ console.error('Failed to convert WebP to PNG for database storage:', error);
+ }
+ }
+
+ extras.push({
+ type: 'imageFile',
+ name: file.name,
+ base64Url
+ });
+ }
+ } else if (getFileTypeCategory(file.type) === FileTypeCategory.AUDIO) {
+ // Process audio files (MP3 and WAV)
+ try {
+ const base64Data = await readFileAsBase64(file.file);
+
+ extras.push({
+ type: 'audioFile',
+ name: file.name,
+ base64Data: base64Data,
+ mimeType: file.type
+ });
+ } catch (error) {
+ console.error(`Failed to process audio file ${file.name}:`, error);
+ }
+ } else if (getFileTypeCategory(file.type) === FileTypeCategory.PDF) {
+ try {
+ // Always get base64 data for preview functionality
+ const base64Data = await readFileAsBase64(file.file);
+ const currentConfig = config();
+ const hasVisionSupport = supportsVision();
+
+ // Force PDF-to-text for non-vision models
+ let shouldProcessAsImages = Boolean(currentConfig.pdfAsImage) && hasVisionSupport;
+
+ // If user had pdfAsImage enabled but model doesn't support vision, update setting and notify
+ if (currentConfig.pdfAsImage && !hasVisionSupport) {
+ console.log('Non-vision model detected: forcing PDF-to-text mode and updating settings');
+
+ // Update the setting in localStorage
+ settingsStore.updateConfig('pdfAsImage', false);
+
+ // Show toast notification to user
+ toast.warning(
+ 'PDF setting changed: Non-vision model detected, PDFs will be processed as text instead of images.',
+ {
+ duration: 5000
+ }
+ );
+
+ shouldProcessAsImages = false;
+ }
+
+ if (shouldProcessAsImages) {
+ // Process PDF as images (only for vision models)
+ try {
+ const images = await convertPDFToImage(file.file);
+
+ // Show success toast for PDF image processing
+ toast.success(
+ `PDF "${file.name}" processed as ${images.length} images for vision model.`,
+ {
+ duration: 3000
+ }
+ );
+
+ extras.push({
+ type: 'pdfFile',
+ name: file.name,
+ content: `PDF file with ${images.length} pages`,
+ images: images,
+ processedAsImages: true,
+ base64Data: base64Data
+ });
+ } catch (imageError) {
+ console.warn(
+ `Failed to process PDF ${file.name} as images, falling back to text:`,
+ imageError
+ );
+
+ // Fallback to text processing
+ const content = await convertPDFToText(file.file);
+
+ extras.push({
+ type: 'pdfFile',
+ name: file.name,
+ content: content,
+ processedAsImages: false,
+ base64Data: base64Data
+ });
+ }
+ } else {
+ // Process PDF as text (default or forced for non-vision models)
+ const content = await convertPDFToText(file.file);
+
+ // Show success toast for PDF text processing
+ toast.success(`PDF "${file.name}" processed as text content.`, {
+ duration: 3000
+ });
+
+ extras.push({
+ type: 'pdfFile',
+ name: file.name,
+ content: content,
+ processedAsImages: false,
+ base64Data: base64Data
+ });
+ }
+ } catch (error) {
+ console.error(`Failed to process PDF file ${file.name}:`, error);
+ }
+ } else {
+ try {
+ const content = await readFileAsText(file.file);
+
+ // Check if file is empty
+ if (content.trim() === '') {
+ console.warn(`File ${file.name} is empty and will be skipped`);
+ emptyFiles.push(file.name);
+ } else if (isLikelyTextFile(content)) {
+ extras.push({
+ type: 'textFile',
+ name: file.name,
+ content: content
+ });
+ } else {
+ console.warn(`File ${file.name} appears to be binary and will be skipped`);
+ }
+ } catch (error) {
+ console.error(`Failed to read file ${file.name}:`, error);
+ }
+ }
+ }
+
+ return { extras, emptyFiles };
+}
--- /dev/null
+import { toast } from 'svelte-sonner';
+
+/**
+ * Copy text to clipboard with toast notification
+ * Uses modern clipboard API when available, falls back to legacy method for non-secure contexts
+ * @param text - Text to copy to clipboard
+ * @param successMessage - Custom success message (optional)
+ * @param errorMessage - Custom error message (optional)
+ * @returns Promise<boolean> - True if successful, false otherwise
+ */
+export async function copyToClipboard(
+ text: string,
+ successMessage = 'Copied to clipboard',
+ errorMessage = 'Failed to copy to clipboard'
+): Promise<boolean> {
+ try {
+ // Try modern clipboard API first (secure contexts only)
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ await navigator.clipboard.writeText(text);
+ toast.success(successMessage);
+ return true;
+ }
+
+ // Fallback for non-secure contexts
+ const textArea = document.createElement('textarea');
+ textArea.value = text;
+ textArea.style.position = 'fixed';
+ textArea.style.left = '-999999px';
+ textArea.style.top = '-999999px';
+ document.body.appendChild(textArea);
+ textArea.focus();
+ textArea.select();
+
+ const successful = document.execCommand('copy');
+ document.body.removeChild(textArea);
+
+ if (successful) {
+ toast.success(successMessage);
+ return true;
+ } else {
+ throw new Error('execCommand failed');
+ }
+ } catch (error) {
+ console.error('Failed to copy to clipboard:', error);
+ toast.error(errorMessage);
+ return false;
+ }
+}
+
+/**
+ * Copy code with HTML entity decoding and toast notification
+ * @param rawCode - Raw code string that may contain HTML entities
+ * @param successMessage - Custom success message (optional)
+ * @param errorMessage - Custom error message (optional)
+ * @returns Promise<boolean> - True if successful, false otherwise
+ */
+export async function copyCodeToClipboard(
+ rawCode: string,
+ successMessage = 'Code copied to clipboard',
+ errorMessage = 'Failed to copy code'
+): Promise<boolean> {
+ // Decode HTML entities
+ const decodedCode = rawCode
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, "'");
+
+ return copyToClipboard(decodedCode, successMessage, errorMessage);
+}
--- /dev/null
+/**
+ * Formats file size in bytes to human readable format
+ * @param bytes - File size in bytes
+ * @returns Formatted file size string
+ */
+export function formatFileSize(bytes: number): string {
+ if (bytes === 0) return '0 Bytes';
+
+ const k = 1024;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+}
+
+/**
+ * Gets a display label for a file type
+ * @param fileType - The file type/mime type
+ * @returns Formatted file type label
+ */
+export function getFileTypeLabel(fileType: string): string {
+ return fileType.split('/').pop()?.toUpperCase() || 'FILE';
+}
+
+/**
+ * Truncates text content for preview display
+ * @param content - The text content to truncate
+ * @returns Truncated content with ellipsis if needed
+ */
+export function getPreviewText(content: string): string {
+ return content.length > 150 ? content.substring(0, 150) + '...' : content;
+}
--- /dev/null
+import {
+ AUDIO_FILE_TYPES,
+ IMAGE_FILE_TYPES,
+ PDF_FILE_TYPES,
+ TEXT_FILE_TYPES
+} from '$lib/constants/supported-file-types';
+import { FileTypeCategory } from '$lib/enums/files';
+
+export function getFileTypeCategory(mimeType: string): FileTypeCategory | null {
+ if (
+ Object.values(IMAGE_FILE_TYPES).some((type) =>
+ (type.mimeTypes as readonly string[]).includes(mimeType)
+ )
+ ) {
+ return FileTypeCategory.IMAGE;
+ }
+
+ if (
+ Object.values(AUDIO_FILE_TYPES).some((type) =>
+ (type.mimeTypes as readonly string[]).includes(mimeType)
+ )
+ ) {
+ return FileTypeCategory.AUDIO;
+ }
+
+ if (
+ Object.values(PDF_FILE_TYPES).some((type) =>
+ (type.mimeTypes as readonly string[]).includes(mimeType)
+ )
+ ) {
+ return FileTypeCategory.PDF;
+ }
+
+ if (
+ Object.values(TEXT_FILE_TYPES).some((type) =>
+ (type.mimeTypes as readonly string[]).includes(mimeType)
+ )
+ ) {
+ return FileTypeCategory.TEXT;
+ }
+
+ return null;
+}
+
+export function getFileTypeByExtension(filename: string): string | null {
+ const extension = filename.toLowerCase().substring(filename.lastIndexOf('.'));
+
+ for (const [key, type] of Object.entries(IMAGE_FILE_TYPES)) {
+ if ((type.extensions as readonly string[]).includes(extension)) {
+ return `${FileTypeCategory.IMAGE}:${key}`;
+ }
+ }
+
+ for (const [key, type] of Object.entries(AUDIO_FILE_TYPES)) {
+ if ((type.extensions as readonly string[]).includes(extension)) {
+ return `${FileTypeCategory.AUDIO}:${key}`;
+ }
+ }
+
+ for (const [key, type] of Object.entries(PDF_FILE_TYPES)) {
+ if ((type.extensions as readonly string[]).includes(extension)) {
+ return `${FileTypeCategory.PDF}:${key}`;
+ }
+ }
+
+ for (const [key, type] of Object.entries(TEXT_FILE_TYPES)) {
+ if ((type.extensions as readonly string[]).includes(extension)) {
+ return `${FileTypeCategory.TEXT}:${key}`;
+ }
+ }
+
+ return null;
+}
+
+export function isFileTypeSupported(filename: string, mimeType?: string): boolean {
+ if (mimeType && getFileTypeCategory(mimeType)) {
+ return true;
+ }
+
+ return getFileTypeByExtension(filename) !== null;
+}
--- /dev/null
+/**
+ * File validation utilities based on model modalities
+ * Ensures only compatible file types are processed based on model capabilities
+ */
+
+import { getFileTypeCategory } from '$lib/utils/file-type';
+import { supportsVision, supportsAudio } from '$lib/stores/server.svelte';
+import {
+ FileExtensionAudio,
+ FileExtensionImage,
+ FileExtensionPdf,
+ FileExtensionText,
+ MimeTypeAudio,
+ MimeTypeImage,
+ MimeTypeApplication,
+ MimeTypeText,
+ FileTypeCategory
+} from '$lib/enums/files';
+
+/**
+ * Check if a file type is supported by the current model's modalities
+ * @param filename - The filename to check
+ * @param mimeType - The MIME type of the file
+ * @returns true if the file type is supported by the current model
+ */
+export function isFileTypeSupportedByModel(filename: string, mimeType?: string): boolean {
+ const category = mimeType ? getFileTypeCategory(mimeType) : null;
+
+ // If we can't determine the category from MIME type, fall back to general support check
+ if (!category) {
+ // For unknown types, only allow if they might be text files
+ // This is a conservative approach for edge cases
+ return true; // Let the existing isFileTypeSupported handle this
+ }
+
+ switch (category) {
+ case FileTypeCategory.TEXT:
+ // Text files are always supported
+ return true;
+
+ case FileTypeCategory.PDF:
+ // PDFs are always supported (will be processed as text for non-vision models)
+ return true;
+
+ case FileTypeCategory.IMAGE:
+ // Images require vision support
+ return supportsVision();
+
+ case FileTypeCategory.AUDIO:
+ // Audio files require audio support
+ return supportsAudio();
+
+ default:
+ // Unknown categories - be conservative and allow
+ return true;
+ }
+}
+
+/**
+ * Filter files based on model modalities and return supported/unsupported lists
+ * @param files - Array of files to filter
+ * @returns Object with supportedFiles and unsupportedFiles arrays
+ */
+export function filterFilesByModalities(files: File[]): {
+ supportedFiles: File[];
+ unsupportedFiles: File[];
+ modalityReasons: Record<string, string>;
+} {
+ const supportedFiles: File[] = [];
+ const unsupportedFiles: File[] = [];
+ const modalityReasons: Record<string, string> = {};
+
+ const hasVision = supportsVision();
+ const hasAudio = supportsAudio();
+
+ for (const file of files) {
+ const category = getFileTypeCategory(file.type);
+ let isSupported = true;
+ let reason = '';
+
+ switch (category) {
+ case FileTypeCategory.IMAGE:
+ if (!hasVision) {
+ isSupported = false;
+ reason = 'Images require a vision-capable model';
+ }
+ break;
+
+ case FileTypeCategory.AUDIO:
+ if (!hasAudio) {
+ isSupported = false;
+ reason = 'Audio files require an audio-capable model';
+ }
+ break;
+
+ case FileTypeCategory.TEXT:
+ case FileTypeCategory.PDF:
+ // Always supported
+ break;
+
+ default:
+ // For unknown types, check if it's a generally supported file type
+ // This handles edge cases and maintains backward compatibility
+ break;
+ }
+
+ if (isSupported) {
+ supportedFiles.push(file);
+ } else {
+ unsupportedFiles.push(file);
+ modalityReasons[file.name] = reason;
+ }
+ }
+
+ return { supportedFiles, unsupportedFiles, modalityReasons };
+}
+
+/**
+ * Generate a user-friendly error message for unsupported files
+ * @param unsupportedFiles - Array of unsupported files
+ * @param modalityReasons - Reasons why files are unsupported
+ * @returns Formatted error message
+ */
+export function generateModalityErrorMessage(
+ unsupportedFiles: File[],
+ modalityReasons: Record<string, string>
+): string {
+ if (unsupportedFiles.length === 0) return '';
+
+ const hasVision = supportsVision();
+ const hasAudio = supportsAudio();
+
+ let message = '';
+
+ if (unsupportedFiles.length === 1) {
+ const file = unsupportedFiles[0];
+ const reason = modalityReasons[file.name];
+ message = `The file "${file.name}" cannot be uploaded: ${reason}.`;
+ } else {
+ const fileNames = unsupportedFiles.map((f) => f.name).join(', ');
+ message = `The following files cannot be uploaded: ${fileNames}.`;
+ }
+
+ // Add helpful information about what is supported
+ const supportedTypes: string[] = ['text files', 'PDFs'];
+ if (hasVision) supportedTypes.push('images');
+ if (hasAudio) supportedTypes.push('audio files');
+
+ message += ` This model supports: ${supportedTypes.join(', ')}.`;
+
+ return message;
+}
+
+/**
+ * Generate file input accept string based on current model modalities
+ * @returns Accept string for HTML file input element
+ */
+export function generateModalityAwareAcceptString(): string {
+ const hasVision = supportsVision();
+ const hasAudio = supportsAudio();
+
+ const acceptedExtensions: string[] = [];
+ const acceptedMimeTypes: string[] = [];
+
+ // Always include text files and PDFs
+ acceptedExtensions.push(...Object.values(FileExtensionText));
+ acceptedMimeTypes.push(...Object.values(MimeTypeText));
+ acceptedExtensions.push(...Object.values(FileExtensionPdf));
+ acceptedMimeTypes.push(...Object.values(MimeTypeApplication));
+
+ // Include images only if vision is supported
+ if (hasVision) {
+ acceptedExtensions.push(...Object.values(FileExtensionImage));
+ acceptedMimeTypes.push(...Object.values(MimeTypeImage));
+ }
+
+ // Include audio only if audio is supported
+ if (hasAudio) {
+ acceptedExtensions.push(...Object.values(FileExtensionAudio));
+ acceptedMimeTypes.push(...Object.values(MimeTypeAudio));
+ }
+
+ return [...acceptedExtensions, ...acceptedMimeTypes].join(',');
+}
--- /dev/null
+/**
+ * PDF processing utilities using PDF.js
+ * Handles PDF text extraction and image conversion in the browser
+ */
+
+import { browser } from '$app/environment';
+import { MimeTypeApplication, MimeTypeImage } from '$lib/enums/files';
+import * as pdfjs from 'pdfjs-dist';
+
+type TextContent = {
+ items: Array<{ str: string }>;
+};
+
+if (browser) {
+ // Import worker as text and create blob URL for inline bundling
+ import('pdfjs-dist/build/pdf.worker.min.mjs?raw')
+ .then((workerModule) => {
+ const workerBlob = new Blob([workerModule.default], { type: 'application/javascript' });
+ pdfjs.GlobalWorkerOptions.workerSrc = URL.createObjectURL(workerBlob);
+ })
+ .catch(() => {
+ console.warn('Failed to load PDF.js worker, PDF processing may not work');
+ });
+}
+
+/**
+ * Convert a File object to ArrayBuffer for PDF.js processing
+ * @param file - The PDF file to convert
+ * @returns Promise resolving to the file's ArrayBuffer
+ */
+async function getFileAsBuffer(file: File): Promise<ArrayBuffer> {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ if (event.target?.result) {
+ resolve(event.target.result as ArrayBuffer);
+ } else {
+ reject(new Error('Failed to read file.'));
+ }
+ };
+ reader.onerror = () => {
+ reject(new Error('Failed to read file.'));
+ };
+ reader.readAsArrayBuffer(file);
+ });
+}
+
+/**
+ * Extract text content from a PDF file
+ * @param file - The PDF file to process
+ * @returns Promise resolving to the extracted text content
+ */
+export async function convertPDFToText(file: File): Promise<string> {
+ if (!browser) {
+ throw new Error('PDF processing is only available in the browser');
+ }
+
+ try {
+ const buffer = await getFileAsBuffer(file);
+ const pdf = await pdfjs.getDocument(buffer).promise;
+ const numPages = pdf.numPages;
+
+ const textContentPromises: Promise<TextContent>[] = [];
+
+ for (let i = 1; i <= numPages; i++) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ textContentPromises.push(pdf.getPage(i).then((page: any) => page.getTextContent()));
+ }
+
+ const textContents = await Promise.all(textContentPromises);
+ const textItems = textContents.flatMap((textContent: TextContent) =>
+ textContent.items.map((item) => item.str ?? '')
+ );
+
+ return textItems.join('\n');
+ } catch (error) {
+ console.error('Error converting PDF to text:', error);
+ throw new Error(
+ `Failed to convert PDF to text: ${error instanceof Error ? error.message : 'Unknown error'}`
+ );
+ }
+}
+
+/**
+ * Convert PDF pages to PNG images as data URLs
+ * @param file - The PDF file to convert
+ * @param scale - Rendering scale factor (default: 1.5)
+ * @returns Promise resolving to array of PNG data URLs
+ */
+export async function convertPDFToImage(file: File, scale: number = 1.5): Promise<string[]> {
+ if (!browser) {
+ throw new Error('PDF processing is only available in the browser');
+ }
+
+ try {
+ const buffer = await getFileAsBuffer(file);
+ const doc = await pdfjs.getDocument(buffer).promise;
+ const pages: Promise<string>[] = [];
+
+ for (let i = 1; i <= doc.numPages; i++) {
+ const page = await doc.getPage(i);
+ const viewport = page.getViewport({ scale });
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ canvas.width = viewport.width;
+ canvas.height = viewport.height;
+
+ if (!ctx) {
+ throw new Error('Failed to get 2D context from canvas');
+ }
+
+ const task = page.render({
+ canvasContext: ctx,
+ viewport: viewport,
+ canvas: canvas
+ });
+ pages.push(
+ task.promise.then(() => {
+ return canvas.toDataURL(MimeTypeImage.PNG);
+ })
+ );
+ }
+
+ return await Promise.all(pages);
+ } catch (error) {
+ console.error('Error converting PDF to images:', error);
+ throw new Error(
+ `Failed to convert PDF to images: ${error instanceof Error ? error.message : 'Unknown error'}`
+ );
+ }
+}
+
+/**
+ * Check if a file is a PDF based on its MIME type
+ * @param file - The file to check
+ * @returns True if the file is a PDF
+ */
+export function isPdfFile(file: File): boolean {
+ return file.type === MimeTypeApplication.PDF;
+}
+
+/**
+ * Check if a MIME type represents a PDF
+ * @param mimeType - The MIME type to check
+ * @returns True if the MIME type is application/pdf
+ */
+export function isApplicationMimeType(mimeType: string): boolean {
+ return mimeType === MimeTypeApplication.PDF;
+}
--- /dev/null
+import { isSvgMimeType, svgBase64UrlToPngDataURL } from './svg-to-png';
+import { isTextFileByName } from './text-files';
+import { isWebpMimeType, webpBase64UrlToPngDataURL } from './webp-to-png';
+import { FileTypeCategory } from '$lib/enums/files';
+import { getFileTypeCategory } from '$lib/utils/file-type';
+import { supportsVision } from '$lib/stores/server.svelte';
+import { settingsStore } from '$lib/stores/settings.svelte';
+import { toast } from 'svelte-sonner';
+
+/**
+ * Read a file as a data URL (base64 encoded)
+ * @param file - The file to read
+ * @returns Promise resolving to the data URL string
+ */
+function readFileAsDataURL(file: File): Promise<string> {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = () => resolve(reader.result as string);
+ reader.onerror = () => reject(reader.error);
+ reader.readAsDataURL(file);
+ });
+}
+
+/**
+ * Read a file as UTF-8 text
+ * @param file - The file to read
+ * @returns Promise resolving to the text content
+ */
+function readFileAsUTF8(file: File): Promise<string> {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = () => resolve(reader.result as string);
+ reader.onerror = () => reject(reader.error);
+ reader.readAsText(file);
+ });
+}
+
+/**
+ * Process uploaded files into ChatUploadedFile format with previews and content
+ *
+ * This function processes various file types and generates appropriate previews:
+ * - Images: Base64 data URLs with format normalization (SVG/WebP → PNG)
+ * - Text files: UTF-8 content extraction
+ * - PDFs: Metadata only (processed later in conversion pipeline)
+ * - Audio: Base64 data URLs for preview
+ *
+ * @param files - Array of File objects to process
+ * @returns Promise resolving to array of ChatUploadedFile objects
+ */
+export async function processFilesToChatUploaded(files: File[]): Promise<ChatUploadedFile[]> {
+ const results: ChatUploadedFile[] = [];
+
+ for (const file of files) {
+ const id = Date.now().toString() + Math.random().toString(36).substr(2, 9);
+ const base: ChatUploadedFile = {
+ id,
+ name: file.name,
+ size: file.size,
+ type: file.type,
+ file
+ };
+
+ try {
+ if (getFileTypeCategory(file.type) === FileTypeCategory.IMAGE) {
+ let preview = await readFileAsDataURL(file);
+
+ // Normalize SVG and WebP to PNG in previews
+ if (isSvgMimeType(file.type)) {
+ try {
+ preview = await svgBase64UrlToPngDataURL(preview);
+ } catch (err) {
+ console.error('Failed to convert SVG to PNG:', err);
+ }
+ } else if (isWebpMimeType(file.type)) {
+ try {
+ preview = await webpBase64UrlToPngDataURL(preview);
+ } catch (err) {
+ console.error('Failed to convert WebP to PNG:', err);
+ }
+ }
+
+ results.push({ ...base, preview });
+ } else if (
+ getFileTypeCategory(file.type) === FileTypeCategory.TEXT ||
+ isTextFileByName(file.name)
+ ) {
+ try {
+ const textContent = await readFileAsUTF8(file);
+ results.push({ ...base, textContent });
+ } catch (err) {
+ console.warn('Failed to read text file, adding without content:', err);
+ results.push(base);
+ }
+ } else if (getFileTypeCategory(file.type) === FileTypeCategory.PDF) {
+ // PDFs handled later when building extras; keep metadata only
+ results.push(base);
+
+ // Show suggestion toast if vision model is available but PDF as image is disabled
+ const hasVisionSupport = supportsVision();
+ const currentConfig = settingsStore.config;
+ if (hasVisionSupport && !currentConfig.pdfAsImage) {
+ toast.info(`You can enable parsing PDF as images with vision models.`, {
+ duration: 8000,
+ action: {
+ label: 'Enable PDF as Images',
+ onClick: () => {
+ settingsStore.updateConfig('pdfAsImage', true);
+ toast.success('PDF parsing as images enabled!', {
+ duration: 3000
+ });
+ }
+ }
+ });
+ }
+ } else if (getFileTypeCategory(file.type) === FileTypeCategory.AUDIO) {
+ // Generate preview URL for audio files
+ const preview = await readFileAsDataURL(file);
+ results.push({ ...base, preview });
+ } else {
+ // Other files: add as-is
+ results.push(base);
+ }
+ } catch (error) {
+ console.error('Error processing file', file.name, error);
+ results.push(base);
+ }
+ }
+
+ return results;
+}
--- /dev/null
+import { MimeTypeImage } from '$lib/enums/files';
+
+/**
+ * Convert an SVG base64 data URL to a PNG data URL
+ * @param base64UrlSvg - The SVG base64 data URL to convert
+ * @param backgroundColor - Background color for the PNG (default: 'white')
+ * @returns Promise resolving to PNG data URL
+ */
+export function svgBase64UrlToPngDataURL(
+ base64UrlSvg: string,
+ backgroundColor: string = 'white'
+): Promise<string> {
+ return new Promise((resolve, reject) => {
+ try {
+ const img = new Image();
+
+ img.onload = () => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ if (!ctx) {
+ reject(new Error('Failed to get 2D canvas context.'));
+ return;
+ }
+
+ const targetWidth = img.naturalWidth || 300;
+ const targetHeight = img.naturalHeight || 300;
+
+ canvas.width = targetWidth;
+ canvas.height = targetHeight;
+
+ if (backgroundColor) {
+ ctx.fillStyle = backgroundColor;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ }
+ ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
+
+ resolve(canvas.toDataURL(MimeTypeImage.PNG));
+ };
+
+ img.onerror = () => {
+ reject(new Error('Failed to load SVG image. Ensure the SVG data is valid.'));
+ };
+
+ img.src = base64UrlSvg;
+ } catch (error) {
+ const message = error instanceof Error ? error.message : String(error);
+ const errorMessage = `Error converting SVG to PNG: ${message}`;
+ console.error(errorMessage, error);
+ reject(new Error(errorMessage));
+ }
+ });
+}
+
+/**
+ * Check if a file is an SVG based on its MIME type
+ * @param file - The file to check
+ * @returns True if the file is an SVG
+ */
+export function isSvgFile(file: File): boolean {
+ return file.type === MimeTypeImage.SVG;
+}
+
+/**
+ * Check if a MIME type represents an SVG
+ * @param mimeType - The MIME type to check
+ * @returns True if the MIME type is image/svg+xml
+ */
+export function isSvgMimeType(mimeType: string): boolean {
+ return mimeType === MimeTypeImage.SVG;
+}
--- /dev/null
+/**
+ * Text file processing utilities
+ * Handles text file detection, reading, and validation
+ */
+
+import { FileExtensionText } from '$lib/enums/files';
+
+/**
+ * Check if a filename indicates a text file based on its extension
+ * @param filename - The filename to check
+ * @returns True if the filename has a recognized text file extension
+ */
+export function isTextFileByName(filename: string): boolean {
+ const textExtensions = Object.values(FileExtensionText);
+
+ return textExtensions.some((ext: FileExtensionText) => filename.toLowerCase().endsWith(ext));
+}
+
+/**
+ * Read a file's content as text
+ * @param file - The file to read
+ * @returns Promise resolving to the file's text content
+ */
+export async function readFileAsText(file: File): Promise<string> {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+
+ reader.onload = (event) => {
+ if (event.target?.result !== null && event.target?.result !== undefined) {
+ resolve(event.target.result as string);
+ } else {
+ reject(new Error('Failed to read file'));
+ }
+ };
+
+ reader.onerror = () => reject(new Error('File reading error'));
+
+ reader.readAsText(file);
+ });
+}
+
+/**
+ * Heuristic check to determine if content is likely from a text file
+ * Detects binary files by counting suspicious characters and null bytes
+ * @param content - The file content to analyze
+ * @returns True if the content appears to be text-based
+ */
+export function isLikelyTextFile(content: string): boolean {
+ if (!content) return true;
+
+ const sample = content.substring(0, 1000);
+
+ let suspiciousCount = 0;
+ let nullCount = 0;
+
+ for (let i = 0; i < sample.length; i++) {
+ const charCode = sample.charCodeAt(i);
+
+ // Count null bytes
+ if (charCode === 0) {
+ nullCount++;
+ suspiciousCount++;
+
+ continue;
+ }
+
+ // Count suspicious control characters (excluding common ones like tab, newline, carriage return)
+ if (charCode < 32 && charCode !== 9 && charCode !== 10 && charCode !== 13) {
+ suspiciousCount++;
+ }
+
+ // Count replacement characters (indicates encoding issues)
+ if (charCode === 0xfffd) {
+ suspiciousCount++;
+ }
+ }
+
+ // Reject if too many null bytes or suspicious characters
+ if (nullCount > 2) return false;
+ if (suspiciousCount / sample.length > 0.1) return false;
+
+ return true;
+}
--- /dev/null
+/**
+ * Parses thinking content from a message that may contain <think> tags
+ * Returns an object with thinking content and cleaned message content
+ * Handles both complete <think>...</think> blocks and incomplete <think> blocks (streaming)
+ * @param content - The message content to parse
+ * @returns An object containing the extracted thinking content and the cleaned message content
+ */
+export function parseThinkingContent(content: string): {
+ thinking: string | null;
+ cleanContent: string;
+} {
+ const incompleteMatch = content.includes('<think>') && !content.includes('</think>');
+
+ if (incompleteMatch) {
+ // Remove the entire <think>... part from clean content
+ const cleanContent = content.split('</think>')?.[1]?.trim();
+ // Extract everything after <think> as thinking content
+ const thinkingContent = content.split('<think>')?.[1]?.trim();
+
+ return {
+ cleanContent,
+ thinking: thinkingContent
+ };
+ }
+
+ const completeMatch = content.includes('</think>');
+
+ if (completeMatch) {
+ return {
+ thinking: content.split('</think>')?.[0]?.trim(),
+ cleanContent: content.split('</think>')?.[1]?.trim()
+ };
+ }
+
+ return {
+ thinking: null,
+ cleanContent: content
+ };
+}
+
+/**
+ * Checks if content contains an opening <think> tag (for streaming)
+ * @param content - The message content to check
+ * @returns True if the content contains an opening <think> tag
+ */
+export function hasThinkingStart(content: string): boolean {
+ return content.includes('<think>') || content.includes('<|channel|>analysis');
+}
+
+/**
+ * Checks if content contains a closing </think> tag (for streaming)
+ * @param content - The message content to check
+ * @returns True if the content contains a closing </think> tag
+ */
+export function hasThinkingEnd(content: string): boolean {
+ return content.includes('</think>');
+}
+
+/**
+ * Extracts partial thinking content during streaming
+ * Used when we have <think> but not yet </think>
+ * @param content - The message content to extract partial thinking from
+ * @returns An object containing the extracted partial thinking content and the remaining content
+ */
+export function extractPartialThinking(content: string): {
+ thinking: string | null;
+ remainingContent: string;
+} {
+ const startIndex = content.indexOf('<think>');
+ if (startIndex === -1) {
+ return { thinking: null, remainingContent: content };
+ }
+
+ const endIndex = content.indexOf('</think>');
+ if (endIndex === -1) {
+ // Still streaming thinking content
+ const thinkingStart = startIndex + '<think>'.length;
+ return {
+ thinking: content.substring(thinkingStart),
+ remainingContent: content.substring(0, startIndex)
+ };
+ }
+
+ // Complete thinking block found
+ const parsed = parseThinkingContent(content);
+ return {
+ thinking: parsed.thinking,
+ remainingContent: parsed.cleanContent
+ };
+}
--- /dev/null
+import { FileExtensionImage, MimeTypeImage } from '$lib/enums/files';
+
+/**
+ * Convert a WebP base64 data URL to a PNG data URL
+ * @param base64UrlWebp - The WebP base64 data URL to convert
+ * @param backgroundColor - Background color for the PNG (default: 'white')
+ * @returns Promise resolving to PNG data URL
+ */
+export function webpBase64UrlToPngDataURL(
+ base64UrlWebp: string,
+ backgroundColor: string = 'white'
+): Promise<string> {
+ return new Promise((resolve, reject) => {
+ try {
+ const img = new Image();
+
+ img.onload = () => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+
+ if (!ctx) {
+ reject(new Error('Failed to get 2D canvas context.'));
+ return;
+ }
+
+ const targetWidth = img.naturalWidth || 300;
+ const targetHeight = img.naturalHeight || 300;
+
+ canvas.width = targetWidth;
+ canvas.height = targetHeight;
+
+ if (backgroundColor) {
+ ctx.fillStyle = backgroundColor;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ }
+ ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
+
+ resolve(canvas.toDataURL(MimeTypeImage.PNG));
+ };
+
+ img.onerror = () => {
+ reject(new Error('Failed to load WebP image. Ensure the WebP data is valid.'));
+ };
+
+ img.src = base64UrlWebp;
+ } catch (error) {
+ const message = error instanceof Error ? error.message : String(error);
+ const errorMessage = `Error converting WebP to PNG: ${message}`;
+ console.error(errorMessage, error);
+ reject(new Error(errorMessage));
+ }
+ });
+}
+
+/**
+ * Check if a file is a WebP based on its MIME type
+ * @param file - The file to check
+ * @returns True if the file is a WebP
+ */
+export function isWebpFile(file: File): boolean {
+ return (
+ file.type === MimeTypeImage.WEBP || file.name.toLowerCase().endsWith(FileExtensionImage.WEBP)
+ );
+}
+
+/**
+ * Check if a MIME type represents a WebP
+ * @param mimeType - The MIME type to check
+ * @returns True if the MIME type is image/webp
+ */
+export function isWebpMimeType(mimeType: string): boolean {
+ return mimeType === MimeTypeImage.WEBP;
+}
+++ /dev/null
-import { StrictMode } from 'react';
-import { createRoot } from 'react-dom/client';
-import './index.scss';
-import App from './App.tsx';
-
-createRoot(document.getElementById('root')!).render(
- <StrictMode>
- <App />
- </StrictMode>
-);
--- /dev/null
+<script lang="ts">
+ import { page } from '$app/stores';
+ import { goto } from '$app/navigation';
+ import { ServerErrorSplash } from '$lib/components/app';
+
+ let error = $derived($page.error);
+ let status = $derived($page.status);
+
+ // Check if this is an API key related error
+ let isApiKeyError = $derived(
+ status === 401 ||
+ status === 403 ||
+ error?.message?.toLowerCase().includes('access denied') ||
+ error?.message?.toLowerCase().includes('unauthorized') ||
+ error?.message?.toLowerCase().includes('invalid api key')
+ );
+
+ function handleRetry() {
+ // Navigate back to home page after successful API key validation
+ goto('/');
+ }
+</script>
+
+<svelte:head>
+ <title>Error {status} - WebUI</title>
+</svelte:head>
+
+{#if isApiKeyError}
+ <ServerErrorSplash
+ error={error?.message || 'Access denied - check server permissions'}
+ onRetry={handleRetry}
+ showRetry={false}
+ showTroubleshooting={false}
+ />
+{:else}
+ <!-- Generic error page for non-API key errors -->
+ <div class="flex h-full items-center justify-center">
+ <div class="w-full max-w-md px-4 text-center">
+ <div class="mb-6">
+ <div
+ class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-destructive/10"
+ >
+ <svg
+ class="h-8 w-8 text-destructive"
+ fill="none"
+ stroke="currentColor"
+ viewBox="0 0 24 24"
+ >
+ <path
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ stroke-width="2"
+ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
+ />
+ </svg>
+ </div>
+ <h1 class="mb-2 text-2xl font-bold">Error {status}</h1>
+ <p class="text-muted-foreground">
+ {error?.message || 'Something went wrong'}
+ </p>
+ </div>
+ <button
+ onclick={() => goto('/')}
+ class="rounded-md bg-primary px-4 py-2 text-primary-foreground hover:bg-primary/90"
+ >
+ Go Home
+ </button>
+ </div>
+ </div>
+{/if}
--- /dev/null
+<script lang="ts">
+ import '../app.css';
+ import { page } from '$app/state';
+ import {
+ ChatSidebar,
+ ConversationTitleUpdateDialog,
+ MaximumContextAlertDialog
+ } from '$lib/components/app';
+ import {
+ activeMessages,
+ isLoading,
+ setTitleUpdateConfirmationCallback
+ } from '$lib/stores/chat.svelte';
+ import * as Sidebar from '$lib/components/ui/sidebar/index.js';
+ import { serverStore } from '$lib/stores/server.svelte';
+ import { config } from '$lib/stores/settings.svelte';
+ import { ModeWatcher } from 'mode-watcher';
+ import { Toaster } from 'svelte-sonner';
+ import { goto } from '$app/navigation';
+
+ let { children } = $props();
+
+ let isChatRoute = $derived(page.route.id === '/chat/[id]');
+ let isHomeRoute = $derived(page.route.id === '/');
+ let isNewChatMode = $derived(page.url.searchParams.get('new_chat') === 'true');
+ let showSidebarByDefault = $derived(activeMessages().length > 0 || isLoading());
+ let sidebarOpen = $state(false);
+ let chatSidebar:
+ | { activateSearchMode?: () => void; editActiveConversation?: () => void }
+ | undefined = $state();
+
+ // Conversation title update dialog state
+ let titleUpdateDialogOpen = $state(false);
+ let titleUpdateCurrentTitle = $state('');
+ let titleUpdateNewTitle = $state('');
+ let titleUpdateResolve: ((value: boolean) => void) | null = null;
+
+ // Global keyboard shortcuts
+ function handleKeydown(event: KeyboardEvent) {
+ const isCtrlOrCmd = event.ctrlKey || event.metaKey;
+
+ if (isCtrlOrCmd && event.key === 'k') {
+ event.preventDefault();
+ if (chatSidebar?.activateSearchMode) {
+ chatSidebar.activateSearchMode();
+ sidebarOpen = true;
+ }
+ }
+
+ if (isCtrlOrCmd && event.shiftKey && event.key === 'o') {
+ event.preventDefault();
+ goto('/?new_chat=true');
+ }
+
+ if (event.shiftKey && isCtrlOrCmd && event.key === 'e') {
+ event.preventDefault();
+
+ if (chatSidebar?.editActiveConversation) {
+ chatSidebar.editActiveConversation();
+ }
+ }
+ }
+
+ function handleTitleUpdateCancel() {
+ titleUpdateDialogOpen = false;
+ if (titleUpdateResolve) {
+ titleUpdateResolve(false);
+ titleUpdateResolve = null;
+ }
+ }
+
+ function handleTitleUpdateConfirm() {
+ titleUpdateDialogOpen = false;
+ if (titleUpdateResolve) {
+ titleUpdateResolve(true);
+ titleUpdateResolve = null;
+ }
+ }
+
+ $effect(() => {
+ if (isHomeRoute && !isNewChatMode) {
+ // Auto-collapse sidebar when navigating to home route (but not in new chat mode)
+ sidebarOpen = false;
+ } else if (isHomeRoute && isNewChatMode) {
+ // Keep sidebar open in new chat mode
+ sidebarOpen = true;
+ } else if (isChatRoute) {
+ // On chat routes, show sidebar by default
+ sidebarOpen = true;
+ } else {
+ // Other routes follow default behavior
+ sidebarOpen = showSidebarByDefault;
+ }
+ });
+
+ // Initialize server properties on app load
+ $effect(() => {
+ serverStore.fetchServerProps();
+ });
+
+ // Monitor API key changes and redirect to error page if removed or changed when required
+ $effect(() => {
+ const apiKey = config().apiKey;
+
+ if (
+ (page.route.id === '/' || page.route.id === '/chat/[id]') &&
+ page.status !== 401 &&
+ page.status !== 403
+ ) {
+ const headers: Record<string, string> = {
+ 'Content-Type': 'application/json'
+ };
+
+ if (apiKey && apiKey.trim() !== '') {
+ headers.Authorization = `Bearer ${apiKey.trim()}`;
+ }
+
+ fetch('/props', { headers })
+ .then((response) => {
+ if (response.status === 401 || response.status === 403) {
+ window.location.reload();
+ }
+ })
+ .catch((e) => {
+ console.error('Error checking API key:', e);
+ });
+ }
+ });
+
+ // Set up title update confirmation callback
+ $effect(() => {
+ setTitleUpdateConfirmationCallback(async (currentTitle: string, newTitle: string) => {
+ return new Promise<boolean>((resolve) => {
+ titleUpdateCurrentTitle = currentTitle;
+ titleUpdateNewTitle = newTitle;
+ titleUpdateResolve = resolve;
+ titleUpdateDialogOpen = true;
+ });
+ });
+ });
+</script>
+
+<ModeWatcher />
+
+<Toaster richColors />
+
+<MaximumContextAlertDialog />
+
+<ConversationTitleUpdateDialog
+ bind:open={titleUpdateDialogOpen}
+ currentTitle={titleUpdateCurrentTitle}
+ newTitle={titleUpdateNewTitle}
+ onConfirm={handleTitleUpdateConfirm}
+ onCancel={handleTitleUpdateCancel}
+/>
+
+<Sidebar.Provider bind:open={sidebarOpen}>
+ <div class="flex h-screen w-full">
+ <Sidebar.Root class="h-full">
+ <ChatSidebar bind:this={chatSidebar} />
+ </Sidebar.Root>
+
+ <Sidebar.Trigger
+ class="transition-left absolute h-8 w-8 duration-200 ease-linear {sidebarOpen
+ ? 'md:left-[var(--sidebar-width)]'
+ : 'left-0'}"
+ style="translate: 1rem 1rem; z-index: 99999;"
+ />
+
+ <Sidebar.Inset class="flex flex-1 flex-col overflow-hidden">
+ {@render children?.()}
+ </Sidebar.Inset>
+ </div>
+</Sidebar.Provider>
+
+<svelte:window onkeydown={handleKeydown} />
--- /dev/null
+export const csr = true;
+export const prerender = false;
+export const ssr = false;
--- /dev/null
+<script lang="ts">
+ import { ChatScreen } from '$lib/components/app';
+ import { chatStore, isInitialized } from '$lib/stores/chat.svelte';
+ import { onMount } from 'svelte';
+
+ onMount(async () => {
+ if (!isInitialized) {
+ await chatStore.initialize();
+ }
+
+ chatStore.clearActiveConversation();
+ });
+</script>
+
+<svelte:head>
+ <title>llama.cpp - AI Chat Interface</title>
+</svelte:head>
+
+<ChatScreen showCenteredEmpty={true} />
--- /dev/null
+import type { PageLoad } from './$types';
+import { validateApiKey } from '$lib/utils/api-key-validation';
+
+export const load: PageLoad = async ({ fetch }) => {
+ await validateApiKey(fetch);
+};
--- /dev/null
+<script lang="ts">
+ import { goto } from '$app/navigation';
+ import { page } from '$app/state';
+ import { beforeNavigate } from '$app/navigation';
+ import { ChatScreen } from '$lib/components/app';
+ import {
+ chatStore,
+ activeConversation,
+ isLoading,
+ stopGeneration,
+ gracefulStop
+ } from '$lib/stores/chat.svelte';
+ import { onDestroy } from 'svelte';
+
+ let chatId = $derived(page.params.id);
+ let currentChatId: string | undefined = undefined;
+
+ beforeNavigate(async ({ cancel, to }) => {
+ if (isLoading()) {
+ console.log(
+ 'Navigation detected while streaming - aborting stream and saving partial response'
+ );
+
+ cancel();
+
+ await gracefulStop();
+
+ if (to?.url) {
+ await goto(to.url.pathname + to.url.search);
+ }
+ }
+ });
+
+ $effect(() => {
+ if (chatId && chatId !== currentChatId) {
+ if (isLoading()) {
+ console.log('Chat switch detected while streaming - aborting stream');
+ stopGeneration();
+ }
+
+ currentChatId = chatId;
+
+ (async () => {
+ const success = await chatStore.loadConversation(chatId);
+
+ if (!success) {
+ await goto('/');
+ }
+ })();
+ }
+ });
+
+ $effect(() => {
+ if (typeof window !== 'undefined') {
+ const handleBeforeUnload = () => {
+ if (isLoading()) {
+ console.log('Page unload detected while streaming - aborting stream');
+ stopGeneration();
+ }
+ };
+
+ window.addEventListener('beforeunload', handleBeforeUnload);
+
+ return () => {
+ window.removeEventListener('beforeunload', handleBeforeUnload);
+ };
+ }
+ });
+
+ onDestroy(() => {
+ if (isLoading()) {
+ stopGeneration();
+ }
+ });
+</script>
+
+<svelte:head>
+ <title>{activeConversation()?.name || 'Chat'} - llama.cpp</title>
+</svelte:head>
+
+<ChatScreen />
--- /dev/null
+import type { PageLoad } from './$types';
+import { validateApiKey } from '$lib/utils/api-key-validation';
+
+export const load: PageLoad = async ({ fetch }) => {
+ await validateApiKey(fetch);
+};
--- /dev/null
+import { describe, it } from 'vitest';
+import { render } from 'vitest-browser-svelte';
+import Page from './+page.svelte';
+
+describe('/+page.svelte', () => {
+ it('should render page', async () => {
+ render(Page);
+
+ // todo - add tests
+ });
+});
--- /dev/null
+<script module lang="ts">
+ import { defineMeta } from '@storybook/addon-svelte-csf';
+ import ChatForm from '$lib/components/app/chat/ChatForm/ChatForm.svelte';
+ import { expect } from 'storybook/internal/test';
+ import { mockServerProps, mockConfigs } from './fixtures/storybook-mocks';
+ import jpgAsset from './fixtures/assets/1.jpg?url';
+ import svgAsset from './fixtures/assets/hf-logo.svg?url';
+ import pdfAsset from './fixtures/assets/example.pdf?raw';
+
+ const { Story } = defineMeta({
+ title: 'Components/ChatScreen/ChatForm',
+ component: ChatForm,
+ parameters: {
+ layout: 'centered'
+ }
+ });
+
+ let fileAttachments = $state([
+ {
+ id: '1',
+ name: '1.jpg',
+ type: 'image/jpeg',
+ size: 44891,
+ preview: jpgAsset,
+ file: new File([''], '1.jpg', { type: 'image/jpeg' })
+ },
+ {
+ id: '2',
+ name: 'hf-logo.svg',
+ type: 'image/svg+xml',
+ size: 1234,
+ preview: svgAsset,
+ file: new File([''], 'hf-logo.svg', { type: 'image/svg+xml' })
+ },
+ {
+ id: '3',
+ name: 'example.pdf',
+ type: 'application/pdf',
+ size: 351048,
+ file: new File([pdfAsset], 'example.pdf', { type: 'application/pdf' })
+ }
+ ]);
+</script>
+
+<Story
+ name="Default"
+ args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
+ play={async ({ canvas, userEvent }) => {
+ mockServerProps(mockConfigs.noModalities);
+
+ const textarea = await canvas.findByRole('textbox');
+ const submitButton = await canvas.findByRole('button', { name: 'Send' });
+
+ // Expect the input to be focused after the component is mounted
+ await expect(textarea).toHaveFocus();
+
+ // Expect the submit button to be disabled
+ await expect(submitButton).toBeDisabled();
+
+ const text = 'What is the meaning of life?';
+
+ await userEvent.clear(textarea);
+ await userEvent.type(textarea, text);
+
+ await expect(textarea).toHaveValue(text);
+
+ const fileInput = document.querySelector('input[type="file"]');
+ const acceptAttr = fileInput?.getAttribute('accept');
+ await expect(fileInput).toHaveAttribute('accept');
+ await expect(acceptAttr).not.toContain('image/');
+ await expect(acceptAttr).not.toContain('audio/');
+
+ const fileUploadButton = canvas.getByText('Attach files');
+
+ await userEvent.click(fileUploadButton);
+
+ const recordButton = canvas.getAllByRole('button', { name: 'Start recording' })[1];
+ const imagesButton = document.querySelector('.images-button');
+ const audioButton = document.querySelector('.audio-button');
+
+ await expect(recordButton).toBeDisabled();
+ await expect(imagesButton).toHaveAttribute('data-disabled');
+ await expect(audioButton).toHaveAttribute('data-disabled');
+ }}
+/>
+
+<Story name="Loading" args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]', isLoading: true }} />
+
+<Story
+ name="VisionModality"
+ args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
+ play={async ({ canvas, userEvent }) => {
+ mockServerProps(mockConfigs.visionOnly);
+
+ // Test initial file input state (should accept images but not audio)
+ const fileInput = document.querySelector('input[type="file"]');
+ const acceptAttr = fileInput?.getAttribute('accept');
+ console.log('Vision modality accept attr:', acceptAttr);
+
+ const fileUploadButton = canvas.getByText('Attach files');
+ await userEvent.click(fileUploadButton);
+
+ // Test that record button is disabled (no audio support)
+ const recordButton = canvas.getAllByRole('button', { name: 'Start recording' })[1];
+ await expect(recordButton).toBeDisabled();
+
+ // Test that Images button is enabled (vision support)
+ const imagesButton = document.querySelector('.images-button');
+ await expect(imagesButton).not.toHaveAttribute('data-disabled');
+
+ // Test that Audio button is disabled (no audio support)
+ const audioButton = document.querySelector('.audio-button');
+ await expect(audioButton).toHaveAttribute('data-disabled');
+
+ // Fix for dropdown menu side effect
+ const body = document.querySelector('body');
+ if (body) body.style.pointerEvents = 'all';
+
+ console.log('✅ Vision modality: Images enabled, Audio/Recording disabled');
+ }}
+/>
+
+<Story
+ name="AudioModality"
+ args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
+ play={async ({ canvas, userEvent }) => {
+ mockServerProps(mockConfigs.audioOnly);
+
+ // Test initial file input state (should accept audio but not images)
+ const fileInput = document.querySelector('input[type="file"]');
+ const acceptAttr = fileInput?.getAttribute('accept');
+ console.log('Audio modality accept attr:', acceptAttr);
+
+ const fileUploadButton = canvas.getByText('Attach files');
+ await userEvent.click(fileUploadButton);
+
+ // Test that record button is enabled (audio support)
+ const recordButton = canvas.getAllByRole('button', { name: 'Start recording' })[1];
+ await expect(recordButton).not.toBeDisabled();
+
+ // Test that Images button is disabled (no vision support)
+ const imagesButton = document.querySelector('.images-button');
+ await expect(imagesButton).toHaveAttribute('data-disabled');
+
+ // Test that Audio button is enabled (audio support)
+ const audioButton = document.querySelector('.audio-button');
+ await expect(audioButton).not.toHaveAttribute('data-disabled');
+
+ // Fix for dropdown menu side effect
+ const body = document.querySelector('body');
+ if (body) body.style.pointerEvents = 'all';
+
+ console.log('✅ Audio modality: Audio/Recording enabled, Images disabled');
+ }}
+/>
+
+<Story
+ name="FileAttachments"
+ args={{
+ class: 'max-w-[56rem] w-[calc(100vw-2rem)]',
+ uploadedFiles: fileAttachments
+ }}
+ play={async ({ canvas }) => {
+ mockServerProps(mockConfigs.bothModalities);
+
+ const jpgAttachment = canvas.getByAltText('1.jpg');
+ const svgAttachment = canvas.getByAltText('hf-logo.svg');
+ const pdfFileExtension = canvas.getByText('PDF');
+ const pdfAttachment = canvas.getByText('example.pdf');
+ const pdfSize = canvas.getByText('342.82 KB');
+
+ await expect(jpgAttachment).toBeInTheDocument();
+ await expect(jpgAttachment).toHaveAttribute('src', jpgAsset);
+
+ await expect(svgAttachment).toBeInTheDocument();
+ await expect(svgAttachment).toHaveAttribute('src', svgAsset);
+
+ await expect(pdfFileExtension).toBeInTheDocument();
+ await expect(pdfAttachment).toBeInTheDocument();
+ await expect(pdfSize).toBeInTheDocument();
+ }}
+/>
--- /dev/null
+<script module lang="ts">
+ import { defineMeta } from '@storybook/addon-svelte-csf';
+ import ChatMessage from '$lib/components/app/chat/ChatMessages/ChatMessage.svelte';
+
+ const { Story } = defineMeta({
+ title: 'Components/ChatScreen/ChatMessage',
+ component: ChatMessage,
+ parameters: {
+ layout: 'centered'
+ }
+ });
+
+ // Mock messages for different scenarios
+ const userMessage: DatabaseMessage = {
+ id: '1',
+ convId: 'conv-1',
+ type: 'message',
+ timestamp: Date.now() - 1000 * 60 * 5,
+ role: 'user',
+ content: 'What is the meaning of life, the universe, and everything?',
+ parent: '',
+ thinking: '',
+ children: []
+ };
+
+ const assistantMessage: DatabaseMessage = {
+ id: '2',
+ convId: 'conv-1',
+ type: 'message',
+ timestamp: Date.now() - 1000 * 60 * 3,
+ role: 'assistant',
+ content:
+ 'The answer to the ultimate question of life, the universe, and everything is **42**.\n\nThis comes from Douglas Adams\' "The Hitchhiker\'s Guide to the Galaxy," where a supercomputer named Deep Thought calculated this answer over 7.5 million years. However, the question itself was never properly formulated, which is why the answer seems meaningless without context.',
+ parent: '1',
+ thinking: '',
+ children: []
+ };
+
+ let processingMessage = $state({
+ id: '4',
+ convId: 'conv-1',
+ type: 'message',
+ timestamp: 0, // No timestamp = processing
+ role: 'assistant',
+ content: '',
+ parent: '1',
+ thinking: '',
+ children: []
+ });
+
+ let streamingMessage = $state({
+ id: '5',
+ convId: 'conv-1',
+ type: 'message',
+ timestamp: 0, // No timestamp = streaming
+ role: 'assistant',
+ content: '',
+ parent: '1',
+ thinking: '',
+ children: []
+ });
+</script>
+
+<Story
+ name="User"
+ args={{
+ message: userMessage
+ }}
+/>
+
+<Story
+ name="Assistant"
+ args={{
+ class: 'max-w-[56rem] w-[calc(100vw-2rem)]',
+ message: assistantMessage
+ }}
+/>
+
+<Story
+ name="WithThinkingBlock"
+ args={{
+ message: streamingMessage
+ }}
+ asChild
+ play={async () => {
+ // Phase 1: Stream reasoning content in chunks
+ let reasoningText =
+ 'I need to think about this carefully. Let me break down the problem:\n\n1. The user is asking for help with something complex\n2. I should provide a thorough and helpful response\n3. I need to consider multiple approaches\n4. The best solution would be to explain step by step\n\nThis approach will ensure clarity and understanding.';
+
+ let reasoningChunk = 'I';
+ let i = 0;
+ while (i < reasoningText.length) {
+ const chunkSize = Math.floor(Math.random() * 5) + 3; // Random 3-7 characters
+ const chunk = reasoningText.slice(i, i + chunkSize);
+ reasoningChunk += chunk;
+
+ // Update the reactive state directly
+ streamingMessage.thinking = reasoningChunk;
+
+ i += chunkSize;
+ await new Promise((resolve) => setTimeout(resolve, 50));
+ }
+
+ const regularText =
+ "Based on my analysis, here's the solution:\n\n**Step 1:** First, we need to understand the requirements clearly.\n\n**Step 2:** Then we can implement the solution systematically.\n\n**Step 3:** Finally, we test and validate the results.\n\nThis approach ensures we cover all aspects of the problem effectively.";
+
+ let contentChunk = '';
+ i = 0;
+
+ while (i < regularText.length) {
+ const chunkSize = Math.floor(Math.random() * 5) + 3; // Random 3-7 characters
+ const chunk = regularText.slice(i, i + chunkSize);
+ contentChunk += chunk;
+
+ // Update the reactive state directly
+ streamingMessage.content = contentChunk;
+
+ i += chunkSize;
+ await new Promise((resolve) => setTimeout(resolve, 50));
+ }
+
+ streamingMessage.timestamp = Date.now();
+ }}
+>
+ <div class="w-[56rem]">
+ <ChatMessage message={streamingMessage} />
+ </div>
+</Story>
+
+<Story
+ name="Processing"
+ args={{
+ message: processingMessage
+ }}
+ play={async () => {
+ // Import the chat store to simulate loading state
+ const { chatStore } = await import('$lib/stores/chat.svelte');
+
+ // Set loading state to true to trigger the processing UI
+ chatStore.isLoading = true;
+
+ // Simulate the processing state hook behavior
+ // This will show the "Generating..." text and parameter details
+ await new Promise(resolve => setTimeout(resolve, 100));
+ }}
+/>
--- /dev/null
+<script module>
+ import { defineMeta } from '@storybook/addon-svelte-csf';
+ import { ChatSettingsDialog } from '$lib/components/app';
+ import { fn } from 'storybook/test';
+
+ const { Story } = defineMeta({
+ title: 'Components/ChatSettingsDialog',
+ component: ChatSettingsDialog,
+ parameters: {
+ layout: 'fullscreen'
+ },
+ argTypes: {
+ open: {
+ control: 'boolean',
+ description: 'Whether the dialog is open'
+ }
+ },
+ args: {
+ onOpenChange: fn()
+ }
+ });
+</script>
+
+<Story name="Open" args={{ open: true }} />
+
+<Story name="Closed" args={{ open: false }} />
--- /dev/null
+<script module lang="ts">
+ import { defineMeta } from '@storybook/addon-svelte-csf';
+ import ChatSidebar from '$lib/components/app/chat/ChatSidebar/ChatSidebar.svelte';
+ import { waitFor } from 'storybook/internal/test';
+ import { screen } from 'storybook/test';
+
+ const { Story } = defineMeta({
+ title: 'Components/ChatSidebar',
+ component: ChatSidebar,
+ parameters: {
+ layout: 'centered'
+ }
+ });
+
+ // Mock conversations for the sidebar
+ const mockConversations: DatabaseConversation[] = [
+ {
+ id: 'conv-1',
+ name: 'Getting Started with AI',
+ lastModified: Date.now() - 1000 * 60 * 5, // 5 minutes ago
+ currNode: 'msg-1'
+ },
+ {
+ id: 'conv-2',
+ name: 'Python Programming Help',
+ lastModified: Date.now() - 1000 * 60 * 60 * 2, // 2 hours ago
+ currNode: 'msg-2'
+ },
+ {
+ id: 'conv-3',
+ name: 'Creative Writing Ideas',
+ lastModified: Date.now() - 1000 * 60 * 60 * 24, // 1 day ago
+ currNode: 'msg-3'
+ },
+ {
+ id: 'conv-4',
+ name: 'This is a very long conversation title that should be truncated properly when displayed',
+ lastModified: Date.now() - 1000 * 60 * 60 * 24 * 3, // 3 days ago
+ currNode: 'msg-4'
+ },
+ {
+ id: 'conv-5',
+ name: 'Math Problem Solving',
+ lastModified: Date.now() - 1000 * 60 * 60 * 24 * 7, // 1 week ago
+ currNode: 'msg-5'
+ }
+ ];
+</script>
+
+<Story
+ asChild
+ name="Default"
+ play={async () => {
+ const { chatStore } = await import('$lib/stores/chat.svelte');
+
+ waitFor(() => setTimeout(() => {
+ chatStore.conversations = mockConversations;
+ }, 0));
+ }}
+>
+ <div class="flex-column h-full h-screen w-72 bg-background">
+ <ChatSidebar />
+ </div>
+</Story>
+
+<Story
+ asChild
+ name="SearchActive"
+ play={async ({ userEvent }) => {
+ const { chatStore } = await import('$lib/stores/chat.svelte');
+
+ waitFor(() => setTimeout(() => {
+ chatStore.conversations = mockConversations;
+ }, 0));
+
+ const searchTrigger = screen.getByText('Search conversations');
+ userEvent.click(searchTrigger);
+ }}
+>
+ <div class="flex-column h-full h-screen w-72 bg-background">
+ <ChatSidebar />
+ </div>
+</Story>
+
+<Story
+ asChild
+ name="Empty"
+ play={async () => {
+ // Mock empty conversations store
+ const { chatStore } = await import('$lib/stores/chat.svelte');
+ chatStore.conversations = [];
+ }}
+>
+ <div class="flex-column h-full h-screen w-72 bg-background">
+ <ChatSidebar />
+ </div>
+</Story>
--- /dev/null
+import { Meta } from '@storybook/addon-docs/blocks';
+
+<Meta title="Introduction" />
+
+# llama.cpp Web UI
+
+Welcome to the **llama.cpp Web UI** component library! This Storybook showcases the components used in the modern web interface for the llama.cpp server.
+
+## 🚀 About This Project
+
+WebUI is a modern web interface for the llama.cpp server, built with SvelteKit and ShadCN UI. Features include:
+
+- **Real-time chat conversations** with AI assistants
+- **Multi-conversation management** with persistent storage
+- **Advanced parameter tuning** for model behavior
+- **File upload support** for multimodal interactions
+- **Responsive design** that works on desktop and mobile
+
+## 🎨 Design System
+
+The UI is built using:
+
+- **SvelteKit** - Modern web framework with excellent performance
+- **Tailwind CSS** - Utility-first CSS framework for rapid styling
+- **ShadCN/UI** - High-quality, accessible component library
+- **Lucide Icons** - Beautiful, consistent icon set
+
+## 🔧 Development
+
+This Storybook serves as both documentation and a development environment for the UI components. Each story demonstrates:
+
+- **Component variations** - Different states and configurations
+- **Interactive examples** - Live components you can interact with
+- **Usage patterns** - How components work together
+- **Styling consistency** - Unified design language
+
+## 🚀 Getting Started
+
+To explore the components:
+
+1. **Browse the sidebar** to see all available components
+2. **Click on stories** to see different component states
+3. **Use the controls panel** to interact with component props
+4. **Check the docs tab** for detailed component information
--- /dev/null
+<script module lang="ts">
+ import { defineMeta } from '@storybook/addon-svelte-csf';
+ import { MarkdownContent } from '$lib/components/app';
+ import { AI_TUTORIAL_MD } from './fixtures/ai-tutorial.js';
+ import { API_DOCS_MD } from './fixtures/api-docs.js';
+ import { BLOG_POST_MD } from './fixtures/blog-post.js';
+ import { DATA_ANALYSIS_MD } from './fixtures/data-analysis.js';
+ import { README_MD } from './fixtures/readme.js';
+ import { MATH_FORMULAS_MD } from './fixtures/math-formulas.js';
+ import { EMPTY_MD } from './fixtures/empty.js';
+
+ const { Story } = defineMeta({
+ title: 'Components/MarkdownContent',
+ component: MarkdownContent,
+ parameters: {
+ layout: 'centered'
+ }
+ });
+</script>
+
+<Story name="Empty" args={{ content: EMPTY_MD, class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }} />
+
+<Story
+ name="AI Tutorial"
+ args={{ content: AI_TUTORIAL_MD, class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
+/>
+
+<Story
+ name="API Documentation"
+ args={{ content: API_DOCS_MD, class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
+/>
+
+<Story
+ name="Technical Blog"
+ args={{ content: BLOG_POST_MD, class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
+/>
+
+<Story
+ name="Data Analysis"
+ args={{ content: DATA_ANALYSIS_MD, class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
+/>
+
+<Story
+ name="README file"
+ args={{ content: README_MD, class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
+/>
+
+<Story
+ name="Math Formulas"
+ args={{ content: MATH_FORMULAS_MD, class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
+/>
+
+<Story
+ name="URL Links"
+ args={{
+ content: `# URL Links Test
+
+Here are some example URLs that should open in new tabs:
+
+- [Hugging Face Homepage](https://huggingface.co)
+- [GitHub Repository](https://github.com/ggml-org/llama.cpp)
+- [OpenAI Website](https://openai.com)
+- [Google Search](https://www.google.com)
+
+You can also test inline links like https://example.com or https://docs.python.org.
+
+All links should have \`target="_blank"\` and \`rel="noopener noreferrer"\` attributes for security.`,
+ class: 'max-w-[56rem] w-[calc(100vw-2rem)]'
+ }}
+ play={async ({ canvasElement }) => {
+ const { expect } = await import('storybook/internal/test');
+
+ // Wait for component to render
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ // Find all links in the rendered content
+ const links = canvasElement.querySelectorAll('a[href]');
+
+ // Test that we have the expected number of links
+ expect(links.length).toBeGreaterThan(0);
+
+ // Test each link for proper attributes
+ links.forEach((link) => {
+ const href = link.getAttribute('href');
+
+ // Test that external links have proper security attributes
+ if (href && (href.startsWith('http://') || href.startsWith('https://'))) {
+ expect(link.getAttribute('target')).toBe('_blank');
+ expect(link.getAttribute('rel')).toBe('noopener noreferrer');
+ }
+ });
+
+ // Test specific links exist
+ const hugginFaceLink = Array.from(links).find(link =>
+ link.getAttribute('href') === 'https://huggingface.co'
+ );
+ expect(hugginFaceLink).toBeTruthy();
+ expect(hugginFaceLink?.textContent).toBe('Hugging Face Homepage');
+
+ const githubLink = Array.from(links).find(link =>
+ link.getAttribute('href') === 'https://github.com/ggml-org/llama.cpp'
+ );
+ expect(githubLink).toBeTruthy();
+ expect(githubLink?.textContent).toBe('GitHub Repository');
+
+ const openaiLink = Array.from(links).find(link =>
+ link.getAttribute('href') === 'https://openai.com'
+ );
+ expect(openaiLink).toBeTruthy();
+ expect(openaiLink?.textContent).toBe('OpenAI Website');
+
+ const googleLink = Array.from(links).find(link =>
+ link.getAttribute('href') === 'https://www.google.com'
+ );
+ expect(googleLink).toBeTruthy();
+ expect(googleLink?.textContent).toBe('Google Search');
+
+ // Test inline links (auto-linked URLs)
+ const exampleLink = Array.from(links).find(link =>
+ link.getAttribute('href') === 'https://example.com'
+ );
+ expect(exampleLink).toBeTruthy();
+
+ const pythonDocsLink = Array.from(links).find(link =>
+ link.getAttribute('href') === 'https://docs.python.org'
+ );
+ expect(pythonDocsLink).toBeTruthy();
+
+ console.log(`✅ URL Links test passed - Found ${links.length} links with proper attributes`);
+ }}
+/>
--- /dev/null
+// AI Assistant Tutorial Response
+export const AI_TUTORIAL_MD = String.raw`
+# Building a Modern Chat Application with SvelteKit
+
+I'll help you create a **production-ready chat application** using SvelteKit, TypeScript, and WebSockets. This implementation includes real-time messaging, user authentication, and message persistence.
+
+## 🚀 Quick Start
+
+First, let's set up the project:
+
+${'```'}bash
+npm create svelte@latest chat-app
+cd chat-app
+npm install
+npm install socket.io socket.io-client
+npm install @prisma/client prisma
+npm run dev
+${'```'}
+
+## 📁 Project Structure
+
+${'```'}
+chat-app/
+├── src/
+│ ├── routes/
+│ │ ├── +layout.svelte
+│ │ ├── +page.svelte
+│ │ └── api/
+│ │ └── socket/+server.ts
+│ ├── lib/
+│ │ ├── components/
+│ │ │ ├── ChatMessage.svelte
+│ │ │ └── ChatInput.svelte
+│ │ └── stores/
+│ │ └── chat.ts
+│ └── app.html
+├── prisma/
+│ └── schema.prisma
+└── package.json
+${'```'}
+
+## 💻 Implementation
+
+### WebSocket Server
+
+${'```'}typescript
+// src/lib/server/socket.ts
+import { Server } from 'socket.io';
+import type { ViteDevServer } from 'vite';
+
+export function initializeSocketIO(server: ViteDevServer) {
+ const io = new Server(server.httpServer || server, {
+ cors: {
+ origin: process.env.ORIGIN || 'http://localhost:5173',
+ credentials: true
+ }
+ });
+
+ io.on('connection', (socket) => {
+ console.log('User connected:', socket.id);
+
+ socket.on('message', async (data) => {
+ // Broadcast to all clients
+ io.emit('new-message', {
+ id: crypto.randomUUID(),
+ userId: socket.id,
+ content: data.content,
+ timestamp: new Date().toISOString()
+ });
+ });
+
+ socket.on('disconnect', () => {
+ console.log('User disconnected:', socket.id);
+ });
+ });
+
+ return io;
+}
+${'```'}
+
+### Client Store
+
+${'```'}typescript
+// src/lib/stores/chat.ts
+import { writable } from 'svelte/store';
+import io from 'socket.io-client';
+
+export interface Message {
+ id: string;
+ userId: string;
+ content: string;
+ timestamp: string;
+}
+
+function createChatStore() {
+ const { subscribe, update } = writable<Message[]>([]);
+ let socket: ReturnType<typeof io>;
+
+ return {
+ subscribe,
+ connect: () => {
+ socket = io('http://localhost:5173');
+
+ socket.on('new-message', (message: Message) => {
+ update(messages => [...messages, message]);
+ });
+ },
+ sendMessage: (content: string) => {
+ if (socket && content.trim()) {
+ socket.emit('message', { content });
+ }
+ }
+ };
+}
+
+export const chatStore = createChatStore();
+${'```'}
+
+## 🎯 Key Features
+
+✅ **Real-time messaging** with WebSockets
+✅ **Message persistence** using Prisma + PostgreSQL
+✅ **Type-safe** with TypeScript
+✅ **Responsive UI** for all devices
+✅ **Auto-reconnection** on connection loss
+
+## 📊 Performance Metrics
+
+| Metric | Value |
+|--------|-------|
+| **Message Latency** | < 50ms |
+| **Concurrent Users** | 10,000+ |
+| **Messages/Second** | 5,000+ |
+| **Uptime** | 99.9% |
+
+## 🔧 Configuration
+
+### Environment Variables
+
+${'```'}env
+DATABASE_URL="postgresql://user:password@localhost:5432/chat"
+JWT_SECRET="your-secret-key"
+REDIS_URL="redis://localhost:6379"
+${'```'}
+
+## 🚢 Deployment
+
+Deploy to production using Docker:
+
+${'```'}dockerfile
+FROM node:20-alpine
+WORKDIR /app
+COPY package*.json ./
+RUN npm ci --only=production
+COPY . .
+RUN npm run build
+EXPOSE 3000
+CMD ["node", "build"]
+${'```'}
+
+---
+
+*Need help? Check the [documentation](https://kit.svelte.dev) or [open an issue](https://github.com/sveltejs/kit/issues)*
+`;
--- /dev/null
+// API Documentation
+export const API_DOCS_MD = String.raw`
+# REST API Documentation
+
+## 🔐 Authentication
+
+All API requests require authentication using **Bearer tokens**. Include your API key in the Authorization header:
+
+${'```'}http
+GET /api/v1/users
+Host: api.example.com
+Authorization: Bearer YOUR_API_KEY
+Content-Type: application/json
+${'```'}
+
+## 📍 Endpoints
+
+### Users API
+
+#### **GET** /api/v1/users
+
+Retrieve a paginated list of users.
+
+**Query Parameters:**
+
+| Parameter | Type | Default | Description |
+|-----------|------|---------|-------------|
+| page | integer | 1 | Page number |
+| limit | integer | 20 | Items per page |
+| sort | string | "created_at" | Sort field |
+| order | string | "desc" | Sort order |
+
+**Response:** 200 OK
+
+${'```'}json
+{
+ "data": [
+ {
+ "id": "usr_1234567890",
+ "email": "user@example.com",
+ "name": "John Doe",
+ "role": "admin",
+ "created_at": "2024-01-15T10:30:00Z"
+ }
+ ],
+ "pagination": {
+ "page": 1,
+ "limit": 20,
+ "total": 156,
+ "pages": 8
+ }
+}
+${'```'}
+
+#### **POST** /api/v1/users
+
+Create a new user account.
+
+**Request Body:**
+
+${'```'}json
+{
+ "email": "newuser@example.com",
+ "password": "SecurePassword123!",
+ "name": "Jane Smith",
+ "role": "user"
+}
+${'```'}
+
+**Response:** 201 Created
+
+${'```'}json
+{
+ "id": "usr_9876543210",
+ "email": "newuser@example.com",
+ "name": "Jane Smith",
+ "role": "user",
+ "created_at": "2024-01-21T09:15:00Z"
+}
+${'```'}
+
+### Error Responses
+
+The API returns errors in a consistent format:
+
+${'```'}json
+{
+ "error": {
+ "code": "VALIDATION_ERROR",
+ "message": "Invalid request parameters",
+ "details": [
+ {
+ "field": "email",
+ "message": "Email format is invalid"
+ }
+ ]
+ }
+}
+${'```'}
+
+### Rate Limiting
+
+| Tier | Requests/Hour | Burst |
+|------|--------------|-------|
+| **Free** | 1,000 | 100 |
+| **Pro** | 10,000 | 500 |
+| **Enterprise** | Unlimited | - |
+
+**Headers:**
+- X-RateLimit-Limit
+- X-RateLimit-Remaining
+- X-RateLimit-Reset
+
+### Webhooks
+
+Configure webhooks to receive real-time events:
+
+${'```'}javascript
+// Webhook payload
+{
+ "event": "user.created",
+ "timestamp": "2024-01-21T09:15:00Z",
+ "data": {
+ "id": "usr_9876543210",
+ "email": "newuser@example.com"
+ },
+ "signature": "sha256=abcd1234..."
+}
+${'```'}
+
+### SDK Examples
+
+**JavaScript/TypeScript:**
+
+${'```'}typescript
+import { ApiClient } from '@example/api-sdk';
+
+const client = new ApiClient({
+ apiKey: process.env.API_KEY
+});
+
+const users = await client.users.list({
+ page: 1,
+ limit: 20
+});
+${'```'}
+
+**Python:**
+
+${'```'}python
+from example_api import Client
+
+client = Client(api_key=os.environ['API_KEY'])
+users = client.users.list(page=1, limit=20)
+${'```'}
+
+---
+
+📚 [Full API Reference](https://api.example.com/docs) | 💬 [Support](https://support.example.com)
+`;
--- /dev/null
+<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M230.721 172.7C230.183 170.673 229.313 168.75 228.146 167.008C228.396 166.091 228.587 165.159 228.714 164.217C229.543 158.241 227.471 152.77 223.567 148.537C221.452 146.225 219.185 144.698 216.784 143.761C218.36 137.018 219.157 130.117 219.161 123.193C219.161 120.03 218.982 116.932 218.682 113.88C218.526 112.356 218.337 110.836 218.115 109.32C217.428 104.847 216.408 100.431 215.064 96.11C214.183 93.2707 213.164 90.476 212.01 87.736C210.281 83.6782 208.262 79.75 205.969 75.982C204.465 73.475 202.827 71.0508 201.062 68.72C200.197 67.543 199.296 66.3938 198.358 65.274C195.58 61.898 192.561 58.7277 189.325 55.788C188.25 54.7997 187.145 53.8453 186.01 52.926C184.893 51.9943 183.751 51.0927 182.586 50.222C180.241 48.4766 177.818 46.8392 175.324 45.315C161.543 36.945 145.382 32.145 128.109 32.145C77.817 32.145 37.057 72.907 37.057 123.196C37.055 130.208 37.867 137.196 39.477 144.02C37.317 144.958 35.247 146.42 33.327 148.535C29.424 152.766 27.351 158.217 28.18 164.193C28.306 165.142 28.495 166.082 28.747 167.006C27.5811 168.749 26.7117 170.673 26.174 172.7C24.974 177.261 25.369 181.374 26.894 184.978C25.236 189.688 25.65 194.704 27.809 199.065C29.379 202.25 31.626 204.714 34.396 206.916C37.689 209.534 41.811 211.758 46.783 213.892C52.715 216.422 59.956 218.799 63.249 219.671C71.755 221.873 79.911 223.269 88.177 223.337C99.954 223.446 110.096 220.677 117.357 213.59C120.924 214.027 124.515 214.246 128.109 214.244C131.906 214.236 135.699 213.997 139.467 213.529C146.711 220.661 156.892 223.455 168.712 223.343C176.977 223.277 185.133 221.881 193.617 219.676C196.932 218.804 204.17 216.427 210.105 213.897C215.077 211.76 219.199 209.536 222.514 206.922C225.263 204.719 227.508 202.256 229.079 199.071C231.26 194.709 231.652 189.693 230.017 184.983C231.527 181.379 231.92 177.257 230.721 172.7ZM222.281 184.673C223.952 187.844 224.059 191.427 222.585 194.764C220.349 199.821 214.795 203.805 204.008 208.082C197.3 210.742 191.158 212.443 191.104 212.458C182.232 214.759 174.208 215.928 167.262 215.928C155.76 215.928 147.201 212.754 141.773 206.486C132.594 208.05 123.222 208.103 114.026 206.644C108.591 212.808 100.081 215.928 88.676 215.928C81.729 215.928 73.706 214.759 64.833 212.458C64.779 212.443 58.639 210.742 51.929 208.082C41.143 203.805 35.587 199.824 33.352 194.764C31.878 191.427 31.985 187.844 33.656 184.673C33.81 184.378 33.976 184.091 34.153 183.813C33.1516 182.309 32.4799 180.61 32.182 178.827C31.8842 177.045 31.967 175.22 32.425 173.472C33.089 170.949 34.46 168.851 36.322 167.344C35.425 165.87 34.8365 164.23 34.592 162.522C34.056 158.808 35.289 155.1 38.062 152.076C40.222 149.723 43.275 148.428 46.655 148.428H46.745C44.1965 140.259 42.9044 131.75 42.913 123.193C42.913 76.522 80.749 38.683 127.427 38.683C174.104 38.683 211.94 76.518 211.94 123.193C211.947 131.773 210.646 140.304 208.081 148.492C208.489 148.452 208.889 148.432 209.282 148.431C212.662 148.431 215.716 149.726 217.874 152.079C220.647 155.1 221.881 158.811 221.344 162.525C221.1 164.233 220.511 165.873 219.615 167.347C221.477 168.854 222.849 170.952 223.512 173.475C223.97 175.223 224.053 177.048 223.755 178.831C223.458 180.613 222.786 182.312 221.784 183.816C221.961 184.091 222.129 184.378 222.281 184.673Z" fill="white"/>
+<path d="M221.784 183.816C222.786 182.312 223.458 180.613 223.756 178.831C224.053 177.048 223.97 175.223 223.512 173.475C222.848 170.952 221.476 168.854 219.615 167.347C220.512 165.873 221.1 164.233 221.344 162.525C221.881 158.811 220.648 155.103 217.874 152.079C215.716 149.726 212.662 148.431 209.282 148.431C208.889 148.431 208.489 148.452 208.081 148.492C210.643 140.304 211.942 131.774 211.933 123.195C211.933 76.5231 174.097 38.6851 127.424 38.6851C80.75 38.6851 42.9099 76.5191 42.9099 123.195C42.9015 131.752 44.1936 140.261 46.742 148.43H46.6519C43.2719 148.43 40.219 149.724 38.06 152.077C35.287 155.098 34.0529 158.81 34.5899 162.523C34.8346 164.231 35.4231 165.872 36.3199 167.346C34.4579 168.852 33.086 170.95 32.422 173.473C31.9642 175.222 31.8817 177.047 32.1799 178.83C32.4781 180.612 33.1501 182.312 34.1519 183.816C33.9739 184.094 33.8099 184.381 33.6549 184.676C31.9849 187.847 31.877 191.43 33.352 194.767C35.588 199.824 41.1419 203.808 51.9289 208.085C58.6359 210.745 64.779 212.446 64.833 212.461C73.705 214.762 81.729 215.931 88.675 215.931C100.081 215.931 108.591 212.811 114.026 206.647C123.222 208.106 132.594 208.052 141.773 206.489C147.201 212.757 155.76 215.931 167.262 215.931C174.208 215.931 182.232 214.762 191.103 212.461C191.158 212.446 197.298 210.745 204.008 208.085C214.795 203.808 220.35 199.824 222.585 194.767C224.059 191.43 223.952 187.847 222.281 184.676C222.129 184.379 221.961 184.091 221.784 183.816ZM110.137 196.997C109.669 197.815 109.168 198.614 108.635 199.391C107.23 201.448 105.382 203.02 103.237 204.188C99.1369 206.424 93.947 207.205 88.675 207.205C80.346 207.205 71.808 205.256 67.023 204.015C66.787 203.954 37.689 195.735 41.373 188.739C41.993 187.562 43.0129 187.092 44.2979 187.092C49.4849 187.092 58.9299 194.816 62.9889 194.816C63.8959 194.816 64.5359 194.43 64.7969 193.488C66.5269 187.284 38.5039 184.676 40.8639 175.692C41.2799 174.102 42.41 173.456 43.998 173.456C50.856 173.455 66.248 185.516 69.467 185.516C69.714 185.516 69.8909 185.443 69.9869 185.291C70.0009 185.268 70.015 185.246 70.028 185.222C71.539 182.727 70.6719 180.913 60.3209 174.573L59.3269 173.968C47.9359 167.074 39.9409 162.925 44.4879 157.975C45.0109 157.404 45.7529 157.151 46.6539 157.151C47.7219 157.151 49.0149 157.508 50.4389 158.108C56.4549 160.645 64.793 167.564 68.276 170.581C68.8239 171.057 69.3683 171.538 69.9089 172.022C69.9089 172.022 74.319 176.608 76.985 176.608C77.599 176.608 78.1199 176.366 78.4729 175.768C80.364 172.58 60.9099 157.838 59.8129 151.755C59.0689 147.634 60.3349 145.546 62.6749 145.546C63.7879 145.546 65.1459 146.02 66.6449 146.971C71.2949 149.922 80.2729 165.35 83.5599 171.352C84.6619 173.363 86.5429 174.213 88.2379 174.213C91.6009 174.213 94.2299 170.87 88.5459 166.622C80.0029 160.23 83.001 149.782 87.078 149.139C87.252 149.111 87.4279 149.097 87.6029 149.097C91.3109 149.097 92.9459 155.486 92.9459 155.486C92.9459 155.486 97.7399 167.524 105.975 175.753C113.447 183.222 114.491 189.351 110.137 196.997ZM136.766 198.407L136.339 198.458L135.611 198.541C135.228 198.581 134.844 198.619 134.459 198.654L134.084 198.688L133.741 198.717L133.255 198.756L132.718 198.795L132.182 198.83L132.063 198.838C131.923 198.846 131.783 198.855 131.641 198.862L131.462 198.872C131.296 198.881 131.13 198.889 130.962 198.896L130.381 198.921L129.854 198.939L129.502 198.949H129.323C129.213 198.949 129.104 198.955 128.994 198.956H128.82C128.71 198.956 128.601 198.956 128.491 198.961L128.043 198.967H127.418C126.927 198.967 126.437 198.962 125.949 198.952L125.553 198.943C125.44 198.943 125.327 198.938 125.216 198.934L124.796 198.922L124.275 198.902L123.805 198.881L123.684 198.876L123.237 198.853C123.112 198.846 122.989 198.84 122.865 198.831L122.576 198.814C122.213 198.791 121.85 198.766 121.487 198.738L121.107 198.707C120.947 198.695 120.787 198.68 120.628 198.666C120.441 198.65 120.254 198.632 120.067 198.614C119.754 198.585 119.441 198.553 119.128 198.519H119.113C123.683 188.324 121.372 178.802 112.137 169.575C106.08 163.526 102.051 154.594 101.215 152.633C99.5229 146.828 95.045 140.375 87.608 140.375C86.979 140.375 86.351 140.425 85.73 140.523C82.472 141.036 79.624 142.911 77.592 145.733C75.396 143.002 73.262 140.831 71.332 139.605C68.422 137.76 65.5179 136.824 62.6889 136.824C59.1579 136.824 56.0019 138.274 53.8019 140.904L53.7459 140.971C53.7039 140.798 53.6639 140.625 53.6229 140.451L53.6179 140.428C53.1992 138.638 52.8477 136.833 52.5639 135.016C52.5639 135.004 52.5639 134.992 52.5579 134.98C52.5359 134.843 52.5159 134.705 52.4949 134.568C52.4334 134.162 52.3757 133.755 52.3219 133.348C52.2979 133.163 52.2719 132.978 52.2489 132.793L52.1809 132.238C52.1589 132.053 52.1409 131.885 52.1209 131.709L52.115 131.665C52.0351 130.945 51.9651 130.225 51.9049 129.503L51.8829 129.226L51.8479 128.754C51.8379 128.625 51.8279 128.495 51.8209 128.365C51.8209 128.334 51.8159 128.304 51.8149 128.275C51.7895 127.913 51.7678 127.55 51.7499 127.187C51.7399 126.998 51.7299 126.81 51.7219 126.62L51.7019 126.124L51.6969 125.974L51.6809 125.517L51.6709 125.128C51.6709 124.973 51.6629 124.818 51.6609 124.663C51.6579 124.508 51.6539 124.338 51.6529 124.174C51.6509 124.01 51.6529 123.848 51.6479 123.685C51.6439 123.521 51.6479 123.358 51.6479 123.195C51.6479 81.3421 85.5789 47.4111 127.436 47.4111C169.292 47.4111 203.222 81.3411 203.222 123.195V124.174C203.222 124.337 203.217 124.501 203.214 124.663C203.214 124.798 203.208 124.931 203.204 125.068C203.204 125.188 203.199 125.309 203.195 125.425C203.195 125.578 203.186 125.731 203.181 125.884V125.896L203.16 126.427C203.153 126.582 203.147 126.738 203.139 126.893L203.134 127.003L203.107 127.499C203.048 128.562 202.967 129.623 202.866 130.683V130.696C202.849 130.87 202.832 131.044 202.813 131.218L202.768 131.629L202.679 132.433L202.628 132.84L202.565 133.319C202.542 133.493 202.519 133.668 202.493 133.841C202.467 134.036 202.438 134.23 202.409 134.424L202.34 134.883L202.258 135.403C202.23 135.576 202.2 135.748 202.168 135.92C202.135 136.093 202.109 136.265 202.079 136.437C202.019 136.781 201.956 137.125 201.89 137.468C201.789 137.981 201.686 138.493 201.58 139.005L201.47 139.512C201.434 139.681 201.395 139.851 201.357 140.02C199.224 137.947 196.399 136.818 193.284 136.818C190.457 136.818 187.55 137.753 184.641 139.598C182.711 140.824 180.578 142.996 178.381 145.726C176.346 142.904 173.498 141.029 170.242 140.516C169.621 140.418 168.993 140.368 168.364 140.368C160.925 140.368 156.45 146.821 154.757 152.626C153.917 154.587 149.887 163.519 143.825 169.577C134.596 178.775 132.268 188.254 136.766 198.407ZM215.007 177.998L214.977 178.087C214.901 178.288 214.813 178.484 214.714 178.674C214.639 178.814 214.558 178.95 214.47 179.082C214.303 179.331 214.12 179.569 213.921 179.793C213.875 179.845 213.831 179.897 213.779 179.948C213.707 180.025 213.634 180.101 213.559 180.175C212.213 181.509 210.161 182.679 207.841 183.752C207.578 183.871 207.311 183.99 207.042 184.11L206.774 184.229C206.595 184.308 206.416 184.386 206.228 184.463C206.049 184.541 205.863 184.619 205.677 184.695L205.119 184.925C203.814 185.462 202.477 185.974 201.173 186.479L200.615 186.696L200.064 186.912C199.697 187.055 199.335 187.198 198.979 187.341L198.448 187.555L197.926 187.768L197.67 187.876C197.499 187.947 197.332 188.018 197.165 188.089C193.328 189.736 190.567 191.411 191.147 193.489C191.163 193.548 191.181 193.604 191.201 193.659C191.253 193.813 191.324 193.958 191.413 194.095C191.465 194.176 191.525 194.253 191.592 194.323C192.274 195.032 193.515 194.92 195.08 194.357C195.3 194.276 195.519 194.192 195.736 194.104L195.872 194.048C196.23 193.896 196.609 193.726 196.996 193.542C197.093 193.496 197.191 193.452 197.289 193.401C199.203 192.465 201.372 191.205 203.524 190.058C204.385 189.593 205.258 189.152 206.142 188.733C208.18 187.774 210.096 187.094 211.636 187.094C212.359 187.094 212.997 187.242 213.529 187.582L213.618 187.641C213.952 187.876 214.232 188.178 214.441 188.528C214.482 188.595 214.522 188.666 214.561 188.739C215.322 190.184 214.685 191.68 213.194 193.147C211.763 194.556 209.537 195.937 207.007 197.215C206.819 197.31 206.631 197.405 206.44 197.498C198.91 201.196 189.049 203.981 188.912 204.016C186.284 204.697 182.526 205.591 178.292 206.26L177.666 206.358L177.563 206.373C177.089 206.445 176.614 206.512 176.138 206.574C175.655 206.639 175.167 206.698 174.676 206.753L174.586 206.763C172.806 206.968 171.019 207.104 169.228 207.169H169.202C168.554 207.192 167.907 207.204 167.259 207.204H166.512C165.524 207.191 164.538 207.146 163.553 207.07C163.53 207.07 163.505 207.07 163.482 207.064C163.129 207.037 162.777 207.004 162.425 206.965C162.06 206.926 161.696 206.882 161.333 206.833C161.094 206.801 160.856 206.765 160.618 206.726C160.376 206.687 160.134 206.647 159.893 206.605L159.564 206.543L159.539 206.538C159.192 206.472 158.847 206.399 158.503 206.319C158.303 206.274 158.104 206.23 157.907 206.176L157.788 206.146C157.69 206.122 157.595 206.096 157.498 206.07L157.445 206.056L157.137 205.966C157.025 205.935 156.913 205.901 156.801 205.868L156.762 205.857L156.471 205.768C156.361 205.734 156.251 205.698 156.142 205.662L155.874 205.573L155.677 205.504C155.487 205.437 155.298 205.368 155.111 205.296L154.933 205.226L154.786 205.168C154.502 205.054 154.22 204.935 153.941 204.81L153.756 204.72L153.725 204.706C153.659 204.675 153.594 204.644 153.528 204.617C153.399 204.555 153.271 204.491 153.144 204.426L153.105 204.407L152.921 204.31C152.594 204.139 152.274 203.957 151.96 203.764L151.788 203.658C151.702 203.605 151.616 203.55 151.532 203.494L151.308 203.346L151.067 203.18L150.923 203.077C150.771 202.969 150.622 202.857 150.476 202.742L150.243 202.563C150.15 202.488 150.058 202.412 149.967 202.335C149.89 202.272 149.815 202.206 149.74 202.14L149.734 202.135C149.653 202.064 149.574 201.993 149.495 201.92C149.417 201.849 149.339 201.777 149.263 201.704L149.254 201.695C149.174 201.619 149.096 201.542 149.019 201.463C148.942 201.385 148.863 201.307 148.788 201.227C148.713 201.148 148.636 201.067 148.562 200.984C148.488 200.902 148.42 200.827 148.35 200.746L148.327 200.719C148.259 200.641 148.192 200.562 148.126 200.481C147.983 200.31 147.844 200.135 147.71 199.956C147.575 199.776 147.443 199.592 147.314 199.405L147.191 199.221C147.027 198.981 146.867 198.739 146.712 198.493C146.596 198.316 146.483 198.138 146.373 197.957C146.302 197.844 146.234 197.73 146.166 197.618L146.138 197.572C146.073 197.462 146.009 197.354 145.947 197.245C145.911 197.186 145.877 197.127 145.845 197.066C145.812 197.004 145.774 196.941 145.739 196.878L145.682 196.779L145.647 196.715C145.58 196.595 145.514 196.474 145.45 196.352C145.42 196.298 145.391 196.244 145.36 196.192L145.271 196.019L145.181 195.848C144.956 195.398 144.743 194.942 144.543 194.48L144.472 194.311C144.426 194.198 144.383 194.086 144.337 193.975C144.315 193.921 144.293 193.868 144.274 193.814C144.167 193.537 144.067 193.257 143.975 192.975C143.942 192.874 143.91 192.775 143.88 192.675C143.808 192.448 143.743 192.219 143.685 191.988C143.614 191.719 143.551 191.448 143.498 191.175C143.487 191.12 143.476 191.065 143.467 191.012C143.415 190.745 143.373 190.476 143.34 190.206C143.332 190.153 143.326 190.1 143.32 190.047L143.303 189.885C143.281 189.673 143.264 189.46 143.254 189.247C143.254 189.193 143.249 189.139 143.247 189.087C143.242 188.981 143.24 188.875 143.239 188.769C143.183 184.496 145.345 180.388 149.968 175.767C158.203 167.54 162.997 155.501 162.997 155.501C162.997 155.501 163.126 154.996 163.394 154.269C163.431 154.168 163.47 154.064 163.514 153.955C163.67 153.548 163.846 153.148 164.041 152.758L164.08 152.683C164.246 152.351 164.428 152.027 164.624 151.712C164.67 151.639 164.714 151.567 164.765 151.494C164.912 151.277 165.067 151.065 165.23 150.86C165.319 150.749 165.416 150.639 165.513 150.532C165.552 150.49 165.59 150.448 165.631 150.408C166.108 149.915 166.653 149.513 167.27 149.299L167.348 149.273C167.4 149.256 167.452 149.24 167.505 149.225C167.566 149.209 167.627 149.195 167.69 149.182L167.719 149.176C167.849 149.15 167.981 149.133 168.114 149.124H168.125C168.194 149.124 168.264 149.117 168.335 149.117C168.424 149.117 168.507 149.117 168.594 149.126C168.684 149.134 168.773 149.144 168.863 149.158C169.605 149.276 170.311 149.718 170.919 150.4C171.15 150.66 171.358 150.94 171.54 151.236C171.66 151.428 171.773 151.631 171.88 151.845C171.923 151.934 171.964 152.016 172.004 152.104C172.108 152.33 172.202 152.56 172.284 152.795C172.479 153.345 172.626 153.911 172.723 154.487C172.807 154.992 172.857 155.502 172.873 156.013C172.881 156.286 172.881 156.563 172.873 156.842C172.819 158.14 172.553 159.421 172.086 160.634C172.044 160.745 171.997 160.857 171.952 160.969C171.86 161.195 171.759 161.417 171.65 161.634C171.569 161.799 171.484 161.965 171.392 162.13C171.332 162.24 171.269 162.35 171.206 162.46C171.045 162.734 170.871 163.006 170.684 163.277L170.571 163.439C170.129 164.055 169.637 164.633 169.099 165.167C168.569 165.698 168.001 166.189 167.4 166.637C166.798 167.083 166.233 167.577 165.711 168.114C164.208 169.691 163.858 171.083 164.196 172.138C164.25 172.304 164.321 172.465 164.407 172.617C164.508 172.791 164.628 172.951 164.764 173.097L164.817 173.152L164.871 173.206C164.925 173.258 164.982 173.309 165.043 173.359L165.103 173.407C165.248 173.519 165.402 173.619 165.563 173.707C165.61 173.732 165.652 173.757 165.705 173.781C165.879 173.866 166.058 173.939 166.242 173.998C166.293 174.015 166.344 174.03 166.396 174.046L166.461 174.063L166.551 174.087L166.628 174.106L166.712 174.124L166.795 174.141L166.874 174.154C166.932 174.164 166.992 174.174 167.052 174.181L167.109 174.19L167.213 174.2L167.277 174.207L167.382 174.214H167.444L167.554 174.22H167.9L167.999 174.214L168.113 174.207L168.252 174.194L168.382 174.179C168.412 174.179 168.442 174.171 168.472 174.165C168.872 174.107 169.264 174.001 169.639 173.849L169.798 173.782C169.887 173.743 169.977 173.702 170.059 173.658C170.235 173.57 170.406 173.47 170.57 173.361C170.799 173.211 171.015 173.043 171.217 172.858C171.265 172.815 171.312 172.769 171.358 172.725C171.381 172.703 171.403 172.682 171.425 172.658C171.469 172.613 171.514 172.569 171.558 172.52C171.878 172.168 172.155 171.78 172.383 171.363C174.34 167.804 176.391 164.298 178.534 160.849L178.828 160.378L179.125 159.907C179.273 159.668 179.423 159.433 179.572 159.199L179.722 158.965C180.22 158.185 180.726 157.41 181.241 156.641L181.546 156.185C182.158 155.278 182.768 154.396 183.373 153.558L183.674 153.143C184.332 152.236 185.017 151.348 185.728 150.482L186.01 150.144C186.057 150.088 186.1 150.032 186.151 149.978C186.244 149.868 186.337 149.761 186.428 149.657C186.474 149.604 186.517 149.552 186.566 149.5L186.834 149.198L186.968 149.051C187.103 148.906 187.235 148.767 187.365 148.634C187.455 148.544 187.538 148.455 187.624 148.371C188.131 147.853 188.69 147.388 189.293 146.985L189.433 146.895C189.567 146.805 189.706 146.721 189.848 146.645C192.212 145.303 194.169 145.204 195.296 146.331C195.978 147.013 196.356 148.144 196.335 149.718C196.335 149.787 196.335 149.857 196.33 149.929V150.006C196.33 150.078 196.324 150.15 196.318 150.223C196.318 150.313 196.308 150.402 196.299 150.492C196.29 150.581 196.285 150.649 196.276 150.729C196.276 150.751 196.272 150.774 196.268 150.798C196.262 150.867 196.253 150.938 196.243 151.009C196.243 151.03 196.243 151.052 196.235 151.074C196.224 151.169 196.21 151.263 196.194 151.357C196.183 151.447 196.168 151.531 196.152 151.619L196.126 151.768C196.1 151.91 196.067 152.05 196.026 152.188C195.948 152.447 195.854 152.7 195.743 152.946C195.588 153.284 195.417 153.613 195.229 153.933C195.125 154.111 195.018 154.286 194.907 154.459C194.793 154.638 194.673 154.819 194.549 155.002C194.233 155.454 193.905 155.897 193.564 156.33L193.408 156.527C192.852 157.22 192.278 157.899 191.686 158.562L191.499 158.772C191.247 159.053 190.991 159.336 190.729 159.62L190.532 159.834C190.401 159.977 190.264 160.12 190.132 160.264C190.001 160.407 189.864 160.552 189.726 160.697L189.315 161.13L188.898 161.566L188.478 162.002C188.196 162.294 187.913 162.586 187.628 162.878C183.573 167.037 179.301 171.182 177.855 173.766C177.758 173.934 177.671 174.108 177.593 174.285C177.387 174.755 177.301 175.157 177.36 175.482C177.379 175.589 177.416 175.691 177.471 175.785C177.552 175.926 177.651 176.056 177.766 176.172C177.819 176.224 177.875 176.272 177.934 176.316C178.232 176.528 178.591 176.637 178.957 176.627H179.071L179.188 176.618L179.305 176.605L179.402 176.591C179.415 176.589 179.429 176.587 179.442 176.583L179.531 176.566L179.554 176.561L179.653 176.54L179.688 176.531C179.723 176.522 179.757 176.513 179.792 176.503C179.827 176.493 179.875 176.48 179.917 176.466C180.093 176.413 180.265 176.35 180.434 176.278C180.523 176.242 180.61 176.203 180.696 176.161C180.741 176.141 180.786 176.12 180.828 176.098L180.962 176.032C181.282 175.866 181.594 175.685 181.898 175.491L182.031 175.401C182.076 175.373 182.121 175.344 182.164 175.312L182.297 175.223L182.368 175.174L182.56 175.039C182.739 174.916 182.906 174.789 183.075 174.66L183.09 174.648L183.359 174.44C183.726 174.15 184.074 173.858 184.39 173.583L184.6 173.399L184.619 173.381L184.729 173.284C184.987 173.052 185.217 172.836 185.408 172.658L185.487 172.581C185.556 172.516 185.619 172.455 185.676 172.403L185.788 172.292L185.828 172.253L185.839 172.242L185.956 172.125L186.03 172.048L186.039 172.041L186.074 172.009L186.118 171.969L186.132 171.956L186.169 171.922L186.373 171.743L186.487 171.641C186.548 171.588 186.607 171.534 186.666 171.479L186.802 171.358C186.827 171.338 186.851 171.316 186.876 171.294L187.019 171.169L187.229 170.984L187.341 170.887C187.776 170.509 188.305 170.052 188.913 169.537L189.162 169.326L189.573 168.981L189.994 168.63C190.544 168.173 191.136 167.688 191.762 167.185L192.173 166.855C192.523 166.576 192.882 166.292 193.246 166.006C193.393 165.891 193.542 165.776 193.694 165.662C194.066 165.373 194.44 165.086 194.817 164.803C195.675 164.155 196.56 163.506 197.456 162.874L197.84 162.606C198.109 162.421 198.377 162.235 198.645 162.054L198.888 161.89C199.367 161.565 199.853 161.248 200.343 160.939L200.586 160.786L200.827 160.636C201.069 160.486 201.309 160.339 201.548 160.196L201.787 160.053L202.265 159.775L202.734 159.506L202.829 159.454L203.2 159.25C203.355 159.166 203.509 159.085 203.663 159.006L203.892 158.888L204.115 158.776C204.193 158.739 204.27 158.7 204.346 158.663C204.848 158.415 205.36 158.187 205.88 157.979C206.021 157.919 206.161 157.865 206.3 157.818L206.71 157.674C206.833 157.633 206.953 157.594 207.068 157.559L207.108 157.547C207.17 157.527 207.232 157.509 207.293 157.493L207.311 157.488C207.439 157.451 207.566 157.419 207.691 157.389H207.7C208.054 157.304 208.414 157.243 208.777 157.206C208.944 157.189 209.111 157.18 209.279 157.181H209.363C209.475 157.181 209.583 157.188 209.69 157.199C209.739 157.199 209.788 157.209 209.836 157.215H209.856C209.904 157.221 209.952 157.228 210 157.239C210.047 157.248 210.095 157.256 210.141 157.267H210.156C210.203 157.277 210.245 157.289 210.294 157.303C210.548 157.374 210.79 157.484 211.012 157.628C211.121 157.699 211.223 157.779 211.317 157.868L211.344 157.894C211.362 157.91 211.379 157.927 211.395 157.944L211.444 157.997C211.846 158.418 212.178 158.901 212.428 159.427L212.466 159.517C212.551 159.717 212.618 159.924 212.666 160.135C212.808 160.781 212.753 161.455 212.508 162.07C212.415 162.318 212.302 162.557 212.169 162.785C211.858 163.309 211.489 163.796 211.07 164.237L210.981 164.332C210.848 164.472 210.71 164.612 210.565 164.752C210.501 164.815 210.434 164.877 210.367 164.94L210.162 165.129L210.055 165.224C209.797 165.454 209.532 165.677 209.263 165.893C209.1 166.025 208.936 166.154 208.77 166.281C208.184 166.729 207.587 167.161 206.979 167.578C206.612 167.83 206.242 168.077 205.869 168.321C204.95 168.924 204.021 169.512 203.083 170.084C201.115 171.294 198.934 172.588 196.609 173.995L196.007 174.36C195.348 174.762 194.726 175.146 194.14 175.512L193.845 175.697L193.287 176.055C192.917 176.292 192.548 176.531 192.179 176.77L191.882 176.966C191.737 177.06 191.593 177.156 191.449 177.252L191.308 177.342L190.876 177.633L190.647 177.79L190.379 177.976L190.13 178.149C189.713 178.444 189.325 178.725 188.968 178.992L188.834 179.094C188.624 179.253 188.416 179.415 188.211 179.58C187.902 179.829 187.62 180.067 187.367 180.296L187.243 180.409C187.172 180.474 187.102 180.539 187.035 180.603C186.989 180.648 186.946 180.693 186.898 180.736L186.834 180.8C186.691 180.944 186.551 181.091 186.416 181.242L186.35 181.318C186.203 181.488 186.075 181.651 185.963 181.81L185.913 181.881C185.825 182.009 185.744 182.141 185.671 182.277C185.652 182.311 185.635 182.345 185.618 182.379L185.569 182.481L185.536 182.555L185.515 182.605L185.498 182.65L185.475 182.711C185.413 182.88 185.37 183.056 185.345 183.234L185.337 183.296L185.331 183.354V183.669C185.331 183.695 185.331 183.721 185.338 183.749L185.343 183.797C185.343 183.823 185.349 183.848 185.353 183.876C185.357 183.902 185.364 183.949 185.372 183.986V183.991C185.379 184.026 185.386 184.06 185.395 184.095C185.404 184.13 185.413 184.17 185.424 184.206C185.443 184.277 185.467 184.347 185.492 184.417C185.508 184.459 185.523 184.5 185.54 184.541C185.54 184.549 185.546 184.558 185.55 184.566L185.586 184.647L185.636 184.758C185.69 184.873 185.749 184.985 185.813 185.094L185.879 185.208L185.947 185.322C185.959 185.341 185.973 185.359 185.988 185.376L186.01 185.399L186.035 185.422L186.061 185.442C186.099 185.469 186.14 185.49 186.183 185.505C186.206 185.513 186.23 185.519 186.254 185.525C186.831 185.655 188.017 185.178 189.593 184.346C189.682 184.298 189.78 184.248 189.875 184.196L190.355 183.934L190.589 183.804C190.756 183.715 190.926 183.614 191.1 183.515L191.417 183.336C193.5 182.137 195.988 180.597 198.56 179.093C198.801 178.952 199.043 178.811 199.285 178.672L199.771 178.361C200.335 178.038 200.902 177.719 201.471 177.404C202.188 177.01 202.91 176.626 203.639 176.254L204.115 176.013C204.431 175.857 204.744 175.705 205.053 175.557C205.651 175.273 206.256 175.003 206.868 174.748L207.203 174.612L207.243 174.596C209.018 173.893 210.627 173.459 211.929 173.459C212.21 173.456 212.492 173.48 212.769 173.528H212.778C212.867 173.544 212.948 173.562 213.031 173.582H213.046C213.259 173.636 213.466 173.713 213.662 173.812C213.937 173.954 214.184 174.143 214.393 174.371C214.489 174.477 214.574 174.592 214.649 174.714C214.789 174.929 214.899 175.162 214.978 175.406C215.01 175.501 215.038 175.594 215.067 175.693C215.278 176.45 215.257 177.253 215.007 177.998Z" fill="#FF9D00"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M203.21 123.685V123.194C203.21 81.34 169.292 47.411 127.435 47.411C85.5791 47.411 51.648 81.342 51.648 123.194V123.358C51.646 123.467 51.645 123.576 51.648 123.685C51.6529 123.848 51.6546 124.011 51.653 124.174L51.6581 124.534L51.661 124.663C51.661 124.723 51.6631 124.782 51.6651 124.842C51.6681 124.937 51.67 125.033 51.67 125.128L51.681 125.517L51.697 125.974L51.702 126.124L51.722 126.597V126.62C51.73 126.805 51.7401 126.989 51.7491 127.173L51.75 127.187C51.76 127.375 51.7701 127.564 51.7821 127.753C51.7921 127.927 51.802 128.101 51.815 128.275L51.8171 128.306C51.8258 128.455 51.8358 128.605 51.847 128.754L51.85 128.794L51.883 129.226L51.8861 129.254C51.8921 129.338 51.898 129.422 51.906 129.503C51.9658 130.224 52.0355 130.945 52.1151 131.664L52.12 131.709L52.181 132.238L52.2491 132.793L52.299 133.17L52.322 133.347C52.3753 133.755 52.433 134.162 52.495 134.568L52.4991 134.595L52.558 134.979C52.8435 136.808 53.1971 138.626 53.618 140.429L53.6231 140.451L53.655 140.586L53.746 140.971L53.802 140.904C56.002 138.274 59.158 136.824 62.689 136.824C65.519 136.824 68.4221 137.76 71.3321 139.605C73.2621 140.831 75.3961 143.002 77.5921 145.733C79.6241 142.911 82.4721 141.035 85.7301 140.523C86.3513 140.425 86.9792 140.376 87.6081 140.375C95.0441 140.375 99.523 146.828 101.215 152.633C102.051 154.594 106.08 163.526 112.156 169.568C121.392 178.795 123.703 188.316 119.132 198.511H119.148C119.459 198.546 119.772 198.578 120.087 198.607C120.274 198.625 120.46 198.643 120.648 198.659L120.714 198.665L121.127 198.7L121.507 198.73C121.869 198.758 122.232 198.784 122.596 198.807L122.885 198.824L123.114 198.838L123.256 198.846L123.703 198.869L123.825 198.874L124.294 198.895L124.816 198.915L125.235 198.927L125.305 198.929C125.394 198.933 125.483 198.936 125.572 198.936L125.668 198.939C126.258 198.953 126.847 198.96 127.437 198.959H128.063L128.51 198.954C128.62 198.949 128.729 198.949 128.84 198.949H129.014L129.165 198.945C129.224 198.943 129.283 198.941 129.343 198.941H129.522L129.873 198.932L130.401 198.914L130.982 198.888C131.15 198.882 131.316 198.873 131.482 198.865L131.661 198.854L131.927 198.84L132.083 198.831L132.201 198.823L132.738 198.788L133.274 198.749L133.761 198.71L134.103 198.681L134.479 198.647C135.107 198.591 135.733 198.525 136.359 198.45L136.786 198.399C132.287 188.247 134.616 178.767 143.813 169.577C149.876 163.519 153.905 154.587 154.745 152.625C156.438 146.821 160.914 140.368 168.352 140.368C168.981 140.368 169.61 140.418 170.231 140.516C173.486 141.028 176.334 142.904 178.369 145.726C180.566 142.996 182.699 140.823 184.63 139.597C187.539 137.753 190.445 136.817 193.272 136.817C196.388 136.817 199.212 137.947 201.345 140.02C201.384 139.851 201.422 139.682 201.459 139.512L201.568 139.006C201.607 138.821 201.646 138.636 201.683 138.451C201.749 138.124 201.815 137.797 201.878 137.467C201.944 137.125 202.007 136.781 202.067 136.437L202.098 136.251C202.117 136.141 202.135 136.031 202.156 135.92C202.19 135.748 202.218 135.576 202.246 135.402L202.257 135.336L202.328 134.883L202.398 134.424V134.42C202.449 134.081 202.497 133.742 202.542 133.403L202.553 133.319L202.616 132.841L202.667 132.433L202.757 131.629L202.792 131.306L202.801 131.218C202.82 131.044 202.838 130.87 202.854 130.696V130.682C202.867 130.544 202.881 130.405 202.893 130.266C202.964 129.478 203.024 128.686 203.072 127.891C203.081 127.761 203.088 127.63 203.096 127.499V127.493L203.122 127.002L203.128 126.892C203.144 126.56 203.158 126.228 203.169 125.896V125.884L203.174 125.754C203.179 125.645 203.183 125.535 203.183 125.425L203.185 125.381C203.189 125.278 203.193 125.172 203.193 125.067L203.196 124.977C203.199 124.872 203.202 124.768 203.202 124.663L203.204 124.574C203.207 124.441 203.21 124.307 203.21 124.174V123.685ZM108.638 199.391C114.64 190.59 114.214 183.984 105.98 175.754C97.7441 167.523 92.951 155.487 92.951 155.487C92.951 155.487 91.1621 148.496 87.0821 149.138C83.0021 149.78 80.0091 160.227 88.5521 166.622C97.0941 173.017 86.8521 177.353 83.5641 171.352C80.2761 165.35 71.299 149.923 66.645 146.972C61.991 144.021 58.718 145.675 59.815 151.757C60.36 154.776 65.4281 159.929 70.1631 164.743C74.9671 169.627 79.428 174.163 78.474 175.768C76.581 178.955 69.9141 172.023 69.9141 172.023C69.9141 172.023 49.038 153.025 44.494 157.976C40.304 162.539 46.765 166.418 56.7211 172.397C57.5671 172.905 58.4391 173.429 59.3321 173.969C70.7231 180.865 71.609 182.684 69.992 185.293C69.395 186.257 65.582 183.968 60.892 181.153C52.897 176.352 42.3551 170.023 40.8661 175.688C39.5781 180.591 47.334 183.595 54.368 186.32C60.228 188.59 65.5881 190.666 64.7991 193.484C63.9821 196.406 59.5531 193.969 54.7121 191.305C49.2771 188.314 43.3221 185.038 41.3731 188.735C37.6901 195.725 66.7831 203.954 67.0231 204.015C76.4231 206.453 100.295 211.619 108.638 199.391ZM147.303 199.391C141.301 190.59 141.727 183.984 149.962 175.754C158.197 167.523 162.99 155.487 162.99 155.487C162.99 155.487 164.779 148.496 168.859 149.138C172.939 149.78 175.932 160.227 167.39 166.622C158.847 173.017 169.089 177.353 172.377 171.352C175.666 165.35 184.637 149.923 189.291 146.972C193.945 144.021 197.22 145.675 196.122 151.757C195.578 154.776 190.509 159.929 185.774 164.744C180.97 169.628 176.509 174.163 177.462 175.768C179.355 178.955 186.027 172.019 186.027 172.019C186.027 172.019 206.902 153.022 211.448 157.973C215.637 162.535 209.176 166.415 199.219 172.394C198.348 172.917 197.478 173.441 196.609 173.966C185.218 180.862 184.332 182.681 185.948 185.289C186.546 186.254 190.359 183.964 195.048 181.149C203.044 176.349 213.586 170.019 215.075 175.685C216.364 180.588 208.607 183.592 201.573 186.317C195.713 188.587 190.353 190.663 191.141 193.481C191.957 196.402 196.385 193.965 201.225 191.301C206.66 188.31 212.616 185.032 214.564 188.732C218.248 195.726 189.15 203.947 188.915 204.007C179.515 206.453 155.643 211.619 147.303 199.391Z" fill="#FFD21E"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M152.047 102.567C153.229 102.985 154.108 104.257 154.944 105.468C156.074 107.104 157.126 108.627 158.74 107.769C160.644 106.756 162.205 105.202 163.225 103.302C164.246 101.402 164.681 99.2427 164.475 97.096C164.321 95.4908 163.813 93.9398 162.987 92.5548C162.161 91.1697 161.038 89.985 159.7 89.0862C158.361 88.1874 156.839 87.5968 155.245 87.3569C153.65 87.117 152.022 87.2339 150.478 87.699C148.934 88.1639 147.513 88.9653 146.316 90.0455C145.119 91.1257 144.176 92.4578 143.556 93.946C142.936 95.4342 142.653 97.0415 142.728 98.652C142.804 100.263 143.235 101.836 143.992 103.26C144.74 104.667 146.4 104.003 148.152 103.302C149.525 102.753 150.956 102.181 152.047 102.567ZM100.672 102.567C99.49 102.985 98.611 104.258 97.775 105.468C96.645 107.105 95.592 108.627 93.979 107.769C91.5845 106.501 89.7482 104.386 88.8278 101.838C87.9075 99.2895 87.9692 96.4896 89.0008 93.9841C90.0324 91.4786 91.9601 89.4471 94.408 88.2855C96.856 87.1239 99.6488 86.9156 102.242 87.701C104.307 88.3228 106.141 89.5427 107.513 91.2065C108.885 92.8704 109.732 94.9035 109.949 97.049C110.165 99.1945 109.74 101.356 108.728 103.26C107.979 104.667 106.319 104.003 104.567 103.303C103.193 102.753 101.764 102.181 100.672 102.567ZM144.099 149.318C152.242 142.903 155.233 132.429 155.233 125.977C155.233 120.877 151.802 122.482 146.309 125.202L145.999 125.355C140.957 127.852 134.245 131.177 126.877 131.177C119.508 131.177 112.796 127.852 107.755 125.354C102.084 122.545 98.527 120.783 98.527 125.978C98.527 132.634 101.709 143.563 110.443 149.912C111.596 147.573 113.219 145.497 115.211 143.813C117.202 142.129 119.52 140.874 122.018 140.126C122.89 139.866 123.788 141.367 124.707 142.904C125.594 144.386 126.501 145.902 127.423 145.902C128.406 145.902 129.371 144.408 130.314 142.95C131.299 141.425 132.26 139.94 133.189 140.237C137.864 141.738 141.775 144.993 144.099 149.318Z" fill="#32343D"/>
+<path d="M144.097 149.317C139.856 152.659 134.219 154.9 126.878 154.9C119.981 154.9 114.587 152.922 110.443 149.911C111.596 147.572 113.219 145.495 115.211 143.812C117.202 142.128 119.52 140.873 122.018 140.125C123.73 139.614 125.545 145.901 127.423 145.901C129.433 145.901 131.37 139.655 133.189 140.236C137.863 141.738 141.773 144.993 144.097 149.317Z" fill="#FF323D"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M81.2 111.64C80.2312 112.288 79.1173 112.687 77.9572 112.801C76.7971 112.916 75.6267 112.742 74.55 112.295C73.6893 111.94 72.9072 111.418 72.2488 110.759C71.5903 110.101 71.0684 109.319 70.713 108.458C70.267 107.381 70.0935 106.211 70.2082 105.051C70.3228 103.891 70.7219 102.777 71.37 101.808C72.1488 100.642 73.2558 99.7333 74.5512 99.1967C75.8466 98.6601 77.272 98.5197 78.6471 98.7935C80.0223 99.0672 81.2853 99.7427 82.2764 100.734C83.2675 101.726 83.9422 102.99 84.215 104.365C84.4883 105.74 84.3477 107.165 83.8113 108.46C83.2748 109.755 82.3654 110.861 81.2 111.64ZM182.613 111.64C181.644 112.288 180.53 112.687 179.37 112.801C178.209 112.916 177.039 112.742 175.962 112.295C175.101 111.939 174.319 111.418 173.661 110.759C173.003 110.101 172.481 109.319 172.125 108.458C171.68 107.381 171.507 106.211 171.621 105.051C171.736 103.891 172.135 102.777 172.782 101.808C173.364 100.936 174.133 100.205 175.032 99.6658C175.931 99.1269 176.938 98.7942 177.981 98.6917C179.025 98.5891 180.078 98.7193 181.064 99.0728C182.051 99.4264 182.947 99.9944 183.688 100.736C184.68 101.727 185.355 102.99 185.628 104.365C185.902 105.74 185.761 107.165 185.224 108.46C184.687 109.755 183.779 110.861 182.613 111.64Z" fill="#FFAD03"/>
+</svg>
\ No newline at end of file
--- /dev/null
+// Blog Post Content
+export const BLOG_POST_MD = String.raw`
+# Understanding Rust's Ownership System
+
+*Published on March 15, 2024 • 8 min read*
+
+Rust's ownership system is one of its most distinctive features, enabling memory safety without garbage collection. In this post, we'll explore how ownership works and why it's revolutionary for systems programming.
+
+## What is Ownership?
+
+Ownership is a set of rules that governs how Rust manages memory. These rules are checked at compile time, ensuring memory safety without runtime overhead.
+
+### The Three Rules of Ownership
+
+1. **Each value has a single owner**
+2. **There can only be one owner at a time**
+3. **When the owner goes out of scope, the value is dropped**
+
+## Memory Management Without GC
+
+Traditional approaches to memory management:
+
+- **Manual management** (C/C++): Error-prone, leads to bugs
+- **Garbage collection** (Java, Python): Runtime overhead
+- **Ownership** (Rust): Compile-time safety, zero runtime cost
+
+## Basic Examples
+
+### Variable Scope
+
+${'```'}rust
+fn main() {
+ let s = String::from("hello"); // s comes into scope
+
+ // s is valid here
+ println!("{}", s);
+
+} // s goes out of scope and is dropped
+${'```'}
+
+### Move Semantics
+
+${'```'}rust
+fn main() {
+ let s1 = String::from("hello");
+ let s2 = s1; // s1 is moved to s2
+
+ // println!("{}", s1); // ❌ ERROR: s1 is no longer valid
+ println!("{}", s2); // ✅ OK: s2 owns the string
+}
+${'```'}
+
+## Borrowing and References
+
+Instead of transferring ownership, you can **borrow** values:
+
+### Immutable References
+
+${'```'}rust
+fn calculate_length(s: &String) -> usize {
+ s.len() // s is a reference, doesn't own the String
+}
+
+fn main() {
+ let s1 = String::from("hello");
+ let len = calculate_length(&s1); // Borrow s1
+ println!("Length of '{}' is {}", s1, len); // s1 still valid
+}
+${'```'}
+
+### Mutable References
+
+${'```'}rust
+fn main() {
+ let mut s = String::from("hello");
+
+ let r1 = &mut s;
+ r1.push_str(", world");
+ println!("{}", r1);
+
+ // let r2 = &mut s; // ❌ ERROR: cannot borrow twice
+}
+${'```'}
+
+## Common Pitfalls
+
+### Dangling References
+
+${'```'}rust
+fn dangle() -> &String { // ❌ ERROR: missing lifetime specifier
+ let s = String::from("hello");
+ &s // s will be dropped, leaving a dangling reference
+}
+${'```'}
+
+### ✅ Solution
+
+${'```'}rust
+fn no_dangle() -> String {
+ let s = String::from("hello");
+ s // Ownership is moved out
+}
+${'```'}
+
+## Benefits
+
+- ✅ **No null pointer dereferences**
+- ✅ **No data races**
+- ✅ **No use-after-free**
+- ✅ **No memory leaks**
+
+## Conclusion
+
+Rust's ownership system eliminates entire classes of bugs at compile time. While it has a learning curve, the benefits in safety and performance are worth it.
+
+## Further Reading
+
+- [The Rust Book - Ownership](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html)
+- [Rust by Example - Ownership](https://doc.rust-lang.org/rust-by-example/scope/move.html)
+- [Rustlings Exercises](https://github.com/rust-lang/rustlings)
+
+---
+
+*Questions? Reach out on [Twitter](https://twitter.com/rustlang) or join the [Rust Discord](https://discord.gg/rust-lang)*
+`;
--- /dev/null
+// Data Analysis Report
+export const DATA_ANALYSIS_MD = String.raw`
+# Q4 2024 Business Analytics Report
+
+*Executive Summary • Generated on January 15, 2025*
+
+## 📊 Key Performance Indicators
+
+${'```'}
+Daily Active Users (DAU): 1.2M (+65% YoY)
+Monthly Active Users (MAU): 4.5M (+48% YoY)
+User Retention (Day 30): 68% (+12pp YoY)
+Average Session Duration: 24min (+35% YoY)
+${'```'}
+
+## 🎯 Product Performance
+
+### Feature Adoption Rates
+
+1. **AI Assistant**: 78% of users (↑ from 45%)
+2. **Collaboration Tools**: 62% of users (↑ from 38%)
+3. **Analytics Dashboard**: 54% of users (↑ from 31%)
+4. **Mobile App**: 41% of users (↑ from 22%)
+
+### Customer Satisfaction
+
+| Metric | Q4 2024 | Q3 2024 | Change |
+|--------|---------|---------|--------|
+| **NPS Score** | 72 | 68 | +4 |
+| **CSAT** | 4.6/5 | 4.4/5 | +0.2 |
+| **Support Tickets** | 2,340 | 2,890 | -19% |
+| **Resolution Time** | 4.2h | 5.1h | -18% |
+
+## 💰 Revenue Metrics
+
+### Monthly Recurring Revenue (MRR)
+
+- **Current MRR**: $2.8M (+42% YoY)
+- **New MRR**: $340K
+- **Expansion MRR**: $180K
+- **Churned MRR**: $95K
+- **Net New MRR**: $425K
+
+### Customer Acquisition
+
+${'```'}
+Cost per Acquisition (CAC): $127 (-23% YoY)
+Customer Lifetime Value: $1,840 (+31% YoY)
+LTV:CAC Ratio: 14.5:1
+Payback Period: 3.2 months
+${'```'}
+
+## 🌍 Geographic Performance
+
+### Revenue by Region
+
+1. **North America**: 45% ($1.26M)
+2. **Europe**: 32% ($896K)
+3. **Asia-Pacific**: 18% ($504K)
+4. **Other**: 5% ($140K)
+
+### Growth Opportunities
+
+- **APAC**: 89% YoY growth potential
+- **Latin America**: Emerging market entry
+- **Middle East**: Enterprise expansion
+
+## 📱 Channel Performance
+
+### Traffic Sources
+
+| Channel | Sessions | Conversion | Revenue |
+|---------|----------|------------|---------|
+| **Organic Search** | 45% | 3.2% | $1.1M |
+| **Direct** | 28% | 4.1% | $850K |
+| **Social Media** | 15% | 2.8% | $420K |
+| **Paid Ads** | 12% | 5.5% | $430K |
+
+### Marketing ROI
+
+- **Content Marketing**: 340% ROI
+- **Email Campaigns**: 280% ROI
+- **Social Media**: 190% ROI
+- **Paid Search**: 220% ROI
+
+## 🔍 User Behavior Analysis
+
+### Session Patterns
+
+- **Peak Hours**: 9-11 AM, 2-4 PM EST
+- **Mobile Usage**: 67% of sessions
+- **Average Pages/Session**: 4.8
+- **Bounce Rate**: 23% (↓ from 31%)
+
+### Feature Usage Heatmap
+
+Most used features in order:
+1. Dashboard (89% of users)
+2. Search (76% of users)
+3. Reports (64% of users)
+4. Settings (45% of users)
+5. Integrations (32% of users)
+
+## 💡 Recommendations
+
+1. **Invest** in AI capabilities (+$2M budget)
+2. **Expand** sales team in APAC region
+3. **Improve** onboarding to reduce churn
+4. **Launch** enterprise security features
+
+## Appendix
+
+### Methodology
+
+Data collected from:
+- Internal analytics (Amplitude)
+- Customer surveys (n=2,450)
+- Financial systems (NetSuite)
+- Market research (Gartner)
+
+---
+
+*Report prepared by Data Analytics Team • [View Interactive Dashboard](https://analytics.example.com)*
+`;
--- /dev/null
+// Empty state
+export const EMPTY_MD = '';
--- /dev/null
+// Math Formulas Content
+export const MATH_FORMULAS_MD = String.raw`
+# Mathematical Formulas and Expressions
+
+This document demonstrates various mathematical notation and formulas that can be rendered using LaTeX syntax in markdown.
+
+## Basic Arithmetic
+
+### Addition and Summation
+$$\sum_{i=1}^{n} i = \frac{n(n+1)}{2}$$
+
+## Algebra
+
+### Quadratic Formula
+The solutions to $ax^2 + bx + c = 0$ are:
+$$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$
+
+### Binomial Theorem
+$$(x + y)^n = \sum_{k=0}^{n} \binom{n}{k} x^{n-k} y^k$$
+
+## Calculus
+
+### Derivatives
+The derivative of $f(x) = x^n$ is:
+$$f'(x) = nx^{n-1}$$
+
+### Integration
+$$\int_a^b f(x) \, dx = F(b) - F(a)$$
+
+### Fundamental Theorem of Calculus
+$$\frac{d}{dx} \int_a^x f(t) \, dt = f(x)$$
+
+## Linear Algebra
+
+### Matrix Multiplication
+If $A$ is an $m \times n$ matrix and $B$ is an $n \times p$ matrix, then:
+$$C_{ij} = \sum_{k=1}^{n} A_{ik} B_{kj}$$
+
+### Eigenvalues and Eigenvectors
+For a square matrix $A$, if $Av = \lambda v$ for some non-zero vector $v$, then:
+- $\lambda$ is an eigenvalue
+- $v$ is an eigenvector
+
+## Statistics and Probability
+
+### Normal Distribution
+The probability density function is:
+$$f(x) = \frac{1}{\sigma\sqrt{2\pi}} e^{-\frac{1}{2}\left(\frac{x-\mu}{\sigma}\right)^2}$$
+
+### Bayes' Theorem
+$$P(A|B) = \frac{P(B|A) \cdot P(A)}{P(B)}$$
+
+### Central Limit Theorem
+For large $n$, the sample mean $\bar{X}$ is approximately:
+$$\bar{X} \sim N\left(\mu, \frac{\sigma^2}{n}\right)$$
+
+## Trigonometry
+
+### Pythagorean Identity
+$$\sin^2\theta + \cos^2\theta = 1$$
+
+### Euler's Formula
+$$e^{i\theta} = \cos\theta + i\sin\theta$$
+
+### Taylor Series for Sine
+$$\sin x = \sum_{n=0}^{\infty} \frac{(-1)^n}{(2n+1)!} x^{2n+1} = x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!} + \cdots$$
+
+## Complex Analysis
+
+### Complex Numbers
+A complex number can be written as:
+$$z = a + bi = r e^{i\theta}$$
+
+where $r = |z| = \sqrt{a^2 + b^2}$ and $\theta = \arg(z)$
+
+### Cauchy-Riemann Equations
+For a function $f(z) = u(x,y) + iv(x,y)$ to be analytic:
+$$\frac{\partial u}{\partial x} = \frac{\partial v}{\partial y}, \quad \frac{\partial u}{\partial y} = -\frac{\partial v}{\partial x}$$
+
+## Differential Equations
+
+### First-order Linear ODE
+$$\frac{dy}{dx} + P(x)y = Q(x)$$
+
+Solution: $y = e^{-\int P(x)dx}\left[\int Q(x)e^{\int P(x)dx}dx + C\right]$
+
+### Heat Equation
+$$\frac{\partial u}{\partial t} = \alpha \frac{\partial^2 u}{\partial x^2}$$
+
+## Number Theory
+
+### Prime Number Theorem
+$$\pi(x) \sim \frac{x}{\ln x}$$
+
+where $\pi(x)$ is the number of primes less than or equal to $x$.
+
+### Fermat's Last Theorem
+For $n > 2$, there are no positive integers $a$, $b$, and $c$ such that:
+$$a^n + b^n = c^n$$
+
+## Set Theory
+
+### De Morgan's Laws
+$$\overline{A \cup B} = \overline{A} \cap \overline{B}$$
+$$\overline{A \cap B} = \overline{A} \cup \overline{B}$$
+
+## Advanced Topics
+
+### Riemann Zeta Function
+$$\zeta(s) = \sum_{n=1}^{\infty} \frac{1}{n^s} = \prod_{p \text{ prime}} \frac{1}{1-p^{-s}}$$
+
+### Maxwell's Equations
+$$\nabla \cdot \mathbf{E} = \frac{\rho}{\epsilon_0}$$
+$$\nabla \cdot \mathbf{B} = 0$$
+$$\nabla \times \mathbf{E} = -\frac{\partial \mathbf{B}}{\partial t}$$
+$$\nabla \times \mathbf{B} = \mu_0\mathbf{J} + \mu_0\epsilon_0\frac{\partial \mathbf{E}}{\partial t}$$
+
+### Schrödinger Equation
+$$i\hbar\frac{\partial}{\partial t}\Psi(\mathbf{r},t) = \hat{H}\Psi(\mathbf{r},t)$$
+
+## Inline Math Examples
+
+Here are some inline mathematical expressions:
+
+- The golden ratio: $\phi = \frac{1 + \sqrt{5}}{2} \approx 1.618$
+- Euler's number: $e = \lim_{n \to \infty} \left(1 + \frac{1}{n}\right)^n$
+- Pi: $\pi = 4 \sum_{n=0}^{\infty} \frac{(-1)^n}{2n+1}$
+- Square root of 2: $\sqrt{2} = 1.41421356...$
+
+## Fractions and Radicals
+
+Complex fraction: $\frac{\frac{a}{b} + \frac{c}{d}}{\frac{e}{f} - \frac{g}{h}}$
+
+Nested radicals: $\sqrt{2 + \sqrt{3 + \sqrt{4 + \sqrt{5}}}}$
+
+## Summations and Products
+
+### Geometric Series
+$$\sum_{n=0}^{\infty} ar^n = \frac{a}{1-r} \quad \text{for } |r| < 1$$
+
+### Product Notation
+$$n! = \prod_{k=1}^{n} k$$
+
+### Double Summation
+$$\sum_{i=1}^{m} \sum_{j=1}^{n} a_{ij}$$
+
+## Limits
+
+$$\lim_{x \to 0} \frac{\sin x}{x} = 1$$
+
+$$\lim_{n \to \infty} \left(1 + \frac{x}{n}\right)^n = e^x$$
+
+---
+
+*This document showcases various mathematical notation and formulas that can be rendered in markdown using LaTeX syntax.*
+`;
--- /dev/null
+// README Content
+export const README_MD = String.raw`
+# 🚀 Awesome Web Framework
+
+[](https://www.npmjs.com/package/awesome-framework)
+[](https://github.com/awesome/framework/actions)
+[](https://codecov.io/gh/awesome/framework)
+[](https://opensource.org/licenses/MIT)
+
+> A modern, fast, and flexible web framework for building scalable applications
+
+## ✨ Features
+
+- 🎯 **Type-Safe** - Full TypeScript support out of the box
+- ⚡ **Lightning Fast** - Built on Vite for instant HMR
+- 📦 **Zero Config** - Works out of the box for most use cases
+- 🎨 **Flexible** - Unopinionated with sensible defaults
+- 🔧 **Extensible** - Plugin system for custom functionality
+- 📱 **Responsive** - Mobile-first approach
+- 🌍 **i18n Ready** - Built-in internationalization
+- 🔒 **Secure** - Security best practices by default
+
+## 📦 Installation
+
+${'```'}bash
+npm install awesome-framework
+# or
+yarn add awesome-framework
+# or
+pnpm add awesome-framework
+${'```'}
+
+## 🚀 Quick Start
+
+### Create a new project
+
+${'```'}bash
+npx create-awesome-app my-app
+cd my-app
+npm run dev
+${'```'}
+
+### Basic Example
+
+${'```'}javascript
+import { createApp } from 'awesome-framework';
+
+const app = createApp({
+ port: 3000,
+ middleware: ['cors', 'helmet', 'compression']
+});
+
+app.get('/', (req, res) => {
+ res.json({ message: 'Hello World!' });
+});
+
+app.listen(() => {
+ console.log('Server running on http://localhost:3000');
+});
+${'```'}
+
+## 📖 Documentation
+
+### Core Concepts
+
+- [Getting Started](https://docs.awesome.dev/getting-started)
+- [Configuration](https://docs.awesome.dev/configuration)
+- [Routing](https://docs.awesome.dev/routing)
+- [Middleware](https://docs.awesome.dev/middleware)
+- [Database](https://docs.awesome.dev/database)
+- [Authentication](https://docs.awesome.dev/authentication)
+
+### Advanced Topics
+
+- [Performance Optimization](https://docs.awesome.dev/performance)
+- [Deployment](https://docs.awesome.dev/deployment)
+- [Testing](https://docs.awesome.dev/testing)
+- [Security](https://docs.awesome.dev/security)
+
+## 🛠️ Development
+
+### Prerequisites
+
+- Node.js >= 18
+- pnpm >= 8
+
+### Setup
+
+${'```'}bash
+git clone https://github.com/awesome/framework.git
+cd framework
+pnpm install
+pnpm dev
+${'```'}
+
+### Testing
+
+${'```'}bash
+pnpm test # Run unit tests
+pnpm test:e2e # Run end-to-end tests
+pnpm test:watch # Run tests in watch mode
+${'```'}
+
+## 🤝 Contributing
+
+We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
+
+### Contributors
+
+<a href="https://github.com/awesome/framework/graphs/contributors">
+ <img src="https://contrib.rocks/image?repo=awesome/framework" />
+</a>
+
+## 📊 Benchmarks
+
+| Framework | Requests/sec | Latency (ms) | Memory (MB) |
+|-----------|-------------|--------------|-------------|
+| **Awesome** | **45,230** | **2.1** | **42** |
+| Express | 28,450 | 3.5 | 68 |
+| Fastify | 41,200 | 2.3 | 48 |
+| Koa | 32,100 | 3.1 | 52 |
+
+*Benchmarks performed on MacBook Pro M2, Node.js 20.x*
+
+## 📝 License
+
+MIT © [Awesome Team](https://github.com/awesome)
+
+## 🙏 Acknowledgments
+
+Special thanks to all our sponsors and contributors who make this project possible.
+
+---
+
+**[Website](https://awesome.dev)** • **[Documentation](https://docs.awesome.dev)** • **[Discord](https://discord.gg/awesome)** • **[Twitter](https://twitter.com/awesomeframework)**
+`;
--- /dev/null
+import { serverStore } from '$lib/stores/server.svelte';
+
+/**
+ * Mock server properties for Storybook testing
+ * This utility allows setting mock server configurations without polluting production code
+ */
+export function mockServerProps(props: Partial<ApiLlamaCppServerProps>): void {
+ // Directly set the private _serverProps for testing purposes
+ (serverStore as unknown as { _serverProps: ApiLlamaCppServerProps })._serverProps = {
+ model_path: props.model_path || 'test-model',
+ modalities: {
+ vision: props.modalities?.vision ?? false,
+ audio: props.modalities?.audio ?? false
+ },
+ ...props
+ } as ApiLlamaCppServerProps;
+}
+
+/**
+ * Reset server store to clean state for testing
+ */
+export function resetServerStore(): void {
+ (serverStore as unknown as { _serverProps: ApiLlamaCppServerProps })._serverProps = {
+ model_path: '',
+ modalities: {
+ vision: false,
+ audio: false
+ }
+ } as ApiLlamaCppServerProps;
+ (serverStore as unknown as { _error: string })._error = '';
+ (serverStore as unknown as { _loading: boolean })._loading = false;
+}
+
+/**
+ * Common mock configurations for Storybook stories
+ */
+export const mockConfigs = {
+ visionOnly: {
+ modalities: { vision: true, audio: false }
+ },
+ audioOnly: {
+ modalities: { vision: false, audio: true }
+ },
+ bothModalities: {
+ modalities: { vision: true, audio: true }
+ },
+ noModalities: {
+ modalities: { vision: false, audio: false }
+ }
+} as const;
+++ /dev/null
-import React, { createContext, useContext, useEffect, useState } from 'react';
-import {
- APIMessage,
- CanvasData,
- Conversation,
- LlamaCppServerProps,
- Message,
- PendingMessage,
- ViewingChat,
-} from './types';
-import StorageUtils from './storage';
-import {
- filterThoughtFromMsgs,
- normalizeMsgsForAPI,
- getSSEStreamAsync,
- getServerProps,
-} from './misc';
-import { BASE_URL, CONFIG_DEFAULT, isDev } from '../Config';
-import { matchPath, useLocation, useNavigate } from 'react-router';
-import toast from 'react-hot-toast';
-
-interface AppContextValue {
- // conversations and messages
- viewingChat: ViewingChat | null;
- pendingMessages: Record<Conversation['id'], PendingMessage>;
- isGenerating: (convId: string) => boolean;
- sendMessage: (
- convId: string | null,
- leafNodeId: Message['id'] | null,
- content: string,
- extra: Message['extra'],
- onChunk: CallbackGeneratedChunk
- ) => Promise<boolean>;
- stopGenerating: (convId: string) => void;
- replaceMessageAndGenerate: (
- convId: string,
- parentNodeId: Message['id'], // the parent node of the message to be replaced
- content: string | null,
- extra: Message['extra'],
- onChunk: CallbackGeneratedChunk
- ) => Promise<void>;
-
- // canvas
- canvasData: CanvasData | null;
- setCanvasData: (data: CanvasData | null) => void;
-
- // config
- config: typeof CONFIG_DEFAULT;
- saveConfig: (config: typeof CONFIG_DEFAULT) => void;
- showSettings: boolean;
- setShowSettings: (show: boolean) => void;
-
- // props
- serverProps: LlamaCppServerProps | null;
-}
-
-// this callback is used for scrolling to the bottom of the chat and switching to the last node
-export type CallbackGeneratedChunk = (currLeafNodeId?: Message['id']) => void;
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-const AppContext = createContext<AppContextValue>({} as any);
-
-const getViewingChat = async (convId: string): Promise<ViewingChat | null> => {
- const conv = await StorageUtils.getOneConversation(convId);
- if (!conv) return null;
- return {
- conv: conv,
- // all messages from all branches, not filtered by last node
- messages: await StorageUtils.getMessages(convId),
- };
-};
-
-export const AppContextProvider = ({
- children,
-}: {
- children: React.ReactElement;
-}) => {
- const { pathname } = useLocation();
- const navigate = useNavigate();
- const params = matchPath('/chat/:convId', pathname);
- const convId = params?.params?.convId;
-
- const [serverProps, setServerProps] = useState<LlamaCppServerProps | null>(
- null
- );
- const [viewingChat, setViewingChat] = useState<ViewingChat | null>(null);
- const [pendingMessages, setPendingMessages] = useState<
- Record<Conversation['id'], PendingMessage>
- >({});
- const [aborts, setAborts] = useState<
- Record<Conversation['id'], AbortController>
- >({});
- const [config, setConfig] = useState(StorageUtils.getConfig());
- const [canvasData, setCanvasData] = useState<CanvasData | null>(null);
- const [showSettings, setShowSettings] = useState(false);
-
- // get server props
- useEffect(() => {
- getServerProps(BASE_URL, config.apiKey)
- .then((props) => {
- console.debug('Server props:', props);
- setServerProps(props);
- })
- .catch((err) => {
- console.error(err);
- toast.error('Failed to fetch server props');
- });
- // eslint-disable-next-line
- }, []);
-
- // handle change when the convId from URL is changed
- useEffect(() => {
- // also reset the canvas data
- setCanvasData(null);
- const handleConversationChange = async (changedConvId: string) => {
- if (changedConvId !== convId) return;
- setViewingChat(await getViewingChat(changedConvId));
- };
- StorageUtils.onConversationChanged(handleConversationChange);
- getViewingChat(convId ?? '').then(setViewingChat);
- return () => {
- StorageUtils.offConversationChanged(handleConversationChange);
- };
- }, [convId]);
-
- const setPending = (convId: string, pendingMsg: PendingMessage | null) => {
- // if pendingMsg is null, remove the key from the object
- if (!pendingMsg) {
- setPendingMessages((prev) => {
- const newState = { ...prev };
- delete newState[convId];
- return newState;
- });
- } else {
- setPendingMessages((prev) => ({ ...prev, [convId]: pendingMsg }));
- }
- };
-
- const setAbort = (convId: string, controller: AbortController | null) => {
- if (!controller) {
- setAborts((prev) => {
- const newState = { ...prev };
- delete newState[convId];
- return newState;
- });
- } else {
- setAborts((prev) => ({ ...prev, [convId]: controller }));
- }
- };
-
- ////////////////////////////////////////////////////////////////////////
- // public functions
-
- const isGenerating = (convId: string) => !!pendingMessages[convId];
-
- const generateMessage = async (
- convId: string,
- leafNodeId: Message['id'],
- onChunk: CallbackGeneratedChunk
- ) => {
- if (isGenerating(convId)) return;
-
- const config = StorageUtils.getConfig();
- const currConversation = await StorageUtils.getOneConversation(convId);
- if (!currConversation) {
- throw new Error('Current conversation is not found');
- }
-
- const currMessages = StorageUtils.filterByLeafNodeId(
- await StorageUtils.getMessages(convId),
- leafNodeId,
- false
- );
- const abortController = new AbortController();
- setAbort(convId, abortController);
-
- if (!currMessages) {
- throw new Error('Current messages are not found');
- }
-
- const pendingId = Date.now() + 1;
- let pendingMsg: PendingMessage = {
- id: pendingId,
- convId,
- type: 'text',
- timestamp: pendingId,
- role: 'assistant',
- content: null,
- parent: leafNodeId,
- children: [],
- };
- setPending(convId, pendingMsg);
-
- try {
- // prepare messages for API
- let messages: APIMessage[] = [
- ...(config.systemMessage.length === 0
- ? []
- : [{ role: 'system', content: config.systemMessage } as APIMessage]),
- ...normalizeMsgsForAPI(currMessages),
- ];
- if (config.excludeThoughtOnReq) {
- messages = filterThoughtFromMsgs(messages);
- }
- if (isDev) console.log({ messages });
-
- // prepare params
- const params = {
- messages,
- stream: true,
- cache_prompt: true,
- reasoning_format: 'none',
- samplers: config.samplers,
- temperature: config.temperature,
- dynatemp_range: config.dynatemp_range,
- dynatemp_exponent: config.dynatemp_exponent,
- top_k: config.top_k,
- top_p: config.top_p,
- min_p: config.min_p,
- typical_p: config.typical_p,
- xtc_probability: config.xtc_probability,
- xtc_threshold: config.xtc_threshold,
- repeat_last_n: config.repeat_last_n,
- repeat_penalty: config.repeat_penalty,
- presence_penalty: config.presence_penalty,
- frequency_penalty: config.frequency_penalty,
- dry_multiplier: config.dry_multiplier,
- dry_base: config.dry_base,
- dry_allowed_length: config.dry_allowed_length,
- dry_penalty_last_n: config.dry_penalty_last_n,
- max_tokens: config.max_tokens,
- timings_per_token: !!config.showTokensPerSecond,
- ...(config.custom.length ? JSON.parse(config.custom) : {}),
- };
-
- // send request
- const fetchResponse = await fetch(`${BASE_URL}/v1/chat/completions`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- ...(config.apiKey
- ? { Authorization: `Bearer ${config.apiKey}` }
- : {}),
- },
- body: JSON.stringify(params),
- signal: abortController.signal,
- });
- if (fetchResponse.status !== 200) {
- const body = await fetchResponse.json();
- throw new Error(body?.error?.message || 'Unknown error');
- }
- const chunks = getSSEStreamAsync(fetchResponse);
- for await (const chunk of chunks) {
- // const stop = chunk.stop;
- if (chunk.error) {
- throw new Error(chunk.error?.message || 'Unknown error');
- }
- const addedContent = chunk.choices[0]?.delta.content;
- const lastContent = pendingMsg.content || '';
- if (addedContent) {
- pendingMsg = {
- ...pendingMsg,
- content: lastContent + addedContent,
- };
- }
- const timings = chunk.timings;
- if (timings && config.showTokensPerSecond) {
- // only extract what's really needed, to save some space
- pendingMsg.timings = {
- prompt_n: timings.prompt_n,
- prompt_ms: timings.prompt_ms,
- predicted_n: timings.predicted_n,
- predicted_ms: timings.predicted_ms,
- };
- }
- setPending(convId, pendingMsg);
- onChunk(); // don't need to switch node for pending message
- }
- } catch (err) {
- setPending(convId, null);
- if ((err as Error).name === 'AbortError') {
- // user stopped the generation via stopGeneration() function
- // we can safely ignore this error
- } else {
- console.error(err);
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- toast.error((err as any)?.message ?? 'Unknown error');
- throw err; // rethrow
- }
- }
-
- if (pendingMsg.content !== null) {
- await StorageUtils.appendMsg(pendingMsg as Message, leafNodeId);
- }
- setPending(convId, null);
- onChunk(pendingId); // trigger scroll to bottom and switch to the last node
- };
-
- const sendMessage = async (
- convId: string | null,
- leafNodeId: Message['id'] | null,
- content: string,
- extra: Message['extra'],
- onChunk: CallbackGeneratedChunk
- ): Promise<boolean> => {
- if (isGenerating(convId ?? '') || content.trim().length === 0) return false;
-
- if (convId === null || convId.length === 0 || leafNodeId === null) {
- const conv = await StorageUtils.createConversation(
- content.substring(0, 256)
- );
- convId = conv.id;
- leafNodeId = conv.currNode;
- // if user is creating a new conversation, redirect to the new conversation
- navigate(`/chat/${convId}`);
- }
-
- const now = Date.now();
- const currMsgId = now;
- StorageUtils.appendMsg(
- {
- id: currMsgId,
- timestamp: now,
- type: 'text',
- convId,
- role: 'user',
- content,
- extra,
- parent: leafNodeId,
- children: [],
- },
- leafNodeId
- );
- onChunk(currMsgId);
-
- try {
- await generateMessage(convId, currMsgId, onChunk);
- return true;
- } catch (_) {
- // TODO: rollback
- }
- return false;
- };
-
- const stopGenerating = (convId: string) => {
- setPending(convId, null);
- aborts[convId]?.abort();
- };
-
- // if content is undefined, we remove last assistant message
- const replaceMessageAndGenerate = async (
- convId: string,
- parentNodeId: Message['id'], // the parent node of the message to be replaced
- content: string | null,
- extra: Message['extra'],
- onChunk: CallbackGeneratedChunk
- ) => {
- if (isGenerating(convId)) return;
-
- if (content !== null) {
- const now = Date.now();
- const currMsgId = now;
- StorageUtils.appendMsg(
- {
- id: currMsgId,
- timestamp: now,
- type: 'text',
- convId,
- role: 'user',
- content,
- extra,
- parent: parentNodeId,
- children: [],
- },
- parentNodeId
- );
- parentNodeId = currMsgId;
- }
- onChunk(parentNodeId);
-
- await generateMessage(convId, parentNodeId, onChunk);
- };
-
- const saveConfig = (config: typeof CONFIG_DEFAULT) => {
- StorageUtils.setConfig(config);
- setConfig(config);
- };
-
- return (
- <AppContext.Provider
- value={{
- isGenerating,
- viewingChat,
- pendingMessages,
- sendMessage,
- stopGenerating,
- replaceMessageAndGenerate,
- canvasData,
- setCanvasData,
- config,
- saveConfig,
- showSettings,
- setShowSettings,
- serverProps,
- }}
- >
- {children}
- </AppContext.Provider>
- );
-};
-
-export const useAppContext = () => useContext(AppContext);
+++ /dev/null
-export const XCloseButton: React.ElementType<
- React.ClassAttributes<HTMLButtonElement> &
- React.HTMLAttributes<HTMLButtonElement>
-> = ({ className, ...props }) => (
- <button className={`btn btn-square btn-sm ${className ?? ''}`} {...props}>
- <svg
- xmlns="http://www.w3.org/2000/svg"
- className="h-6 w-6"
- fill="none"
- viewBox="0 0 24 24"
- stroke="currentColor"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth="2"
- d="M6 18L18 6M6 6l12 12"
- />
- </svg>
- </button>
-);
-
-export const OpenInNewTab = ({
- href,
- children,
-}: {
- href: string;
- children: string;
-}) => (
- <a
- className="underline"
- href={href}
- target="_blank"
- rel="noopener noreferrer"
- >
- {children}
- </a>
-);
-
-export function BtnWithTooltips({
- className,
- onClick,
- onMouseLeave,
- children,
- tooltipsContent,
- disabled,
-}: {
- className?: string;
- onClick: () => void;
- onMouseLeave?: () => void;
- children: React.ReactNode;
- tooltipsContent: string;
- disabled?: boolean;
-}) {
- // the onClick handler is on the container, so screen readers can safely ignore the inner button
- // this prevents the label from being read twice
- return (
- <div
- className="tooltip tooltip-bottom"
- data-tip={tooltipsContent}
- role="button"
- onClick={onClick}
- >
- <button
- className={`${className ?? ''} flex items-center justify-center`}
- disabled={disabled}
- onMouseLeave={onMouseLeave}
- aria-hidden={true}
- >
- {children}
- </button>
- </div>
- );
-}
+++ /dev/null
-import { useEffect } from 'react';
-import { ChatTextareaApi } from '../components/useChatTextarea.ts';
-import { ChatExtraContextApi } from '../components/useChatExtraContext.tsx';
-
-// Extra context when using llama.cpp WebUI from llama-vscode, inside an iframe
-// Ref: https://github.com/ggml-org/llama.cpp/pull/11940
-
-interface SetTextEvData {
- text: string;
- context: string;
-}
-
-/**
- * To test it:
- * window.postMessage({ command: 'setText', text: 'Spot the syntax error', context: 'def test()\n return 123' }, '*');
- */
-
-export const useVSCodeContext = (
- textarea: ChatTextareaApi,
- extraContext: ChatExtraContextApi
-) => {
- // Accept setText message from a parent window and set inputMsg and extraContext
- useEffect(() => {
- const handleMessage = (event: MessageEvent) => {
- if (event.data?.command === 'setText') {
- const data: SetTextEvData = event.data;
- textarea.setValue(data?.text);
- if (data?.context && data.context.length > 0) {
- extraContext.clearItems();
- extraContext.addItems([
- {
- type: 'context',
- name: 'Extra context',
- content: data.context,
- },
- ]);
- }
- textarea.focus();
- setTimeout(() => {
- textarea.refOnSubmit.current?.();
- }, 10); // wait for setExtraContext to finish
- }
- };
-
- window.addEventListener('message', handleMessage);
- return () => window.removeEventListener('message', handleMessage);
- }, [textarea, extraContext]);
-
- // Add a keydown listener that sends the "escapePressed" message to the parent window
- useEffect(() => {
- const handleKeyDown = (event: KeyboardEvent) => {
- if (event.key === 'Escape') {
- window.parent.postMessage({ command: 'escapePressed' }, '*');
- }
- };
-
- window.addEventListener('keydown', handleKeyDown);
- return () => window.removeEventListener('keydown', handleKeyDown);
- }, []);
-
- return {};
-};
+++ /dev/null
-// @ts-expect-error this package does not have typing
-import TextLineStream from 'textlinestream';
-import {
- APIMessage,
- APIMessageContentPart,
- LlamaCppServerProps,
- Message,
-} from './types';
-
-// ponyfill for missing ReadableStream asyncIterator on Safari
-import { asyncIterator } from '@sec-ant/readable-stream/ponyfill/asyncIterator';
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export const isString = (x: any) => !!x.toLowerCase;
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export const isBoolean = (x: any) => x === true || x === false;
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export const isNumeric = (n: any) => !isString(n) && !isNaN(n) && !isBoolean(n);
-export const escapeAttr = (str: string) =>
- str.replace(/>/g, '>').replace(/"/g, '"');
-
-// wrapper for SSE
-export async function* getSSEStreamAsync(fetchResponse: Response) {
- if (!fetchResponse.body) throw new Error('Response body is empty');
- const lines: ReadableStream<string> = fetchResponse.body
- .pipeThrough(new TextDecoderStream())
- .pipeThrough(new TextLineStream());
- // @ts-expect-error asyncIterator complains about type, but it should work
- for await (const line of asyncIterator(lines)) {
- //if (isDev) console.log({ line });
- if (line.startsWith('data:') && !line.endsWith('[DONE]')) {
- const data = JSON.parse(line.slice(5));
- yield data;
- } else if (line.startsWith('error:')) {
- const data = JSON.parse(line.slice(6));
- throw new Error(data.message || 'Unknown error');
- }
- }
-}
-
-// copy text to clipboard
-export const copyStr = (textToCopy: string) => {
- // Navigator clipboard api needs a secure context (https)
- if (navigator.clipboard && window.isSecureContext) {
- navigator.clipboard.writeText(textToCopy);
- } else {
- // Use the 'out of viewport hidden text area' trick
- const textArea = document.createElement('textarea');
- textArea.value = textToCopy;
- // Move textarea out of the viewport so it's not visible
- textArea.style.position = 'absolute';
- textArea.style.left = '-999999px';
- document.body.prepend(textArea);
- textArea.select();
- document.execCommand('copy');
- }
-};
-
-/**
- * filter out redundant fields upon sending to API
- * also format extra into text
- */
-export function normalizeMsgsForAPI(messages: Readonly<Message[]>) {
- return messages.map((msg) => {
- if (msg.role !== 'user' || !msg.extra) {
- return {
- role: msg.role,
- content: msg.content,
- } as APIMessage;
- }
-
- // extra content first, then user text message in the end
- // this allow re-using the same cache prefix for long context
- const contentArr: APIMessageContentPart[] = [];
-
- for (const extra of msg.extra ?? []) {
- if (extra.type === 'context') {
- contentArr.push({
- type: 'text',
- text: extra.content,
- });
- } else if (extra.type === 'textFile') {
- contentArr.push({
- type: 'text',
- text: `File: ${extra.name}\nContent:\n\n${extra.content}`,
- });
- } else if (extra.type === 'imageFile') {
- contentArr.push({
- type: 'image_url',
- image_url: { url: extra.base64Url },
- });
- } else if (extra.type === 'audioFile') {
- contentArr.push({
- type: 'input_audio',
- input_audio: {
- data: extra.base64Data,
- format: /wav/.test(extra.mimeType) ? 'wav' : 'mp3',
- },
- });
- } else {
- throw new Error('Unknown extra type');
- }
- }
-
- // add user message to the end
- contentArr.push({
- type: 'text',
- text: msg.content,
- });
-
- return {
- role: msg.role,
- content: contentArr,
- };
- }) as APIMessage[];
-}
-
-/**
- * recommended for DeepsSeek-R1, filter out content between <think> and </think> tags
- */
-export function filterThoughtFromMsgs(messages: APIMessage[]) {
- console.debug({ messages });
- return messages.map((msg) => {
- if (msg.role !== 'assistant') {
- return msg;
- }
- // assistant message is always a string
- const contentStr = msg.content as string;
- return {
- role: msg.role,
- content:
- msg.role === 'assistant'
- ? contentStr
- .split(/<\/think>|<\|end\|>/)
- .at(-1)!
- .trim()
- : contentStr,
- } as APIMessage;
- });
-}
-
-export function classNames(classes: Record<string, boolean>): string {
- return Object.entries(classes)
- .filter(([_, value]) => value)
- .map(([key, _]) => key)
- .join(' ');
-}
-
-export const delay = (ms: number) =>
- new Promise((resolve) => setTimeout(resolve, ms));
-
-export const throttle = <T extends unknown[]>(
- callback: (...args: T) => void,
- delay: number
-) => {
- let isWaiting = false;
-
- return (...args: T) => {
- if (isWaiting) {
- return;
- }
-
- callback(...args);
- isWaiting = true;
-
- setTimeout(() => {
- isWaiting = false;
- }, delay);
- };
-};
-
-export const cleanCurrentUrl = (removeQueryParams: string[]) => {
- const url = new URL(window.location.href);
- removeQueryParams.forEach((param) => {
- url.searchParams.delete(param);
- });
- window.history.replaceState({}, '', url.toString());
-};
-
-export const getServerProps = async (
- baseUrl: string,
- apiKey?: string
-): Promise<LlamaCppServerProps> => {
- try {
- const response = await fetch(`${baseUrl}/props`, {
- headers: {
- 'Content-Type': 'application/json',
- ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
- },
- });
- if (!response.ok) {
- throw new Error('Failed to fetch server props');
- }
- const data = await response.json();
- return data as LlamaCppServerProps;
- } catch (error) {
- console.error('Error fetching server props:', error);
- throw error;
- }
-};
+++ /dev/null
-// coversations is stored in localStorage
-// format: { [convId]: { id: string, lastModified: number, messages: [...] } }
-
-import { CONFIG_DEFAULT } from '../Config';
-import { Conversation, Message, TimingReport } from './types';
-import Dexie, { Table } from 'dexie';
-
-const event = new EventTarget();
-
-type CallbackConversationChanged = (convId: string) => void;
-let onConversationChangedHandlers: [
- CallbackConversationChanged,
- EventListener,
-][] = [];
-const dispatchConversationChange = (convId: string) => {
- event.dispatchEvent(
- new CustomEvent('conversationChange', { detail: { convId } })
- );
-};
-
-const db = new Dexie('LlamacppWebui') as Dexie & {
- conversations: Table<Conversation>;
- messages: Table<Message>;
-};
-
-// https://dexie.org/docs/Version/Version.stores()
-db.version(1).stores({
- // Unlike SQL, you don’t need to specify all properties but only the one you wish to index.
- conversations: '&id, lastModified',
- messages: '&id, convId, [convId+id], timestamp',
-});
-
-// convId is a string prefixed with 'conv-'
-const StorageUtils = {
- /**
- * manage conversations
- */
- async getAllConversations(): Promise<Conversation[]> {
- await migrationLStoIDB().catch(console.error); // noop if already migrated
- return (await db.conversations.toArray()).sort(
- (a, b) => b.lastModified - a.lastModified
- );
- },
- /**
- * can return null if convId does not exist
- */
- async getOneConversation(convId: string): Promise<Conversation | null> {
- return (await db.conversations.where('id').equals(convId).first()) ?? null;
- },
- /**
- * get all message nodes in a conversation
- */
- async getMessages(convId: string): Promise<Message[]> {
- return await db.messages.where({ convId }).toArray();
- },
- /**
- * use in conjunction with getMessages to filter messages by leafNodeId
- * includeRoot: whether to include the root node in the result
- * if node with leafNodeId does not exist, return the path with the latest timestamp
- */
- filterByLeafNodeId(
- msgs: Readonly<Message[]>,
- leafNodeId: Message['id'],
- includeRoot: boolean
- ): Readonly<Message[]> {
- const res: Message[] = [];
- const nodeMap = new Map<Message['id'], Message>();
- for (const msg of msgs) {
- nodeMap.set(msg.id, msg);
- }
- let startNode: Message | undefined = nodeMap.get(leafNodeId);
- if (!startNode) {
- // if not found, we return the path with the latest timestamp
- let latestTime = -1;
- for (const msg of msgs) {
- if (msg.timestamp > latestTime) {
- startNode = msg;
- latestTime = msg.timestamp;
- }
- }
- }
- // traverse the path from leafNodeId to root
- // startNode can never be undefined here
- let currNode: Message | undefined = startNode;
- while (currNode) {
- if (currNode.type !== 'root' || (currNode.type === 'root' && includeRoot))
- res.push(currNode);
- currNode = nodeMap.get(currNode.parent ?? -1);
- }
- res.sort((a, b) => a.timestamp - b.timestamp);
- return res;
- },
- /**
- * create a new conversation with a default root node
- */
- async createConversation(name: string): Promise<Conversation> {
- const now = Date.now();
- const msgId = now;
- const conv: Conversation = {
- id: `conv-${now}`,
- lastModified: now,
- currNode: msgId,
- name,
- };
- await db.conversations.add(conv);
- // create a root node
- await db.messages.add({
- id: msgId,
- convId: conv.id,
- type: 'root',
- timestamp: now,
- role: 'system',
- content: '',
- parent: -1,
- children: [],
- });
- return conv;
- },
- /**
- * update the name of a conversation
- */
- async updateConversationName(convId: string, name: string): Promise<void> {
- await db.conversations.update(convId, {
- name,
- lastModified: Date.now(),
- });
- dispatchConversationChange(convId);
- },
- /**
- * if convId does not exist, throw an error
- */
- async appendMsg(
- msg: Exclude<Message, 'parent' | 'children'>,
- parentNodeId: Message['id']
- ): Promise<void> {
- if (msg.content === null) return;
- const { convId } = msg;
- await db.transaction('rw', db.conversations, db.messages, async () => {
- const conv = await StorageUtils.getOneConversation(convId);
- const parentMsg = await db.messages
- .where({ convId, id: parentNodeId })
- .first();
- // update the currNode of conversation
- if (!conv) {
- throw new Error(`Conversation ${convId} does not exist`);
- }
- if (!parentMsg) {
- throw new Error(
- `Parent message ID ${parentNodeId} does not exist in conversation ${convId}`
- );
- }
- await db.conversations.update(convId, {
- lastModified: Date.now(),
- currNode: msg.id,
- });
- // update parent
- await db.messages.update(parentNodeId, {
- children: [...parentMsg.children, msg.id],
- });
- // create message
- await db.messages.add({
- ...msg,
- parent: parentNodeId,
- children: [],
- });
- });
- dispatchConversationChange(convId);
- },
- /**
- * remove conversation by id
- */
- async remove(convId: string): Promise<void> {
- await db.transaction('rw', db.conversations, db.messages, async () => {
- await db.conversations.delete(convId);
- await db.messages.where({ convId }).delete();
- });
- dispatchConversationChange(convId);
- },
-
- // event listeners
- onConversationChanged(callback: CallbackConversationChanged) {
- const fn = (e: Event) => callback((e as CustomEvent).detail.convId);
- onConversationChangedHandlers.push([callback, fn]);
- event.addEventListener('conversationChange', fn);
- },
- offConversationChanged(callback: CallbackConversationChanged) {
- const fn = onConversationChangedHandlers.find(([cb, _]) => cb === callback);
- if (fn) {
- event.removeEventListener('conversationChange', fn[1]);
- }
- onConversationChangedHandlers = [];
- },
-
- // manage config
- getConfig(): typeof CONFIG_DEFAULT {
- const savedVal = JSON.parse(localStorage.getItem('config') || '{}');
- // to prevent breaking changes in the future, we always provide default value for missing keys
- return {
- ...CONFIG_DEFAULT,
- ...savedVal,
- };
- },
- setConfig(config: typeof CONFIG_DEFAULT) {
- localStorage.setItem('config', JSON.stringify(config));
- },
- getTheme(): string {
- return localStorage.getItem('theme') || 'auto';
- },
- setTheme(theme: string) {
- if (theme === 'auto') {
- localStorage.removeItem('theme');
- } else {
- localStorage.setItem('theme', theme);
- }
- },
-};
-
-export default StorageUtils;
-
-// Migration from localStorage to IndexedDB
-
-// these are old types, LS prefix stands for LocalStorage
-interface LSConversation {
- id: string; // format: `conv-{timestamp}`
- lastModified: number; // timestamp from Date.now()
- messages: LSMessage[];
-}
-interface LSMessage {
- id: number;
- role: 'user' | 'assistant' | 'system';
- content: string;
- timings?: TimingReport;
-}
-async function migrationLStoIDB() {
- if (localStorage.getItem('migratedToIDB')) return;
- const res: LSConversation[] = [];
- for (const key in localStorage) {
- if (key.startsWith('conv-')) {
- res.push(JSON.parse(localStorage.getItem(key) ?? '{}'));
- }
- }
- if (res.length === 0) return;
- await db.transaction('rw', db.conversations, db.messages, async () => {
- let migratedCount = 0;
- for (const conv of res) {
- const { id: convId, lastModified, messages } = conv;
- const firstMsg = messages[0];
- const lastMsg = messages.at(-1);
- if (messages.length < 2 || !firstMsg || !lastMsg) {
- console.log(
- `Skipping conversation ${convId} with ${messages.length} messages`
- );
- continue;
- }
- const name = firstMsg.content ?? '(no messages)';
- await db.conversations.add({
- id: convId,
- lastModified,
- currNode: lastMsg.id,
- name,
- });
- const rootId = messages[0].id - 2;
- await db.messages.add({
- id: rootId,
- convId: convId,
- type: 'root',
- timestamp: rootId,
- role: 'system',
- content: '',
- parent: -1,
- children: [firstMsg.id],
- });
- for (let i = 0; i < messages.length; i++) {
- const msg = messages[i];
- await db.messages.add({
- ...msg,
- type: 'text',
- convId: convId,
- timestamp: msg.id,
- parent: i === 0 ? rootId : messages[i - 1].id,
- children: i === messages.length - 1 ? [] : [messages[i + 1].id],
- });
- }
- migratedCount++;
- console.log(
- `Migrated conversation ${convId} with ${messages.length} messages`
- );
- }
- console.log(
- `Migrated ${migratedCount} conversations from localStorage to IndexedDB`
- );
- localStorage.setItem('migratedToIDB', '1');
- });
-}
+++ /dev/null
-export interface TimingReport {
- prompt_n: number;
- prompt_ms: number;
- predicted_n: number;
- predicted_ms: number;
-}
-
-/**
- * What is conversation "branching"? It is a feature that allows the user to edit an old message in the history, while still keeping the conversation flow.
- * Inspired by ChatGPT / Claude / Hugging Chat where you edit a message, a new branch of the conversation is created, and the old message is still visible.
- *
- * We use the same node-based structure like other chat UIs, where each message has a parent and children. A "root" message is the first message in a conversation, which will not be displayed in the UI.
- *
- * root
- * ├── message 1
- * │ └── message 2
- * │ └── message 3
- * └── message 4
- * └── message 5
- *
- * In the above example, assuming that user wants to edit message 2, a new branch will be created:
- *
- * ├── message 2
- * │ └── message 3
- * └── message 6
- *
- * Message 2 and 6 are siblings, and message 6 is the new branch.
- *
- * We only need to know the last node (aka leaf) to get the current branch. In the above example, message 5 is the leaf of branch containing message 4 and 5.
- *
- * For the implementation:
- * - StorageUtils.getMessages() returns list of all nodes
- * - StorageUtils.filterByLeafNodeId() filters the list of nodes from a given leaf node
- */
-
-// Note: the term "message" and "node" are used interchangeably in this context
-export interface Message {
- id: number;
- convId: string;
- type: 'text' | 'root';
- timestamp: number; // timestamp from Date.now()
- role: 'user' | 'assistant' | 'system';
- content: string;
- timings?: TimingReport;
- extra?: MessageExtra[];
- // node based system for branching
- parent: Message['id'];
- children: Message['id'][];
-}
-
-export type MessageExtra =
- | MessageExtraTextFile
- | MessageExtraImageFile
- | MessageExtraAudioFile
- | MessageExtraContext;
-
-export interface MessageExtraTextFile {
- type: 'textFile';
- name: string;
- content: string;
-}
-
-export interface MessageExtraImageFile {
- type: 'imageFile';
- name: string;
- base64Url: string;
-}
-
-export interface MessageExtraAudioFile {
- type: 'audioFile';
- name: string;
- base64Data: string;
- mimeType: string;
-}
-
-export interface MessageExtraContext {
- type: 'context';
- name: string;
- content: string;
-}
-
-export type APIMessageContentPart =
- | {
- type: 'text';
- text: string;
- }
- | {
- type: 'image_url';
- image_url: { url: string };
- }
- | {
- type: 'input_audio';
- input_audio: { data: string; format: 'wav' | 'mp3' };
- };
-
-export type APIMessage = {
- role: Message['role'];
- content: string | APIMessageContentPart[];
-};
-
-export interface Conversation {
- id: string; // format: `conv-{timestamp}`
- lastModified: number; // timestamp from Date.now()
- currNode: Message['id']; // the current message node being viewed
- name: string;
-}
-
-export interface ViewingChat {
- conv: Readonly<Conversation>;
- messages: Readonly<Message[]>;
-}
-
-export type PendingMessage = Omit<Message, 'content'> & {
- content: string | null;
-};
-
-export enum CanvasType {
- PY_INTERPRETER,
-}
-
-export interface CanvasPyInterpreter {
- type: CanvasType.PY_INTERPRETER;
- content: string;
-}
-
-export type CanvasData = CanvasPyInterpreter;
-
-// a non-complete list of props, only contains the ones we need
-export interface LlamaCppServerProps {
- build_info: string;
- model_path: string;
- n_ctx: number;
- modalities?: {
- vision: boolean;
- audio: boolean;
- };
- // TODO: support params
-}
+++ /dev/null
-/// <reference types="vite/client" />
--- /dev/null
+<svg width="256" xmlns="http://www.w3.org/2000/svg" height="256" id="screenshot-ef94fbb0-dbab-80ed-8006-89429900edbf" viewBox="0 0 256 256" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1"><g id="shape-ef94fbb0-dbab-80ed-8006-89429900edbf" rx="0" ry="0"><g id="shape-ef94fbb0-dbab-80ed-8006-894215755c3a"><g class="fills" id="fills-ef94fbb0-dbab-80ed-8006-894215755c3a"><rect rx="0" ry="0" x="0" y="0" transform="matrix(1.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000)" width="256" height="256" style="fill: rgb(27, 31, 32); fill-opacity: 1;"/></g></g><g id="shape-ef94fbb0-dbab-80ed-8006-89422363ef3f" rx="0" ry="0"><g id="shape-ef94fbb0-dbab-80ed-8006-89422363ef40"><g class="fills" id="fills-ef94fbb0-dbab-80ed-8006-89422363ef40"><path d="M171.66500854492188,99.5302505493164L159.79953002929688,120.62468719482422C144.15451049804688,108.58329010009766,120.9504165649414,106.8254165649414,105.3053970336914,119.7457504272461C80.0798110961914,140.57652282714844,81.8376235961914,188.7422637939453,121.1261978149414,189.00587463378906C132.11300659179688,189.00587463378906,141.42965698242188,183.8201141357422,151.44967651367188,180.39234924316406L156.72335815429688,201.3988494873047C147.84591674804688,205.52989196777344,138.79293823242188,209.7487335205078,129.03683471679688,211.06712341308594C40.08835220336914,223.1964569091797,45.18600845336914,94.78400421142578,125.6088638305664,88.10407257080078C142.48434448242188,86.69782257080078,157.33834838867188,91.09247589111328,171.75314331054688,99.5302505493164Z" class="st0" style="fill: rgb(255, 130, 54); fill-opacity: 1;"/></g></g><g id="shape-ef94fbb0-dbab-80ed-8006-89422363ef41"><g class="fills" id="fills-ef94fbb0-dbab-80ed-8006-89422363ef41"><path d="M110.2272720336914,79.31470489501953C96.6918716430664,83.35785675048828,84.1232681274414,90.8288345336914,74.6305923461914,101.28812408447266C72.8727798461914,80.01782989501953,77.6188735961914,37.03793716430664,101.2621841430664,28.6001033782959C104.7780532836914,27.36964988708496,116.8195571899414,24.293371200561523,116.4679946899414,30.533788681030273C116.1161880493164,36.77426528930664,107.7663345336914,47.49722671508789,105.7450942993164,53.29823684692383C102.2292251586914,63.49386978149414,105.4811782836914,70.52535247802734,110.3154067993164,79.40265655517578Z" class="st0" style="fill: rgb(255, 130, 54); fill-opacity: 1;"/></g></g><g id="shape-ef94fbb0-dbab-80ed-8006-89422363ef42"><g class="fills" id="fills-ef94fbb0-dbab-80ed-8006-89422363ef42"><path d="M143.62692260742188,127.65621185302734L143.62692260742188,143.47706604003906L157.68991088867188,143.47706604003906L157.68991088867188,155.7821807861328L143.62692260742188,155.7821807861328L143.62692260742188,170.7240753173828L130.44284057617188,170.7240753173828L130.44284057617188,155.7821807861328L115.5009536743164,155.7821807861328L115.5009536743164,143.47706604003906L129.12448120117188,143.47706604003906L130.44284057617188,142.15867614746094L130.44284057617188,127.65621185302734L143.62692260742188,127.65621185302734Z" class="st0" style="fill: rgb(255, 130, 54); fill-opacity: 1;"/></g></g><g id="shape-ef94fbb0-dbab-80ed-8006-89422363ef43"><g class="fills" id="fills-ef94fbb0-dbab-80ed-8006-89422363ef43"><path d="M191.96823120117188,127.65621185302734L191.96823120117188,142.15867614746094L193.28683471679688,143.47706604003906L206.91036987304688,143.47706604003906L206.91036987304688,155.7821807861328L191.96823120117188,155.7821807861328L191.96823120117188,170.7240753173828L178.78439331054688,170.7240753173828L178.78439331054688,155.7821807861328L164.72140502929688,155.7821807861328L164.72140502929688,143.47706604003906L178.78439331054688,143.47706604003906L178.78439331054688,127.65621185302734L191.96823120117188,127.65621185302734Z" class="st0" style="fill: rgb(255, 130, 54); fill-opacity: 1;"/></g></g><g id="shape-ef94fbb0-dbab-80ed-8006-89422363ef44"><g class="fills" id="fills-ef94fbb0-dbab-80ed-8006-89422363ef44"><path d="M153.20748901367188,38.092655181884766C154.96554565429688,40.72946548461914,145.03341674804688,52.06770706176758,143.45114135742188,54.96817398071289C138.88082885742188,63.581790924072266,141.95700073242188,68.50382232666016,145.38473510742188,76.67792510986328C135.45285034179688,75.18372344970703,126.2240982055664,76.41425323486328,116.3798599243164,77.55683135986328C118.5773696899414,58.659732818603516,129.21261596679688,31.1490535736084,153.20748901367188,38.092655181884766Z" class="st0" style="fill: rgb(255, 130, 54); fill-opacity: 1;"/></g></g></g></g></svg>
\ No newline at end of file
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="refresh" content="5">
+ </head>
+ <body>
+ <div id="loading">
+ The model is loading. Please wait.<br/>
+ The user interface will appear soon.
+ </div>
+ </body>
+</html>
--- /dev/null
+import { mdsvex } from 'mdsvex';
+import adapter from '@sveltejs/adapter-static';
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
+
+/** @type {import('@sveltejs/kit').Config} */
+const config = {
+ // Consult https://svelte.dev/docs/kit/integrations
+ // for more information about preprocessors
+ preprocess: [vitePreprocess(), mdsvex()],
+ kit: {
+ adapter: adapter({
+ pages: '../public',
+ assets: '../public',
+ fallback: 'index.html',
+ precompress: false,
+ strict: true
+ }),
+ output: {
+ bundleStrategy: 'inline'
+ }
+ },
+ extensions: ['.svelte', '.svx']
+};
+
+export default config;
+++ /dev/null
-/** @type {import('tailwindcss').Config} */
-export default {
- content: [
- "./index.html",
- "./src/**/*.{js,ts,jsx,tsx}",
- ],
- theme: {
- extend: {},
- },
- plugins: [
- require('daisyui'),
- ],
- daisyui: {
- themes: ['light', 'dark', 'cupcake', 'bumblebee', 'emerald', 'corporate', 'synthwave', 'retro', 'cyberpunk', 'valentine', 'halloween', 'garden', 'forest', 'aqua', 'lofi', 'pastel', 'fantasy', 'wireframe', 'black', 'luxury', 'dracula', 'cmyk', 'autumn', 'business', 'acid', 'lemonade', 'night', 'coffee', 'winter', 'dim', 'nord', 'sunset'],
- }
-}
+++ /dev/null
-{
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
- "target": "ES2021",
- "useDefineForClassFields": true,
- "lib": ["ES2021", "DOM", "DOM.Iterable"],
- "module": "ESNext",
- "skipLibCheck": true,
-
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "isolatedModules": true,
- "moduleDetection": "force",
- "noEmit": true,
- "jsx": "react-jsx",
-
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
- },
- "include": ["src"]
-}
{
- "files": [],
- "references": [
- { "path": "./tsconfig.app.json" },
- { "path": "./tsconfig.node.json" }
- ]
+ "extends": "./.svelte-kit/tsconfig.json",
+ "compilerOptions": {
+ "allowJs": true,
+ "checkJs": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "strict": true,
+ "moduleResolution": "bundler"
+ }
+ // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
+ // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
+ //
+ // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
+ // from the referenced tsconfig.json - TypeScript does not merge them in
}
+++ /dev/null
-{
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
- "target": "ES2022",
- "lib": ["ES2023"],
- "module": "ESNext",
- "skipLibCheck": true,
-
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "isolatedModules": true,
- "moduleDetection": "force",
- "noEmit": true,
-
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
- },
- "include": ["vite.config.ts"]
-}
-import { defineConfig, PluginOption } from 'vite';
-import react from '@vitejs/plugin-react';
-import { viteSingleFile } from 'vite-plugin-singlefile';
-import path from 'node:path';
-import fs from 'node:fs';
+import tailwindcss from '@tailwindcss/vite';
+import { sveltekit } from '@sveltejs/kit/vite';
import * as fflate from 'fflate';
-
-/* eslint-disable */
-
-const MAX_BUNDLE_SIZE = 2 * 1024 * 1024; // only increase when absolutely necessary
+import { readFileSync, writeFileSync, existsSync } from 'fs';
+import { resolve } from 'path';
+import { defineConfig } from 'vite';
+import devtoolsJson from 'vite-plugin-devtools-json';
+import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
const GUIDE_FOR_FRONTEND = `
<!--
-->
`.trim();
-const FRONTEND_PLUGINS = [react()];
+const MAX_BUNDLE_SIZE = 2 * 1024 * 1024;
+
+function llamaCppBuildPlugin() {
+ return {
+ name: 'llamacpp:build',
+ apply: 'build' as const,
+ closeBundle() {
+ // Ensure the SvelteKit adapter has finished writing to ../public
+ setTimeout(() => {
+ try {
+ const indexPath = resolve('../public/index.html');
+ const gzipPath = resolve('../public/index.html.gz');
+
+ if (!existsSync(indexPath)) {
+ return;
+ }
+
+ let content = readFileSync(indexPath, 'utf-8');
+
+ const faviconPath = resolve('static/favicon.svg');
+ if (existsSync(faviconPath)) {
+ const faviconContent = readFileSync(faviconPath, 'utf-8');
+ const faviconBase64 = Buffer.from(faviconContent).toString('base64');
+ const faviconDataUrl = `data:image/svg+xml;base64,${faviconBase64}`;
+
+ content = content.replace(/href="[^"]*favicon\.svg"/g, `href="${faviconDataUrl}"`);
+
+ console.log('✓ Inlined favicon.svg as base64 data URL');
+ }
+
+ content = content.replace(/\r/g, '');
+ content = GUIDE_FOR_FRONTEND + '\n' + content;
-const BUILD_PLUGINS = [
- ...FRONTEND_PLUGINS,
- viteSingleFile(),
- (function llamaCppPlugin() {
- let config: any;
- return {
- name: 'llamacpp:build',
- apply: 'build',
- async configResolved(_config: any) {
- config = _config;
- },
- writeBundle() {
- const outputIndexHtml = path.join(config.build.outDir, 'index.html');
- let content =
- GUIDE_FOR_FRONTEND + '\n' + fs.readFileSync(outputIndexHtml, 'utf-8');
- content = content.replace(/\r/g, ''); // remove windows-style line endings
- const compressed = fflate.gzipSync(Buffer.from(content, 'utf-8'), {
- level: 9,
- });
+ const compressed = fflate.gzipSync(Buffer.from(content, 'utf-8'), { level: 9 });
- // because gzip header contains machine-specific info, we must remove these data from the header
- // timestamp
- compressed[0x4] = 0;
- compressed[0x5] = 0;
- compressed[0x6] = 0;
- compressed[0x7] = 0;
- // OS
- compressed[0x9] = 0;
+ compressed[0x4] = 0;
+ compressed[0x5] = 0;
+ compressed[0x6] = 0;
+ compressed[0x7] = 0;
+ compressed[0x9] = 0;
- if (compressed.byteLength > MAX_BUNDLE_SIZE) {
- throw new Error(
- `Bundle size is too large (${Math.ceil(compressed.byteLength / 1024)} KB).\n` +
- `Please reduce the size of the frontend or increase MAX_BUNDLE_SIZE in vite.config.js.\n`
- );
- }
+ if (compressed.byteLength > MAX_BUNDLE_SIZE) {
+ throw new Error(
+ `Bundle size is too large (${Math.ceil(compressed.byteLength / 1024)} KB).\n` +
+ `Please reduce the size of the frontend or increase MAX_BUNDLE_SIZE in vite.config.ts.\n`
+ );
+ }
- const targetOutputFile = path.join(
- config.build.outDir,
- '../../public/index.html.gz'
- );
- fs.writeFileSync(targetOutputFile, compressed);
- },
- } satisfies PluginOption;
- })(),
-];
+ writeFileSync(gzipPath, compressed);
+ console.log('✓ Created index.html.gz');
+ } catch (error) {
+ console.error('Failed to create gzip file:', error);
+ }
+ }, 100);
+ }
+ };
+}
export default defineConfig({
- // @ts-ignore
- plugins: process.env.ANALYZE ? FRONTEND_PLUGINS : BUILD_PLUGINS,
- server: {
- proxy: {
- '/v1': 'http://localhost:8080',
- '/props': 'http://localhost:8080',
- },
- headers: {
- 'Cross-Origin-Embedder-Policy': 'require-corp',
- 'Cross-Origin-Opener-Policy': 'same-origin',
- },
- },
+ plugins: [tailwindcss(), sveltekit(), devtoolsJson(), llamaCppBuildPlugin()],
+ test: {
+ projects: [
+ {
+ extends: './vite.config.ts',
+ test: {
+ name: 'client',
+ environment: 'browser',
+ browser: {
+ enabled: true,
+ provider: 'playwright',
+ instances: [{ browser: 'chromium' }]
+ },
+ include: ['src/**/*.svelte.{test,spec}.{js,ts}'],
+ exclude: ['src/lib/server/**'],
+ setupFiles: ['./vitest-setup-client.ts']
+ }
+ },
+ {
+ extends: './vite.config.ts',
+ test: {
+ name: 'server',
+ environment: 'node',
+ include: ['src/**/*.{test,spec}.{js,ts}'],
+ exclude: ['src/**/*.svelte.{test,spec}.{js,ts}']
+ }
+ },
+ {
+ extends: './vite.config.ts',
+ test: {
+ name: 'ui',
+ environment: 'browser',
+ browser: {
+ enabled: true,
+ provider: 'playwright',
+ instances: [{ browser: 'chromium', headless: true }]
+ },
+ include: ['src/**/*.stories.{js,ts,svelte}'],
+ setupFiles: ['./.storybook/vitest.setup.ts']
+ },
+ plugins: [
+ storybookTest({
+ storybookScript: 'pnpm run storybook --no-open'
+ })
+ ]
+ }
+ ]
+ },
+ server: {
+ proxy: {
+ '/v1': 'http://localhost:8080',
+ '/props': 'http://localhost:8080',
+ '/slots': 'http://localhost:8080'
+ },
+ headers: {
+ 'Cross-Origin-Embedder-Policy': 'require-corp',
+ 'Cross-Origin-Opener-Policy': 'same-origin'
+ }
+ }
});
--- /dev/null
+/// <reference types="@vitest/browser/matchers" />
+/// <reference types="@vitest/browser/providers/playwright" />