didi-lot2-backend/backend/admin-dashboard/src/components/ProvidersManagement/dialogs/AssignmentDialog.tsx
2026-07-10 03:39:53 -07:00

104 lines
5.7 KiB
TypeScript

/**
* Add/Edit dialog for a ComponentAssignment row.
*
* Maps a component (techniques/claims_extract/etc) to a primary provider+model
* with optional fallback provider+model. Models dropdown filters to only show
* models belonging to the currently-selected provider.
*
* Fallback section is fully optional — empty fallback_provider_id disables
* the fallback model dropdown.
*/
import React from 'react';
import {
Box, Dialog, DialogTitle, DialogContent, DialogActions, TextField, FormControl,
InputLabel, Select, MenuItem, FormControlLabel, Checkbox, Button, Typography,
} from '@mui/material';
import type { ComponentAssignment, LlmProvider, LlmModel } from '../ProvidersManagement.types';
interface Props {
open: boolean;
data: Partial<ComponentAssignment> | null;
providers: LlmProvider[];
models: LlmModel[];
onClose: () => void;
onChange: (data: Partial<ComponentAssignment>) => void;
onSave: () => void;
}
export const AssignmentDialog: React.FC<Props> = ({ open, data, providers, models, onClose, onChange, onSave }) => {
if (!data) return null;
const isEdit = 'assignment_id' in data && !!data.assignment_id;
const filteredModels = models.filter((m) => m.provider_id === data.provider_id);
const filteredFallbackModels = models.filter((m) => m.provider_id === data.fallback_provider_id);
return (
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
<DialogTitle>{isEdit ? 'Edit Assignment' : 'Add Assignment'}</DialogTitle>
<DialogContent>
<Box display="flex" flexDirection="column" gap={2} pt={1}>
<TextField label="Component Code" value={data.component_code || ''} onChange={(e) => onChange({ ...data, component_code: e.target.value })} required fullWidth placeholder="techniques, claims_extract, etc." />
<TextField label="Component Name" value={data.component_name || ''} onChange={(e) => onChange({ ...data, component_name: e.target.value })} required fullWidth />
<Typography variant="subtitle2" sx={{ mt: 1 }}>Primary Provider/Model</Typography>
<Box display="flex" gap={2}>
<FormControl sx={{ flex: 1 }} required>
<InputLabel>Provider</InputLabel>
<Select value={data.provider_id || ''} label="Provider" onChange={(e) => onChange({ ...data, provider_id: Number(e.target.value), model_id: undefined })}>
{providers.map((p) => (
<MenuItem key={p.provider_id} value={p.provider_id}>{p.provider_name}</MenuItem>
))}
</Select>
</FormControl>
<FormControl sx={{ flex: 1 }} required>
<InputLabel>Model</InputLabel>
<Select value={data.model_id || ''} label="Model" onChange={(e) => onChange({ ...data, model_id: Number(e.target.value) })}>
{filteredModels.map((m) => (
<MenuItem key={m.model_id} value={m.model_id}>{m.model_name}</MenuItem>
))}
</Select>
</FormControl>
</Box>
<Typography variant="subtitle2" sx={{ mt: 1 }}>Fallback Provider/Model (Optional)</Typography>
<Box display="flex" gap={2}>
<FormControl sx={{ flex: 1 }}>
<InputLabel>Fallback Provider</InputLabel>
<Select value={data.fallback_provider_id || ''} label="Fallback Provider" onChange={(e) => onChange({ ...data, fallback_provider_id: e.target.value ? Number(e.target.value) : null, fallback_model_id: null })}>
<MenuItem value="">None</MenuItem>
{providers.map((p) => (
<MenuItem key={p.provider_id} value={p.provider_id}>{p.provider_name}</MenuItem>
))}
</Select>
</FormControl>
<FormControl sx={{ flex: 1 }}>
<InputLabel>Fallback Model</InputLabel>
<Select value={data.fallback_model_id || ''} label="Fallback Model" onChange={(e) => onChange({ ...data, fallback_model_id: e.target.value ? Number(e.target.value) : null })} disabled={!data.fallback_provider_id}>
<MenuItem value="">None</MenuItem>
{filteredFallbackModels.map((m) => (
<MenuItem key={m.model_id} value={m.model_id}>{m.model_name}</MenuItem>
))}
</Select>
</FormControl>
</Box>
<Typography variant="subtitle2" sx={{ mt: 1 }}>Settings</Typography>
<Box display="flex" gap={2}>
<TextField label="Temperature" type="number" value={data.temperature || 0.3} onChange={(e) => onChange({ ...data, temperature: parseFloat(e.target.value) })} sx={{ flex: 1 }} inputProps={{ step: 0.1, min: 0, max: 2 }} />
<TextField label="Max Tokens" type="number" value={data.max_tokens || 4096} onChange={(e) => onChange({ ...data, max_tokens: parseInt(e.target.value) })} sx={{ flex: 1 }} />
<TextField label="Timeout (ms)" type="number" value={data.timeout_ms || 120000} onChange={(e) => onChange({ ...data, timeout_ms: parseInt(e.target.value) })} sx={{ flex: 1 }} />
</Box>
<TextField label="Description" value={data.description || ''} onChange={(e) => onChange({ ...data, description: e.target.value })} multiline rows={2} fullWidth />
<FormControlLabel control={<Checkbox checked={data.is_enabled !== false} onChange={(e) => onChange({ ...data, is_enabled: e.target.checked })} />} label="Enabled" />
</Box>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<Button variant="contained" onClick={onSave} disabled={!data.component_code || !data.component_name || !data.provider_id || !data.model_id}>
{isEdit ? 'Update' : 'Create'}
</Button>
</DialogActions>
</Dialog>
);
};