Skip to content

Commit d0148d2

Browse files
committed
Add Grok dynamics briefing
Adds a live Grok powertrain read to Driving Dynamics using motor and chassis signals, with interpretation logic and tests. Also adds shared client-side pagination for FSD insight lists and DataTable-backed FSD tables.
1 parent 6337833 commit d0148d2

13 files changed

Lines changed: 1120 additions & 47 deletions
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
import { useCallback, useMemo } from 'react';
2+
import { Link } from 'react-router-dom';
3+
import { useTranslation } from 'react-i18next';
4+
import { AlertTriangle, Lightbulb, ShieldCheck, Sparkles, type LucideIcon } from 'lucide-react';
5+
6+
import { MetricBar, MetricCard } from '@/components/data-display';
7+
import { EmptyState, QueryError, Skeleton } from '@/components/feedback';
8+
import { Grid } from '@/components/layout';
9+
import { Badge, GlassPanel, PanelTitle, Text } from '@/components/ui';
10+
import { CHART_COLORS } from '@/components/charts';
11+
import { useDriveDynamicsLatest, useMotorLatest } from '@/api/hooks/useVehicles';
12+
import { useUnits } from '@/hooks/useUnits';
13+
import { INTERVALS } from '@/lib/constants';
14+
import { cn } from '@/lib/cn';
15+
import { fmtNumber } from '@/lib/numberFormat';
16+
17+
import {
18+
interpretGrokDynamics,
19+
type GrokFindingId,
20+
type GrokMode,
21+
type GrokTone,
22+
} from './grokDynamics';
23+
24+
interface GrokDynamicsBriefingProps {
25+
vehicleId: number | null | undefined;
26+
}
27+
28+
const TONE_ICON: Record<GrokTone, LucideIcon> = {
29+
info: Lightbulb,
30+
positive: ShieldCheck,
31+
caution: AlertTriangle,
32+
};
33+
34+
const TONE_ICON_CLASS: Record<GrokTone, string> = {
35+
info: 'text-sky-300',
36+
positive: 'text-emerald-300',
37+
caution: 'text-amber-300',
38+
};
39+
40+
const MODE_BADGE: Record<GrokMode, 'neutral' | 'info' | 'success' | 'warning'> = {
41+
unknown: 'neutral',
42+
parked: 'neutral',
43+
idle: 'info',
44+
regen: 'success',
45+
blended_brake: 'warning',
46+
launch: 'info',
47+
cornering: 'info',
48+
drive: 'success',
49+
};
50+
51+
/**
52+
* Grok's live Tesla powertrain briefing for /driving-dynamics.
53+
*
54+
* Reads the same live motor + drive-dynamics snapshots as the cockpit
55+
* gauges, then says what the axles, pedals, and accelerometer are doing
56+
* in plain Tesla language. Unknown stays unknown.
57+
*/
58+
export default function GrokDynamicsBriefing({ vehicleId }: GrokDynamicsBriefingProps) {
59+
const { t } = useTranslation();
60+
const { formatPower, formatTemperature } = useUnits();
61+
62+
const motorQuery = useMotorLatest(vehicleId ?? 0, INTERVALS.REALTIME);
63+
const dynamicsQuery = useDriveDynamicsLatest(vehicleId ?? 0, INTERVALS.REALTIME);
64+
65+
const handleRetry = useCallback(() => {
66+
void motorQuery.refetch();
67+
void dynamicsQuery.refetch();
68+
}, [dynamicsQuery, motorQuery]);
69+
70+
const read = useMemo(
71+
() => interpretGrokDynamics(motorQuery.data, dynamicsQuery.data),
72+
[dynamicsQuery.data, motorQuery.data],
73+
);
74+
75+
const headline = headlineFor(read.mode, t);
76+
const modeLabel = modeLabelFor(read.mode, t);
77+
78+
const isLoading = (motorQuery.isLoading || dynamicsQuery.isLoading) && !read.hasSignal;
79+
const isError = (motorQuery.isError || dynamicsQuery.isError) && !read.hasSignal;
80+
81+
return (
82+
<GlassPanel className="p-4 sm:p-5" data-testid="grok-dynamics-briefing">
83+
<PanelTitle className="mb-1 flex items-center gap-2">
84+
<Sparkles className="h-4 w-4 text-cyan-300" aria-hidden="true" />
85+
{t('dynamics.grok.title', "Grok's powertrain read")}
86+
</PanelTitle>
87+
<Text as="p" variant="caption" className="mb-4">
88+
{t(
89+
'dynamics.grok.honesty',
90+
'Interpreted from live motor, pedal, and accelerometer signals. Not Autopilot, not the chassis controller, and not a 0–60 claim.',
91+
)}
92+
</Text>
93+
94+
{isLoading ? (
95+
<div
96+
role="status"
97+
aria-busy="true"
98+
aria-label={t('dynamics.grok.loading', 'Loading Grok powertrain read…')}
99+
>
100+
<Skeleton className="mb-3 h-8 w-2/3" />
101+
<Skeleton className="h-28" />
102+
</div>
103+
) : isError ? (
104+
<QueryError
105+
error={motorQuery.error ?? dynamicsQuery.error}
106+
onRetry={handleRetry}
107+
/>
108+
) : !read.hasSignal ? (
109+
<EmptyState
110+
icon={<Sparkles className="h-8 w-8" aria-hidden="true" />}
111+
message={t(
112+
'dynamics.grok.empty',
113+
'Drive the car so Grok can read axle torque, regen, and chassis g.',
114+
)}
115+
/>
116+
) : (
117+
<div className="space-y-4">
118+
<div className="flex flex-wrap items-center gap-2">
119+
<Badge variant={MODE_BADGE[read.mode]} size="sm">
120+
{modeLabel}
121+
</Badge>
122+
<Text as="p" variant="bodySm">
123+
{headline}
124+
</Text>
125+
</div>
126+
127+
<Grid cols={{ default: 1, sm: 2, xl: 4 }} gap={4}>
128+
<MetricCard
129+
wrapLabel
130+
color="cyan"
131+
label={t('dynamics.grok.drivePower', 'Drive power')}
132+
value={read.drivePowerW == null ? '—' : formatPower(read.drivePowerW, { precision: 1 })}
133+
/>
134+
<MetricCard
135+
wrapLabel
136+
color="green"
137+
label={t('dynamics.grok.regenPower', 'Regen harvest')}
138+
value={read.regenPowerW == null ? '—' : formatPower(read.regenPowerW, { precision: 1 })}
139+
/>
140+
<MetricCard
141+
wrapLabel
142+
color="purple"
143+
label={t('dynamics.grok.torque', 'Axle torque')}
144+
value={read.torqueTotalNm == null ? '—' : `${fmtNumber(read.torqueTotalNm, 0)} Nm`}
145+
/>
146+
<MetricCard
147+
wrapLabel
148+
color="amber"
149+
label={t('dynamics.grok.combinedG', 'Combined g')}
150+
value={read.combinedG == null ? '—' : `${fmtNumber(read.combinedG, 2)} g`}
151+
/>
152+
</Grid>
153+
154+
{read.rearTorqueSharePct != null ? (
155+
<MetricBar
156+
label={t('dynamics.grok.rearShare', 'Rear axle share')}
157+
value={read.rearTorqueSharePct}
158+
max={100}
159+
color={CHART_COLORS[0]}
160+
sublabel={t('dynamics.grok.rearShareValue', '{{value}}% of |torque|', {
161+
value: fmtNumber(read.rearTorqueSharePct, 0),
162+
})}
163+
/>
164+
) : (
165+
<Text as="p" variant="caption">
166+
{t(
167+
'dynamics.grok.rearShareUnknown',
168+
'AWD split unknown — both axles have not reported torque yet.',
169+
)}
170+
</Text>
171+
)}
172+
173+
{read.maxMotorTempC != null ? (
174+
<Text as="p" variant="caption">
175+
{t('dynamics.grok.thermal', 'Hottest stator/inverter {{temp}} · {{band}}', {
176+
temp: formatTemperature(read.maxMotorTempC, { precision: 0 }),
177+
band: thermalBand(read.thermal, t),
178+
})}
179+
</Text>
180+
) : (
181+
<Text as="p" variant="caption">
182+
{t('dynamics.grok.thermalUnknown', 'Motor thermal envelope not reported yet.')}
183+
</Text>
184+
)}
185+
186+
<ul className="grid grid-cols-1 gap-3 lg:grid-cols-2">
187+
{read.findings.map((finding) => {
188+
const Icon = TONE_ICON[finding.tone];
189+
return (
190+
<li
191+
key={finding.id}
192+
data-finding={finding.id}
193+
className={cn(
194+
'flex items-start gap-3 rounded-lg p-3',
195+
'bg-white/[0.03] border border-white/[0.06]',
196+
)}
197+
>
198+
<Icon
199+
className={cn('mt-0.5 h-4 w-4 shrink-0', TONE_ICON_CLASS[finding.tone])}
200+
aria-hidden="true"
201+
/>
202+
<Text as="p" variant="bodySm">
203+
{findingText(finding.id, read, t, formatTemperature)}
204+
</Text>
205+
</li>
206+
);
207+
})}
208+
</ul>
209+
210+
<Link
211+
to="/physics-cockpit"
212+
className="text-sm text-cyan-300 underline-offset-2 hover:underline"
213+
>
214+
{t('dynamics.grok.cockpit', 'Open Tesla physics cockpit')}
215+
</Link>
216+
</div>
217+
)}
218+
</GlassPanel>
219+
);
220+
}
221+
222+
function headlineFor(mode: GrokMode, t: (key: string, fallback: string) => string): string {
223+
switch (mode) {
224+
case 'parked':
225+
return t('dynamics.grok.headline.parked', 'Parked. Axles are waiting, not working.');
226+
case 'idle':
227+
return t('dynamics.grok.headline.idle', 'In gear, but the motors are barely loaded.');
228+
case 'regen':
229+
return t('dynamics.grok.headline.regen', 'Motors are charging the pack — that is free range.');
230+
case 'blended_brake':
231+
return t('dynamics.grok.headline.blended', 'Friction brake is in. Regen is sharing the stop.');
232+
case 'launch':
233+
return t('dynamics.grok.headline.launch', 'High request, high torque. This is a launch, not a cruise.');
234+
case 'cornering':
235+
return t('dynamics.grok.headline.cornering', 'Chassis is seeing real lateral g. Keep inputs smooth.');
236+
case 'drive':
237+
return t('dynamics.grok.headline.drive', 'Dual-motor Tesla doing dual-motor things.');
238+
default:
239+
return t('dynamics.grok.headline.unknown', 'Not enough live signals to call the play.');
240+
}
241+
}
242+
243+
function modeLabelFor(mode: GrokMode, t: (key: string, fallback: string) => string): string {
244+
switch (mode) {
245+
case 'parked':
246+
return t('dynamics.grok.mode.parked', 'Park');
247+
case 'idle':
248+
return t('dynamics.grok.mode.idle', 'Idle');
249+
case 'regen':
250+
return t('dynamics.grok.mode.regen', 'Regen');
251+
case 'blended_brake':
252+
return t('dynamics.grok.mode.blended', 'Blended brake');
253+
case 'launch':
254+
return t('dynamics.grok.mode.launch', 'Launch');
255+
case 'cornering':
256+
return t('dynamics.grok.mode.cornering', 'Cornering');
257+
case 'drive':
258+
return t('dynamics.grok.mode.drive', 'Drive');
259+
default:
260+
return t('dynamics.grok.mode.unknown', 'Unknown');
261+
}
262+
}
263+
264+
function thermalBand(
265+
thermal: 'unknown' | 'cool' | 'warm' | 'hot',
266+
t: (key: string, fallback: string) => string,
267+
): string {
268+
if (thermal === 'hot') return t('dynamics.grok.band.hot', 'hot — ease sustained power');
269+
if (thermal === 'warm') return t('dynamics.grok.band.warm', 'warm cruise band');
270+
if (thermal === 'cool') return t('dynamics.grok.band.cool', 'cool');
271+
return t('dynamics.grok.band.unknown', 'unknown');
272+
}
273+
274+
function findingText(
275+
id: GrokFindingId,
276+
read: ReturnType<typeof interpretGrokDynamics>,
277+
t: (key: string, fallback: string, values?: Record<string, unknown>) => string,
278+
formatTemperature: (value: number | null, options?: { precision?: number }) => string,
279+
): string {
280+
switch (id) {
281+
case 'parked':
282+
return t('dynamics.grok.find.parked', 'Shift state is Park. Treat torque as residual, not a pull.');
283+
case 'idle':
284+
return t('dynamics.grok.find.idle', 'Throttle is quiet and axle torque is near zero. Coast or crawl.');
285+
case 'regen_harvest':
286+
return t('dynamics.grok.find.regen', 'Negative axle torque / pack-bound power — one-pedal harvest.');
287+
case 'one_pedal':
288+
return t('dynamics.grok.find.onePedal', 'Brake switch is off while motors regen. That is one-pedal Tesla.');
289+
case 'blended_brake':
290+
return t('dynamics.grok.find.blended', 'Hydraulic brake is active during regen. Pads are sharing the stop.');
291+
case 'launch':
292+
return t('dynamics.grok.find.launch', 'Pedal and torque are both in the launch neighborhood.');
293+
case 'drive':
294+
return t('dynamics.grok.find.drive', 'Positive torque on the axles. This is propulsion, not theater.');
295+
case 'cornering':
296+
return t(
297+
'dynamics.grok.find.cornering',
298+
'Lateral accelerometer {{g}} g. That is chassis load, not a grip percentage.',
299+
{ g: read.lateralG == null ? '—' : fmtNumber(Math.abs(read.lateralG), 2) },
300+
);
301+
case 'awd_rear':
302+
return t(
303+
'dynamics.grok.find.awdRear',
304+
'Rear axle is carrying {{share}}% of |torque|. Typical Tesla drive bias — measured, not assumed.',
305+
{ share: fmtNumber(read.rearTorqueSharePct ?? 0, 0) },
306+
);
307+
case 'awd_front':
308+
return t(
309+
'dynamics.grok.find.awdFront',
310+
'Front axle is carrying more of |torque| (rear {{share}}%). Often regen or low-traction split.',
311+
{ share: fmtNumber(read.rearTorqueSharePct ?? 0, 0) },
312+
);
313+
case 'awd_balanced':
314+
return t(
315+
'dynamics.grok.find.awdBalanced',
316+
'Axles are sharing |torque| nearly evenly (rear {{share}}%).',
317+
{ share: fmtNumber(read.rearTorqueSharePct ?? 0, 0) },
318+
);
319+
case 'thermal_hot':
320+
return t(
321+
'dynamics.grok.find.thermalHot',
322+
'Hottest motor/inverter {{temp}}. Near derate — stop asking for sustained peak.',
323+
{ temp: formatTemperature(read.maxMotorTempC, { precision: 0 }) },
324+
);
325+
case 'thermal_warm':
326+
return t(
327+
'dynamics.grok.find.thermalWarm',
328+
'Hottest motor/inverter {{temp}}. Normal after a hard pull, not an emergency.',
329+
{ temp: formatTemperature(read.maxMotorTempC, { precision: 0 }) },
330+
);
331+
}
332+
}

0 commit comments

Comments
 (0)