/** Express app factory for @geepers/chat server mode */ import express from 'express'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import type { ServerConfig } from './types.js'; import { getHtml } from './html.js'; import { streamChat } from './providers.js'; import { proxyToGateway } from './gateway.js'; import { resolveApiKey } from './detect.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export function createServer(config: ServerConfig): express.Express { const app = express(); app.use(express.json()); // Serve the IIFE bundle from the dist/ directory (same dir as cli.js) app.get('/dist/index.iife.global.js', (_req, res) => { const iifePath = path.join(__dirname, 'index.iife.global.js'); res.sendFile(iifePath); }); // Landing page with ChatUI app.get('/', (_req, res) => { res.type('html').send(getHtml(config.providers, config.mode)); }); // Provider list app.get('/providers', (_req, res) => { res.json(config.providers.map(p => ({ name: p.name, label: p.label, model: p.model, }))); }); // Health check app.get('/health', (_req, res) => { res.json({ status: 'ok', mode: config.mode, providers: config.providers.map(p => p.name), }); }); // SSE streaming chat endpoint — matches what SSEChat client expects app.post('/llm/chat', async (req, res) => { const { provider, model, messages } = req.body; if (config.mode === 'gateway') { return proxyToGateway(req, res, config.gatewayUrl); } // Direct mode res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); const apiKey = resolveApiKey(provider); if (!apiKey && provider !== 'ollama') { res.write(`data: ${JSON.stringify({ error: `No API key found for ${provider}` })}\n\n`); res.end(); return; } try { for await (const chunk of streamChat(provider, model, messages, apiKey)) { res.write(`data: ${JSON.stringify({ content: chunk })}\n\n`); } res.write(`data: ${JSON.stringify({ done: true })}\n\n`); } catch (error) { const msg = error instanceof Error ? error.message : String(error); res.write(`data: ${JSON.stringify({ error: msg })}\n\n`); } res.end(); }); return app; }