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>
  );
}