// Mock Ollama for the chat page: OpenAI-style streaming with markdown, a code block, a thinking field, // plus a "hang" model that never finishes (to test the Stop button / cancellation). import http from 'node:http'; let cancelled = 0; http.createServer((req, res) => { let b = ''; req.on('data', d => b += d); req.on('end', () => { const j = b ? JSON.parse(b) : {}; const json = o => { res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(o)); }; if (req.url === '/api/version') return json({ version: 'mock' }); if (req.url === '/api/tags') return json({ models: [{ name: 'qwen3:14b', size: 9.3e9 }, { name: 'slow:latest', size: 1e9 }, { name: 'lmthink:latest', size: 5e8 }] }); if (req.url === '/api/ps') return json({ models: [{ name: 'qwen3:14b', size: 9.3e9, expires_at: 'x' }] }); if (req.url === '/cancelled') return json({ cancelled }); if (req.url === '/v1/chat/completions') { res.setHeader('Content-Type', 'text/event-stream'); req.on('close', () => { if (j.model === 'slow:latest') cancelled++; }); if (j.model === 'lmthink:latest') { // LM Studio style: only reasoning_content, then the token limit hits let k = 0; const words = ['Okay,', ' the', ' user', ' wants', ' a', ' greeting.', ' Let', ' me', ' think']; const tt = setInterval(() => { if (res.destroyed) return clearInterval(tt); if (k < words.length) res.write('data: ' + JSON.stringify({ choices: [{ index: 0, delta: { reasoning_content: words[k++] }, finish_reason: null }] }) + '\n\n'); else { res.write('data: ' + JSON.stringify({ choices: [{ index: 0, delta: {}, finish_reason: 'length' }] }) + '\n\n'); res.write('data: ' + JSON.stringify({ choices: [], usage: { prompt_tokens: 10, completion_tokens: 9 } }) + '\n\n'); res.write('data: [DONE]\n\n'); res.end(); clearInterval(tt); } }, 60); return; } const parts = j.model === 'slow:latest' ? Array.from({ length: 200 }, (_, i) => 'word' + i + ' ') : [{ r: 'Let me think… ' }, { r: 'the user greets me. ' }, '# Hello!\n\n', 'Here is **bold** and a list:\n\n', '- one\n- two\n\n', '```python\n', 'print("hi")\n', '```\n\n', 'Done ok.']; let i = 0; const t = setInterval(() => { if (res.destroyed) return clearInterval(t); if (i < parts.length) { const p = parts[i++]; const delta = typeof p === 'string' ? { content: p } : { reasoning: p.r }; res.write('data: ' + JSON.stringify({ choices: [{ delta }] }) + '\n\n'); } else { res.write('data: ' + JSON.stringify({ choices: [], usage: { prompt_tokens: 12, completion_tokens: 40, total_tokens: 52 } }) + '\n\n'); res.write('data: [DONE]\n\n'); res.end(); clearInterval(t); } }, j.model === 'slow:latest' ? 100 : 120); return; } res.statusCode = 404; res.end('nope'); }); }).listen(11434, '127.0.0.1');