Hemang Thakur
a lot of changes
85a4a41
raw
history blame contribute delete
7.48 kB
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>
);
}