diff --git a/examples/ts-vue-chat/.gitignore b/examples/ts-vue-chat/.gitignore new file mode 100644 index 00000000..24ebf80e --- /dev/null +++ b/examples/ts-vue-chat/.gitignore @@ -0,0 +1,22 @@ +# Dependencies +node_modules + +# Build +dist + +# Logs +*.log + +# Environment +.env +.env.local +.env.*.local + +# IDE +.vscode +.idea +*.swp +*.swo + +# OS +.DS_Store diff --git a/examples/ts-vue-chat/README.md b/examples/ts-vue-chat/README.md new file mode 100644 index 00000000..c353259a --- /dev/null +++ b/examples/ts-vue-chat/README.md @@ -0,0 +1,65 @@ +# TanStack AI - Vue Chat Example + +A Vue 3 chat application demonstrating the use of `@tanstack/ai-vue`. + +## Setup + +1. Copy `env.example` to `.env` and add your API keys: + +```bash +cp env.example .env +``` + +2. Install dependencies: + +```bash +pnpm install +``` + +3. Start the development server: + +```bash +pnpm dev +``` + +4. Open [http://localhost:5173](http://localhost:5173) in your browser. + +## Features + +- Real-time streaming chat with multiple AI providers +- Support for OpenAI, Anthropic, Gemini, and Ollama +- Client-side and server-side tools +- Tool approval workflow +- Guitar recommendation demo + +## Project Structure + +``` +src/ +├── main.ts # App entry point +├── App.vue # Root component +├── router.ts # Vue Router setup +├── styles.css # Global styles +├── components/ # UI components +│ ├── Header.vue +│ ├── ChatInput.vue +│ ├── Messages.vue +│ ├── ThinkingPart.vue +│ └── GuitarRecommendation.vue +├── lib/ # Utilities +│ ├── model-selection.ts +│ └── guitar-tools.ts +├── data/ # Example data +│ └── guitars.ts +└── views/ # Page components + ├── ChatView.vue + └── GuitarDetailView.vue +``` + +## Tech Stack + +- Vue 3 with Composition API +- TypeScript +- Vite +- Tailwind CSS +- @tanstack/ai-vue diff --git a/examples/ts-vue-chat/env.example b/examples/ts-vue-chat/env.example new file mode 100644 index 00000000..2b17075b --- /dev/null +++ b/examples/ts-vue-chat/env.example @@ -0,0 +1,11 @@ +# OpenAI +OPENAI_API_KEY=your_openai_api_key_here + +# Anthropic +ANTHROPIC_API_KEY=your_anthropic_api_key_here + +# Gemini +GEMINI_API_KEY=your_gemini_api_key_here + +# Ollama (no API key needed, runs locally) +# Make sure Ollama is running: ollama serve diff --git a/examples/ts-vue-chat/index.html b/examples/ts-vue-chat/index.html new file mode 100644 index 00000000..0d107cd9 --- /dev/null +++ b/examples/ts-vue-chat/index.html @@ -0,0 +1,13 @@ + + + + + + + TanStack AI - Vue Chat + + +
+ + + diff --git a/examples/ts-vue-chat/package.json b/examples/ts-vue-chat/package.json new file mode 100644 index 00000000..79ae900f --- /dev/null +++ b/examples/ts-vue-chat/package.json @@ -0,0 +1,40 @@ +{ + "name": "ts-vue-chat", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview", + "check": "vue-tsc --noEmit" + }, + "dependencies": { + "@tanstack/ai": "workspace:*", + "@tanstack/ai-anthropic": "workspace:*", + "@tanstack/ai-client": "workspace:*", + "@tanstack/ai-gemini": "workspace:*", + "@tanstack/ai-ollama": "workspace:*", + "@tanstack/ai-openai": "workspace:*", + "@tanstack/ai-vue": "workspace:*", + "@tanstack/ai-vue-ui": "workspace:*", + "marked": "^15.0.6", + "vue": "^3.5.25", + "vue-router": "^4.5.0", + "zod": "^4.1.13" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.1.17", + "@types/node": "^24.10.1", + "@vitejs/plugin-vue": "^5.2.3", + "autoprefixer": "^10.4.21", + "concurrently": "^9.1.2", + "dotenv": "^17.2.3", + "express": "^5.1.0", + "tailwindcss": "^4.1.17", + "tsx": "^4.20.6", + "typescript": "5.9.3", + "vite": "^7.2.4", + "vue-tsc": "^2.2.10" + } +} diff --git a/examples/ts-vue-chat/public/example-guitar-flowers.jpg b/examples/ts-vue-chat/public/example-guitar-flowers.jpg new file mode 100644 index 00000000..debe785e Binary files /dev/null and b/examples/ts-vue-chat/public/example-guitar-flowers.jpg differ diff --git a/examples/ts-vue-chat/public/example-guitar-motherboard.jpg b/examples/ts-vue-chat/public/example-guitar-motherboard.jpg new file mode 100644 index 00000000..8f1a8d72 Binary files /dev/null and b/examples/ts-vue-chat/public/example-guitar-motherboard.jpg differ diff --git a/examples/ts-vue-chat/public/example-guitar-racing.jpg b/examples/ts-vue-chat/public/example-guitar-racing.jpg new file mode 100644 index 00000000..44555574 Binary files /dev/null and b/examples/ts-vue-chat/public/example-guitar-racing.jpg differ diff --git a/examples/ts-vue-chat/public/example-guitar-steamer-trunk.jpg b/examples/ts-vue-chat/public/example-guitar-steamer-trunk.jpg new file mode 100644 index 00000000..7b931893 Binary files /dev/null and b/examples/ts-vue-chat/public/example-guitar-steamer-trunk.jpg differ diff --git a/examples/ts-vue-chat/public/example-guitar-superhero.jpg b/examples/ts-vue-chat/public/example-guitar-superhero.jpg new file mode 100644 index 00000000..3bbea274 Binary files /dev/null and b/examples/ts-vue-chat/public/example-guitar-superhero.jpg differ diff --git a/examples/ts-vue-chat/public/example-guitar-traveling.jpg b/examples/ts-vue-chat/public/example-guitar-traveling.jpg new file mode 100644 index 00000000..285647be Binary files /dev/null and b/examples/ts-vue-chat/public/example-guitar-traveling.jpg differ diff --git a/examples/ts-vue-chat/public/example-guitar-video-games.jpg b/examples/ts-vue-chat/public/example-guitar-video-games.jpg new file mode 100644 index 00000000..4987f77e Binary files /dev/null and b/examples/ts-vue-chat/public/example-guitar-video-games.jpg differ diff --git a/examples/ts-vue-chat/public/example-ukelele-tanstack.jpg b/examples/ts-vue-chat/public/example-ukelele-tanstack.jpg new file mode 100644 index 00000000..1e8a5566 Binary files /dev/null and b/examples/ts-vue-chat/public/example-ukelele-tanstack.jpg differ diff --git a/examples/ts-vue-chat/public/favicon.ico b/examples/ts-vue-chat/public/favicon.ico new file mode 100644 index 00000000..a11777cc Binary files /dev/null and b/examples/ts-vue-chat/public/favicon.ico differ diff --git a/examples/ts-vue-chat/public/logo192.png b/examples/ts-vue-chat/public/logo192.png new file mode 100644 index 00000000..fc44b0a3 Binary files /dev/null and b/examples/ts-vue-chat/public/logo192.png differ diff --git a/examples/ts-vue-chat/public/logo512.png b/examples/ts-vue-chat/public/logo512.png new file mode 100644 index 00000000..a4e47a65 Binary files /dev/null and b/examples/ts-vue-chat/public/logo512.png differ diff --git a/examples/ts-vue-chat/public/manifest.json b/examples/ts-vue-chat/public/manifest.json new file mode 100644 index 00000000..078ef501 --- /dev/null +++ b/examples/ts-vue-chat/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "TanStack App", + "name": "Create TanStack App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/examples/ts-vue-chat/public/robots.txt b/examples/ts-vue-chat/public/robots.txt new file mode 100644 index 00000000..e9e57dc4 --- /dev/null +++ b/examples/ts-vue-chat/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/examples/ts-vue-chat/public/tanstack-circle-logo.png b/examples/ts-vue-chat/public/tanstack-circle-logo.png new file mode 100644 index 00000000..9db3e67b Binary files /dev/null and b/examples/ts-vue-chat/public/tanstack-circle-logo.png differ diff --git a/examples/ts-vue-chat/public/tanstack-word-logo-white.svg b/examples/ts-vue-chat/public/tanstack-word-logo-white.svg new file mode 100644 index 00000000..b6ec5086 --- /dev/null +++ b/examples/ts-vue-chat/public/tanstack-word-logo-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/ts-vue-chat/src/App.vue b/examples/ts-vue-chat/src/App.vue new file mode 100644 index 00000000..0f09c0c1 --- /dev/null +++ b/examples/ts-vue-chat/src/App.vue @@ -0,0 +1,10 @@ + + + diff --git a/examples/ts-vue-chat/src/components/ChatInput.vue b/examples/ts-vue-chat/src/components/ChatInput.vue new file mode 100644 index 00000000..d41b6b73 --- /dev/null +++ b/examples/ts-vue-chat/src/components/ChatInput.vue @@ -0,0 +1,92 @@ + + + diff --git a/examples/ts-vue-chat/src/components/GuitarRecommendation.vue b/examples/ts-vue-chat/src/components/GuitarRecommendation.vue new file mode 100644 index 00000000..b4f2aa42 --- /dev/null +++ b/examples/ts-vue-chat/src/components/GuitarRecommendation.vue @@ -0,0 +1,51 @@ + + + diff --git a/examples/ts-vue-chat/src/components/Header.vue b/examples/ts-vue-chat/src/components/Header.vue new file mode 100644 index 00000000..7db9aade --- /dev/null +++ b/examples/ts-vue-chat/src/components/Header.vue @@ -0,0 +1,147 @@ + + + diff --git a/examples/ts-vue-chat/src/components/Messages.vue b/examples/ts-vue-chat/src/components/Messages.vue new file mode 100644 index 00000000..dec68195 --- /dev/null +++ b/examples/ts-vue-chat/src/components/Messages.vue @@ -0,0 +1,142 @@ + + + diff --git a/examples/ts-vue-chat/src/components/ThinkingPart.vue b/examples/ts-vue-chat/src/components/ThinkingPart.vue new file mode 100644 index 00000000..15ce8d57 --- /dev/null +++ b/examples/ts-vue-chat/src/components/ThinkingPart.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/examples/ts-vue-chat/src/data/guitars.ts b/examples/ts-vue-chat/src/data/guitars.ts new file mode 100644 index 00000000..7765e649 --- /dev/null +++ b/examples/ts-vue-chat/src/data/guitars.ts @@ -0,0 +1,93 @@ +export interface Guitar { + id: number + name: string + image: string + description: string + shortDescription: string + price: number +} + +export const guitars: Array = [ + { + id: 1, + name: 'TanStack Ukelele', + image: '/example-ukelele-tanstack.jpg', + description: + "Introducing the TanStack Signature Ukulele—a beautifully handcrafted concert ukulele that combines exceptional sound quality with distinctive style. Featuring a warm, resonant koa-wood body with natural grain patterns, this instrument delivers the rich, mellow tones Hawaii is famous for. The exclusive TanStack palm tree inlay on the soundhole adds a unique touch of island flair, while the matching branded headstock makes this a true collector's piece for developers and musicians alike. Whether you're a beginner looking for a quality starter instrument or an experienced player wanting something special, the TanStack Ukulele brings together craftsmanship, character, and that unmistakable tropical spirit.", + shortDescription: + 'Premium koa-wood ukulele featuring exclusive TanStack branding, perfect for beach vibes and island-inspired melodies.', + price: 299, + }, + { + id: 2, + name: 'Video Game Guitar', + image: '/example-guitar-video-games.jpg', + description: + "The Video Game Guitar is a unique acoustic guitar that features a design inspired by video games. It has a sleek, high-gloss finish and a comfortable playability. The guitar's ergonomic body and fast neck profile ensure comfortable playability for hours on end.", + shortDescription: + 'A unique electric guitar with a video game design, high-gloss finish, and comfortable playability.', + price: 699, + }, + { + id: 3, + name: 'Superhero Guitar', + image: '/example-guitar-superhero.jpg', + description: + "The Superhero Guitar is a bold black electric guitar that stands out with its unique superhero logo design. Its sleek, high-gloss finish and powerful pickups make it perfect for high-energy performances. The guitar's ergonomic body and fast neck profile ensure comfortable playability for hours on end.", + shortDescription: + 'A bold black electric guitar with a unique superhero logo, high-gloss finish, and powerful pickups.', + price: 699, + }, + { + id: 4, + name: 'Motherboard Guitar', + image: '/example-guitar-motherboard.jpg', + description: + "This guitar is a tribute to the motherboard of a computer. It's a unique and stylish instrument that will make you feel like a hacker. The intricate circuit-inspired design features actual LED lights that pulse with your playing intensity, while the neck is inlaid with binary code patterns that glow under stage lights. Each pickup has been custom-wound to produce tones ranging from clean digital precision to glitched-out distortion, perfect for electronic music fusion. The Motherboard Guitar seamlessly bridges the gap between traditional craftsmanship and cutting-edge technology, making it the ultimate instrument for the digital age musician.", + shortDescription: + 'A tech-inspired electric guitar featuring LED lights and binary code inlays that glow under stage lights.', + price: 649, + }, + { + id: 5, + name: 'Racing Guitar', + image: '/example-guitar-racing.jpg', + description: + "Engineered for speed and precision, the Racing Guitar embodies the spirit of motorsport in every curve and contour. Its aerodynamic body, painted in classic racing stripes and high-gloss finish, is crafted from lightweight materials that allow for effortless play during extended performances. The custom low-action setup and streamlined neck profile enable lightning-fast fretwork, while specially designed pickups deliver a high-octane tone that cuts through any mix. Built with performance-grade hardware including racing-inspired control knobs and checkered flag inlays, this guitar isn't just played—it's driven to the limits of musical possibility.", + shortDescription: + 'A lightweight, aerodynamic guitar with racing stripes and a low-action setup designed for speed and precision.', + price: 679, + }, + { + id: 6, + name: 'Steamer Trunk Guitar', + image: '/example-guitar-steamer-trunk.jpg', + description: + 'The Steamer Trunk Guitar is a semi-hollow body instrument that exudes vintage charm and character. Crafted from reclaimed antique luggage wood, it features brass hardware that adds a touch of elegance and durability. The fretboard is adorned with a world map inlay, making it a unique piece that tells a story of travel and adventure.', + shortDescription: + 'A semi-hollow body guitar with brass hardware and a world map inlay, crafted from reclaimed antique luggage wood.', + price: 629, + }, + { + id: 7, + name: "Travelin' Man Guitar", + image: '/example-guitar-traveling.jpg', + description: + "The Travelin' Man Guitar is an acoustic masterpiece adorned with vintage postcards from around the world. Each postcard tells a story of adventure and wanderlust, making this guitar a unique piece of art. Its rich, resonant tones and comfortable playability make it perfect for musicians who love to travel and perform.", + shortDescription: + 'An acoustic guitar with vintage postcards, rich tones, and comfortable playability.', + price: 499, + }, + { + id: 8, + name: 'Flowerly Love Guitar', + image: '/example-guitar-flowers.jpg', + description: + "The Flowerly Love Guitar is an acoustic masterpiece adorned with intricate floral designs on its body. Each flower is hand-painted, adding a touch of nature's beauty to the instrument. Its warm, resonant tones make it perfect for both intimate performances and larger gatherings.", + shortDescription: + 'An acoustic guitar with hand-painted floral designs and warm, resonant tones.', + price: 599, + }, +] + +export default guitars diff --git a/examples/ts-vue-chat/src/lib/guitar-tools.ts b/examples/ts-vue-chat/src/lib/guitar-tools.ts new file mode 100644 index 00000000..fa3abe72 --- /dev/null +++ b/examples/ts-vue-chat/src/lib/guitar-tools.ts @@ -0,0 +1,80 @@ +import { toolDefinition } from '@tanstack/ai' +import { z } from 'zod' + +// Tool definition for getting guitars +export const getGuitarsToolDef = toolDefinition({ + name: 'getGuitars', + description: 'Get all products from the database', + inputSchema: z.object({}), + outputSchema: z.array( + z.object({ + id: z.number(), + name: z.string(), + image: z.string(), + description: z.string(), + shortDescription: z.string(), + price: z.number(), + }), + ), +}) + +// Tool definition for guitar recommendation +export const recommendGuitarToolDef = toolDefinition({ + name: 'recommendGuitar', + description: + 'REQUIRED tool to display a guitar recommendation to the user. This tool MUST be used whenever recommending a guitar - do NOT write recommendations yourself. This displays the guitar in a special appealing format with a buy button.', + inputSchema: z.object({ + id: z + .union([z.string(), z.number()]) + .describe( + 'The ID of the guitar to recommend (from the getGuitars results)', + ), + }), + outputSchema: z.object({ + id: z.number(), + }), +}) + +// Tool definition for personal preference +export const getPersonalGuitarPreferenceToolDef = toolDefinition({ + name: 'getPersonalGuitarPreference', + description: + "Get the user's guitar preference from their local browser storage", + inputSchema: z.object({}), + outputSchema: z.object({ + preference: z.string(), + }), +}) + +// Tool definition for wish list (needs approval) +export const addToWishListToolDef = toolDefinition({ + name: 'addToWishList', + description: "Add a guitar to the user's wish list (requires approval)", + inputSchema: z.object({ + guitarId: z.string(), + }), + outputSchema: z.object({ + success: z.boolean(), + guitarId: z.string(), + totalItems: z.number(), + }), + needsApproval: true, +}) + +// Tool definition for add to cart (server + client) +export const addToCartToolDef = toolDefinition({ + name: 'addToCart', + description: 'Add a guitar to the shopping cart (requires approval)', + inputSchema: z.object({ + guitarId: z.string(), + quantity: z.number(), + }), + outputSchema: z.object({ + success: z.boolean(), + cartId: z.string(), + guitarId: z.string(), + quantity: z.number(), + totalItems: z.number(), + }), + needsApproval: true, +}) diff --git a/examples/ts-vue-chat/src/lib/model-selection.ts b/examples/ts-vue-chat/src/lib/model-selection.ts new file mode 100644 index 00000000..0412d275 --- /dev/null +++ b/examples/ts-vue-chat/src/lib/model-selection.ts @@ -0,0 +1,108 @@ +export type Provider = 'openai' | 'anthropic' | 'gemini' | 'ollama' + +export interface ModelOption { + provider: Provider + model: string + label: string +} + +export const MODEL_OPTIONS: Array = [ + // OpenAI + { provider: 'openai', model: 'gpt-4o', label: 'OpenAI - GPT-4o' }, + { provider: 'openai', model: 'gpt-4o-mini', label: 'OpenAI - GPT-4o Mini' }, + { provider: 'openai', model: 'gpt-5', label: 'OpenAI - GPT-5' }, + + // Anthropic + { + provider: 'anthropic', + model: 'claude-sonnet-4-5-20250929', + label: 'Anthropic - Claude Sonnet 4.5', + }, + { + provider: 'anthropic', + model: 'claude-opus-4-5-20251101', + label: 'Anthropic - Claude Opus 4.5', + }, + { + provider: 'anthropic', + model: 'claude-haiku-4-0-20250514', + label: 'Anthropic - Claude Haiku 4.0', + }, + + // Gemini + { + provider: 'gemini', + model: 'gemini-2.0-flash-exp', + label: 'Gemini - 2.0 Flash', + }, + { + provider: 'gemini', + model: 'gemini-exp-1206', + label: 'Gemini - Exp 1206 (Pro)', + }, + + // Ollama + { + provider: 'ollama', + model: 'mistral:7b', + label: 'Ollama - Mistral 7B', + }, + { + provider: 'ollama', + model: 'mistral', + label: 'Ollama - Mistral', + }, + { + provider: 'ollama', + model: 'gpt-oss:20b', + label: 'Ollama - GPT-OSS 20B', + }, + { + provider: 'ollama', + model: 'granite4:3b', + label: 'Ollama - Granite4 3B', + }, + { + provider: 'ollama', + model: 'smollm', + label: 'Ollama - SmolLM', + }, +] + +const STORAGE_KEY = 'tanstack-ai-model-preference' + +export function getStoredModelPreference(): ModelOption | null { + if (typeof window === 'undefined') return null + + try { + const stored = localStorage.getItem(STORAGE_KEY) + if (!stored) return null + + const parsed = JSON.parse(stored) as { provider: Provider; model: string } + const option = MODEL_OPTIONS.find( + (opt) => opt.provider === parsed.provider && opt.model === parsed.model, + ) + + return option || null + } catch { + return null + } +} + +export function setStoredModelPreference(option: ModelOption): void { + if (typeof window === 'undefined') return + + try { + localStorage.setItem( + STORAGE_KEY, + JSON.stringify({ provider: option.provider, model: option.model }), + ) + } catch { + // Ignore storage errors + } +} + +export function getDefaultModelOption(): ModelOption { + const stored = getStoredModelPreference() + return stored || MODEL_OPTIONS[0] +} diff --git a/examples/ts-vue-chat/src/main.ts b/examples/ts-vue-chat/src/main.ts new file mode 100644 index 00000000..4f1e9be4 --- /dev/null +++ b/examples/ts-vue-chat/src/main.ts @@ -0,0 +1,8 @@ +import { createApp } from 'vue' +import App from './App.vue' +import router from './router' +import './styles.css' + +const app = createApp(App) +app.use(router) +app.mount('#app') diff --git a/examples/ts-vue-chat/src/router.ts b/examples/ts-vue-chat/src/router.ts new file mode 100644 index 00000000..2f91dc00 --- /dev/null +++ b/examples/ts-vue-chat/src/router.ts @@ -0,0 +1,33 @@ +import { createRouter, createWebHistory } from 'vue-router' +import ChatView from './views/ChatView.vue' +import GuitarDetailView from './views/GuitarDetailView.vue' +import GuitarsView from './views/GuitarsView.vue' +import VueUIView from './views/VueUIView.vue' + +const router = createRouter({ + history: createWebHistory(), + routes: [ + { + path: '/', + name: 'chat', + component: ChatView, + }, + { + path: '/vue-ui', + name: 'vue-ui', + component: VueUIView, + }, + { + path: '/guitars', + name: 'guitars', + component: GuitarsView, + }, + { + path: '/guitars/:id', + name: 'guitar-detail', + component: GuitarDetailView, + }, + ], +}) + +export default router diff --git a/examples/ts-vue-chat/src/styles.css b/examples/ts-vue-chat/src/styles.css new file mode 100644 index 00000000..d49a546b --- /dev/null +++ b/examples/ts-vue-chat/src/styles.css @@ -0,0 +1,95 @@ +@import 'tailwindcss'; + +@layer base { + html { + @apply bg-gray-900; + } + + body { + @apply min-h-screen text-white antialiased; + } + + /* Markdown styling */ + .prose { + @apply text-gray-200; + } + + .prose h1, + .prose h2, + .prose h3, + .prose h4 { + @apply text-white font-semibold; + } + + .prose p { + @apply mb-4 last:mb-0; + } + + .prose code { + @apply bg-gray-800 px-1.5 py-0.5 rounded text-orange-400 text-sm; + } + + .prose pre { + @apply bg-gray-800 p-4 rounded-lg overflow-x-auto mb-4; + } + + .prose pre code { + @apply bg-transparent p-0; + } + + .prose ul, + .prose ol { + @apply mb-4 pl-6; + } + + .prose li { + @apply mb-1; + } + + .prose ul li { + @apply list-disc; + } + + .prose ol li { + @apply list-decimal; + } + + .prose a { + @apply text-orange-400 hover:text-orange-300 underline; + } + + .prose blockquote { + @apply border-l-4 border-orange-500 pl-4 italic text-gray-400; + } + + .prose table { + @apply w-full border-collapse mb-4; + } + + .prose th, + .prose td { + @apply border border-gray-700 px-3 py-2 text-left; + } + + .prose th { + @apply bg-gray-800 font-semibold; + } +} + +@layer components { + .btn-primary { + @apply bg-gradient-to-r from-orange-500 to-red-600 text-white px-4 py-2 rounded-lg font-medium hover:opacity-90 transition-opacity; + } + + .btn-secondary { + @apply bg-gray-700 text-white px-4 py-2 rounded-lg font-medium hover:bg-gray-600 transition-colors; + } + + .btn-danger { + @apply bg-red-600 text-white px-4 py-2 rounded-lg font-medium hover:bg-red-700 transition-colors; + } + + .btn-success { + @apply bg-green-600 text-white px-4 py-2 rounded-lg font-medium hover:bg-green-700 transition-colors; + } +} diff --git a/examples/ts-vue-chat/src/views/ChatView.vue b/examples/ts-vue-chat/src/views/ChatView.vue new file mode 100644 index 00000000..cc173794 --- /dev/null +++ b/examples/ts-vue-chat/src/views/ChatView.vue @@ -0,0 +1,130 @@ + + + diff --git a/examples/ts-vue-chat/src/views/GuitarDetailView.vue b/examples/ts-vue-chat/src/views/GuitarDetailView.vue new file mode 100644 index 00000000..d805c295 --- /dev/null +++ b/examples/ts-vue-chat/src/views/GuitarDetailView.vue @@ -0,0 +1,68 @@ + + + diff --git a/examples/ts-vue-chat/src/views/GuitarsView.vue b/examples/ts-vue-chat/src/views/GuitarsView.vue new file mode 100644 index 00000000..83ca255c --- /dev/null +++ b/examples/ts-vue-chat/src/views/GuitarsView.vue @@ -0,0 +1,40 @@ + + + diff --git a/examples/ts-vue-chat/src/views/VueUIView.vue b/examples/ts-vue-chat/src/views/VueUIView.vue new file mode 100644 index 00000000..c45bb4b9 --- /dev/null +++ b/examples/ts-vue-chat/src/views/VueUIView.vue @@ -0,0 +1,149 @@ + + + diff --git a/examples/ts-vue-chat/tsconfig.json b/examples/ts-vue-chat/tsconfig.json new file mode 100644 index 00000000..6e0df1d1 --- /dev/null +++ b/examples/ts-vue-chat/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "types": ["vite/client"], + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + /* Path aliases */ + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/examples/ts-vue-chat/vite.config.ts b/examples/ts-vue-chat/vite.config.ts new file mode 100644 index 00000000..7d3f3093 --- /dev/null +++ b/examples/ts-vue-chat/vite.config.ts @@ -0,0 +1,292 @@ +import { fileURLToPath, URL } from 'node:url' +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import tailwindcss from '@tailwindcss/vite' +import { chat, maxIterations, toStreamResponse } from '@tanstack/ai' +import { openai } from '@tanstack/ai-openai' +import { anthropic } from '@tanstack/ai-anthropic' +import { gemini } from '@tanstack/ai-gemini' +import { ollama } from '@tanstack/ai-ollama' +import { toolDefinition } from '@tanstack/ai' +import { z } from 'zod' +import dotenv from 'dotenv' + +// Load environment variables +dotenv.config() + +// Guitar data (embedded for simplicity) +const guitars = [ + { + id: 1, + name: 'TanStack Ukelele', + image: '/example-ukelele-tanstack.jpg', + description: + 'Premium koa-wood ukulele featuring exclusive TanStack branding.', + shortDescription: 'Premium koa-wood ukulele with TanStack branding.', + price: 299, + }, + { + id: 2, + name: 'Video Game Guitar', + image: '/example-guitar-video-games.jpg', + description: 'A unique acoustic guitar with a video game design.', + shortDescription: 'Unique electric guitar with video game design.', + price: 699, + }, + { + id: 3, + name: 'Superhero Guitar', + image: '/example-guitar-superhero.jpg', + description: 'Bold black electric guitar with superhero logo design.', + shortDescription: 'Bold black electric guitar with superhero logo.', + price: 699, + }, + { + id: 4, + name: 'Motherboard Guitar', + image: '/example-guitar-motherboard.jpg', + description: 'Tech-inspired electric guitar with LED lights.', + shortDescription: 'Tech-inspired guitar with LED lights.', + price: 649, + }, + { + id: 5, + name: 'Racing Guitar', + image: '/example-guitar-racing.jpg', + description: 'Aerodynamic guitar with racing stripes.', + shortDescription: 'Aerodynamic guitar with racing stripes.', + price: 679, + }, + { + id: 6, + name: 'Steamer Trunk Guitar', + image: '/example-guitar-steamer-trunk.jpg', + description: 'Semi-hollow body guitar with brass hardware.', + shortDescription: 'Semi-hollow body guitar with brass hardware.', + price: 629, + }, + { + id: 7, + name: "Travelin' Man Guitar", + image: '/example-guitar-traveling.jpg', + description: 'Acoustic guitar with vintage postcards.', + shortDescription: 'Acoustic guitar with vintage postcards.', + price: 499, + }, + { + id: 8, + name: 'Flowerly Love Guitar', + image: '/example-guitar-flowers.jpg', + description: 'Acoustic guitar with hand-painted floral designs.', + shortDescription: 'Acoustic guitar with floral designs.', + price: 599, + }, +] + +// Tool definitions +const getGuitarsToolDef = toolDefinition({ + name: 'getGuitars', + description: 'Get all products from the database', + inputSchema: z.object({}), + outputSchema: z.array( + z.object({ + id: z.number(), + name: z.string(), + image: z.string(), + description: z.string(), + shortDescription: z.string(), + price: z.number(), + }), + ), +}) + +const getGuitars = getGuitarsToolDef.server(() => guitars) + +const recommendGuitarToolDef = toolDefinition({ + name: 'recommendGuitar', + description: 'REQUIRED tool to display a guitar recommendation to the user.', + inputSchema: z.object({ + id: z + .union([z.string(), z.number()]) + .describe('The ID of the guitar to recommend'), + }), + outputSchema: z.object({ id: z.number() }), +}) + +const getPersonalGuitarPreferenceToolDef = toolDefinition({ + name: 'getPersonalGuitarPreference', + description: + "Get the user's guitar preference from their local browser storage", + inputSchema: z.object({}), + outputSchema: z.object({ preference: z.string() }), +}) + +const addToWishListToolDef = toolDefinition({ + name: 'addToWishList', + description: "Add a guitar to the user's wish list (requires approval)", + inputSchema: z.object({ guitarId: z.string() }), + outputSchema: z.object({ + success: z.boolean(), + guitarId: z.string(), + totalItems: z.number(), + }), + needsApproval: true, +}) + +const addToCartToolDef = toolDefinition({ + name: 'addToCart', + description: 'Add a guitar to the shopping cart (requires approval)', + inputSchema: z.object({ + guitarId: z.string(), + quantity: z.number(), + }), + outputSchema: z.object({ + success: z.boolean(), + cartId: z.string(), + guitarId: z.string(), + quantity: z.number(), + totalItems: z.number(), + }), + needsApproval: true, +}) + +const addToCartToolServer = addToCartToolDef.server((args) => ({ + success: true, + cartId: 'CART_' + Date.now(), + guitarId: args.guitarId, + quantity: args.quantity, + totalItems: args.quantity, +})) + +const SYSTEM_PROMPT = `You are a helpful assistant for a guitar store. + +CRITICAL INSTRUCTIONS - YOU MUST FOLLOW THIS EXACT WORKFLOW: + +When a user asks for a guitar recommendation: +1. FIRST: Use the getGuitars tool (no parameters needed) +2. SECOND: Use the recommendGuitar tool with the ID of the guitar you want to recommend +3. NEVER write a recommendation directly - ALWAYS use the recommendGuitar tool + +IMPORTANT: +- The recommendGuitar tool will display the guitar in a special, appealing format +- You MUST use recommendGuitar for ANY guitar recommendation +- ONLY recommend guitars from our inventory (use getGuitars first) +- The recommendGuitar tool has a buy button - this is how customers purchase +- Do NOT describe the guitar yourself - let the recommendGuitar tool do it +` + +type Provider = 'openai' | 'anthropic' | 'gemini' | 'ollama' + +export default defineConfig({ + plugins: [ + vue(), + tailwindcss(), + { + name: 'api-handler', + configureServer(server) { + server.middlewares.use('/api/chat', async (req, res) => { + if (req.method !== 'POST') { + res.statusCode = 405 + res.end('Method Not Allowed') + return + } + + let body = '' + for await (const chunk of req) { + body += chunk + } + + try { + const { messages, data } = JSON.parse(body) + const provider: Provider = data?.provider || 'openai' + const model: string | undefined = data?.model + + let adapter + let defaultModel + + switch (provider) { + case 'anthropic': + adapter = anthropic() + defaultModel = 'claude-sonnet-4-5-20250929' + break + case 'gemini': + adapter = gemini() + defaultModel = 'gemini-2.0-flash-exp' + break + case 'ollama': + adapter = ollama() + defaultModel = 'mistral:7b' + break + case 'openai': + default: + adapter = openai() + defaultModel = 'gpt-4o' + break + } + + const selectedModel = model || defaultModel + console.log( + `[API] Using provider: ${provider}, model: ${selectedModel}`, + ) + + const abortController = new AbortController() + + const stream = chat({ + adapter: adapter as any, + model: selectedModel as any, + tools: [ + getGuitars, + recommendGuitarToolDef, + addToCartToolServer, + addToWishListToolDef, + getPersonalGuitarPreferenceToolDef, + ], + systemPrompts: [SYSTEM_PROMPT], + agentLoopStrategy: maxIterations(20), + messages, + abortController, + }) + + const response = toStreamResponse(stream, { abortController }) + + // Forward headers + response.headers.forEach((value, key) => { + res.setHeader(key, value) + }) + + // Stream the body + if (response.body) { + const reader = response.body.getReader() + const pump = async () => { + while (true) { + const { done, value } = await reader.read() + if (done) break + res.write(value) + } + res.end() + } + pump().catch((err) => { + console.error('Stream error:', err) + res.end() + }) + } else { + res.end() + } + } catch (error: any) { + console.error('[API] Error:', error) + res.statusCode = 500 + res.setHeader('Content-Type', 'application/json') + res.end( + JSON.stringify({ error: error.message || 'An error occurred' }), + ) + } + }) + }, + }, + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, +}) diff --git a/knip.json b/knip.json index cc60c570..8f2913dc 100644 --- a/knip.json +++ b/knip.json @@ -26,6 +26,16 @@ }, "packages/typescript/ai-openai": { "ignore": ["src/tools/**"] + }, + "packages/typescript/ai-vue-ui": { + "ignoreDependencies": [ + "@crazydos/vue-markdown", + "rehype-highlight", + "rehype-raw", + "rehype-sanitize", + "remark-gfm" + ], + "ignore": ["src/use-chat-context.ts"] } } } diff --git a/packages/typescript/ai-vue-ui/README.md b/packages/typescript/ai-vue-ui/README.md new file mode 100644 index 00000000..7c414307 --- /dev/null +++ b/packages/typescript/ai-vue-ui/README.md @@ -0,0 +1,104 @@ +
+ +
+ +
+ +
+ + + + + + + + + +
+ +
+ + semantic-release + + + Release + + + Follow @TanStack + +
+ +
+ +### [Become a Sponsor!](https://github.com/sponsors/tannerlinsley/) +
+ +# TanStack AI + +A powerful, type-safe AI SDK for building AI-powered applications. + +- Provider-agnostic adapters (OpenAI, Anthropic, Gemini, Ollama, etc.) +- Chat completion, streaming, and agent loop strategies +- Headless chat state management with adapters (SSE, HTTP stream, custom) +- Type-safe tools with server/client execution + +### Read the docs → + +## Get Involved + +- We welcome issues and pull requests! +- Participate in [GitHub discussions](https://github.com/TanStack/ai/discussions) +- Chat with the community on [Discord](https://discord.com/invite/WrRKjPJ) +- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions + +## Partners + + + + + + +
+ + + + + CodeRabbit + + + + + + + + Cloudflare + + +
+ +
+AI & you? +

+We're looking for TanStack AI Partners to join our mission! Partner with us to push the boundaries of TanStack AI and build amazing things together. +

+LET'S CHAT +
+ +## Explore the TanStack Ecosystem + +- TanStack Config – Tooling for JS/TS packages +- TanStack DB – Reactive sync client store +- TanStack Devtools – Unified devtools panel +- TanStack Form – Type‑safe form state +- TanStack Pacer – Debouncing, throttling, batching +- TanStack Query – Async state & caching +- TanStack Ranger – Range & slider primitives +- TanStack Router – Type‑safe routing, caching & URL state +- TanStack Start – Full‑stack SSR & streaming +- TanStack Store – Reactive data store +- TanStack Table – Headless datagrids +- TanStack Virtual – Virtualized rendering + +… and more at TanStack.com » + + diff --git a/packages/typescript/ai-vue-ui/package.json b/packages/typescript/ai-vue-ui/package.json new file mode 100644 index 00000000..6d6015e7 --- /dev/null +++ b/packages/typescript/ai-vue-ui/package.json @@ -0,0 +1,61 @@ +{ + "name": "@tanstack/ai-vue-ui", + "version": "0.0.0", + "description": "Headless Vue components for building AI chat interfaces", + "module": "./src/index.ts", + "types": "./src/index.ts", + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/TanStack/ai.git", + "directory": "packages/typescript/ai-vue-ui" + }, + "exports": { + ".": { + "types": "./dist/esm/index.d.ts", + "import": "./dist/esm/index.js" + } + }, + "scripts": { + "build": "vite build", + "clean": "premove ./build ./dist", + "test:build": "publint --strict", + "test:eslint": "eslint ./src", + "test:lib": "vitest --passWithNoTests", + "test:lib:dev": "pnpm test:lib --watch", + "test:types": "vue-tsc --noEmit" + }, + "keywords": [ + "tanstack", + "ai", + "vue", + "chat", + "ui", + "headless", + "components" + ], + "dependencies": { + "@crazydos/vue-markdown": "^1.1.4", + "@tanstack/ai-client": "workspace:*", + "@tanstack/ai-vue": "workspace:*", + "rehype-highlight": "^7.0.2", + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", + "remark-gfm": "^4.0.1" + }, + "peerDependencies": { + "@tanstack/ai-client": "workspace:*", + "@tanstack/ai-vue": "workspace:*", + "@vitejs/plugin-vue": "^6.0.2", + "vue": ">=3.5.0" + }, + "devDependencies": { + "@vitest/coverage-v8": "4.0.14", + "vite": "^7.2.4", + "vue-tsc": "^2.2.10" + }, + "files": [ + "src", + "dist" + ] +} diff --git a/packages/typescript/ai-vue-ui/src/chat-input.vue b/packages/typescript/ai-vue-ui/src/chat-input.vue new file mode 100644 index 00000000..3befaa74 --- /dev/null +++ b/packages/typescript/ai-vue-ui/src/chat-input.vue @@ -0,0 +1,144 @@ + + + diff --git a/packages/typescript/ai-vue-ui/src/chat-message.vue b/packages/typescript/ai-vue-ui/src/chat-message.vue new file mode 100644 index 00000000..deeaeb03 --- /dev/null +++ b/packages/typescript/ai-vue-ui/src/chat-message.vue @@ -0,0 +1,77 @@ + + + diff --git a/packages/typescript/ai-vue-ui/src/chat-messages.vue b/packages/typescript/ai-vue-ui/src/chat-messages.vue new file mode 100644 index 00000000..02b0ad62 --- /dev/null +++ b/packages/typescript/ai-vue-ui/src/chat-messages.vue @@ -0,0 +1,67 @@ + + + diff --git a/packages/typescript/ai-vue-ui/src/chat.vue b/packages/typescript/ai-vue-ui/src/chat.vue new file mode 100644 index 00000000..ba8245ad --- /dev/null +++ b/packages/typescript/ai-vue-ui/src/chat.vue @@ -0,0 +1,40 @@ + + + diff --git a/packages/typescript/ai-vue-ui/src/index.ts b/packages/typescript/ai-vue-ui/src/index.ts new file mode 100644 index 00000000..2528e32f --- /dev/null +++ b/packages/typescript/ai-vue-ui/src/index.ts @@ -0,0 +1,30 @@ +import Chat from './chat.vue' +import ChatInput from './chat-input.vue' +import ChatMessage from './chat-message.vue' +import ChatMessages from './chat-messages.vue' +import ThinkingPart from './thinking-part.vue' +import TextPart from './text-part.vue' +import ToolApproval from './tool-approval.vue' + +export { + Chat, + ChatInput, + ChatMessage, + ChatMessages, + ThinkingPart, + TextPart, + ToolApproval, +} + +export type { + ChatProps, + ChatInputProps, + ChatInputRenderProps, + ChatMessageProps, + ChatMessagesProps, + ThinkingPartProps, + TextPartProps, + ToolApprovalProps, + ToolApprovalRenderProps, + ToolCallRenderProps, +} from './types' diff --git a/packages/typescript/ai-vue-ui/src/message-part.vue b/packages/typescript/ai-vue-ui/src/message-part.vue new file mode 100644 index 00000000..855fe772 --- /dev/null +++ b/packages/typescript/ai-vue-ui/src/message-part.vue @@ -0,0 +1,125 @@ + + + diff --git a/packages/typescript/ai-vue-ui/src/text-part.vue b/packages/typescript/ai-vue-ui/src/text-part.vue new file mode 100644 index 00000000..9b1c3c58 --- /dev/null +++ b/packages/typescript/ai-vue-ui/src/text-part.vue @@ -0,0 +1,35 @@ + + + diff --git a/packages/typescript/ai-vue-ui/src/thinking-part.vue b/packages/typescript/ai-vue-ui/src/thinking-part.vue new file mode 100644 index 00000000..15efb3b1 --- /dev/null +++ b/packages/typescript/ai-vue-ui/src/thinking-part.vue @@ -0,0 +1,39 @@ + + + diff --git a/packages/typescript/ai-vue-ui/src/tool-approval.vue b/packages/typescript/ai-vue-ui/src/tool-approval.vue new file mode 100644 index 00000000..b868415b --- /dev/null +++ b/packages/typescript/ai-vue-ui/src/tool-approval.vue @@ -0,0 +1,74 @@ + + + diff --git a/packages/typescript/ai-vue-ui/src/types.ts b/packages/typescript/ai-vue-ui/src/types.ts new file mode 100644 index 00000000..d4e07419 --- /dev/null +++ b/packages/typescript/ai-vue-ui/src/types.ts @@ -0,0 +1,124 @@ +import type { ConnectionAdapter, UIMessage } from '@tanstack/ai-vue' + +export interface ChatProps { + /** CSS class name for the root element */ + class?: string + /** Connection adapter for communicating with your API */ + connection: ConnectionAdapter + /** Initial messages to display */ + initialMessages?: Array + /** Custom message ID generator */ + id?: string + /** Additional body data to send with requests */ + body?: any + /** Client-side tools with execute functions */ + tools?: Array + /** Custom tool components registry for rendering */ + // toolComponents?: Record< + // string, + // (props: { input: any; output?: any }) => JSX.Element + // > +} + +export interface ChatInputProps { + /** CSS class name */ + class?: string + /** Placeholder text */ + placeholder?: string + /** Disable input */ + disabled?: boolean + /** Submit on Enter (Shift+Enter for new line) */ + submitOnEnter?: boolean +} + +export interface ChatInputRenderProps { + /** Current input value (use v-model on ChatInput to control) */ + value: string + /** Submit the message */ + onSubmit: () => void + /** Is the chat currently loading */ + isLoading: boolean + /** Is input disabled */ + disabled: boolean +} + +export interface ThinkingPartProps { + /** The thinking content to render */ + content: string + /** Base class applied to thinking parts */ + class?: string + /** Whether thinking is complete (has text content after) */ + isComplete?: boolean +} + +export interface ToolCallRenderProps { + id: string + name: string + arguments: string + state: string + approval?: any + output?: any +} + +export interface ChatMessageProps { + /** The message to render (accepts readonly from useChat) */ + message: any // Using any to accept DeepReadonly from useChat + /** Base CSS class name */ + class?: string + /** Additional class for user messages */ + userClass?: string + /** Additional class for assistant messages */ + assistantClass?: string +} + +export interface ChatMessagesProps { + /** CSS class name */ + class?: string + /** Auto-scroll to bottom on new messages */ + autoScroll?: boolean +} + +export interface TextPartProps { + /** The text content to render */ + content: string + /** The role of the message (user, assistant, or system) - optional for standalone use */ + role?: 'user' | 'assistant' | 'system' + /** Base class applied to all text parts */ + class?: string + /** Additional class for user messages */ + userClass?: string + /** Additional class for assistant messages (also used for system messages) */ + assistantClass?: string +} + +export interface ToolApprovalProps { + /** Tool call ID */ + toolCallId: string + /** Tool name */ + toolName: string + /** Parsed tool arguments/input */ + input: any + /** Approval metadata */ + approval: { + id: string + needsApproval: boolean + approved?: boolean + } + /** CSS class name */ + class?: string +} + +export interface ToolApprovalRenderProps { + /** Tool name */ + toolName: string + /** Parsed input */ + input: any + /** Approve the tool call */ + onApprove: () => void + /** Deny the tool call */ + onDeny: () => void + /** Whether user has responded */ + hasResponded: boolean + /** User's decision (if responded) */ + approved?: boolean +} diff --git a/packages/typescript/ai-vue-ui/src/use-chat-context.ts b/packages/typescript/ai-vue-ui/src/use-chat-context.ts new file mode 100644 index 00000000..75dd0fbe --- /dev/null +++ b/packages/typescript/ai-vue-ui/src/use-chat-context.ts @@ -0,0 +1,19 @@ +import { inject } from 'vue' +import type { UseChatReturn } from '@tanstack/ai-vue' +import type { InjectionKey } from 'vue' + +export const CHAT_KEY = Symbol() as InjectionKey + +/** + * Composable to access chat context + * @throws Error if used outside of Chat component + */ +export function useChatContext(): UseChatReturn { + const context = inject(CHAT_KEY) + if (!context) { + throw new Error( + "Chat components must be wrapped in . Make sure you're using Chat.Messages, Chat.Input, etc. inside a component.", + ) + } + return context +} diff --git a/packages/typescript/ai-vue-ui/tsconfig.json b/packages/typescript/ai-vue-ui/tsconfig.json new file mode 100644 index 00000000..b96b7d78 --- /dev/null +++ b/packages/typescript/ai-vue-ui/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "jsx": "preserve", + "declaration": true, + "declarationMap": true, + "composite": true, + "lib": ["ES2022", "DOM"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/typescript/ai-vue-ui/vite.config.ts b/packages/typescript/ai-vue-ui/vite.config.ts new file mode 100644 index 00000000..58e0135d --- /dev/null +++ b/packages/typescript/ai-vue-ui/vite.config.ts @@ -0,0 +1,38 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import { tanstackViteConfig } from '@tanstack/vite-config' +import vue from '@vitejs/plugin-vue' +import packageJson from './package.json' + +const config = defineConfig({ + plugins: [vue()], + test: { + name: packageJson.name, + dir: './', + watch: false, + globals: true, + environment: 'node', + include: ['tests/**/*.test.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html', 'lcov'], + exclude: [ + 'node_modules/', + 'dist/', + 'tests/', + '**/*.test.ts', + '**/*.config.ts', + '**/types.ts', + ], + include: ['src/**/*.ts'], + }, + }, +}) + +export default mergeConfig( + config, + tanstackViteConfig({ + entry: ['./src/index.ts'], + srcDir: './src', + cjs: false, + }), +) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f8718d1f..c0c4a417 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -435,6 +435,82 @@ importers: specifier: ^5.1.0 version: 5.1.0 + examples/ts-vue-chat: + dependencies: + '@tanstack/ai': + specifier: workspace:* + version: link:../../packages/typescript/ai + '@tanstack/ai-anthropic': + specifier: workspace:* + version: link:../../packages/typescript/ai-anthropic + '@tanstack/ai-client': + specifier: workspace:* + version: link:../../packages/typescript/ai-client + '@tanstack/ai-gemini': + specifier: workspace:* + version: link:../../packages/typescript/ai-gemini + '@tanstack/ai-ollama': + specifier: workspace:* + version: link:../../packages/typescript/ai-ollama + '@tanstack/ai-openai': + specifier: workspace:* + version: link:../../packages/typescript/ai-openai + '@tanstack/ai-vue': + specifier: workspace:* + version: link:../../packages/typescript/ai-vue + '@tanstack/ai-vue-ui': + specifier: workspace:* + version: link:../../packages/typescript/ai-vue-ui + marked: + specifier: ^15.0.6 + version: 15.0.12 + vue: + specifier: ^3.5.25 + version: 3.5.25(typescript@5.9.3) + vue-router: + specifier: ^4.5.0 + version: 4.6.3(vue@3.5.25(typescript@5.9.3)) + zod: + specifier: ^4.1.13 + version: 4.1.13 + devDependencies: + '@tailwindcss/vite': + specifier: ^4.1.17 + version: 4.1.17(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + '@types/node': + specifier: ^24.10.1 + version: 24.10.1 + '@vitejs/plugin-vue': + specifier: ^5.2.3 + version: 5.2.4(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.25(typescript@5.9.3)) + autoprefixer: + specifier: ^10.4.21 + version: 10.4.22(postcss@8.5.6) + concurrently: + specifier: ^9.1.2 + version: 9.2.1 + dotenv: + specifier: ^17.2.3 + version: 17.2.3 + express: + specifier: ^5.1.0 + version: 5.2.1 + tailwindcss: + specifier: ^4.1.17 + version: 4.1.17 + tsx: + specifier: ^4.20.6 + version: 4.20.6 + typescript: + specifier: 5.9.3 + version: 5.9.3 + vite: + specifier: ^7.2.4 + version: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + vue-tsc: + specifier: ^2.2.10 + version: 2.2.12(typescript@5.9.3) + examples/vanilla-chat: dependencies: '@tanstack/ai-client': @@ -757,6 +833,46 @@ importers: specifier: ^3.5.25 version: 3.5.25(typescript@5.9.3) + packages/typescript/ai-vue-ui: + dependencies: + '@crazydos/vue-markdown': + specifier: ^1.1.4 + version: 1.1.4(vue@3.5.25(typescript@5.9.3)) + '@tanstack/ai-client': + specifier: workspace:* + version: link:../ai-client + '@tanstack/ai-vue': + specifier: workspace:* + version: link:../ai-vue + '@vitejs/plugin-vue': + specifier: ^6.0.2 + version: 6.0.2(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.25(typescript@5.9.3)) + rehype-highlight: + specifier: ^7.0.2 + version: 7.0.2 + rehype-raw: + specifier: ^7.0.0 + version: 7.0.0 + rehype-sanitize: + specifier: ^6.0.0 + version: 6.0.0 + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + vue: + specifier: '>=3.5.0' + version: 3.5.25(typescript@5.9.3) + devDependencies: + '@vitest/coverage-v8': + specifier: 4.0.14 + version: 4.0.14(vitest@4.0.14(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.2.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + vite: + specifier: ^7.2.4 + version: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + vue-tsc: + specifier: ^2.2.10 + version: 2.2.12(typescript@5.9.3) + packages/typescript/react-ai-devtools: dependencies: '@tanstack/ai-devtools-core': @@ -1154,6 +1270,12 @@ packages: resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==} engines: {node: '>=18.0.0'} + '@crazydos/vue-markdown@1.1.4': + resolution: {integrity: sha512-0I1QMP59LJ3aEjE7bolgvPU4JAFt+pykdDo5674CbsCwFo7OVFos50+MPhGdWflCz1mac5t152lB1qvV/tR/rw==} + engines: {node: '>=20.0.0'} + peerDependencies: + vue: ^3.0.0 + '@csstools/color-helpers@5.1.0': resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} @@ -1889,6 +2011,9 @@ packages: '@rolldown/pluginutils@1.0.0-beta.47': resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} + '@rolldown/pluginutils@1.0.0-beta.50': + resolution: {integrity: sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==} + '@rolldown/pluginutils@1.0.0-beta.53': resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} @@ -2988,6 +3113,20 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitejs/plugin-vue@5.2.4': + resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + + '@vitejs/plugin-vue@6.0.2': + resolution: {integrity: sha512-iHmwV3QcVGGvSC1BG5bZ4z6iwa1SOpAPWmnjOErd4Ske+lZua5K9TtAVdx0gMBClJ28DViCbSmZitjWZsWO3LA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vue: ^3.2.25 + '@vitest/coverage-v8@4.0.14': resolution: {integrity: sha512-EYHLqN/BY6b47qHH7gtMxAg++saoGmsjWmAq9MlXxAz4M0NcHh9iOyKhBZyU4yxZqOd8Xnqp80/5saeitz4Cng==} peerDependencies: @@ -3026,12 +3165,21 @@ packages: '@vitest/utils@4.0.14': resolution: {integrity: sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==} + '@volar/language-core@2.4.15': + resolution: {integrity: sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==} + '@volar/language-core@2.4.23': resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} + '@volar/source-map@2.4.15': + resolution: {integrity: sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==} + '@volar/source-map@2.4.23': resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==} + '@volar/typescript@2.4.15': + resolution: {integrity: sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==} + '@volar/typescript@2.4.23': resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} @@ -3056,6 +3204,9 @@ packages: '@vue/compiler-vue2@2.7.16': resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + '@vue/language-core@2.1.6': resolution: {integrity: sha512-MW569cSky9R/ooKMh6xa2g1D0AtRKbL56k83dzus/bx//RDJk24RHWkMzbAlXjMdDNyxAaagKPRquBIxkxlCkg==} peerDependencies: @@ -3064,6 +3215,14 @@ packages: typescript: optional: true + '@vue/language-core@2.2.12': + resolution: {integrity: sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@vue/reactivity@3.5.25': resolution: {integrity: sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==} @@ -3106,6 +3265,10 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -3150,6 +3313,9 @@ packages: ajv@8.13.0: resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} + alien-signals@1.0.13: + resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -3231,6 +3397,13 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + autoprefixer@10.4.22: + resolution: {integrity: sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + axios@1.13.2: resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} @@ -3307,6 +3480,10 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + body-parser@2.2.1: + resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==} + engines: {node: '>=18'} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -3341,6 +3518,10 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + c12@3.3.1: resolution: {integrity: sha512-LcWQ01LT9tkoUINHgpIOv3mMs+Abv7oVCrtpMRi1PaapVEpWoMga5WuT7/DqFTu7URP9ftbOmimNw1KNIGh9DQ==} peerDependencies: @@ -3357,12 +3538,16 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001752: - resolution: {integrity: sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g==} + caniuse-lite@1.0.30001759: + resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==} capnweb@0.1.0: resolution: {integrity: sha512-+pygKx1JFTZTRdd1hHgaBRg5BwULEDZq8ZoHXkYP2GXNV3lrjXLj5qzlGz+SgBCJjWUmNBtlh7JPWdr0wIbY8w==} @@ -3513,6 +3698,14 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -3522,6 +3715,14 @@ packages: cookie-es@2.0.0: resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cookie@1.0.2: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} @@ -3984,6 +4185,10 @@ packages: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + exsolve@1.0.7: resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} @@ -4046,6 +4251,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -4091,6 +4300,13 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + fresh@2.0.0: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} @@ -4329,6 +4545,10 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -4407,6 +4627,10 @@ packages: resolution: {integrity: sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==} engines: {node: '>=12.22.0'} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} @@ -4479,6 +4703,9 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} @@ -4852,6 +5079,11 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marked@15.0.12: + resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} + engines: {node: '>= 18'} + hasBin: true + marked@17.0.1: resolution: {integrity: sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==} engines: {node: '>= 20'} @@ -4912,10 +5144,18 @@ packages: mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + merge-anything@5.1.7: resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==} engines: {node: '>=12.13'} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -5108,6 +5348,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + nitropack@2.12.9: resolution: {integrity: sha512-t6qqNBn2UDGMWogQuORjbL2UPevB8PvIPsPHmqvWpeGOlPr4P8Oc5oA8t3wFwGmaolM2M/s2SwT23nx9yARmOg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5173,6 +5417,10 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} @@ -5201,6 +5449,10 @@ packages: engines: {node: ^14.16.0 || >=16.10.0} hasBin: true + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} @@ -5343,6 +5595,9 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -5391,6 +5646,9 @@ packages: engines: {node: '>=18'} hasBin: true + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -5448,6 +5706,10 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -5464,6 +5726,10 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -5480,6 +5746,10 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} @@ -5661,6 +5931,10 @@ packages: rou3@0.7.10: resolution: {integrity: sha512-aoFj6f7MJZ5muJ+Of79nrhs9N3oLGqi2VEMe94Zbkjb6Wupha46EuoYgpWSOZlXww3bbd8ojgXTAA2mzimX5Ww==} + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -5797,6 +6071,22 @@ packages: resolution: {integrity: sha512-5n7zqPAjL+RzR7n09NPKpWBXmDCtuRpQzIL+ycj8pe6MayV7cDuFmceoyPQJ0c95oFj6feY7SZvhX/+S0i1ukg==} hasBin: true + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -6144,6 +6434,10 @@ packages: resolution: {integrity: sha512-wQ531tuWvB6oK+pchHIu5lHe5f5wpSCqB8Kf4dWQRbOYc9HTge7JL0G4Qd44bh6QuJCccIzL3bugb8GI0MwHrg==} engines: {node: '>=20'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typedoc-plugin-frontmatter@1.3.0: resolution: {integrity: sha512-xYQFMAecMlsRUjmf9oM/Sq2FVz4zlgcbIeVFNLdO118CHTN06gIKJNSlyExh9+Xl8sK0YhIvoQwViUURxritWA==} peerDependencies: @@ -6252,6 +6546,10 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + unplugin-utils@0.3.1: resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} engines: {node: '>=20.19.0'} @@ -6366,6 +6664,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -6502,6 +6804,17 @@ packages: peerDependencies: eslint: ^8.57.0 || ^9.0.0 + vue-router@4.6.3: + resolution: {integrity: sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==} + peerDependencies: + vue: ^3.5.0 + + vue-tsc@2.2.12: + resolution: {integrity: sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + vue@3.5.25: resolution: {integrity: sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==} peerDependencies: @@ -7064,6 +7377,18 @@ snapshots: dependencies: mime: 3.0.0 + '@crazydos/vue-markdown@1.1.4(vue@3.5.25(typescript@5.9.3))': + dependencies: + deepmerge: 4.3.1 + property-information: 6.5.0 + rehype-sanitize: 6.0.0 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + vue: 3.5.25(typescript@5.9.3) + transitivePeerDependencies: + - supports-color + '@csstools/color-helpers@5.1.0': {} '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': @@ -7654,6 +7979,8 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.47': {} + '@rolldown/pluginutils@1.0.0-beta.50': {} + '@rolldown/pluginutils@1.0.0-beta.53': {} '@rollup/plugin-alias@5.1.1(rollup@4.53.3)': @@ -9079,6 +9406,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitejs/plugin-vue@5.2.4(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.25(typescript@5.9.3))': + dependencies: + vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + vue: 3.5.25(typescript@5.9.3) + + '@vitejs/plugin-vue@6.0.2(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.25(typescript@5.9.3))': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.50 + vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + vue: 3.5.25(typescript@5.9.3) + '@vitest/coverage-v8@4.0.14(vitest@4.0.14(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.2.0(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@bcoe/v8-coverage': 1.0.2 @@ -9135,12 +9473,24 @@ snapshots: '@vitest/pretty-format': 4.0.14 tinyrainbow: 3.0.3 + '@volar/language-core@2.4.15': + dependencies: + '@volar/source-map': 2.4.15 + '@volar/language-core@2.4.23': dependencies: '@volar/source-map': 2.4.23 + '@volar/source-map@2.4.15': {} + '@volar/source-map@2.4.23': {} + '@volar/typescript@2.4.15': + dependencies: + '@volar/language-core': 2.4.15 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + '@volar/typescript@2.4.23': dependencies: '@volar/language-core': 2.4.23 @@ -9195,6 +9545,8 @@ snapshots: de-indent: 1.0.2 he: 1.2.0 + '@vue/devtools-api@6.6.4': {} + '@vue/language-core@2.1.6(typescript@5.9.3)': dependencies: '@volar/language-core': 2.4.23 @@ -9208,6 +9560,19 @@ snapshots: optionalDependencies: typescript: 5.9.3 + '@vue/language-core@2.2.12(typescript@5.9.3)': + dependencies: + '@volar/language-core': 2.4.15 + '@vue/compiler-dom': 3.5.25 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.25 + alien-signals: 1.0.13 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.9.3 + '@vue/reactivity@3.5.25': dependencies: '@vue/shared': 3.5.25 @@ -9256,6 +9621,11 @@ snapshots: dependencies: event-target-shim: 5.0.1 + accepts@2.0.0: + dependencies: + mime-types: 3.0.1 + negotiator: 1.0.0 + acorn-import-attributes@1.9.5(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -9297,6 +9667,8 @@ snapshots: require-from-string: 2.0.2 uri-js: 4.4.1 + alien-signals@1.0.13: {} + ansi-colors@4.1.3: {} ansi-regex@5.0.1: {} @@ -9378,6 +9750,16 @@ snapshots: asynckit@0.4.0: {} + autoprefixer@10.4.22(postcss@8.5.6): + dependencies: + browserslist: 4.27.0 + caniuse-lite: 1.0.30001759 + fraction.js: 5.3.4 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + axios@1.13.2: dependencies: follow-redirects: 1.15.11 @@ -9449,6 +9831,20 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + body-parser@2.2.1: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.0 + iconv-lite: 0.7.0 + on-finished: 2.4.1 + qs: 6.14.0 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + boolbase@1.0.0: {} brace-expansion@1.1.12: @@ -9467,7 +9863,7 @@ snapshots: browserslist@4.27.0: dependencies: baseline-browser-mapping: 2.8.22 - caniuse-lite: 1.0.30001752 + caniuse-lite: 1.0.30001759 electron-to-chromium: 1.5.244 node-releases: 2.0.27 update-browserslist-db: 1.1.4(browserslist@4.27.0) @@ -9488,6 +9884,8 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + bytes@3.1.2: {} + c12@3.3.1(magicast@0.5.1): dependencies: chokidar: 4.0.3 @@ -9512,9 +9910,14 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} - caniuse-lite@1.0.30001752: {} + caniuse-lite@1.0.30001759: {} capnweb@0.1.0: {} @@ -9666,12 +10069,20 @@ snapshots: consola@3.4.2: {} + content-disposition@1.0.1: {} + + content-type@1.0.5: {} + convert-source-map@2.0.0: {} cookie-es@1.2.2: {} cookie-es@2.0.0: {} + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + cookie@1.0.2: {} core-util-is@1.0.3: {} @@ -10125,6 +10536,39 @@ snapshots: expect-type@1.2.2: {} + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.1 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + mime-types: 3.0.1 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.0 + serve-static: 2.2.0 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + exsolve@1.0.7: {} extend@3.0.2: {} @@ -10180,6 +10624,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -10222,6 +10677,10 @@ snapshots: dependencies: fetch-blob: 3.2.0 + forwarded@0.2.0: {} + + fraction.js@5.3.4: {} + fresh@2.0.0: {} front-matter@4.0.2: @@ -10550,6 +11009,14 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -10622,6 +11089,8 @@ snapshots: transitivePeerDependencies: - supports-color + ipaddr.js@1.9.1: {} + iron-webcrypto@1.2.1: {} is-alphabetical@2.0.1: {} @@ -10673,6 +11142,8 @@ snapshots: is-potential-custom-element-name@1.0.1: {} + is-promise@4.0.0: {} + is-reference@1.2.1: dependencies: '@types/estree': 1.0.8 @@ -11061,6 +11532,8 @@ snapshots: markdown-table@3.0.4: {} + marked@15.0.12: {} + marked@17.0.1: {} math-intrinsics@1.1.0: {} @@ -11222,10 +11695,14 @@ snapshots: mdurl@2.0.0: {} + media-typer@1.1.0: {} + merge-anything@5.1.7: dependencies: is-what: 4.1.16 + merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -11500,6 +11977,8 @@ snapshots: natural-compare@1.4.0: {} + negotiator@1.0.0: {} + nitropack@2.12.9(rolldown@1.0.0-beta.53): dependencies: '@cloudflare/kv-asset-handler': 0.4.0 @@ -11638,6 +12117,8 @@ snapshots: normalize-path@3.0.0: {} + normalize-range@0.1.2: {} + npm-run-path@4.0.1: dependencies: path-key: 3.1.1 @@ -11709,6 +12190,8 @@ snapshots: pkg-types: 2.3.0 tinyexec: 1.0.2 + object-inspect@1.13.4: {} + obug@2.1.1: {} ofetch@1.5.0: @@ -11876,6 +12359,8 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-to-regexp@8.3.0: {} + path-type@4.0.0: {} path-type@6.0.0: {} @@ -11914,6 +12399,8 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + postcss-value-parser@4.2.0: {} + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -11957,6 +12444,11 @@ snapshots: proto-list@1.2.4: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + proxy-from-env@1.1.0: {} publint@0.3.15: @@ -11970,6 +12462,10 @@ snapshots: punycode@2.3.1: {} + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + quansync@0.2.11: {} queue-microtask@1.2.3: {} @@ -11982,6 +12478,13 @@ snapshots: range-parser@1.2.1: {} + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.0 + unpipe: 1.0.0 + rc9@2.1.2: dependencies: defu: 6.1.4 @@ -12243,6 +12746,16 @@ snapshots: rou3@0.7.10: {} + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -12367,6 +12880,34 @@ snapshots: sherif-windows-arm64: 1.9.0 sherif-windows-x64: 1.9.0 + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} signal-exit@3.0.7: {} @@ -12709,6 +13250,12 @@ snapshots: dependencies: tagged-tag: 1.0.0 + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.1 + typedoc-plugin-frontmatter@1.3.0(typedoc-plugin-markdown@4.9.0(typedoc@0.28.14(typescript@5.9.3))): dependencies: typedoc-plugin-markdown: 4.9.0(typedoc@0.28.14(typescript@5.9.3)) @@ -12846,6 +13393,8 @@ snapshots: universalify@0.1.2: {} + unpipe@1.0.0: {} + unplugin-utils@0.3.1: dependencies: pathe: 2.0.3 @@ -12942,6 +13491,8 @@ snapshots: util-deprecate@1.0.2: {} + vary@1.1.2: {} + vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 @@ -13082,6 +13633,17 @@ snapshots: transitivePeerDependencies: - supports-color + vue-router@4.6.3(vue@3.5.25(typescript@5.9.3)): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.25(typescript@5.9.3) + + vue-tsc@2.2.12(typescript@5.9.3): + dependencies: + '@volar/typescript': 2.4.15 + '@vue/language-core': 2.2.12(typescript@5.9.3) + typescript: 5.9.3 + vue@3.5.25(typescript@5.9.3): dependencies: '@vue/compiler-dom': 3.5.25