fixtures.ts
Language TypeScript9.0 KB205 lines
// Invented sample data for the browser build: tests, screenshots, `npm run dev`.// Prices are DeepInfra's public list prices (per million tokens) as of the// bundled snapshot; the conversations and people are made up.import type { ModelInfo } from '../gen/ModelInfo'import type { Provider } from '../gen/Provider'const m = (provider_id: string,id: string,input: number,output: number,context: number | null,tags: string[],cache: number | null = null,): ModelInfo => ({provider_id,id,prices: { input_per_m: input, output_per_m: output, cache_read_per_m: cache },context_length: context,max_tokens: null,tags,})export const providers: Provider[] = [{id: 'deepinfra',kind: 'deepinfra',name: 'DeepInfra',base_url: 'https://api.deepinfra.com/v1/openai',requires_key: true,},{id: 'ollama',kind: 'openai_compatible',name: 'Ollama',base_url: 'http://localhost:11434/v1',requires_key: false,},]export const models: Record<string, ModelInfo[]> = {deepinfra: [m('deepinfra', 'deepseek-ai/DeepSeek-V3.1', 0.25, 0.95, 163840, ['chat', 'prompt_cache', 'reasoning'], 0.13),m('deepinfra', 'deepseek-ai/DeepSeek-V3.2', 0.26, 0.38, 163840, ['chat', 'prompt_cache'], 0.13),m('deepinfra', 'google/gemma-3-12b-it', 0.05, 0.15, 131072, ['chat', 'vlm', 'vision']),m('deepinfra', 'google/gemma-3-27b-it', 0.08, 0.16, 131072, ['chat', 'vlm', 'vision']),m('deepinfra', 'mistralai/Mistral-Small-3.2-24B-Instruct-2506', 0.075, 0.2, 128000, ['chat', 'vlm', 'vision']),m('deepinfra','moonshotai/Kimi-K2.6',0.75,3.5,262144,['chat', 'vlm', 'vision', 'prompt_cache', 'reasoning'],0.15,),m('deepinfra', 'Qwen/Qwen3-235B-A22B-Instruct-2507', 0.09, 0.55, 262144, ['chat']),m('deepinfra', 'Qwen/Qwen3-VL-30B-A3B-Instruct', 0.15, 0.6, 262144, ['chat', 'vlm', 'vision']),m('deepinfra','zai-org/GLM-5.3-Flash',0.15,0.5,1048576,['chat', 'vlm', 'vision', 'prompt_cache', 'reasoning'],0.03,),],ollama: [m('ollama', 'llama3.2', 0, 0, null, ['chat', 'local']),m('ollama', 'qwen3:8b', 0, 0, null, ['chat', 'local']),],}const HOUR = 3_600_000const DAY = 24 * HOURexport interface FixtureTurn {user: stringassistant: stringreasoning?: stringprompt: numbercompletion: numberestimated?: booleanimage?: { filename: string; width: number; height: number }}export interface FixtureChat {title: stringprovider: stringmodel: stringsystem: stringageMs: numberpinned?: booleanturns: FixtureTurn[]}export const chats: FixtureChat[] = [{title: 'Tidy a bash backup script',provider: 'deepinfra',model: 'Qwen/Qwen3-235B-A22B-Instruct-2507',system: 'You are a careful shell reviewer. Prefer small, safe changes.',ageMs: 2 * HOUR,pinned: true,turns: [{user: 'Can you tidy this up? It copies my notes folder to an external drive every night.\n\ncp -r ~/notes /Volumes/Backup/notes-$(date +%F)',assistant:'Three changes make it safer and quieter:\n\n1. **Quote every path**, so a space in a folder name cannot split it.\n2. **Stop early** when the drive is not mounted, instead of creating a folder on your internal disk.\n3. **Use `rsync`**, which copies only what changed and keeps timestamps.\n\n```bash\n#!/usr/bin/env bash\nset -euo pipefail\n\nsrc="$HOME/notes/"\ndest="/Volumes/Backup/notes"\n\nif [[ ! -d /Volumes/Backup ]]; then\n echo "Backup drive not mounted; skipping." >&2\n exit 0\nfi\n\nrsync -a --delete "$src" "$dest/"\n```\n\n`--delete` keeps the copy an exact mirror. Leave it off if you want deleted notes to survive on the drive.',prompt: 318,completion: 196,},{user: 'Keep a dated copy each week as well?',assistant:'Add a weekly snapshot with hard links, so unchanged files take no extra space:\n\n```bash\nweek=$(date +%G-W%V)\nrsync -a --link-dest=\"$dest\" \"$src\" \"/Volumes/Backup/weekly/$week/\"\n```\n\nRun it from the same script on Sundays: `[[ $(date +%u) == 7 ]] && ...`.',prompt: 548,completion: 104,},],},{title: 'How FTS5 bm25 ranking works',provider: 'deepinfra',model: 'deepseek-ai/DeepSeek-V3.2',system: '',ageMs: 26 * HOUR,turns: [{user: 'In SQLite FTS5, why does bm25() return negative numbers, and how do I weight the title column higher?',assistant:"FTS5 negates the classic BM25 score so that **better matches sort first with `ORDER BY rank`** (ascending). More negative means more relevant.\n\nTo weight columns, pass one weight per column in table order:\n\n```sql\nSELECT title, bm25(docs, 10.0, 1.0) AS score\nFROM docs\nWHERE docs MATCH 'backup*'\nORDER BY score;\n```\n\nHere a hit in `title` counts ten times as much as one in `body`.",prompt: 412,completion: 1093,},],},{title: 'Compare two pricing tiers',provider: 'deepinfra',model: 'google/gemma-3-27b-it',system: 'Answer with a small table first, then one sentence.',ageMs: 3 * DAY,turns: [{user: 'Which of these plans is cheaper for a team of eight?',image: { filename: 'plans.png', width: 1200, height: 675 },assistant:'| Plan | Per seat | Eight seats |\n|---|---|---|\n| Studio | $12 | $96 |\n| Team | $9 + $20 base | $92 |\n\nTeam is $4 a month cheaper at eight seats, and the gap grows with every seat you add.',prompt: 1308,completion: 88,},],},{title: 'Weekend sourdough schedule',provider: 'ollama',model: 'llama3.2',system: '',ageMs: 9 * DAY,turns: [{user: 'I want bread out of the oven by Sunday noon. When do I feed the starter?',assistant:'Work backwards from noon on Sunday:\n\n- **Saturday 9:00**: feed the starter.\n- **Saturday 14:00**: mix the dough, then four sets of folds over two hours.\n- **Saturday 20:00**: shape, cover, and refrigerate overnight.\n- **Sunday 10:00**: bake straight from the fridge, 45 minutes.',prompt: 240,completion: 131,estimated: true,},],},{title: 'Shorter note about a leaking radiator',provider: 'deepinfra',model: 'moonshotai/Kimi-K2.6',system: "Edit for brevity. Keep the writer's voice. Prefer short sentences.",ageMs: 16 * DAY,turns: [{user: 'Make this two sentences: "Hi, I wanted to let you know that the radiator in the back bedroom has started leaking again, it is the same one that was repaired in March, and there is a small puddle under it most mornings now."',reasoning:'The core facts: the back bedroom radiator, leaking again, repaired in March, a puddle most mornings. Drop the throat-clearing.',assistant:'The radiator in the back bedroom is leaking again, the same one that was repaired in March. There is a small puddle under it most mornings.',prompt: 164,completion: 38,},],},]/** Canned replies for the mock stream, picked by keywords in the prompt. */export function cannedReply(text: string, images: number): string {const t = text.toLowerCase()if (images > 0)return `I can see ${images === 1 ? 'the image' : `${images} images`}. The layout reads left to right, with the totals in the last column.`if (t.includes('sql') || t.includes('index'))return 'Add an index on the column you filter by most:\n\n```sql\nCREATE INDEX messages_created ON messages (created_at);\n```\n\nThen check the plan with `EXPLAIN QUERY PLAN`.'if (t.includes('hello') || t.length < 12) return 'Hello. What are you working on?'return `Here is a short answer.\n\n- It depends on what you need most: speed, cost, or quality.\n- For quick questions, a small model is usually enough and costs a fraction of a cent.\n- For long documents, pick a model with a larger context.\n\nYou asked: "${text.trim().slice(0, 80)}"`}/** A made-up two-plan chart, shown as the image attachment. */export const PLANS_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 675"><rect width="1200" height="675" fill="#f7f5fb"/><text x="60" y="90" font-family="sans-serif" font-size="44" fill="#2a2440">Plans</text><rect x="60" y="150" width="500" height="440" rx="24" fill="#fff" stroke="#d9d2ee" stroke-width="4"/><text x="100" y="230" font-family="sans-serif" font-size="40" fill="#3b2d6e">Studio</text><text x="100" y="320" font-family="sans-serif" font-size="72" fill="#2a2440">$12</text><text x="100" y="370" font-family="sans-serif" font-size="28" fill="#6b6385">per seat, per month</text><rect x="640" y="150" width="500" height="440" rx="24" fill="#fff" stroke="#9fe3d6" stroke-width="4"/><text x="680" y="230" font-family="sans-serif" font-size="40" fill="#0e6f6a">Team</text><text x="680" y="320" font-family="sans-serif" font-size="72" fill="#2a2440">$9</text><text x="680" y="370" font-family="sans-serif" font-size="28" fill="#6b6385">per seat, plus $20 a month</text></svg>`