import { Router } from 'express'; import { z } from 'zod'; import { grok } from '../services/llm/GrokHeadlessClient.js'; import { createLogger } from '../lib/logger.js'; import crypto from 'crypto'; const log = createLogger('chat-api'); const router = Router(); const ChatRequestSchema = z.object({ message: z.string().min(1).max(2000), vehicle: z.object({ name: z.string(), rangeKm: z.number() }), itinerary: z.any().optional(), history: z.array(z.object({ role: z.enum(['user', 'assistant']), content: z.string() })).optional(), selectedVariant: z.enum(['fast', 'scenic', 'cheap']).optional(), }); router.post('/chat', async (req, res) => { const requestId = crypto.randomUUID().slice(0, 8); const start = Date.now(); log.info({ requestId, body: req.body }, '=== INCOMING /api/chat REQUEST ==='); try { const parsed = ChatRequestSchema.safeParse(req.body); if (!parsed.success) { log.error({ requestId, errors: parsed.error.format() }, 'Invalid request body'); return res.status(400).json({ error: 'Invalid request' }); } const { message, vehicle, itinerary, history = [], selectedVariant = 'fast' } = parsed.data; log.info({ requestId, userMessage: message, vehicle: vehicle.name, historyLength: history.length, currentItineraryDays: itinerary?.days?.length || 0, selectedVariant, }, 'Parsed chat request'); // Call Grok (this will produce very detailed logs inside GrokHeadlessClient) const result = await grok.chat( [...history, { role: 'user' as const, content: message }], itinerary, vehicle, selectedVariant, ); const duration = Date.now() - start; const payload: any = { reply: result.text }; if (result.updatedItinerary) { payload.itinerary = result.updatedItinerary; } if (result.variants && Array.isArray(result.variants)) { payload.variants = result.variants; } if (result.selectedVariant) { payload.selectedVariant = result.selectedVariant; } log.info({ requestId, durationMs: duration, replyLength: result.text.length, itineraryUpdated: !!result.updatedItinerary, newDays: result.updatedItinerary?.days?.length || 0, }, '=== SENDING RESPONSE TO FRONTEND ==='); if (result.updatedItinerary) { log.debug({ requestId, fullItinerary: result.updatedItinerary }, 'Full updated itinerary being sent'); } res.json(payload); } catch (err) { log.error({ requestId, err }, 'Chat route crashed'); res.status(500).json({ reply: "Something went wrong on the server." }); } }); router.get('/grok/status', async (_req, res) => { try { const status = await grok.getStatus(); res.json(status); } catch (err) { res.json({ provider: 'fallback', label: 'Fallback', detail: 'Basic mode', isLocal: false, model: 'unknown', }); } }); export default router;