import type { Plugin } from 'vite' // Dev-only in-memory mock of the /api surface the Fallback page needs, so the // frontend can be exercised without a running backend. Enabled only when the // dev server is started with MOCK_API=1 (see vite.config.ts) — never bundled. const PRESETS: Record = { balanced: { reliability: 0.5, speed: 0.25, intelligence: 0.25 }, smartest: { reliability: 0.35, speed: 0.1, intelligence: 0.55 }, fastest: { reliability: 0.35, speed: 0.55, intelligence: 0.1 }, reliable: { reliability: 0.7, speed: 0.15, intelligence: 0.15 }, } interface MockModel { modelDbId: number platform: string modelId: string displayName: string sizeLabel: string intelligenceRank: number speedRank: number monthlyTokenBudget: string supportsVision: boolean rpmLimit: number | null rpdLimit: number | null enabled: boolean priority: number reliability: number speed: number intelligence: number headroom: number rateLimit: number totalRequests: number } let strategy = 'balanced' // User-tuned vector for the 'custom' strategy (kept normalized, like the server). let customWeights = { ...PRESETS.balanced } function activeWeights() { if (strategy === 'custom') return customWeights return PRESETS[strategy] ?? PRESETS.balanced } const models: MockModel[] = [ { modelDbId: 1, platform: 'google', modelId: 'gemini-2.5-pro', displayName: 'Gemini 2.5 Pro', sizeLabel: 'Frontier', intelligenceRank: 1, speedRank: 8, monthlyTokenBudget: '~12M', supportsVision: true, rpmLimit: 5, rpdLimit: 100, enabled: true, priority: 1, reliability: 0.94, speed: 0.55, intelligence: 1.0, headroom: 1, rateLimit: 1, totalRequests: 412 }, { modelDbId: 2, platform: 'groq', modelId: 'openai/gpt-oss-120b', displayName: 'GPT-OSS 120B', sizeLabel: 'Frontier', intelligenceRank: 6, speedRank: 2, monthlyTokenBudget: '~6M', supportsVision: false, rpmLimit: 30, rpdLimit: 1000, enabled: true, priority: 2, reliability: 0.9, speed: 0.98, intelligence: 0.72, headroom: 1, rateLimit: 0.7, totalRequests: 833 }, { modelDbId: 3, platform: 'cerebras', modelId: 'llama-3.3-70b', displayName: 'Llama 3.3 70B', sizeLabel: 'Large', intelligenceRank: 4, speedRank: 1, monthlyTokenBudget: '~50M', supportsVision: false, rpmLimit: 30, rpdLimit: 14400, enabled: true, priority: 3, reliability: 0.88, speed: 1.0, intelligence: 0.5, headroom: 1, rateLimit: 1, totalRequests: 256 }, { modelDbId: 4, platform: 'google', modelId: 'gemini-2.5-flash', displayName: 'Gemini 2.5 Flash', sizeLabel: 'Large', intelligenceRank: 4, speedRank: 5, monthlyTokenBudget: '~3M', supportsVision: true, rpmLimit: 10, rpdLimit: 20, enabled: true, priority: 4, reliability: 0.97, speed: 0.78, intelligence: 0.52, headroom: 0.45, rateLimit: 1, totalRequests: 90 }, { modelDbId: 5, platform: 'nvidia', modelId: 'llama-4-scout', displayName: 'Llama 4 Scout', sizeLabel: 'Medium', intelligenceRank: 7, speedRank: 4, monthlyTokenBudget: '~30M', supportsVision: true, rpmLimit: 40, rpdLimit: null, enabled: true, priority: 5, reliability: 0.7, speed: 0.83, intelligence: 0.33, headroom: 1, rateLimit: 1, totalRequests: 47 }, { modelDbId: 6, platform: 'openrouter', modelId: 'deepseek/deepseek-v3.1:free', displayName: 'DeepSeek V3.1', sizeLabel: 'Frontier', intelligenceRank: 2, speedRank: 10, monthlyTokenBudget: '~6M', supportsVision: false, rpmLimit: 20, rpdLimit: 200, enabled: false, priority: 6, reliability: 0.5, speed: 0.4, intelligence: 0.67, headroom: 1, rateLimit: 1, totalRequests: 0 }, { modelDbId: 7, platform: 'mistral', modelId: 'mistral-large', displayName: 'Mistral Large', sizeLabel: 'Large', intelligenceRank: 5, speedRank: 6, monthlyTokenBudget: '~15M', supportsVision: false, rpmLimit: 60, rpdLimit: 500, enabled: true, priority: 7, reliability: 0.82, speed: 0.6, intelligence: 0.45, headroom: 1, rateLimit: 1, totalRequests: 178 }, ] function budgetTokens(label: string): number { const m = label.match(/~?([\d.]+)(?:-([\d.]+))?([MK])?/) if (!m) return 0 const n = parseFloat(m[2] ?? m[1]) const unit = m[3] === 'M' ? 1_000_000 : m[3] === 'K' ? 1_000 : 1 return n * unit } function score(m: MockModel): number { const w = activeWeights() const base = w.reliability * m.reliability + w.speed * m.speed + w.intelligence * m.intelligence return base * m.headroom * m.rateLimit } function readBody(req: any): Promise { return new Promise(resolve => { let data = '' req.on('data', (c: any) => { data += c }) req.on('end', () => { try { resolve(JSON.parse(data || '{}')) } catch { resolve({}) } }) }) } export function mockApiPlugin(): Plugin { return { name: 'freellmapi-dev-mock', configureServer(server) { server.middlewares.use(async (req: any, res: any, next: any) => { const url = (req.url || '').split('?')[0] if (!url.startsWith('/api/')) return next() const send = (obj: any, status = 200) => { res.statusCode = status res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify(obj)) } // Auth gate — always "logged in" in mock mode. if (url === '/api/auth/status') return send({ needsSetup: false, authenticated: true, email: 'dev@local' }) if (url === '/api/ping') return send({ status: 'ok', timestamp: new Date().toISOString() }) if (url === '/api/fallback' && req.method === 'GET') { const rows = [...models].sort((a, b) => a.priority - b.priority).map(m => ({ modelDbId: m.modelDbId, priority: m.priority, effectivePriority: m.priority, penalty: m.rateLimit < 1 ? Math.round((1 - m.rateLimit) * 10) : 0, rateLimitHits: m.rateLimit < 1 ? 3 : 0, enabled: m.enabled, platform: m.platform, modelId: m.modelId, displayName: m.displayName, intelligenceRank: m.intelligenceRank, speedRank: m.speedRank, sizeLabel: m.sizeLabel, rpmLimit: m.rpmLimit, rpdLimit: m.rpdLimit, monthlyTokenBudget: m.monthlyTokenBudget, supportsVision: m.supportsVision, keyCount: 3, })) return send(rows) } if (url === '/api/fallback' && req.method === 'PUT') { const body = await readBody(req) for (const e of body as any[]) { const m = models.find(x => x.modelDbId === e.modelDbId) if (m) { m.priority = e.priority; m.enabled = e.enabled } } return send({ success: true }) } if (url === '/api/fallback/routing' && req.method === 'GET') { const scores = [...models] .map(m => ({ modelDbId: m.modelDbId, platform: m.platform, modelId: m.modelId, displayName: m.displayName, enabled: m.enabled, reliability: m.reliability, speed: m.speed, intelligence: m.intelligence, headroom: m.headroom, rateLimit: m.rateLimit, score: score(m), totalRequests: m.totalRequests, })) .sort((a, b) => b.score - a.score) return send({ strategy, weights: strategy === 'priority' ? null : activeWeights(), customWeights, scores }) } if (url === '/api/fallback/routing' && req.method === 'PUT') { const body = await readBody(req) if (typeof body.strategy === 'string') strategy = body.strategy if (body.weights && typeof body.weights === 'object') { const { reliability = 0, speed = 0, intelligence = 0 } = body.weights const sum = reliability + speed + intelligence if (sum > 0) { customWeights = { reliability: reliability / sum, speed: speed / sum, intelligence: intelligence / sum } } } return send({ strategy, presets: PRESETS, customWeights }) } if (url === '/api/fallback/token-usage') { const withKeys = models const modelBudgets = withKeys.map(m => ({ displayName: m.displayName, platform: m.platform, budget: budgetTokens(m.monthlyTokenBudget) })) const totalBudget = modelBudgets.reduce((s, m) => s + m.budget, 0) return send({ totalBudget, totalUsed: Math.round(totalBudget * 0.18), models: modelBudgets }) } // Anything else under /api → empty 200 so pages don't hard-crash. return send({}) }) }, } }