104 lines
5.7 KiB
TypeScript
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>
|
|
);
|
|
};
|