feat: travel dates + sea-crossing chooser, Tesla in-car polish, Fleet API stub
- Travel dates: TopBar chip + popover (outbound/return/travellers); sent to Grok prompt; itinerary.needsTravelDates drives a nudge banner; cache and prefetch ledger invalidate when dates change - Sea crossings: CrossingOption schema (Eurotunnel, DFDS, P&O, Brittany, Stena Line); CrossingSwapBlock under tunnel/ferry/crossing stops with trip-impact deltas and Book links; prompt requires 3-5 real options for every UK ↔ mainland route; picking a crossing triggers silent re-plan - Tesla in-car polish: UA + heuristic detection sets <html class="incar">; CSS overrides kill backdrop-filter, scale fonts, enforce 44px tap targets, disable hover flicker; geolocation + reverse geocode + crosshair button inside the From input; up/down arrow reorder buttons replace touch-broken HTML5 drag-and-drop - Tesla Fleet API stub: /.well-known/appspecific/com.tesla.3p.public-key.pem served from TESLA_FLEET_PUBLIC_KEY for partner domain verification; OAuth callback + vehicle_data stub return 503 until partner approval - Dockerfile + .dockerignore for Dokku deployment; server now serves client/dist in production
This commit is contained in:
+10
-4
@@ -15,6 +15,11 @@ const ChatRequestSchema = z.object({
|
||||
selectedVariant: z.enum(['fast', 'scenic', 'cheap']).optional(),
|
||||
origin: z.string().optional(),
|
||||
destination: z.string().optional(),
|
||||
travelDates: z.object({
|
||||
outbound: z.string().nullable().optional(),
|
||||
return: z.string().nullable().optional(),
|
||||
travellers: z.number().int().min(1).max(8).optional(),
|
||||
}).optional(),
|
||||
});
|
||||
|
||||
router.post('/chat', async (req, res) => {
|
||||
@@ -30,7 +35,7 @@ router.post('/chat', async (req, res) => {
|
||||
return res.status(400).json({ error: 'Invalid request' });
|
||||
}
|
||||
|
||||
const { message, vehicle, itinerary, history = [], selectedVariant = 'fast', origin, destination } = parsed.data;
|
||||
const { message, vehicle, itinerary, history = [], selectedVariant = 'fast', origin, destination, travelDates } = parsed.data;
|
||||
|
||||
log.info({
|
||||
requestId,
|
||||
@@ -41,6 +46,7 @@ router.post('/chat', async (req, res) => {
|
||||
selectedVariant,
|
||||
origin,
|
||||
destination,
|
||||
travelDates,
|
||||
}, 'Parsed chat request');
|
||||
|
||||
// Call Grok (this will produce very detailed logs inside GrokHeadlessClient)
|
||||
@@ -49,7 +55,7 @@ router.post('/chat', async (req, res) => {
|
||||
itinerary,
|
||||
vehicle,
|
||||
selectedVariant,
|
||||
{ origin, destination },
|
||||
{ origin, destination, travelDates },
|
||||
);
|
||||
|
||||
const duration = Date.now() - start;
|
||||
@@ -91,7 +97,7 @@ router.post('/chat/stream', async (req, res) => {
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: 'Invalid request', issues: parsed.error.format() });
|
||||
}
|
||||
const { message, vehicle, itinerary, history = [], selectedVariant = 'fast', origin, destination } = parsed.data;
|
||||
const { message, vehicle, itinerary, history = [], selectedVariant = 'fast', origin, destination, travelDates } = parsed.data;
|
||||
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
res.setHeader('Cache-Control', 'no-cache, no-transform');
|
||||
@@ -124,7 +130,7 @@ router.post('/chat/stream', async (req, res) => {
|
||||
itinerary,
|
||||
vehicle,
|
||||
selectedVariant,
|
||||
{ origin, destination },
|
||||
{ origin, destination, travelDates },
|
||||
);
|
||||
for await (const ev of stream) {
|
||||
if (cancelled) break;
|
||||
|
||||
Reference in New Issue
Block a user