chore: initial checkpoint - Tesla Roadtrip planner

- Proactive Grok integration (xAI API + local CLI fallback)
- Real road routing via OSRM (no more bird's-eye lines)
- Heavy structured logging for fast iteration
- Strong sanitization + geocoding + ErrorBoundary (no black screens)
- Playwright E2E tests (API diagnostic + full UI flow)
- scripts/dev.sh for one-command startup
- Clean .env.example + documentation

This is a stable checkpoint before further prompt/UI refinement.
This commit is contained in:
2026-05-15 19:24:35 +01:00
commit d516e93323
29 changed files with 11927 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
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(),
});
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 = [] } = parsed.data;
log.info({
requestId,
userMessage: message,
vehicle: vehicle.name,
historyLength: history.length,
currentItineraryDays: itinerary?.days?.length || 0,
}, '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
);
const duration = Date.now() - start;
const payload: any = { reply: result.text };
if (result.updatedItinerary) {
payload.itinerary = result.updatedItinerary;
}
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." });
}
});
export default router;