Spaces:
Paused
Paused
File size: 4,147 Bytes
bf8faa9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | /**
* Structured-query builder for taxonomy mining: one dropdown chip per axis
* plus a free-text field that the backend parses DETERMINISTICALLY (keyword
* map, no LLM) into the same filterable query object.
*/
import { useState } from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Chip from '@mui/material/Chip';
import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
const AXIS_OPTIONS: Record<string, string[]> = {
actor: ['pedestrian', 'cyclist', 'vehicle', 'none'],
vulnerability: ['VRU', 'NON_VRU'],
legality: ['YIELD', 'RESTRICTED_PATH', 'LANE_ASSOCIATION', 'SIGNAL', 'MERGE', 'STOP'],
interaction: ['CROSSING', 'MERGING', 'FOLLOWING', 'OTHER', 'NONE'],
behavior: [
'proceed_without_yield',
'enter_restricted_path',
'unpermitted_maneuver',
'run_red_signal',
'insufficient_gap_merge',
'rolling_stop',
],
road_geometry: ['uncontrolled', 'controlled', 'none'],
traffic_control: ['none', 'signal', 'stop_sign'],
visibility: ['clear', 'low'],
lighting: ['day', 'dusk', 'night'],
weather: ['clear', 'rain'],
consequence_class: [
'NO_MATERIAL_CONSEQUENCE',
'DEGRADED_COMFORT',
'PLANNER_INTERVENTION',
'SAFETY_CRITICAL',
],
primary_layer: [
'perception',
'prediction',
'planning',
'localization',
'map',
'control',
'policy_rule',
'data_label',
],
};
interface Props {
onRun: (text: string | undefined, filters: Record<string, string>) => void;
busy?: boolean;
}
export default function RotrQueryBuilder({ onRun, busy }: Props) {
const [text, setText] = useState(
'failed to yield to pedestrian at uncontrolled intersection during low visibility'
);
const [filters, setFilters] = useState<Record<string, string>>({});
const setAxis = (axis: string, value: string) => {
setFilters((f) => {
const next = { ...f };
if (value) next[axis] = value;
else delete next[axis];
return next;
});
};
return (
<Box>
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center', mb: 1 }}>
<TextField
size="small"
fullWidth
label="natural-language query (parsed by a deterministic keyword map — no LLM)"
value={text}
onChange={(e) => setText(e.target.value)}
/>
<Button variant="contained" disabled={busy} onClick={() => onRun(text || undefined, filters)}>
Run query
</Button>
</Box>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
{Object.entries(AXIS_OPTIONS).map(([axis, options]) => (
<FormControl key={axis} size="small" sx={{ minWidth: 148 }}>
<InputLabel sx={{ fontSize: 12 }}>{axis}</InputLabel>
<Select
label={axis}
value={filters[axis] ?? ''}
onChange={(e) => setAxis(axis, e.target.value)}
sx={{ fontSize: 12 }}
>
<MenuItem value="">
<em>any</em>
</MenuItem>
{options.map((o) => (
<MenuItem key={o} value={o} sx={{ fontSize: 12 }}>
{o}
</MenuItem>
))}
</Select>
</FormControl>
))}
</Box>
{Object.keys(filters).length ? (
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', mt: 1, alignItems: 'center' }}>
<Typography variant="caption" sx={{ color: '#8a949e' }}>
active chips:
</Typography>
{Object.entries(filters).map(([axis, value]) => (
<Chip
key={axis}
size="small"
label={`${axis}=${value}`}
onDelete={() => setAxis(axis, '')}
sx={{ height: 20, fontSize: 10, fontFamily: 'monospace' }}
/>
))}
</Box>
) : null}
</Box>
);
}
|