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 ( {localLoading ? ( Loading Evaluation... ) : ( <> Select Metrics {noMetricsError && ( {noMetricsError} )} {isEvaluating && ( Evaluating... )} {evaluationResult && ( {evaluationResult} )} )} ); }