Spaces:
Paused
Paused
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 'contents' is undefined in the payload | |
if (evaluation && evaluation.contents === undefined) { | |
setMetricOptions([ | |
"Bias", | |
"Toxicity", | |
"Summarization", | |
"Answer Correctness", | |
]); | |
} else { | |
// Else, all except "Answer Correctness" | |
setMetricOptions([ | |
"Bias", | |
"Toxicity", | |
"Summarization", | |
"Faithfulness", | |
"Hallucination", | |
"Answer Relevancy", | |
"Contextual Relevancy", | |
"Contextual Recall", | |
]); | |
} | |
}, [evaluation]); | |
// Reset the form fields | |
React.useEffect(() => { | |
// Reset the form and evaluation result | |
setPersonName([]); | |
setSelectedMetrics([]); | |
setEvaluationResult(""); | |
setLocalLoading(true); | |
setNoMetricsError(""); | |
// Simulate a loading delay | |
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)); | |
}; | |
// Function to convert a string to title case. | |
const titleCase = (str) => { | |
return str | |
.split(' ') | |
.map(word => word.charAt(0).toUpperCase() + word.slice(1)) | |
.join(' '); | |
}; | |
const handleEvaluateClick = async () => { | |
// Clear previous evaluation result immediately. | |
setEvaluationResult(""); | |
// Check if no metrics selected | |
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"); | |
} | |
// Format the JSON into Markdown. | |
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) { | |
// Use the callback to trigger the error block in ChatWindow | |
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); | |
}; | |
// Finds the matching display name for a metric. | |
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> | |
); | |
} |