|
import * as React from 'react'; |
|
import ReactMarkdown from 'react-markdown'; |
|
import { useTheme } from '@mui/material/styles'; |
|
import Box from '@mui/material/Box'; |
|
import OutlinedInput from '@mui/material/OutlinedInput'; |
|
import InputLabel from '@mui/material/InputLabel'; |
|
import MenuItem from '@mui/material/MenuItem'; |
|
import FormControl from '@mui/material/FormControl'; |
|
import Select from '@mui/material/Select'; |
|
import Chip from '@mui/material/Chip'; |
|
import Button from '@mui/material/Button'; |
|
import Typography from '@mui/material/Typography'; |
|
import './Evaluate.css'; |
|
|
|
const MenuProps = { |
|
PaperProps: { |
|
className: 'evaluate-menu', |
|
}, |
|
disableScrollLock: true |
|
}; |
|
|
|
function getStyles(name, selectedNames, theme) { |
|
return { |
|
fontWeight: selectedNames.includes(name.toLowerCase()) |
|
? theme.typography.fontWeightMedium |
|
: theme.typography.fontWeightRegular, |
|
}; |
|
} |
|
|
|
export default function MultipleSelectChip({ evaluation }) { |
|
const theme = useTheme(); |
|
const [personName, setPersonName] = React.useState([]); |
|
const [selectedMetrics, setSelectedMetrics] = React.useState([]); |
|
const [evaluationResult, setEvaluationResult] = React.useState(""); |
|
const [isEvaluating, setIsEvaluating] = React.useState(false); |
|
const [localLoading, setLocalLoading] = React.useState(false); |
|
const [noMetricsError, setNoMetricsError] = React.useState(""); |
|
const [metricOptions, setMetricOptions] = React.useState([]); |
|
|
|
React.useEffect(() => { |
|
|
|
if (evaluation && evaluation.contents === undefined) { |
|
setMetricOptions([ |
|
"Bias", |
|
"Toxicity", |
|
"Summarization", |
|
"Answer Correctness", |
|
]); |
|
} else { |
|
|
|
setMetricOptions([ |
|
"Bias", |
|
"Toxicity", |
|
"Summarization", |
|
"Faithfulness", |
|
"Hallucination", |
|
"Answer Relevancy", |
|
"Contextual Relevancy", |
|
"Contextual Recall", |
|
]); |
|
} |
|
}, [evaluation]); |
|
|
|
|
|
React.useEffect(() => { |
|
|
|
setPersonName([]); |
|
setSelectedMetrics([]); |
|
setEvaluationResult(""); |
|
setLocalLoading(true); |
|
setNoMetricsError(""); |
|
|
|
|
|
const timer = setTimeout(() => { |
|
setLocalLoading(false); |
|
}, 500); |
|
return () => clearTimeout(timer); |
|
}, [evaluation]); |
|
|
|
const handleChange = (event) => { |
|
const { target: { value } } = event; |
|
const metrics = typeof value === 'string' ? value.split(',') : value; |
|
setPersonName(metrics); |
|
setSelectedMetrics(metrics); |
|
setNoMetricsError(""); |
|
}; |
|
|
|
const handleDelete = (chipToDelete) => { |
|
setPersonName((chips) => chips.filter((chip) => chip !== chipToDelete)); |
|
}; |
|
|
|
|
|
const titleCase = (str) => { |
|
return str |
|
.split(' ') |
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1)) |
|
.join(' '); |
|
}; |
|
|
|
const handleEvaluateClick = async () => { |
|
|
|
setEvaluationResult(""); |
|
|
|
|
|
if (selectedMetrics.length === 0) { |
|
setNoMetricsError("No metrics selected"); |
|
return; |
|
} |
|
|
|
setNoMetricsError(""); |
|
setIsEvaluating(true); |
|
|
|
const payload = { ...evaluation, metrics: selectedMetrics }; |
|
try { |
|
const res = await fetch("/action/evaluate", { |
|
method: "POST", |
|
headers: { "Content-Type": "application/json" }, |
|
body: JSON.stringify(payload), |
|
}); |
|
if (!res.ok) { |
|
const error = await res.json(); |
|
throw new Error(`Evaluation Error: ${error.error}`); |
|
} |
|
|
|
const data = await res.json(); |
|
if (!data.result) { |
|
throw new Error("No results returned from evaluation"); |
|
} |
|
|
|
|
|
let markdown = "### Result\n\n"; |
|
for (const [metric, details] of Object.entries(data.result)) { |
|
let score = details.score; |
|
if (typeof score === "number") { |
|
const percentage = score * 100; |
|
score = Number.isInteger(percentage) |
|
? percentage.toFixed(0) + "%" |
|
: percentage.toFixed(2) + "%"; |
|
} |
|
let reason = details.reason; |
|
markdown += `**${titleCase(metric)}:** ${score}\n\n${reason}\n\n`; |
|
} |
|
setEvaluationResult(markdown); |
|
} catch (err) { |
|
|
|
if (evaluation.onError && evaluation.blockId) { |
|
evaluation.onError(evaluation.blockId, err.message || "Evaluation failed"); |
|
} |
|
else { |
|
console.error("Evaluation prop is missing or incomplete:", evaluation); |
|
} |
|
} |
|
setIsEvaluating(false); |
|
}; |
|
|
|
|
|
const getDisplayName = (lowerValue) => { |
|
const found = metricOptions.find(n => n.toLowerCase() === lowerValue); |
|
return found ? found : lowerValue; |
|
}; |
|
|
|
return ( |
|
<Box className="evaluate-container"> |
|
{localLoading ? ( |
|
<Box> |
|
<Typography variant="body2">Loading Evaluation...</Typography> |
|
</Box> |
|
) : ( |
|
<> |
|
<FormControl className="evaluate-form-control"> |
|
<InputLabel id="chip-label">Select Metrics</InputLabel> |
|
<Select |
|
labelId="chip-label" |
|
id="multiple-chip" |
|
multiple |
|
value={personName} |
|
onChange={handleChange} |
|
input={ |
|
<OutlinedInput |
|
id="select-multiple-chip" |
|
label="Select Metrics" |
|
className="evaluate-outlined-input" |
|
/> |
|
} |
|
renderValue={(selected) => ( |
|
<Box className="chip-container"> |
|
{selected.map((value) => ( |
|
<Chip |
|
className="evaluate-chip" |
|
key={value} |
|
label={getDisplayName(value)} |
|
onDelete={() => handleDelete(value)} |
|
onMouseDown={(event) => event.stopPropagation()} |
|
/> |
|
))} |
|
</Box> |
|
)} |
|
MenuProps={MenuProps} |
|
> |
|
{metricOptions.map((name) => ( |
|
<MenuItem |
|
key={name} |
|
value={name.toLowerCase()} // underlying value is lowercase |
|
style={getStyles(name, personName, theme)} |
|
> |
|
{name} |
|
</MenuItem> |
|
))} |
|
</Select> |
|
</FormControl> |
|
|
|
<Box mt={1}> |
|
<Button |
|
variant="contained" |
|
onClick={handleEvaluateClick} |
|
className="evaluate-button" |
|
> |
|
Evaluate |
|
</Button> |
|
</Box> |
|
|
|
{noMetricsError && ( |
|
<Box className="no-metrics-message"> |
|
{noMetricsError} |
|
</Box> |
|
)} |
|
|
|
{isEvaluating && ( |
|
<Box mt={1} display="flex" alignItems="center"> |
|
<Box className="custom-spinner" /> |
|
<Box ml={1}>Evaluating...</Box> |
|
</Box> |
|
)} |
|
|
|
{evaluationResult && ( |
|
<Box mt={2}> |
|
<ReactMarkdown>{evaluationResult}</ReactMarkdown> |
|
</Box> |
|
)} |
|
</> |
|
)} |
|
</Box> |
|
); |
|
} |