promptfoo 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -35
- package/dist/package.json +87 -0
- package/dist/src/__mocks__/esm.d.ts.map +1 -0
- package/dist/src/__mocks__/esm.js.map +1 -0
- package/dist/{assertions.d.ts → src/assertions.d.ts} +1 -1
- package/dist/src/assertions.d.ts.map +1 -0
- package/dist/src/assertions.js +374 -0
- package/dist/src/assertions.js.map +1 -0
- package/dist/src/cache.d.ts.map +1 -0
- package/dist/src/cache.js.map +1 -0
- package/dist/src/esm.d.ts.map +1 -0
- package/dist/src/esm.js.map +1 -0
- package/dist/src/evaluator.d.ts.map +1 -0
- package/dist/{evaluator.js → src/evaluator.js} +3 -1
- package/dist/src/evaluator.js.map +1 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/{index.js → src/index.js} +10 -7
- package/dist/src/index.js.map +1 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/main.d.ts.map +1 -0
- package/dist/{main.js → src/main.js} +35 -13
- package/dist/src/main.js.map +1 -0
- package/dist/src/onboarding.d.ts.map +1 -0
- package/dist/src/onboarding.js.map +1 -0
- package/dist/src/prompts.d.ts.map +1 -0
- package/dist/src/prompts.js.map +1 -0
- package/dist/src/providers/localai.d.ts.map +1 -0
- package/dist/src/providers/localai.js.map +1 -0
- package/dist/src/providers/openai.d.ts.map +1 -0
- package/dist/src/providers/openai.js.map +1 -0
- package/dist/src/providers/shared.d.ts.map +1 -0
- package/dist/src/providers/shared.js.map +1 -0
- package/dist/src/providers.d.ts.map +1 -0
- package/dist/src/providers.js.map +1 -0
- package/dist/src/suggestions.d.ts.map +1 -0
- package/dist/src/suggestions.js.map +1 -0
- package/dist/src/telemetry.d.ts +10 -0
- package/dist/src/telemetry.d.ts.map +1 -0
- package/dist/src/telemetry.js +48 -0
- package/dist/src/telemetry.js.map +1 -0
- package/dist/{types.d.ts → src/types.d.ts} +6 -2
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/updates.d.ts +3 -0
- package/dist/src/updates.d.ts.map +1 -0
- package/dist/src/updates.js +36 -0
- package/dist/src/updates.js.map +1 -0
- package/dist/{util.d.ts → src/util.d.ts} +3 -3
- package/dist/src/util.d.ts.map +1 -0
- package/dist/{util.js → src/util.js} +12 -5
- package/dist/src/util.js.map +1 -0
- package/dist/src/web/client/assets/index-87905193.css +1 -0
- package/dist/src/web/client/assets/index-eb6d3769.js +199 -0
- package/dist/src/web/client/assets/js-yaml-8bbf9398.js +32 -0
- package/dist/{web → src/web}/client/index.html +2 -2
- package/dist/src/web/server.d.ts.map +1 -0
- package/dist/{web → src/web}/server.js +3 -4
- package/dist/src/web/server.js.map +1 -0
- package/package.json +13 -9
- package/src/assertions.ts +247 -41
- package/src/evaluator.ts +5 -2
- package/src/index.ts +7 -4
- package/src/main.ts +50 -13
- package/src/telemetry.ts +57 -0
- package/src/types.ts +23 -2
- package/src/updates.ts +37 -0
- package/src/util.ts +28 -6
- package/src/web/client/package-lock.json +3 -6
- package/src/web/client/package.json +1 -0
- package/src/web/client/src/App.tsx +32 -12
- package/src/web/client/src/ConfigModal.tsx +81 -0
- package/src/web/client/src/ResultsTable.css +18 -6
- package/src/web/client/src/ResultsTable.tsx +101 -35
- package/src/web/client/src/ResultsView.tsx +148 -12
- package/src/web/client/src/ShareModal.tsx +70 -0
- package/src/web/client/src/index.css +6 -0
- package/src/web/client/src/store.ts +6 -1
- package/src/web/client/src/types.ts +4 -0
- package/src/web/server.ts +3 -7
- package/dist/__mocks__/esm.d.ts.map +0 -1
- package/dist/__mocks__/esm.js.map +0 -1
- package/dist/assertions.d.ts.map +0 -1
- package/dist/assertions.js +0 -233
- package/dist/assertions.js.map +0 -1
- package/dist/cache.d.ts.map +0 -1
- package/dist/cache.js.map +0 -1
- package/dist/esm.d.ts.map +0 -1
- package/dist/esm.js.map +0 -1
- package/dist/evaluator.d.ts.map +0 -1
- package/dist/evaluator.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/main.d.ts.map +0 -1
- package/dist/main.js.map +0 -1
- package/dist/onboarding.d.ts.map +0 -1
- package/dist/onboarding.js.map +0 -1
- package/dist/prompts.d.ts.map +0 -1
- package/dist/prompts.js.map +0 -1
- package/dist/providers/localai.d.ts.map +0 -1
- package/dist/providers/localai.js.map +0 -1
- package/dist/providers/openai.d.ts.map +0 -1
- package/dist/providers/openai.js.map +0 -1
- package/dist/providers/shared.d.ts.map +0 -1
- package/dist/providers/shared.js.map +0 -1
- package/dist/providers.d.ts.map +0 -1
- package/dist/providers.js.map +0 -1
- package/dist/suggestions.d.ts.map +0 -1
- package/dist/suggestions.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/util.d.ts.map +0 -1
- package/dist/util.js.map +0 -1
- package/dist/web/client/assets/index-9a9ba400.css +0 -1
- package/dist/web/client/assets/index-b72d3ca9.js +0 -172
- package/dist/web/server.d.ts.map +0 -1
- package/dist/web/server.js.map +0 -1
- /package/dist/{__mocks__ → src/__mocks__}/esm.d.ts +0 -0
- /package/dist/{__mocks__ → src/__mocks__}/esm.js +0 -0
- /package/dist/{cache.d.ts → src/cache.d.ts} +0 -0
- /package/dist/{cache.js → src/cache.js} +0 -0
- /package/dist/{esm.d.ts → src/esm.d.ts} +0 -0
- /package/dist/{esm.js → src/esm.js} +0 -0
- /package/dist/{evaluator.d.ts → src/evaluator.d.ts} +0 -0
- /package/dist/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/{logger.d.ts → src/logger.d.ts} +0 -0
- /package/dist/{logger.js → src/logger.js} +0 -0
- /package/dist/{main.d.ts → src/main.d.ts} +0 -0
- /package/dist/{onboarding.d.ts → src/onboarding.d.ts} +0 -0
- /package/dist/{onboarding.js → src/onboarding.js} +0 -0
- /package/dist/{prompts.d.ts → src/prompts.d.ts} +0 -0
- /package/dist/{prompts.js → src/prompts.js} +0 -0
- /package/dist/{providers → src/providers}/localai.d.ts +0 -0
- /package/dist/{providers → src/providers}/localai.js +0 -0
- /package/dist/{providers → src/providers}/openai.d.ts +0 -0
- /package/dist/{providers → src/providers}/openai.js +0 -0
- /package/dist/{providers → src/providers}/shared.d.ts +0 -0
- /package/dist/{providers → src/providers}/shared.js +0 -0
- /package/dist/{providers.d.ts → src/providers.d.ts} +0 -0
- /package/dist/{providers.js → src/providers.js} +0 -0
- /package/dist/{suggestions.d.ts → src/suggestions.d.ts} +0 -0
- /package/dist/{suggestions.js → src/suggestions.js} +0 -0
- /package/dist/{types.js → src/types.js} +0 -0
- /package/dist/{web → src/web}/client/favicon.ico +0 -0
- /package/dist/{web → src/web}/client/logo.svg +0 -0
- /package/dist/{web → src/web}/server.d.ts +0 -0
|
@@ -9,12 +9,14 @@ import {
|
|
|
9
9
|
getCoreRowModel,
|
|
10
10
|
useReactTable,
|
|
11
11
|
} from '@tanstack/react-table';
|
|
12
|
+
import Checkbox from '@mui/material/Checkbox';
|
|
13
|
+
import FormControlLabel from '@mui/material/FormControlLabel';
|
|
12
14
|
|
|
13
15
|
import { useStore } from './store.js';
|
|
14
16
|
|
|
15
17
|
import type { CellContext, VisibilityState } from '@tanstack/table-core';
|
|
16
18
|
|
|
17
|
-
import type { EvalRow } from './types.js';
|
|
19
|
+
import type { EvalRow, FilterMode } from './types.js';
|
|
18
20
|
|
|
19
21
|
import './ResultsTable.css';
|
|
20
22
|
|
|
@@ -114,12 +116,23 @@ function TableHeader({ text, maxLength, smallText }: TruncatedTextProps & { smal
|
|
|
114
116
|
);
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
interface
|
|
119
|
+
interface ResultsTableProps {
|
|
118
120
|
maxTextLength: number;
|
|
119
121
|
columnVisibility: VisibilityState;
|
|
122
|
+
wordBreak: 'break-word' | 'break-all';
|
|
123
|
+
filterMode: FilterMode;
|
|
124
|
+
failureFilter: { [key: string]: boolean };
|
|
125
|
+
onFailureFilterToggle: (columnId: string, checked: boolean) => void;
|
|
120
126
|
}
|
|
121
127
|
|
|
122
|
-
export default function ResultsTable({
|
|
128
|
+
export default function ResultsTable({
|
|
129
|
+
maxTextLength,
|
|
130
|
+
columnVisibility,
|
|
131
|
+
wordBreak,
|
|
132
|
+
filterMode,
|
|
133
|
+
failureFilter,
|
|
134
|
+
onFailureFilterToggle,
|
|
135
|
+
}: ResultsTableProps) {
|
|
123
136
|
const { table, setTable } = useStore();
|
|
124
137
|
invariant(table, 'Table should be defined');
|
|
125
138
|
const { head, body } = table;
|
|
@@ -146,6 +159,10 @@ export default function ResultsTable({ maxTextLength, columnVisibility }: Result
|
|
|
146
159
|
});
|
|
147
160
|
};
|
|
148
161
|
|
|
162
|
+
const highestPassingIndex = numGood.reduce((maxIndex, currentPassCount, currentIndex, array) => {
|
|
163
|
+
return currentPassCount > array[maxIndex] ? currentIndex : maxIndex;
|
|
164
|
+
}, 0);
|
|
165
|
+
const highestPassingCount = numGood[highestPassingIndex];
|
|
149
166
|
const columnHelper = createColumnHelper<EvalRow>();
|
|
150
167
|
const columns = [
|
|
151
168
|
columnHelper.group({
|
|
@@ -164,6 +181,8 @@ export default function ResultsTable({ maxTextLength, columnVisibility }: Result
|
|
|
164
181
|
cell: (info: CellContext<EvalRow, string>) => (
|
|
165
182
|
<TruncatedText text={info.getValue()} maxLength={maxTextLength} />
|
|
166
183
|
),
|
|
184
|
+
// Minimize the size of Variable columns.
|
|
185
|
+
size: 50,
|
|
167
186
|
}),
|
|
168
187
|
),
|
|
169
188
|
}),
|
|
@@ -173,16 +192,41 @@ export default function ResultsTable({ maxTextLength, columnVisibility }: Result
|
|
|
173
192
|
columns: head.prompts.map((prompt, idx) =>
|
|
174
193
|
columnHelper.accessor((row: EvalRow) => row.outputs[idx], {
|
|
175
194
|
id: `Prompt ${idx + 1}`,
|
|
176
|
-
header: () =>
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
195
|
+
header: () => {
|
|
196
|
+
const pct = ((numGood[idx] / body.length) * 100.0).toFixed(2);
|
|
197
|
+
const isHighestPassing =
|
|
198
|
+
numGood[idx] === highestPassingCount && highestPassingCount !== 0;
|
|
199
|
+
const columnId = `Prompt ${idx + 1}`;
|
|
200
|
+
const isChecked = failureFilter[columnId] || false;
|
|
201
|
+
return (
|
|
202
|
+
<>
|
|
203
|
+
<TableHeader
|
|
204
|
+
smallText={`Prompt ${idx + 1}`}
|
|
205
|
+
text={prompt}
|
|
206
|
+
maxLength={maxTextLength}
|
|
207
|
+
/>
|
|
208
|
+
{filterMode === 'failures' && (
|
|
209
|
+
<FormControlLabel
|
|
210
|
+
sx={{
|
|
211
|
+
'& .MuiFormControlLabel-label': {
|
|
212
|
+
fontSize: '0.75rem',
|
|
213
|
+
},
|
|
214
|
+
}}
|
|
215
|
+
control={
|
|
216
|
+
<Checkbox
|
|
217
|
+
checked={isChecked}
|
|
218
|
+
onChange={(event) => onFailureFilterToggle(columnId, event.target.checked)}
|
|
219
|
+
/>
|
|
220
|
+
}
|
|
221
|
+
label="Show failures"
|
|
222
|
+
/>
|
|
223
|
+
)}
|
|
224
|
+
<div className={`summary ${isHighestPassing ? 'highlight' : ''}`}>
|
|
225
|
+
Passing: <strong>{pct}%</strong> ({numGood[idx]} / {body.length})
|
|
226
|
+
</div>
|
|
227
|
+
</>
|
|
228
|
+
);
|
|
229
|
+
},
|
|
186
230
|
cell: (info: CellContext<EvalRow, string>) => (
|
|
187
231
|
<PromptOutput
|
|
188
232
|
text={info.getValue()}
|
|
@@ -197,8 +241,24 @@ export default function ResultsTable({ maxTextLength, columnVisibility }: Result
|
|
|
197
241
|
}),
|
|
198
242
|
];
|
|
199
243
|
|
|
244
|
+
const filteredBody = React.useMemo(() => {
|
|
245
|
+
if (filterMode === 'failures') {
|
|
246
|
+
if (Object.values(failureFilter).every((v) => !v)) {
|
|
247
|
+
return body;
|
|
248
|
+
}
|
|
249
|
+
return body.filter((row) => {
|
|
250
|
+
return row.outputs.some((output, idx) => {
|
|
251
|
+
const columnId = `Prompt ${idx + 1}`;
|
|
252
|
+
const isFail = output.startsWith('[FAIL] ');
|
|
253
|
+
return failureFilter[columnId] && isFail;
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return body;
|
|
258
|
+
}, [body, failureFilter, filterMode]);
|
|
259
|
+
|
|
200
260
|
const reactTable = useReactTable({
|
|
201
|
-
data:
|
|
261
|
+
data: filteredBody,
|
|
202
262
|
columns,
|
|
203
263
|
columnResizeMode: 'onChange',
|
|
204
264
|
getCoreRowModel: getCoreRowModel(),
|
|
@@ -209,32 +269,38 @@ export default function ResultsTable({ maxTextLength, columnVisibility }: Result
|
|
|
209
269
|
});
|
|
210
270
|
|
|
211
271
|
return (
|
|
212
|
-
<table
|
|
272
|
+
<table
|
|
273
|
+
style={{
|
|
274
|
+
wordBreak,
|
|
275
|
+
}}
|
|
276
|
+
>
|
|
213
277
|
<thead>
|
|
214
278
|
{reactTable.getHeaderGroups().map((headerGroup) => (
|
|
215
279
|
<tr key={headerGroup.id} className="header">
|
|
216
|
-
{headerGroup.headers.map((header) =>
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
key: header.id,
|
|
220
|
-
colSpan: header.colSpan,
|
|
221
|
-
style: {
|
|
222
|
-
width: header.getSize(),
|
|
223
|
-
},
|
|
224
|
-
}}
|
|
225
|
-
>
|
|
226
|
-
{header.isPlaceholder
|
|
227
|
-
? null
|
|
228
|
-
: flexRender(header.column.columnDef.header, header.getContext())}
|
|
229
|
-
<div
|
|
280
|
+
{headerGroup.headers.map((header) => {
|
|
281
|
+
return (
|
|
282
|
+
<th
|
|
230
283
|
{...{
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
284
|
+
key: header.id,
|
|
285
|
+
colSpan: header.colSpan,
|
|
286
|
+
style: {
|
|
287
|
+
width: header.getSize(),
|
|
288
|
+
},
|
|
234
289
|
}}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
290
|
+
>
|
|
291
|
+
{header.isPlaceholder
|
|
292
|
+
? null
|
|
293
|
+
: flexRender(header.column.columnDef.header, header.getContext())}
|
|
294
|
+
<div
|
|
295
|
+
{...{
|
|
296
|
+
onMouseDown: header.getResizeHandler(),
|
|
297
|
+
onTouchStart: header.getResizeHandler(),
|
|
298
|
+
className: `resizer ${header.column.getIsResizing() ? 'isResizing' : ''}`,
|
|
299
|
+
}}
|
|
300
|
+
/>
|
|
301
|
+
</th>
|
|
302
|
+
);
|
|
303
|
+
})}
|
|
238
304
|
</tr>
|
|
239
305
|
))}
|
|
240
306
|
</thead>
|
|
@@ -1,23 +1,41 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
3
|
import invariant from 'tiny-invariant';
|
|
4
|
+
import Button from '@mui/material/Button';
|
|
4
5
|
import Box from '@mui/material/Box';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import Slider from '@mui/material/Slider';
|
|
8
|
-
import Typography from '@mui/material/Typography';
|
|
9
|
-
import OutlinedInput from '@mui/material/OutlinedInput';
|
|
10
|
-
import InputLabel from '@mui/material/InputLabel';
|
|
11
|
-
import MenuItem from '@mui/material/MenuItem';
|
|
6
|
+
import Checkbox from '@mui/material/Checkbox';
|
|
7
|
+
import CircularProgress from '@mui/material/CircularProgress';
|
|
12
8
|
import FormControl from '@mui/material/FormControl';
|
|
9
|
+
import FormControlLabel from '@mui/material/FormControlLabel';
|
|
10
|
+
import InputLabel from '@mui/material/InputLabel';
|
|
13
11
|
import ListItemText from '@mui/material/ListItemText';
|
|
12
|
+
import MenuItem from '@mui/material/MenuItem';
|
|
13
|
+
import OutlinedInput from '@mui/material/OutlinedInput';
|
|
14
|
+
import Paper from '@mui/material/Box';
|
|
14
15
|
import Select, { SelectChangeEvent } from '@mui/material/Select';
|
|
15
|
-
import
|
|
16
|
+
import Slider from '@mui/material/Slider';
|
|
17
|
+
import Stack from '@mui/material/Stack';
|
|
18
|
+
import Tooltip from '@mui/material/Tooltip';
|
|
19
|
+
import Typography from '@mui/material/Typography';
|
|
20
|
+
import ShareIcon from '@mui/icons-material/Share';
|
|
21
|
+
import VisibilityIcon from '@mui/icons-material/Visibility';
|
|
22
|
+
import { styled } from '@mui/system';
|
|
16
23
|
|
|
17
24
|
import ResultsTable from './ResultsTable.js';
|
|
25
|
+
import ConfigModal from './ConfigModal';
|
|
26
|
+
import ShareModal from './ShareModal';
|
|
18
27
|
import { useStore } from './store.js';
|
|
19
28
|
|
|
20
29
|
import type { VisibilityState } from '@tanstack/table-core';
|
|
30
|
+
import type { FilterMode } from './types.js';
|
|
31
|
+
|
|
32
|
+
const ResponsiveStack = styled(Stack)(({ theme }) => ({
|
|
33
|
+
maxWidth: '100%',
|
|
34
|
+
flexWrap: 'wrap',
|
|
35
|
+
[theme.breakpoints.down('sm')]: {
|
|
36
|
+
flexDirection: 'column',
|
|
37
|
+
},
|
|
38
|
+
}));
|
|
21
39
|
|
|
22
40
|
export default function ResultsView() {
|
|
23
41
|
const { table } = useStore();
|
|
@@ -25,6 +43,62 @@ export default function ResultsView() {
|
|
|
25
43
|
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
|
|
26
44
|
const [selectedColumns, setSelectedColumns] = React.useState<string[]>([]);
|
|
27
45
|
|
|
46
|
+
const [failureFilter, setFailureFilter] = React.useState<{ [key: string]: boolean }>({});
|
|
47
|
+
const handleFailureFilterToggle = (columnId: string, checked: boolean) => {
|
|
48
|
+
setFailureFilter((prevFailureFilter) => ({ ...prevFailureFilter, [columnId]: checked }));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const [filterMode, setFilterMode] = React.useState<FilterMode>('all');
|
|
52
|
+
const handleFilterModeChange = (event: SelectChangeEvent<unknown>) => {
|
|
53
|
+
const mode = event.target.value as FilterMode;
|
|
54
|
+
setFilterMode(mode);
|
|
55
|
+
|
|
56
|
+
const newFailureFilter: { [key: string]: boolean } = {};
|
|
57
|
+
head.prompts.forEach((_, idx) => {
|
|
58
|
+
const columnId = `Prompt ${idx + 1}`;
|
|
59
|
+
newFailureFilter[columnId] = mode === 'failures';
|
|
60
|
+
});
|
|
61
|
+
setFailureFilter(newFailureFilter);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const [wordBreak, setWordBreak] = React.useState<'break-word' | 'break-all'>('break-all');
|
|
65
|
+
const handleWordBreakChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
66
|
+
setWordBreak(event.target.checked ? 'break-all' : 'break-word');
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const [shareModalOpen, setShareModalOpen] = React.useState(false);
|
|
70
|
+
const [shareUrl, setShareUrl] = React.useState('');
|
|
71
|
+
const [shareLoading, setShareLoading] = React.useState(false);
|
|
72
|
+
|
|
73
|
+
const handleShareButtonClick = async () => {
|
|
74
|
+
setShareLoading(true);
|
|
75
|
+
try {
|
|
76
|
+
const response = await fetch('https://api.promptfoo.dev/eval', {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: {
|
|
79
|
+
'Content-Type': 'application/json',
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify({
|
|
82
|
+
data: {
|
|
83
|
+
version: 1,
|
|
84
|
+
table,
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const { id } = await response.json();
|
|
90
|
+
const shareUrl = `https://app.promptfoo.dev/eval/${id}`;
|
|
91
|
+
setShareUrl(shareUrl);
|
|
92
|
+
setShareModalOpen(true);
|
|
93
|
+
} catch {
|
|
94
|
+
alert('Sorry, something went wrong.');
|
|
95
|
+
} finally {
|
|
96
|
+
setShareLoading(false);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const [configModalOpen, setConfigModalOpen] = React.useState(false);
|
|
101
|
+
|
|
28
102
|
invariant(table, 'Table data must be loaded before rendering ResultsView');
|
|
29
103
|
const { head } = table;
|
|
30
104
|
|
|
@@ -71,9 +145,9 @@ export default function ResultsView() {
|
|
|
71
145
|
return (
|
|
72
146
|
<div>
|
|
73
147
|
<Paper py="md">
|
|
74
|
-
<
|
|
148
|
+
<ResponsiveStack direction="row" spacing={8} alignItems="center">
|
|
75
149
|
<Box>
|
|
76
|
-
<FormControl sx={{ m: 1, minWidth:
|
|
150
|
+
<FormControl sx={{ m: 1, minWidth: 200 }} size="small">
|
|
77
151
|
<InputLabel id="visible-columns-label">Visible columns</InputLabel>
|
|
78
152
|
<Select
|
|
79
153
|
labelId="visible-columns-label"
|
|
@@ -93,6 +167,21 @@ export default function ResultsView() {
|
|
|
93
167
|
</Select>
|
|
94
168
|
</FormControl>
|
|
95
169
|
</Box>
|
|
170
|
+
<Box>
|
|
171
|
+
<FormControl sx={{ minWidth: 180 }} size="small">
|
|
172
|
+
<InputLabel id="failure-filter-mode-label">Filter</InputLabel>
|
|
173
|
+
<Select
|
|
174
|
+
labelId="filter-mode-label"
|
|
175
|
+
id="filter-mode"
|
|
176
|
+
value={filterMode}
|
|
177
|
+
onChange={handleFilterModeChange}
|
|
178
|
+
label="Filter"
|
|
179
|
+
>
|
|
180
|
+
<MenuItem value="all">Show all results</MenuItem>
|
|
181
|
+
<MenuItem value="failures">Show only failures</MenuItem>
|
|
182
|
+
</Select>
|
|
183
|
+
</FormControl>
|
|
184
|
+
</Box>
|
|
96
185
|
<Box>
|
|
97
186
|
<Typography mt={2}>Max text length: {maxTextLength}</Typography>
|
|
98
187
|
<Slider
|
|
@@ -102,9 +191,56 @@ export default function ResultsView() {
|
|
|
102
191
|
onChange={(_, val: number | number[]) => setMaxTextLength(val as number)}
|
|
103
192
|
/>
|
|
104
193
|
</Box>
|
|
105
|
-
|
|
194
|
+
<Box>
|
|
195
|
+
<Tooltip title="Forcing line breaks makes it easier to adjust column widths to your liking">
|
|
196
|
+
<FormControlLabel
|
|
197
|
+
control={
|
|
198
|
+
<Checkbox checked={wordBreak === 'break-all'} onChange={handleWordBreakChange} />
|
|
199
|
+
}
|
|
200
|
+
label="Force line breaks"
|
|
201
|
+
/>
|
|
202
|
+
</Tooltip>
|
|
203
|
+
</Box>
|
|
204
|
+
<Box flexGrow={1} />
|
|
205
|
+
<Box display="flex" justifyContent="flex-end">
|
|
206
|
+
<ResponsiveStack direction="row" spacing={2}>
|
|
207
|
+
<Tooltip title="View config">
|
|
208
|
+
<Button
|
|
209
|
+
color="primary"
|
|
210
|
+
onClick={() => setConfigModalOpen(true)}
|
|
211
|
+
startIcon={<VisibilityIcon />}
|
|
212
|
+
>
|
|
213
|
+
Config
|
|
214
|
+
</Button>
|
|
215
|
+
</Tooltip>
|
|
216
|
+
<Tooltip title="Generate a unique URL that others can access">
|
|
217
|
+
<Button
|
|
218
|
+
color="primary"
|
|
219
|
+
onClick={handleShareButtonClick}
|
|
220
|
+
disabled={shareLoading}
|
|
221
|
+
startIcon={shareLoading ? <CircularProgress size={16} /> : <ShareIcon />}
|
|
222
|
+
>
|
|
223
|
+
Share
|
|
224
|
+
</Button>
|
|
225
|
+
</Tooltip>
|
|
226
|
+
</ResponsiveStack>
|
|
227
|
+
</Box>
|
|
228
|
+
</ResponsiveStack>
|
|
106
229
|
</Paper>
|
|
107
|
-
<ResultsTable
|
|
230
|
+
<ResultsTable
|
|
231
|
+
maxTextLength={maxTextLength}
|
|
232
|
+
columnVisibility={columnVisibility}
|
|
233
|
+
wordBreak={wordBreak}
|
|
234
|
+
filterMode={filterMode}
|
|
235
|
+
failureFilter={failureFilter}
|
|
236
|
+
onFailureFilterToggle={handleFailureFilterToggle}
|
|
237
|
+
/>
|
|
238
|
+
<ConfigModal open={configModalOpen} onClose={() => setConfigModalOpen(false)} />
|
|
239
|
+
<ShareModal
|
|
240
|
+
open={shareModalOpen}
|
|
241
|
+
onClose={() => setShareModalOpen(false)}
|
|
242
|
+
shareUrl={shareUrl}
|
|
243
|
+
/>
|
|
108
244
|
</div>
|
|
109
245
|
);
|
|
110
246
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React, { useRef, useState } from 'react';
|
|
2
|
+
import Dialog from '@mui/material/Dialog';
|
|
3
|
+
import DialogTitle from '@mui/material/DialogTitle';
|
|
4
|
+
import DialogContent from '@mui/material/DialogContent';
|
|
5
|
+
import DialogContentText from '@mui/material/DialogContentText';
|
|
6
|
+
import DialogActions from '@mui/material/DialogActions';
|
|
7
|
+
import Button from '@mui/material/Button';
|
|
8
|
+
import TextField from '@mui/material/TextField';
|
|
9
|
+
import IconButton from '@mui/material/IconButton';
|
|
10
|
+
import FileCopyIcon from '@mui/icons-material/FileCopy';
|
|
11
|
+
import CheckIcon from '@mui/icons-material/Check';
|
|
12
|
+
|
|
13
|
+
interface ShareModalProps {
|
|
14
|
+
open: boolean;
|
|
15
|
+
onClose: () => void;
|
|
16
|
+
shareUrl: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ShareModal: React.FC<ShareModalProps> = ({ open, onClose, shareUrl }) => {
|
|
20
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
21
|
+
const [copied, setCopied] = useState(false);
|
|
22
|
+
|
|
23
|
+
const handleCopyClick = () => {
|
|
24
|
+
if (inputRef.current) {
|
|
25
|
+
inputRef.current.select();
|
|
26
|
+
document.execCommand('copy');
|
|
27
|
+
setCopied(true);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleClose = () => {
|
|
32
|
+
onClose();
|
|
33
|
+
setCopied(false);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Dialog
|
|
38
|
+
open={open}
|
|
39
|
+
onClose={handleClose}
|
|
40
|
+
PaperProps={{ style: { minWidth: 'min(660px, 100%)' } }}
|
|
41
|
+
>
|
|
42
|
+
<DialogTitle>Your eval is ready to share</DialogTitle>
|
|
43
|
+
<DialogContent>
|
|
44
|
+
<TextField
|
|
45
|
+
inputRef={inputRef}
|
|
46
|
+
value={shareUrl}
|
|
47
|
+
fullWidth
|
|
48
|
+
InputProps={{
|
|
49
|
+
readOnly: true,
|
|
50
|
+
endAdornment: (
|
|
51
|
+
<IconButton onClick={handleCopyClick}>
|
|
52
|
+
{copied ? <CheckIcon /> : <FileCopyIcon />}
|
|
53
|
+
</IconButton>
|
|
54
|
+
),
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
<DialogContentText sx={{ fontSize: '0.75rem' }}>
|
|
58
|
+
Shared URLs are deleted after 1 week.
|
|
59
|
+
</DialogContentText>
|
|
60
|
+
</DialogContent>
|
|
61
|
+
<DialogActions>
|
|
62
|
+
<Button onClick={handleClose} color="primary">
|
|
63
|
+
Close
|
|
64
|
+
</Button>
|
|
65
|
+
</DialogActions>
|
|
66
|
+
</Dialog>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export default ShareModal;
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
--pass-color: green;
|
|
16
16
|
--fail-color: #ad0000;
|
|
17
17
|
--smalltext-color: gray;
|
|
18
|
+
--success-background-color: #d1ffd7;
|
|
19
|
+
--variable-background-color: #f7f7f7;
|
|
20
|
+
--header-background-color: #fffdf7;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
/* Dark mode colors */
|
|
@@ -38,6 +41,9 @@
|
|
|
38
41
|
--pass-color: #4caf50;
|
|
39
42
|
--fail-color: #f44336;
|
|
40
43
|
--smalltext-color: #888888;
|
|
44
|
+
--success-background-color: #216d2b;
|
|
45
|
+
--variable-background-color: #333;
|
|
46
|
+
--header-background-color: #333;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
html {
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import create from 'zustand';
|
|
2
2
|
|
|
3
|
-
import type { EvalTable } from './types.js';
|
|
3
|
+
import type { EvalTable, UnifiedConfig } from './types.js';
|
|
4
4
|
|
|
5
5
|
interface TableState {
|
|
6
6
|
table: EvalTable | null;
|
|
7
7
|
setTable: (table: EvalTable | null) => void;
|
|
8
|
+
|
|
9
|
+
config: Partial<UnifiedConfig> | null;
|
|
10
|
+
setConfig: (config: Partial<UnifiedConfig> | null) => void;
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
export const useStore = create<TableState>((set) => ({
|
|
11
14
|
table: null,
|
|
12
15
|
setTable: (table: EvalTable | null) => set(() => ({ table })),
|
|
16
|
+
config: null,
|
|
17
|
+
setConfig: (config: Partial<UnifiedConfig> | null) => set(() => ({ config })),
|
|
13
18
|
}));
|
package/src/web/server.ts
CHANGED
|
@@ -9,13 +9,10 @@ import cors from 'cors';
|
|
|
9
9
|
import opener from 'opener';
|
|
10
10
|
import { Server as SocketIOServer } from 'socket.io';
|
|
11
11
|
|
|
12
|
-
import promptfoo from '../index.js';
|
|
13
12
|
import logger from '../logger';
|
|
14
13
|
import { getDirectory } from '../esm';
|
|
15
14
|
import { getLatestResultsPath } from '../util';
|
|
16
15
|
|
|
17
|
-
import type { Request, Response } from 'express';
|
|
18
|
-
|
|
19
16
|
export function init(port = 15500) {
|
|
20
17
|
const app = express();
|
|
21
18
|
|
|
@@ -35,20 +32,19 @@ export function init(port = 15500) {
|
|
|
35
32
|
const latestJsonPath = getLatestResultsPath();
|
|
36
33
|
const readLatestJson = () => {
|
|
37
34
|
const data = fs.readFileSync(latestJsonPath, 'utf8');
|
|
38
|
-
|
|
39
|
-
return jsonData.table;
|
|
35
|
+
return JSON.parse(data);
|
|
40
36
|
};
|
|
41
37
|
|
|
42
38
|
io.on('connection', (socket) => {
|
|
43
39
|
// Send the initial table data when a client connects
|
|
44
|
-
socket.emit('init',
|
|
40
|
+
socket.emit('init', readLatestJson());
|
|
45
41
|
|
|
46
42
|
// Watch for changes to latest.json and emit the update event
|
|
47
43
|
fs.watch(
|
|
48
44
|
latestJsonPath,
|
|
49
45
|
debounce((event: string) => {
|
|
50
46
|
if (event === 'change') {
|
|
51
|
-
socket.emit('update',
|
|
47
|
+
socket.emit('update', readLatestJson);
|
|
52
48
|
}
|
|
53
49
|
}, 250),
|
|
54
50
|
);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"esm.d.ts","sourceRoot":"","sources":["../../src/__mocks__/esm.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,WAE3B"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"esm.js","sourceRoot":"","sources":["../../src/__mocks__/esm.ts"],"names":[],"mappings":";;;AAAA,SAAgB,YAAY;IAC1B,OAAO,WAAW,CAAC;AACrB,CAAC;AAFD,oCAEC"}
|
package/dist/assertions.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"assertions.d.ts","sourceRoot":"","sources":["../src/assertions.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAY,aAAa,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAMjG,wBAAsB,aAAa,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAyBhG;AAED,wBAAsB,YAAY,CAChC,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,aAAa,CAAC,CA0DxB;AAoBD,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,aAAa,CAAC,CA0CxB;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,aAAa,CAAC,CAgDxB;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAmC/D;;;;;AAED,wBAGE"}
|