Hemang Thakur commited on
Commit
8286427
·
1 Parent(s): 1364348

fixed evaluation

Browse files
frontend/src/Components/AiComponents/ChatComponents/Evaluate.js CHANGED
@@ -1,142 +1,244 @@
1
- import React, { useRef } from 'react';
2
- import { FaTimes, FaCheck, FaSpinner } from 'react-icons/fa';
3
- import { BsChevronLeft } from 'react-icons/bs';
4
- import CircularProgress from '@mui/material/CircularProgress';
5
- import Sources from './Sources';
6
- import Evaluate from './Evaluate'
7
- import '../Sidebars/RightSidebar.css';
8
-
9
- function RightSidebar({
10
- isOpen,
11
- rightSidebarWidth,
12
- setRightSidebarWidth,
13
- toggleRightSidebar,
14
- sidebarContent,
15
- tasks = [],
16
- tasksLoading,
17
- sources = [],
18
- sourcesLoading,
19
- onTaskClick,
20
- onSourceClick,
21
- evaluation
22
- }) {
23
- const minWidth = 200;
24
- const maxWidth = 450;
25
- const sidebarRef = useRef(null);
26
-
27
- // Called when the user starts resizing the sidebar.
28
- const startResize = (e) => {
29
- e.preventDefault();
30
- sidebarRef.current.classList.add("resizing"); // Add the "resizing" class to the sidebar when resizing
31
- document.addEventListener("mousemove", resizeSidebar);
32
- document.addEventListener("mouseup", stopResize);
33
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
- const resizeSidebar = (e) => {
36
- let newWidth = window.innerWidth - e.clientX;
37
- if (newWidth < minWidth) newWidth = minWidth;
38
- if (newWidth > maxWidth) newWidth = maxWidth;
39
- setRightSidebarWidth(newWidth);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  };
41
 
42
- const stopResize = () => {
43
- sidebarRef.current.classList.remove("resizing"); // Remove the "resizing" class from the sidebar when resizing stops
44
- document.removeEventListener("mousemove", resizeSidebar);
45
- document.removeEventListener("mouseup", stopResize);
46
  };
47
 
48
- // Default handler for source clicks: open the link in a new tab.
49
- const handleSourceClick = (source) => {
50
- if (source && source.link) {
51
- window.open(source.link, '_blank');
52
- }
 
53
  };
54
 
55
- // Helper function to return the proper icon based on task status.
56
- const getTaskIcon = (task) => {
57
- // If the task is a simple string, default to the completed icon.
58
- if (typeof task === 'string') {
59
- return <FaCheck />;
 
 
 
60
  }
61
- // Use the status field to determine which icon to render.
62
- switch (task.status) {
63
- case 'RUNNING':
64
- // FaSpinner is used for running tasks. The CSS class "spin" can be defined to add animation.
65
- return <FaSpinner className="spin"/>;
66
- case 'DONE':
67
- return <FaCheck className="checkmark" />;
68
- case 'FAILED':
69
- return <FaTimes className="x" />;
70
- default:
71
- return <FaCheck />;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  }
 
 
 
 
 
 
 
73
  };
74
 
75
  return (
76
- <>
77
- <nav
78
- ref={sidebarRef}
79
- className={`right-side-bar ${isOpen ? "open" : "closed"}`}
80
- style={{ width: isOpen ? rightSidebarWidth : 0 }}
81
- >
82
- <div className="sidebar-header">
83
- <h3>
84
- {sidebarContent === "sources"
85
- ? "Sources"
86
- : sidebarContent === "evaluate"
87
- ? "Evaluation"
88
- : "Tasks"}
89
- </h3>
90
- <button className="close-btn" onClick={toggleRightSidebar}>
91
- <FaTimes />
92
- </button>
93
- </div>
94
- <div className="sidebar-content">
95
- {sidebarContent === "sources" ? ( // If the sidebar content is "sources", show the sources component
96
- sourcesLoading ? (
97
- <div className="tasks-loading">
98
- <CircularProgress size={20} sx={{ color: '#ccc' }} />
99
- <span className="loading-tasks-text">Generating sources...</span>
100
- </div>
101
- ) : (
102
- <Sources sources={sources} handleSourceClick={onSourceClick || handleSourceClick} />
103
- )
104
- )
105
- // If the sidebar content is "evaluate", show the evaluation component
106
- : sidebarContent === "evaluate" ? (
107
- <Evaluate evaluation={evaluation} />
108
- ) : (
109
- // Otherwise, show tasks
110
- tasksLoading ? (
111
- <div className="tasks-loading">
112
- <CircularProgress size={20} sx={{ color: '#ccc' }} />
113
- <span className="loading-tasks-text">Generating tasks...</span>
114
- </div>
115
- ) : (
116
- <ul className="nav-links" style={{ listStyle: 'none', padding: 0 }}>
117
- {tasks.map((task, index) => (
118
- <li key={index} className="task-item">
119
- <span className="task-icon">
120
- {getTaskIcon(task)}
121
- </span>
122
- <span className="task-text">
123
- {typeof task === 'string' ? task : task.task}
124
- </span>
125
- </li>
126
- ))}
127
- </ul>
128
- )
 
 
 
 
 
 
 
 
 
 
129
  )}
130
- </div>
131
- <div className="resizer" onMouseDown={startResize}></div>
132
- </nav>
133
- {!isOpen && (
134
- <button className="toggle-btn right-toggle" onClick={toggleRightSidebar}>
135
- <BsChevronLeft />
136
- </button>
 
 
 
 
 
 
 
137
  )}
138
- </>
139
  );
140
- }
141
-
142
- export default RightSidebar;
 
1
+ import * as React from 'react';
2
+ import ReactMarkdown from 'react-markdown';
3
+ import { useTheme } from '@mui/material/styles';
4
+ import Box from '@mui/material/Box';
5
+ import OutlinedInput from '@mui/material/OutlinedInput';
6
+ import InputLabel from '@mui/material/InputLabel';
7
+ import MenuItem from '@mui/material/MenuItem';
8
+ import FormControl from '@mui/material/FormControl';
9
+ import Select from '@mui/material/Select';
10
+ import Chip from '@mui/material/Chip';
11
+ import Button from '@mui/material/Button';
12
+ import Typography from '@mui/material/Typography';
13
+ import './Evaluate.css';
14
+
15
+ const MenuProps = {
16
+ PaperProps: {
17
+ className: 'evaluate-menu',
18
+ },
19
+ disableScrollLock: true
20
+ };
21
+
22
+ function getStyles(name, selectedNames, theme) {
23
+ return {
24
+ fontWeight: selectedNames.includes(name.toLowerCase())
25
+ ? theme.typography.fontWeightMedium
26
+ : theme.typography.fontWeightRegular,
 
 
 
 
 
 
27
  };
28
+ }
29
+
30
+ export default function MultipleSelectChip({ evaluation }) {
31
+ const theme = useTheme();
32
+ const [personName, setPersonName] = React.useState([]);
33
+ const [selectedMetrics, setSelectedMetrics] = React.useState([]);
34
+ const [evaluationResult, setEvaluationResult] = React.useState("");
35
+ const [isEvaluating, setIsEvaluating] = React.useState(false);
36
+ const [localLoading, setLocalLoading] = React.useState(false);
37
+ const [noMetricsError, setNoMetricsError] = React.useState("");
38
+ const [metricOptions, setMetricOptions] = React.useState([]);
39
+
40
+ React.useEffect(() => {
41
+ // If 'contents' is undefined in the payload
42
+ if (evaluation && evaluation.contents === undefined) {
43
+ setMetricOptions([
44
+ "Bias",
45
+ "Toxicity",
46
+ "Summarization",
47
+ "Answer Correctness",
48
+ ]);
49
+ } else {
50
+ // Else, all except "Answer Correctness"
51
+ setMetricOptions([
52
+ "Bias",
53
+ "Toxicity",
54
+ "Summarization",
55
+ "Faithfulness",
56
+ "Hallucination",
57
+ "Answer Relevancy",
58
+ "Contextual Relevancy",
59
+ "Contextual Recall",
60
+ ]);
61
+ }
62
+ }, [evaluation]);
63
 
64
+ // Reset the form fields
65
+ React.useEffect(() => {
66
+ // Reset the form and evaluation result
67
+ setPersonName([]);
68
+ setSelectedMetrics([]);
69
+ setEvaluationResult("");
70
+ setLocalLoading(true);
71
+ setNoMetricsError("");
72
+
73
+ // Simulate a loading delay
74
+ const timer = setTimeout(() => {
75
+ setLocalLoading(false);
76
+ }, 500);
77
+ return () => clearTimeout(timer);
78
+ }, [evaluation]);
79
+
80
+ const handleChange = (event) => {
81
+ const { target: { value } } = event;
82
+ const metrics = typeof value === 'string' ? value.split(',') : value;
83
+ setPersonName(metrics);
84
+ setSelectedMetrics(metrics);
85
+ setNoMetricsError("");
86
  };
87
 
88
+ const handleDelete = (chipToDelete) => {
89
+ setPersonName((chips) => chips.filter((chip) => chip !== chipToDelete));
 
 
90
  };
91
 
92
+ // Function to convert a string to title case.
93
+ const titleCase = (str) => {
94
+ return str
95
+ .split(' ')
96
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
97
+ .join(' ');
98
  };
99
 
100
+ const handleEvaluateClick = async () => {
101
+ // Clear previous evaluation result immediately.
102
+ setEvaluationResult("");
103
+
104
+ // Check if no metrics selected
105
+ if (selectedMetrics.length === 0) {
106
+ setNoMetricsError("No metrics selected");
107
+ return;
108
  }
109
+
110
+ setNoMetricsError("");
111
+ setIsEvaluating(true);
112
+
113
+ const payload = { ...evaluation, metrics: selectedMetrics };
114
+ try {
115
+ const res = await fetch("http://localhost:8000/action/evaluate", {
116
+ method: "POST",
117
+ headers: { "Content-Type": "application/json" },
118
+ body: JSON.stringify(payload),
119
+ });
120
+ if (!res.ok) {
121
+ const error = await res.json();
122
+ throw new Error(`Evaluation Error: ${error.error}`);
123
+ }
124
+
125
+ const data = await res.json();
126
+ if (!data.result) {
127
+ throw new Error("No results returned from evaluation");
128
+ }
129
+
130
+ // Format the JSON into Markdown.
131
+ let markdown = "### Result\n\n";
132
+ for (const [metric, details] of Object.entries(data.result)) {
133
+ let score = details.score;
134
+ if (typeof score === "number") {
135
+ const percentage = score * 100;
136
+ score = Number.isInteger(percentage)
137
+ ? percentage.toFixed(0) + "%"
138
+ : percentage.toFixed(2) + "%";
139
+ }
140
+ let reason = details.reason;
141
+ markdown += `**${titleCase(metric)}:** ${score}\n\n${reason}\n\n`;
142
+ }
143
+ setEvaluationResult(markdown);
144
+ } catch (err) {
145
+ // Use the callback to trigger the error block in ChatWindow
146
+ if (evaluation.onError && evaluation.blockId) {
147
+ evaluation.onError(evaluation.blockId, err.message || "Evaluation failed");
148
+ }
149
+ else {
150
+ console.error("Evaluation prop is missing or incomplete:", evaluation);
151
+ }
152
  }
153
+ setIsEvaluating(false);
154
+ };
155
+
156
+ // Finds the matching display name for a metric.
157
+ const getDisplayName = (lowerValue) => {
158
+ const found = metricOptions.find(n => n.toLowerCase() === lowerValue);
159
+ return found ? found : lowerValue;
160
  };
161
 
162
  return (
163
+ <Box className="evaluate-container">
164
+ {localLoading ? (
165
+ <Box>
166
+ <Typography variant="body2">Loading Evaluation...</Typography>
167
+ </Box>
168
+ ) : (
169
+ <>
170
+ <FormControl className="evaluate-form-control">
171
+ <InputLabel id="chip-label">Select Metrics</InputLabel>
172
+ <Select
173
+ labelId="chip-label"
174
+ id="multiple-chip"
175
+ multiple
176
+ value={personName}
177
+ onChange={handleChange}
178
+ input={
179
+ <OutlinedInput
180
+ id="select-multiple-chip"
181
+ label="Select Metrics"
182
+ className="evaluate-outlined-input"
183
+ />
184
+ }
185
+ renderValue={(selected) => (
186
+ <Box className="chip-container">
187
+ {selected.map((value) => (
188
+ <Chip
189
+ className="evaluate-chip"
190
+ key={value}
191
+ label={getDisplayName(value)}
192
+ onDelete={() => handleDelete(value)}
193
+ onMouseDown={(event) => event.stopPropagation()}
194
+ />
195
+ ))}
196
+ </Box>
197
+ )}
198
+ MenuProps={MenuProps}
199
+ >
200
+ {metricOptions.map((name) => (
201
+ <MenuItem
202
+ key={name}
203
+ value={name.toLowerCase()} // underlying value is lowercase
204
+ style={getStyles(name, personName, theme)}
205
+ >
206
+ {name}
207
+ </MenuItem>
208
+ ))}
209
+ </Select>
210
+ </FormControl>
211
+
212
+ <Box mt={1}>
213
+ <Button
214
+ variant="contained"
215
+ onClick={handleEvaluateClick}
216
+ className="evaluate-button"
217
+ >
218
+ Evaluate
219
+ </Button>
220
+ </Box>
221
+
222
+ {noMetricsError && (
223
+ <Box className="no-metrics-message">
224
+ {noMetricsError}
225
+ </Box>
226
  )}
227
+
228
+ {isEvaluating && (
229
+ <Box mt={1} display="flex" alignItems="center">
230
+ <Box className="custom-spinner" />
231
+ <Box ml={1}>Evaluating...</Box>
232
+ </Box>
233
+ )}
234
+
235
+ {evaluationResult && (
236
+ <Box mt={2}>
237
+ <ReactMarkdown>{evaluationResult}</ReactMarkdown>
238
+ </Box>
239
+ )}
240
+ </>
241
  )}
242
+ </Box>
243
  );
244
+ }