diff --git a/apps/mana/apps/web/package.json b/apps/mana/apps/web/package.json index 2affc33a3..3f95e97ff 100644 --- a/apps/mana/apps/web/package.json +++ b/apps/mana/apps/web/package.json @@ -45,7 +45,7 @@ }, "dependencies": { "@calc/shared": "workspace:*", - "@mlc-ai/web-llm": "^0.2.78", + "@huggingface/transformers": "^4.0.0", "@mana/credits": "workspace:^", "@mana/feedback": "workspace:*", "@mana/help": "workspace:*", diff --git a/apps/mana/apps/web/src/routes/(app)/llm-test/+page.svelte b/apps/mana/apps/web/src/routes/(app)/llm-test/+page.svelte index e9c8319c1..3b4a0f458 100644 --- a/apps/mana/apps/web/src/routes/(app)/llm-test/+page.svelte +++ b/apps/mana/apps/web/src/routes/(app)/llm-test/+page.svelte @@ -42,7 +42,7 @@ } // --- State --- - let selectedModel: ModelKey = $state('qwen-2.5-1.5b'); + let selectedModel: ModelKey = $state('gemma-4-e2b'); let activeTab: 'chat' | 'extract' | 'classify' | 'compare' | 'benchmark' = $state('chat'); const supported = isLocalLlmSupported(); const status = getLocalLlmStatus(); diff --git a/apps/mana/apps/web/vite.config.ts b/apps/mana/apps/web/vite.config.ts index 815874a96..f4164ebd2 100644 --- a/apps/mana/apps/web/vite.config.ts +++ b/apps/mana/apps/web/vite.config.ts @@ -64,7 +64,12 @@ export default defineConfig({ // into the server build forces Vite's interop layer to handle the // CJS↔ESM mismatch correctly. noExternal: [...MANA_SHARED_PACKAGES, ...APP_SHARED_PACKAGES, 'rrule'], - external: ['@mlc-ai/web-llm'], + // transformers.js is browser-only (uses WebGPU + the Cache API). The + // dynamic import in @mana/local-llm only ever fires client-side, but + // SvelteKit's adapter-node Rollup pass would otherwise warn that the + // import is unresolved at SSR time. Marking it external both silences + // the warning and ensures the SSR bundle never tries to load it. + external: ['@huggingface/transformers'], }, optimizeDeps: { exclude: [...MANA_SHARED_PACKAGES, ...APP_SHARED_PACKAGES], diff --git a/packages/local-llm/package.json b/packages/local-llm/package.json index d79e65d99..acc54e1a5 100644 --- a/packages/local-llm/package.json +++ b/packages/local-llm/package.json @@ -1,8 +1,8 @@ { "name": "@mana/local-llm", - "version": "0.1.0", + "version": "0.2.0", "private": true, - "description": "Client-side LLM inference via WebLLM (Qwen 2.5 1.5B) with Svelte 5 reactive stores", + "description": "Client-side LLM inference via transformers.js (Gemma 4 E2B, WebGPU) with Svelte 5 reactive stores", "main": "./src/index.ts", "types": "./src/index.ts", "exports": { @@ -13,7 +13,7 @@ "clean": "rm -rf dist" }, "dependencies": { - "@mlc-ai/web-llm": "^0.2.78" + "@huggingface/transformers": "^4.0.0" }, "devDependencies": { "@types/node": "^24.10.1", diff --git a/packages/local-llm/src/cache.ts b/packages/local-llm/src/cache.ts index 373d4be1a..3ecee020f 100644 --- a/packages/local-llm/src/cache.ts +++ b/packages/local-llm/src/cache.ts @@ -1,12 +1,23 @@ /** - * Check if a model is cached in the browser's Cache API. - * Wraps @mlc-ai/web-llm's hasModelInCache with a dynamic import - * so it doesn't break SSR/Docker builds. + * Check if a transformers.js model is already cached in the browser. + * + * transformers.js stores HuggingFace shards in the standard Cache API under a + * named cache (default "transformers-cache"). We probe for the model's + * tokenizer.json — it's tiny (~few KB), always present, and downloaded + * first, so its presence is a reliable proxy for "this model has been + * loaded at least once before". */ export async function hasModelInCache(modelId: string): Promise { + if (typeof caches === 'undefined') return false; try { - const { hasModelInCache: check } = await import('@mlc-ai/web-llm'); - return await check(modelId); + const cacheNames = await caches.keys(); + const url = `https://huggingface.co/${modelId}/resolve/main/tokenizer.json`; + for (const name of cacheNames) { + const cache = await caches.open(name); + const match = await cache.match(url); + if (match) return true; + } + return false; } catch { return false; } diff --git a/packages/local-llm/src/engine.ts b/packages/local-llm/src/engine.ts index ce7c7b66a..60a7360c0 100644 --- a/packages/local-llm/src/engine.ts +++ b/packages/local-llm/src/engine.ts @@ -1,17 +1,34 @@ /** - * LocalLLMEngine — WebLLM wrapper for client-side inference. + * LocalLLMEngine — transformers.js wrapper for client-side inference. * - * Lazy-loads the model on first use, caches weights in browser Cache API. - * Provides both one-shot and streaming generation. + * Lazy-loads a HuggingFace ONNX model on first use, caches weights in the + * browser's Cache API, and runs inference on the WebGPU backend. + * + * The default model is Google's Gemma 4 E2B (`onnx-community/gemma-4-E2B-it-ONNX`, + * q4f16). The external API of this class is intentionally identical to the + * previous WebLLM implementation so callers (Svelte stores, /llm-test page, + * playground module) need no changes when the underlying engine swaps. */ -import type { MLCEngine } from '@mlc-ai/web-llm'; import type { ChatMessage, GenerateOptions, GenerateResult, LoadingStatus } from './types'; import type { ModelConfig } from './types'; import { MODELS, DEFAULT_MODEL, type ModelKey } from './models'; +// transformers.js types are minimal here on purpose. The library does not +// publish first-class TS types for every model class, and we never expose +// these objects past this file — the public surface (LocalLLMEngine methods) +// is fully typed via our own GenerateResult / LoadingStatus etc. +type TransformersModule = typeof import('@huggingface/transformers'); + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyModel = any; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type AnyProcessor = any; + export class LocalLLMEngine { - private engine: MLCEngine | null = null; + private model: AnyModel = null; + private processor: AnyProcessor = null; + private transformers: TransformersModule | null = null; private loadPromise: Promise | null = null; private currentModel: ModelKey | null = null; private _status: LoadingStatus = { state: 'idle' }; @@ -53,17 +70,17 @@ export class LocalLLMEngine { /** * Load a model. Idempotent — returns immediately if already loaded. - * Model weights are cached in browser Cache API for instant reload. + * Model weights are cached in the browser Cache API for instant reload. */ async load(model: ModelKey = DEFAULT_MODEL): Promise { // Already loaded with this model - if (this.engine && this.currentModel === model) return; + if (this.model && this.currentModel === model) return; // Already loading if (this.loadPromise && this.currentModel === model) return this.loadPromise; // Unload previous model if switching - if (this.engine && this.currentModel !== model) { + if (this.model && this.currentModel !== model) { await this.unload(); } @@ -81,21 +98,60 @@ export class LocalLLMEngine { this.setStatus({ state: 'checking' }); try { - const { CreateMLCEngine } = await import('@mlc-ai/web-llm'); + if (!this.transformers) { + this.transformers = await import('@huggingface/transformers'); + } const config = MODELS[model]; - this.engine = await CreateMLCEngine(config.modelId, { - initProgressCallback: (report) => { - if (report.progress < 1) { - this.setStatus({ - state: 'downloading', - progress: report.progress, - text: report.text, - }); - } else { - this.setStatus({ state: 'loading', text: 'Initializing model...' }); - } - }, + // transformers.js progress callback shape: + // { status: 'initiate'|'download'|'progress'|'done'|'ready', + // name?: string, file?: string, progress?: number, loaded?: number, total?: number } + // We collapse it into our LoadingStatus union. + const progressCallback = (report: { + status: string; + file?: string; + name?: string; + progress?: number; + loaded?: number; + total?: number; + }) => { + const label = report.file ?? report.name ?? ''; + if (report.status === 'progress' || report.status === 'download') { + const pct = typeof report.progress === 'number' ? report.progress : 0; + this.setStatus({ + state: 'downloading', + progress: pct / 100, + text: label + ? `Downloading ${label} (${pct.toFixed(0)}%)` + : `Downloading (${pct.toFixed(0)}%)`, + }); + } else if (report.status === 'initiate') { + this.setStatus({ state: 'downloading', progress: 0, text: `Starting ${label}` }); + } else if (report.status === 'done') { + this.setStatus({ state: 'loading', text: label ? `Loaded ${label}` : 'Loaded shard' }); + } + // 'ready' is handled below after both processor + model finish + }; + + // AutoProcessor wraps tokenizer + image/audio preprocessors. For + // our text-only chat path we use the wrapped tokenizer's + // apply_chat_template, but loading the full processor is the + // path the model card documents and avoids architecture-specific + // special-casing. + const { AutoProcessor, Gemma4ForConditionalGeneration } = this.transformers as unknown as { + AutoProcessor: { from_pretrained(id: string, opts?: unknown): Promise }; + Gemma4ForConditionalGeneration: { + from_pretrained(id: string, opts?: unknown): Promise; + }; + }; + + this.processor = await AutoProcessor.from_pretrained(config.modelId, { + progress_callback: progressCallback, + }); + this.model = await Gemma4ForConditionalGeneration.from_pretrained(config.modelId, { + dtype: config.dtype, + device: 'webgpu', + progress_callback: progressCallback, }); this.setStatus({ state: 'ready' }); @@ -108,13 +164,15 @@ export class LocalLLMEngine { } /** - * Unload the model and free memory. + * Unload the model and free GPU memory. */ async unload(): Promise { - if (this.engine) { - await this.engine.unload(); - this.engine = null; - } + // transformers.js doesn't expose an explicit dispose() yet — dropping + // the references and letting the runtime/GC clean up is the + // recommended path. The WebGPU buffers are tied to the model object + // and get released when it's no longer reachable. + this.model = null; + this.processor = null; this.currentModel = null; this.loadPromise = null; this.setStatus({ state: 'idle' }); @@ -124,70 +182,85 @@ export class LocalLLMEngine { * Generate a response. Auto-loads the model if not yet loaded. */ async generate(options: GenerateOptions): Promise { - if (!this.engine) { + if (!this.model || !this.processor) { await this.load(); } const start = performance.now(); - if (options.onToken) { - return this._generateStreaming(options, start); - } - - const response = await this.engine!.chat.completions.create({ - messages: options.messages, - temperature: options.temperature ?? 0.7, - max_tokens: options.maxTokens ?? 1024, - stream: false, + // Apply Gemma's chat template via the processor's tokenizer wrapper. + // `add_generation_prompt: true` appends the tokens that tell the model + // "now generate an assistant turn". + const inputs = await this.processor.apply_chat_template(options.messages, { + add_generation_prompt: true, + return_dict: true, + return_tensor: 'pt', + }); + + const promptTokenCount = this.tensorLength(inputs.input_ids); + + // Streaming via TextStreamer if requested + let streamer: unknown = undefined; + if (options.onToken) { + const transformers = this.transformers as TransformersModule; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const TextStreamer = (transformers as any).TextStreamer; + streamer = new TextStreamer(this.processor.tokenizer, { + skip_prompt: true, + skip_special_tokens: true, + callback_function: (text: string) => { + options.onToken!(text); + }, + }); + } + + const generated = await this.model.generate({ + ...inputs, + max_new_tokens: options.maxTokens ?? 1024, + temperature: options.temperature ?? 0.7, + do_sample: (options.temperature ?? 0.7) > 0, + streamer, + }); + + // `generated` is a tensor with shape [batch, seq_len_with_prompt]. + // We slice off the prompt portion to get just the new tokens. + const fullSequence = this.tensorRow(generated, 0); + const newTokens = fullSequence.slice(promptTokenCount); + const completionTokenCount = newTokens.length; + + const content: string = this.processor.tokenizer.decode(newTokens, { + skip_special_tokens: true, }); - const choice = response.choices[0]; return { - content: choice.message.content ?? '', + content, usage: { - prompt_tokens: response.usage?.prompt_tokens ?? 0, - completion_tokens: response.usage?.completion_tokens ?? 0, - total_tokens: response.usage?.total_tokens ?? 0, + prompt_tokens: promptTokenCount, + completion_tokens: completionTokenCount, + total_tokens: promptTokenCount + completionTokenCount, }, latencyMs: Math.round(performance.now() - start), }; } - private async _generateStreaming( - options: GenerateOptions, - start: number - ): Promise { - const chunks: string[] = []; - let usage = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }; + /** + * Helper: extract the seq-length of a transformers.js Tensor. + * The tensors expose `.dims` ([batch, seq_len]) and `.data` (TypedArray). + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private tensorLength(tensor: any): number { + if (!tensor || !tensor.dims) return 0; + return tensor.dims[tensor.dims.length - 1]; + } - const stream = await this.engine!.chat.completions.create({ - messages: options.messages, - temperature: options.temperature ?? 0.7, - max_tokens: options.maxTokens ?? 1024, - stream: true, - stream_options: { include_usage: true }, - }); - - for await (const chunk of stream) { - const delta = chunk.choices[0]?.delta?.content; - if (delta) { - chunks.push(delta); - options.onToken!(delta); - } - if (chunk.usage) { - usage = { - prompt_tokens: chunk.usage.prompt_tokens, - completion_tokens: chunk.usage.completion_tokens, - total_tokens: chunk.usage.total_tokens, - }; - } - } - - return { - content: chunks.join(''), - usage, - latencyMs: Math.round(performance.now() - start), - }; + /** + * Helper: extract row N of a 2D tensor as a number array. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private tensorRow(tensor: any, row: number): number[] { + const seqLen = tensor.dims[tensor.dims.length - 1]; + const start = row * seqLen; + return Array.from(tensor.data.slice(start, start + seqLen)) as number[]; } /** diff --git a/packages/local-llm/src/generate.ts b/packages/local-llm/src/generate.ts deleted file mode 100644 index 2ac7a0112..000000000 --- a/packages/local-llm/src/generate.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { getEngine } from './engine.js'; - -export interface ChatMessage { - role: 'system' | 'user' | 'assistant'; - content: string; -} - -export interface GenerateOptions { - messages: ChatMessage[]; - temperature?: number; - maxTokens?: number; - onToken?: (token: string) => void; -} - -export interface GenerateResult { - content: string; - latencyMs: number; - usage: { - prompt_tokens: number; - completion_tokens: number; - }; -} - -export async function generate(options: GenerateOptions): Promise { - const engine = getEngine(); - if (!engine) throw new Error('No model loaded. Call loadLocalLlm() first.'); - - const { messages, temperature = 0.7, maxTokens = 1024, onToken } = options; - const start = performance.now(); - - const reply = await engine.chat.completions.create({ - messages, - temperature, - max_tokens: maxTokens, - stream: !!onToken, - stream_options: onToken ? { include_usage: true } : undefined, - }); - - let content = ''; - let promptTokens = 0; - let completionTokens = 0; - - if (Symbol.asyncIterator in Object(reply)) { - for await (const chunk of reply as AsyncIterable) { - const delta = chunk.choices?.[0]?.delta?.content; - if (delta) { - content += delta; - onToken?.(delta); - } - if (chunk.usage) { - promptTokens = chunk.usage.prompt_tokens ?? 0; - completionTokens = chunk.usage.completion_tokens ?? 0; - } - } - } else { - const completion = reply as any; - content = completion.choices?.[0]?.message?.content ?? ''; - promptTokens = completion.usage?.prompt_tokens ?? 0; - completionTokens = completion.usage?.completion_tokens ?? 0; - } - - const latencyMs = Math.round(performance.now() - start); - - return { - content, - latencyMs, - usage: { prompt_tokens: promptTokens, completion_tokens: completionTokens }, - }; -} - -export async function extractJson(text: string, instruction: string): Promise { - const result = await generate({ - messages: [ - { - role: 'system', - content: - 'You are a JSON extraction assistant. Respond ONLY with valid JSON, no explanation or markdown.', - }, - { - role: 'user', - content: `${instruction}\n\nText:\n${text}`, - }, - ], - temperature: 0.1, - maxTokens: 2048, - }); - - const jsonMatch = result.content.match(/[[{][\s\S]*[}\]]/); - if (!jsonMatch) throw new Error('No JSON found in response'); - return JSON.parse(jsonMatch[0]); -} - -export async function classify(text: string, categories: string[]): Promise { - const result = await generate({ - messages: [ - { - role: 'system', - content: `You are a text classifier. Classify the text into exactly one of these categories: ${categories.join(', ')}. Respond with ONLY the category name, nothing else.`, - }, - { - role: 'user', - content: text, - }, - ], - temperature: 0.1, - maxTokens: 50, - }); - - const response = result.content.trim().toLowerCase(); - const match = categories.find((c) => response.includes(c.toLowerCase())); - return match ?? result.content.trim(); -} diff --git a/packages/local-llm/src/models.ts b/packages/local-llm/src/models.ts index 200219a1a..bf8ab1c69 100644 --- a/packages/local-llm/src/models.ts +++ b/packages/local-llm/src/models.ts @@ -2,40 +2,29 @@ import type { ModelConfig } from './types'; /** * Pre-configured models for client-side inference. - * All models are quantized for browser use via WebLLM/MLC. + * + * All models are ONNX builds loaded via @huggingface/transformers (transformers.js) + * with the WebGPU backend. The default is Google's Gemma 4 E2B — the smallest + * member of the Gemma 4 family released 2026-04-02. E2B stands for "Effective 2B" + * and is multimodal (text + image + audio) at the model level, but our chat-only + * code path only ever passes text. + * + * Adding a new model: pick a HuggingFace ONNX repo (look on huggingface.co/onnx-community + * for community-converted models, or huggingface.co/{org}/{repo}-ONNX for first-party + * builds), confirm it has a `q4f16` quantization in its `onnx/` directory, and add an + * entry below. The /llm-test page picks up new entries automatically. */ export const MODELS = { - /** Default model — fast, good at structured output, multilingual */ - 'qwen-2.5-1.5b': { - modelId: 'Qwen2.5-1.5B-Instruct-q4f16_1-MLC', - displayName: 'Qwen 2.5 1.5B', - downloadSizeMb: 1000, - ramUsageMb: 1800, - }, - /** Smaller variant for low-end devices */ - 'qwen-2.5-0.5b': { - modelId: 'Qwen2.5-0.5B-Instruct-q4f16_1-MLC', - displayName: 'Qwen 2.5 0.5B', - downloadSizeMb: 400, - ramUsageMb: 800, - }, - /** Google Gemma 2 — strong general-purpose model, similar size class to Qwen 1.5B */ - 'gemma-2-2b': { - modelId: 'gemma-2-2b-it-q4f16_1-MLC', - displayName: 'Gemma 2 2B', - downloadSizeMb: 1400, - ramUsageMb: 2200, - }, - /** Google Gemma 2 9B — much higher quality, needs a beefy GPU (~6GB VRAM) */ - 'gemma-2-9b': { - modelId: 'gemma-2-9b-it-q4f16_1-MLC', - displayName: 'Gemma 2 9B', - downloadSizeMb: 5300, - ramUsageMb: 6500, + 'gemma-4-e2b': { + modelId: 'onnx-community/gemma-4-E2B-it-ONNX', + displayName: 'Gemma 4 E2B', + dtype: 'q4f16', + downloadSizeMb: 500, + ramUsageMb: 1500, }, } as const satisfies Record; export type ModelKey = keyof typeof MODELS; -export const DEFAULT_MODEL: ModelKey = 'qwen-2.5-1.5b'; +export const DEFAULT_MODEL: ModelKey = 'gemma-4-e2b'; diff --git a/packages/local-llm/src/status.svelte.ts b/packages/local-llm/src/status.svelte.ts deleted file mode 100644 index b37057d7f..000000000 --- a/packages/local-llm/src/status.svelte.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { subscribe, type LlmState } from './engine.js'; - -/** - * Reactive status wrapper for use in Svelte 5 components. - * Returns an object with a `current` property that updates reactively. - */ -export function getLocalLlmStatus(): { current: LlmState } { - let state = $state({ state: 'idle' }); - - $effect(() => { - const unsub = subscribe((s) => { - state = s; - }); - return unsub; - }); - - return { - get current() { - return state; - }, - }; -} diff --git a/packages/local-llm/src/types.ts b/packages/local-llm/src/types.ts index 1ac245ed5..e9b0c728b 100644 --- a/packages/local-llm/src/types.ts +++ b/packages/local-llm/src/types.ts @@ -33,10 +33,19 @@ export interface GenerateResult { } export interface ModelConfig { - /** WebLLM model identifier */ + /** HuggingFace ONNX repo id, e.g. "onnx-community/gemma-4-E2B-it-ONNX" */ modelId: string; /** Human-readable name */ displayName: string; + /** + * Quantization the transformers.js loader should request. Common values: + * - "fp32" — full precision, biggest, only for tiny models + * - "fp16" — half precision, ~50% smaller than fp32 + * - "q8" — 8-bit weights, fp32 activations + * - "q4" — 4-bit weights, fp32 activations + * - "q4f16" — 4-bit weights, fp16 activations (recommended for WebGPU) + */ + dtype: 'fp32' | 'fp16' | 'q8' | 'q4' | 'q4f16'; /** Approximate download size in MB */ downloadSizeMb: number; /** Approximate VRAM/RAM usage in MB */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8ebe5073..b0be3bea5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -933,6 +933,9 @@ importers: '@calc/shared': specifier: workspace:* version: link:../../../calc/packages/shared + '@huggingface/transformers': + specifier: ^4.0.0 + version: 4.0.1 '@mana/credits': specifier: workspace:^ version: link:../../../../packages/credits @@ -1011,9 +1014,6 @@ importers: '@mana/wallpaper-generator': specifier: workspace:* version: link:../../../../packages/wallpaper-generator - '@mlc-ai/web-llm': - specifier: ^0.2.78 - version: 0.2.82 '@types/suncalc': specifier: ^1.9.2 version: 1.9.2 @@ -2680,9 +2680,9 @@ importers: packages/local-llm: dependencies: - '@mlc-ai/web-llm': - specifier: ^0.2.78 - version: 0.2.82 + '@huggingface/transformers': + specifier: ^4.0.0 + version: 4.0.1 devDependencies: '@types/node': specifier: ^24.10.1 @@ -3333,6 +3333,9 @@ importers: services/mana-analytics: dependencies: + '@mana/shared-hono': + specifier: workspace:* + version: link:../../packages/shared-hono drizzle-orm: specifier: ^0.38.3 version: 0.38.4(@opentelemetry/api@1.9.1)(@types/pg@8.6.1)(@types/react@19.2.14)(bun-types@1.3.11)(kysely@0.28.15)(postgres@3.4.9)(react@19.2.0) @@ -3360,6 +3363,9 @@ importers: services/mana-auth: dependencies: + '@mana/shared-hono': + specifier: workspace:* + version: link:../../packages/shared-hono bcryptjs: specifier: ^3.0.2 version: 3.0.3 @@ -3399,6 +3405,9 @@ importers: services/mana-credits: dependencies: + '@mana/shared-hono': + specifier: workspace:* + version: link:../../packages/shared-hono bcryptjs: specifier: ^3.0.2 version: 3.0.3 @@ -3574,6 +3583,9 @@ importers: services/mana-subscriptions: dependencies: + '@mana/shared-hono': + specifier: workspace:* + version: link:../../packages/shared-hono drizzle-orm: specifier: ^0.38.3 version: 0.38.4(@opentelemetry/api@1.9.1)(@types/pg@8.6.1)(@types/react@19.2.14)(bun-types@1.3.11)(kysely@0.28.15)(postgres@3.4.9)(react@19.2.0) @@ -3604,6 +3616,9 @@ importers: services/mana-user: dependencies: + '@mana/shared-hono': + specifier: workspace:* + version: link:../../packages/shared-hono drizzle-orm: specifier: ^0.38.3 version: 0.38.4(@opentelemetry/api@1.9.1)(@types/pg@8.6.1)(@types/react@19.2.14)(bun-types@1.3.11)(kysely@0.28.15)(postgres@3.4.9)(react@19.2.0) @@ -6222,6 +6237,16 @@ packages: '@modelcontextprotocol/sdk': optional: true + '@huggingface/jinja@0.5.6': + resolution: {integrity: sha512-MyMWyLnjqo+KRJYSH7oWNbsOn5onuIvfXYPcc0WOGxU0eHUV7oAYUoQTl2BMdu7ml+ea/bu11UM+EshbeHwtIA==} + engines: {node: '>=18'} + + '@huggingface/tokenizers@0.1.3': + resolution: {integrity: sha512-8rF/RRT10u+kn7YuUbUg0OF30K8rjTc78aHpxT+qJ1uWSqxT1MHi8+9ltwYfkFYJzT/oS+qw3JVfHtNMGAdqyA==} + + '@huggingface/transformers@4.0.1': + resolution: {integrity: sha512-tAQYEy+cnW0ku/NxBSjFXCymi+DZa1/JkoGf4McxjzO36CZZIL/J4TF6X7i/tzs75yTjshUDgsvSz03s2xym2A==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -6640,9 +6665,6 @@ packages: '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} - '@mlc-ai/web-llm@0.2.82': - resolution: {integrity: sha512-ONhW+28PPVSUI1m0RkJcm7suwc47b65i5b/rTEIADq5I22p1+9uf/CBbDPRkkjj1WJB9s8oFp0ywAW0NY1G6fg==} - '@mozilla/readability@0.5.0': resolution: {integrity: sha512-Z+CZ3QaosfFaTqvhQsIktyGrjFjSC0Fa4EMph4mqKnWhmyoGICsV/8QK+8HpXut6zV7zwfWwqDmEjtk1Qf6EgQ==} engines: {node: '>=14.0.0'} @@ -9221,6 +9243,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + adm-zip@0.5.17: + resolution: {integrity: sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==} + engines: {node: '>=12.0'} + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -9711,6 +9737,10 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + bowser@2.14.1: resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} @@ -10442,6 +10472,9 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + deterministic-object-hash@2.0.2: resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} engines: {node: '>=18'} @@ -11059,6 +11092,9 @@ packages: resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} engines: {node: '>=0.10'} + es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + es6-iterator@2.0.3: resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} @@ -12267,6 +12303,9 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} + flatbuffers@25.9.23: + resolution: {integrity: sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==} + flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} @@ -12490,6 +12529,10 @@ packages: engines: {node: '>=16 || 14 >=14.17'} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + global-agent@3.0.0: + resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + engines: {node: '>=10.0'} + globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} @@ -12542,6 +12585,9 @@ packages: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} + guid-typescript@1.0.9: + resolution: {integrity: sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==} + h3@1.15.11: resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} @@ -13366,6 +13412,9 @@ packages: resolution: {integrity: sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==} engines: {node: '>= 0.4'} + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -13691,10 +13740,6 @@ packages: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} - loglevel@1.9.2: - resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} - engines: {node: '>= 0.6.0'} - long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} @@ -13786,6 +13831,10 @@ packages: marky@1.3.0: resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -14483,6 +14532,19 @@ packages: oniguruma-to-es@4.3.5: resolution: {integrity: sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==} + onnxruntime-common@1.24.0-dev.20251116-b39e144322: + resolution: {integrity: sha512-BOoomdHYmNRL5r4iQ4bMvsl2t0/hzVQ3OM3PHD0gxeXu1PmggqBv3puZicEUVOA3AtHHYmqZtjMj9FOfGrATTw==} + + onnxruntime-common@1.24.3: + resolution: {integrity: sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA==} + + onnxruntime-node@1.24.3: + resolution: {integrity: sha512-JH7+czbc8ALA819vlTgcV+Q214/+VjGeBHDjX81+ZCD0PCVCIFGFNtT0V4sXG/1JXypKPgScQcB3ij/hk3YnTg==} + os: [win32, darwin, linux] + + onnxruntime-web@1.25.0-dev.20260327-722743c0e2: + resolution: {integrity: sha512-8PXdZy4Ekhg10CLg+cFFt39b4tFDGMRJB6lGjnQL6eA+2boUQYDymZ0gtxiS+H6oIWoCjQp/ziyirvFbaFKfiw==} + open@7.4.2: resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} engines: {node: '>=8'} @@ -14754,6 +14816,9 @@ packages: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} + platform@1.3.6: + resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} + playwright-core@1.59.1: resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==} engines: {node: '>=18'} @@ -15732,6 +15797,10 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} + rollup@2.80.0: resolution: {integrity: sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==} engines: {node: '>=10.0.0'} @@ -15836,6 +15905,9 @@ packages: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -15868,6 +15940,10 @@ packages: resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==} engines: {node: '>=0.10.0'} + serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} @@ -16058,6 +16134,9 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} @@ -16628,6 +16707,10 @@ packages: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} + type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + type-fest@0.16.0: resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} engines: {node: '>=10'} @@ -21353,6 +21436,18 @@ snapshots: - supports-color - utf-8-validate + '@huggingface/jinja@0.5.6': {} + + '@huggingface/tokenizers@0.1.3': {} + + '@huggingface/transformers@4.0.1': + dependencies: + '@huggingface/jinja': 0.5.6 + '@huggingface/tokenizers': 0.1.3 + onnxruntime-node: 1.24.3 + onnxruntime-web: 1.25.0-dev.20260327-722743c0e2 + sharp: 0.34.5 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -21850,10 +21945,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@mlc-ai/web-llm@0.2.82': - dependencies: - loglevel: 1.9.2 - '@mozilla/readability@0.5.0': {} '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': @@ -26002,6 +26093,8 @@ snapshots: acorn@8.16.0: {} + adm-zip@0.5.17: {} + agent-base@7.1.4: {} agentkeepalive@4.6.0: @@ -26885,6 +26978,8 @@ snapshots: boolbase@1.0.0: {} + boolean@3.2.0: {} + bowser@2.14.1: {} boxen@8.0.1: @@ -27634,6 +27729,8 @@ snapshots: detect-node-es@1.1.0: {} + detect-node@2.1.0: {} + deterministic-object-hash@2.0.2: dependencies: base-64: 1.0.0 @@ -27973,6 +28070,8 @@ snapshots: esniff: 2.0.1 next-tick: 1.1.0 + es6-error@4.1.1: {} + es6-iterator@2.0.3: dependencies: d: 1.0.2 @@ -30635,6 +30734,8 @@ snapshots: flatted: 3.4.2 keyv: 4.5.4 + flatbuffers@25.9.23: {} + flatted@3.4.2: {} flattie@1.1.1: {} @@ -30888,6 +30989,15 @@ snapshots: minipass: 4.2.8 path-scurry: 1.11.1 + global-agent@3.0.0: + dependencies: + boolean: 3.2.0 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.7.4 + serialize-error: 7.0.1 + globals@13.24.0: dependencies: type-fest: 0.20.2 @@ -30942,6 +31052,8 @@ snapshots: section-matter: 1.0.0 strip-bom-string: 1.0.0 + guid-typescript@1.0.9: {} + h3@1.15.11: dependencies: cookie-es: 1.2.3 @@ -32170,6 +32282,8 @@ snapshots: jsonify: 0.0.1 object-keys: 1.1.1 + json-stringify-safe@5.0.1: {} + json5@1.0.2: dependencies: minimist: 1.2.8 @@ -32449,8 +32563,6 @@ snapshots: strip-ansi: 7.2.0 wrap-ansi: 9.0.2 - loglevel@1.9.2: {} - long@5.3.2: {} longest-streak@3.1.0: {} @@ -32528,6 +32640,10 @@ snapshots: marky@1.3.0: {} + matcher@3.0.0: + dependencies: + escape-string-regexp: 4.0.0 + math-intrinsics@1.1.0: {} mdast-util-definitions@6.0.0: @@ -33786,6 +33902,25 @@ snapshots: regex: 6.1.0 regex-recursion: 6.0.2 + onnxruntime-common@1.24.0-dev.20251116-b39e144322: {} + + onnxruntime-common@1.24.3: {} + + onnxruntime-node@1.24.3: + dependencies: + adm-zip: 0.5.17 + global-agent: 3.0.0 + onnxruntime-common: 1.24.3 + + onnxruntime-web@1.25.0-dev.20260327-722743c0e2: + dependencies: + flatbuffers: 25.9.23 + guid-typescript: 1.0.9 + long: 5.3.2 + onnxruntime-common: 1.24.0-dev.20251116-b39e144322 + platform: 1.3.6 + protobufjs: 7.5.4 + open@7.4.2: dependencies: is-docker: 2.2.1 @@ -34094,6 +34229,8 @@ snapshots: dependencies: find-up: 3.0.0 + platform@1.3.6: {} + playwright-core@1.59.1: {} playwright@1.59.1: @@ -35557,6 +35694,15 @@ snapshots: dependencies: glob: 7.2.3 + roarr@2.15.4: + dependencies: + boolean: 3.2.0 + detect-node: 2.1.0 + globalthis: 1.0.4 + json-stringify-safe: 5.0.1 + semver-compare: 1.0.0 + sprintf-js: 1.1.3 + rollup@2.80.0: optionalDependencies: fsevents: 2.3.3 @@ -35690,6 +35836,8 @@ snapshots: extend-shallow: 2.0.1 kind-of: 6.0.3 + semver-compare@1.0.0: {} + semver@6.3.1: {} semver@7.6.3: {} @@ -35720,6 +35868,10 @@ snapshots: serialize-error@2.1.0: {} + serialize-error@7.0.1: + dependencies: + type-fest: 0.13.1 + serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 @@ -35974,6 +36126,8 @@ snapshots: sprintf-js@1.0.3: {} + sprintf-js@1.1.3: {} + stable-hash@0.0.5: {} stack-utils@2.0.6: @@ -36604,6 +36758,8 @@ snapshots: type-detect@4.0.8: {} + type-fest@0.13.1: {} + type-fest@0.16.0: {} type-fest@0.20.2: {}