promptfoo 0.3.0 → 0.4.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 +26 -7
- package/dist/evaluator.d.ts.map +1 -1
- package/dist/evaluator.js +31 -39
- package/dist/evaluator.js.map +1 -1
- package/dist/main.js +43 -20
- package/dist/main.js.map +1 -1
- package/dist/providers.d.ts.map +1 -1
- package/dist/providers.js +2 -1
- package/dist/providers.js.map +1 -1
- package/dist/types.d.ts +14 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/util.d.ts +3 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +24 -1
- package/dist/util.js.map +1 -1
- package/dist/web/client/assets/index-710f1308.css +1 -0
- package/dist/web/client/assets/index-900b20c0.js +172 -0
- package/dist/web/client/favicon.ico +0 -0
- package/dist/web/client/index.html +15 -0
- package/dist/web/client/logo.svg +30 -0
- package/dist/web/server.d.ts +2 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +74 -0
- package/dist/web/server.js.map +1 -0
- package/package.json +14 -3
- package/src/evaluator.ts +38 -44
- package/src/main.ts +31 -9
- package/src/providers.ts +2 -1
- package/src/types.ts +16 -1
- package/src/util.ts +27 -1
- package/src/web/client/.eslintrc.cjs +14 -0
- package/src/web/client/index.html +13 -0
- package/src/web/client/package.json +37 -0
- package/src/web/client/public/favicon.ico +0 -0
- package/src/web/client/public/logo.svg +30 -0
- package/src/web/client/src/App.css +0 -0
- package/src/web/client/src/App.tsx +43 -0
- package/src/web/client/src/Logo.css +13 -0
- package/src/web/client/src/Logo.tsx +11 -0
- package/src/web/client/src/NavBar.css +3 -0
- package/src/web/client/src/NavBar.tsx +11 -0
- package/src/web/client/src/ResultsTable.css +133 -0
- package/src/web/client/src/ResultsTable.tsx +261 -0
- package/src/web/client/src/ResultsView.tsx +110 -0
- package/src/web/client/src/index.css +35 -0
- package/src/web/client/src/main.tsx +10 -0
- package/src/web/client/src/store.ts +13 -0
- package/src/web/client/src/types.ts +14 -0
- package/src/web/client/src/vite-env.d.ts +1 -0
- package/src/web/client/tsconfig.json +24 -0
- package/src/web/client/tsconfig.node.json +10 -0
- package/src/web/client/vite.config.ts +7 -0
- package/src/web/server.ts +96 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { io as SocketIOClient } from 'socket.io-client';
|
|
4
|
+
|
|
5
|
+
import ResultsView from './ResultsView.js';
|
|
6
|
+
import NavBar from './NavBar.js';
|
|
7
|
+
import { useStore } from './store.js';
|
|
8
|
+
|
|
9
|
+
import './App.css';
|
|
10
|
+
|
|
11
|
+
function App() {
|
|
12
|
+
const { table, setTable } = useStore();
|
|
13
|
+
const [loaded, setLoaded] = React.useState<boolean>(false);
|
|
14
|
+
|
|
15
|
+
React.useEffect(() => {
|
|
16
|
+
const socket = SocketIOClient(`http://${window.location.host}`);
|
|
17
|
+
//const socket = SocketIOClient(`http://localhost:15500`);
|
|
18
|
+
|
|
19
|
+
socket.on('init', (data) => {
|
|
20
|
+
console.log('Initialized socket connection');
|
|
21
|
+
setLoaded(true);
|
|
22
|
+
setTable(data.table);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
socket.on('update', (data) => {
|
|
26
|
+
console.log('Received data update');
|
|
27
|
+
setTable(data.table);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return () => {
|
|
31
|
+
socket.disconnect();
|
|
32
|
+
};
|
|
33
|
+
}, [loaded, setTable]);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<NavBar />
|
|
38
|
+
{loaded && table ? <ResultsView /> : <div>Loading...</div>}
|
|
39
|
+
</>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default App;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
* {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
html {
|
|
6
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif,
|
|
7
|
+
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
|
8
|
+
font-size: 16px;
|
|
9
|
+
background-color: var(--background-color);
|
|
10
|
+
color: var(--text-color);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
table,
|
|
14
|
+
.divTable {
|
|
15
|
+
border: 1px solid var(--table-border-color);
|
|
16
|
+
border-collapse: collapse;
|
|
17
|
+
width: 100%;
|
|
18
|
+
|
|
19
|
+
margin: 1rem 0;
|
|
20
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.tr {
|
|
24
|
+
display: flex;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
tr,
|
|
28
|
+
.tr {
|
|
29
|
+
width: fit-content;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
tr:hover,
|
|
33
|
+
.tr:hover {
|
|
34
|
+
background-color: rgba(0, 0, 0, 0.05);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
th,
|
|
38
|
+
.th,
|
|
39
|
+
td,
|
|
40
|
+
.td {
|
|
41
|
+
position: relative;
|
|
42
|
+
box-shadow: inset 0 0 0 1px var(--border-color);
|
|
43
|
+
word-break: break-all;
|
|
44
|
+
vertical-align: top;
|
|
45
|
+
|
|
46
|
+
padding: 1.5rem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
th,
|
|
50
|
+
.th {
|
|
51
|
+
padding: 1rem;
|
|
52
|
+
position: relative;
|
|
53
|
+
text-align: center;
|
|
54
|
+
font-weight: semi-bold;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
tr .cell {
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
tr .cell-rating {
|
|
61
|
+
visibility: hidden;
|
|
62
|
+
position: absolute;
|
|
63
|
+
bottom: 1.25rem;
|
|
64
|
+
right: -1rem;
|
|
65
|
+
line-height: 0;
|
|
66
|
+
font-size: 1.75rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
tr:hover .cell-rating {
|
|
70
|
+
visibility: visible;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
tr .cell-rating .rating {
|
|
74
|
+
cursor: pointer;
|
|
75
|
+
margin-right: 1rem;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
th .smalltext {
|
|
79
|
+
visibility: hidden;
|
|
80
|
+
font-weight: normal;
|
|
81
|
+
font-size: 0.75rem;
|
|
82
|
+
color: var(--smalltext-color);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
th:hover .smalltext {
|
|
86
|
+
visibility: visible;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
td,
|
|
90
|
+
.td {
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
td .status {
|
|
94
|
+
margin-bottom: 0.5rem;
|
|
95
|
+
font-weight: bold;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
td .pass {
|
|
99
|
+
color: var(--pass-color);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
td .fail {
|
|
103
|
+
color: var(--fail-color);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.resizer {
|
|
107
|
+
position: absolute;
|
|
108
|
+
right: 0;
|
|
109
|
+
top: 0;
|
|
110
|
+
height: 100%;
|
|
111
|
+
width: 5px;
|
|
112
|
+
cursor: col-resize;
|
|
113
|
+
user-select: none;
|
|
114
|
+
touch-action: none;
|
|
115
|
+
|
|
116
|
+
background: var(--text-color);
|
|
117
|
+
opacity: 0.5;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.resizer.isResizing {
|
|
121
|
+
background: var(--text-color);
|
|
122
|
+
opacity: 1;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@media (hover: hover) {
|
|
126
|
+
.resizer {
|
|
127
|
+
opacity: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
*:hover > .resizer {
|
|
131
|
+
opacity: 1;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import './index.css';
|
|
4
|
+
|
|
5
|
+
import invariant from 'tiny-invariant';
|
|
6
|
+
import {
|
|
7
|
+
createColumnHelper,
|
|
8
|
+
flexRender,
|
|
9
|
+
getCoreRowModel,
|
|
10
|
+
useReactTable,
|
|
11
|
+
} from '@tanstack/react-table';
|
|
12
|
+
|
|
13
|
+
import { useStore } from './store.js';
|
|
14
|
+
|
|
15
|
+
import type { CellContext, VisibilityState } from '@tanstack/table-core';
|
|
16
|
+
|
|
17
|
+
import type { EvalRow } from './types.js';
|
|
18
|
+
|
|
19
|
+
import './ResultsTable.css';
|
|
20
|
+
|
|
21
|
+
interface TruncatedTextProps {
|
|
22
|
+
text: string;
|
|
23
|
+
maxLength: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function TruncatedText({ text, maxLength }: TruncatedTextProps) {
|
|
27
|
+
const [isTruncated, setIsTruncated] = React.useState<boolean>(true);
|
|
28
|
+
|
|
29
|
+
const toggleTruncate = () => {
|
|
30
|
+
setIsTruncated(!isTruncated);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const renderTruncatedText = () => {
|
|
34
|
+
if (text.length <= maxLength) {
|
|
35
|
+
return text;
|
|
36
|
+
}
|
|
37
|
+
if (isTruncated) {
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
<span style={{ cursor: 'pointer' }} onClick={toggleTruncate}>
|
|
41
|
+
{text.substring(0, maxLength)} ...
|
|
42
|
+
</span>
|
|
43
|
+
</>
|
|
44
|
+
);
|
|
45
|
+
} else {
|
|
46
|
+
return (
|
|
47
|
+
<>
|
|
48
|
+
<span style={{ cursor: 'pointer' }} onClick={toggleTruncate}>
|
|
49
|
+
{text}
|
|
50
|
+
</span>
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return <div>{renderTruncatedText()}</div>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface PromptOutputProps {
|
|
60
|
+
text: string;
|
|
61
|
+
maxTextLength: number;
|
|
62
|
+
rowIndex: number;
|
|
63
|
+
promptIndex: number;
|
|
64
|
+
onRating: (rowIndex: number, promptIndex: number, isPass: boolean) => void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function PromptOutput({ text, maxTextLength, rowIndex, promptIndex, onRating }: PromptOutputProps) {
|
|
68
|
+
const isPass = text.startsWith('[PASS] ');
|
|
69
|
+
const isFail = text.startsWith('[FAIL] ');
|
|
70
|
+
let chunks: string[] = [];
|
|
71
|
+
if (isPass) {
|
|
72
|
+
text = text.substring(7);
|
|
73
|
+
} else if (isFail) {
|
|
74
|
+
if (text.includes('---')) {
|
|
75
|
+
chunks = text.split('---');
|
|
76
|
+
text = chunks.slice(1).join('---');
|
|
77
|
+
} else {
|
|
78
|
+
chunks = ['[FAIL]'];
|
|
79
|
+
if (text.startsWith('[FAIL] ')) {
|
|
80
|
+
text = text.substring(7);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const handleClick = (isPass: boolean) => {
|
|
86
|
+
onRating(rowIndex, promptIndex, isPass);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<>
|
|
91
|
+
<div className="cell">
|
|
92
|
+
{isPass && <div className="status pass">[PASS]</div>}
|
|
93
|
+
{isFail && <div className="status fail">{chunks[0]}</div>}{' '}
|
|
94
|
+
<TruncatedText text={text} maxLength={maxTextLength} />
|
|
95
|
+
</div>
|
|
96
|
+
<div className="cell-rating">
|
|
97
|
+
<span className="rating" onClick={() => handleClick(true)}>
|
|
98
|
+
👍
|
|
99
|
+
</span>
|
|
100
|
+
<span className="rating" onClick={() => handleClick(false)}>
|
|
101
|
+
👎
|
|
102
|
+
</span>
|
|
103
|
+
</div>
|
|
104
|
+
</>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function TableHeader({ text, maxLength, smallText }: TruncatedTextProps & { smallText: string }) {
|
|
109
|
+
return (
|
|
110
|
+
<div>
|
|
111
|
+
<TruncatedText text={text} maxLength={maxLength} />
|
|
112
|
+
<span className="smalltext">{smallText}</span>
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
interface ResultsViewProps {
|
|
118
|
+
maxTextLength: number;
|
|
119
|
+
columnVisibility: VisibilityState;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export default function ResultsTable({ maxTextLength, columnVisibility }: ResultsViewProps) {
|
|
123
|
+
const { table, setTable } = useStore();
|
|
124
|
+
invariant(table, 'Table should be defined');
|
|
125
|
+
const { head, body } = table;
|
|
126
|
+
// TODO(ian): Correctly plumb through the results instead of parsing the string.
|
|
127
|
+
const numGood = head.prompts.map((_, idx) =>
|
|
128
|
+
body.reduce((acc, row) => {
|
|
129
|
+
return acc + (row.outputs[idx].startsWith('[PASS]') ? 1 : 0);
|
|
130
|
+
}, 0),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const handleRating = (rowIndex: number, promptIndex: number, isPass: boolean) => {
|
|
134
|
+
const updatedData = [...body];
|
|
135
|
+
const updatedRow = { ...updatedData[rowIndex] };
|
|
136
|
+
const updatedOutputs = [...updatedRow.outputs];
|
|
137
|
+
const updatedOutput = isPass
|
|
138
|
+
? `[PASS] ${updatedOutputs[promptIndex].replace(/^\[(PASS|FAIL)\] /, '')}`
|
|
139
|
+
: `[FAIL] ${updatedOutputs[promptIndex].replace(/^\[(PASS|FAIL)\] /, '')}`;
|
|
140
|
+
updatedOutputs[promptIndex] = updatedOutput;
|
|
141
|
+
updatedRow.outputs = updatedOutputs;
|
|
142
|
+
updatedData[rowIndex] = updatedRow;
|
|
143
|
+
setTable({
|
|
144
|
+
head,
|
|
145
|
+
body: updatedData,
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const columnHelper = createColumnHelper<EvalRow>();
|
|
150
|
+
const columns = [
|
|
151
|
+
columnHelper.group({
|
|
152
|
+
id: 'prompts',
|
|
153
|
+
header: () => <span>Prompts</span>,
|
|
154
|
+
columns: head.prompts.map((prompt, idx) =>
|
|
155
|
+
columnHelper.accessor((row: EvalRow) => row.outputs[idx], {
|
|
156
|
+
id: `Prompt ${idx + 1}`,
|
|
157
|
+
header: () => (
|
|
158
|
+
<>
|
|
159
|
+
<TableHeader
|
|
160
|
+
smallText={`Prompt ${idx + 1}`}
|
|
161
|
+
text={prompt}
|
|
162
|
+
maxLength={maxTextLength}
|
|
163
|
+
/>
|
|
164
|
+
{numGood[idx]} / {body.length} 👍
|
|
165
|
+
</>
|
|
166
|
+
),
|
|
167
|
+
cell: (info: CellContext<EvalRow, string>) => (
|
|
168
|
+
<PromptOutput
|
|
169
|
+
text={info.getValue()}
|
|
170
|
+
maxTextLength={maxTextLength}
|
|
171
|
+
rowIndex={info.row.index}
|
|
172
|
+
promptIndex={idx}
|
|
173
|
+
onRating={handleRating}
|
|
174
|
+
/>
|
|
175
|
+
),
|
|
176
|
+
}),
|
|
177
|
+
),
|
|
178
|
+
}),
|
|
179
|
+
columnHelper.group({
|
|
180
|
+
id: 'vars',
|
|
181
|
+
header: () => <span>Variables</span>,
|
|
182
|
+
columns: head.vars.map((varName, idx) =>
|
|
183
|
+
columnHelper.accessor((row: EvalRow) => row.vars[idx], {
|
|
184
|
+
id: `Variable ${idx + 1}`,
|
|
185
|
+
header: () => (
|
|
186
|
+
<TableHeader
|
|
187
|
+
smallText={`Variable ${idx + 1}`}
|
|
188
|
+
text={varName}
|
|
189
|
+
maxLength={maxTextLength}
|
|
190
|
+
/>
|
|
191
|
+
),
|
|
192
|
+
cell: (info: CellContext<EvalRow, string>) => (
|
|
193
|
+
<TruncatedText text={info.getValue()} maxLength={maxTextLength} />
|
|
194
|
+
),
|
|
195
|
+
}),
|
|
196
|
+
),
|
|
197
|
+
}),
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
const reactTable = useReactTable({
|
|
201
|
+
data: body,
|
|
202
|
+
columns,
|
|
203
|
+
columnResizeMode: 'onChange',
|
|
204
|
+
getCoreRowModel: getCoreRowModel(),
|
|
205
|
+
|
|
206
|
+
state: {
|
|
207
|
+
columnVisibility,
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<table>
|
|
213
|
+
<thead>
|
|
214
|
+
{reactTable.getHeaderGroups().map((headerGroup) => (
|
|
215
|
+
<tr key={headerGroup.id}>
|
|
216
|
+
{headerGroup.headers.map((header) => (
|
|
217
|
+
<th
|
|
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
|
|
230
|
+
{...{
|
|
231
|
+
onMouseDown: header.getResizeHandler(),
|
|
232
|
+
onTouchStart: header.getResizeHandler(),
|
|
233
|
+
className: `resizer ${header.column.getIsResizing() ? 'isResizing' : ''}`,
|
|
234
|
+
}}
|
|
235
|
+
/>
|
|
236
|
+
</th>
|
|
237
|
+
))}
|
|
238
|
+
</tr>
|
|
239
|
+
))}
|
|
240
|
+
</thead>
|
|
241
|
+
<tbody>
|
|
242
|
+
{reactTable.getRowModel().rows.map((row) => (
|
|
243
|
+
<tr key={row.id}>
|
|
244
|
+
{row.getVisibleCells().map((cell) => (
|
|
245
|
+
<td
|
|
246
|
+
{...{
|
|
247
|
+
key: cell.id,
|
|
248
|
+
style: {
|
|
249
|
+
width: cell.column.getSize(),
|
|
250
|
+
},
|
|
251
|
+
}}
|
|
252
|
+
>
|
|
253
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
254
|
+
</td>
|
|
255
|
+
))}
|
|
256
|
+
</tr>
|
|
257
|
+
))}
|
|
258
|
+
</tbody>
|
|
259
|
+
</table>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import invariant from 'tiny-invariant';
|
|
4
|
+
import Box from '@mui/material/Box';
|
|
5
|
+
import Paper from '@mui/material/Box';
|
|
6
|
+
import Stack from '@mui/material/Stack';
|
|
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';
|
|
12
|
+
import FormControl from '@mui/material/FormControl';
|
|
13
|
+
import ListItemText from '@mui/material/ListItemText';
|
|
14
|
+
import Select, { SelectChangeEvent } from '@mui/material/Select';
|
|
15
|
+
import Checkbox from '@mui/material/Checkbox';
|
|
16
|
+
|
|
17
|
+
import ResultsTable from './ResultsTable.js';
|
|
18
|
+
import { useStore } from './store.js';
|
|
19
|
+
|
|
20
|
+
import type { VisibilityState } from '@tanstack/table-core';
|
|
21
|
+
|
|
22
|
+
export default function ResultsView() {
|
|
23
|
+
const { table } = useStore();
|
|
24
|
+
const [maxTextLength, setMaxTextLength] = React.useState(250);
|
|
25
|
+
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
|
|
26
|
+
const [selectedColumns, setSelectedColumns] = React.useState<string[]>([]);
|
|
27
|
+
|
|
28
|
+
invariant(table, 'Table data must be loaded before rendering ResultsView');
|
|
29
|
+
const { head } = table;
|
|
30
|
+
|
|
31
|
+
const handleChange = (event: SelectChangeEvent<typeof selectedColumns>) => {
|
|
32
|
+
const {
|
|
33
|
+
target: { value },
|
|
34
|
+
} = event;
|
|
35
|
+
setSelectedColumns(typeof value === 'string' ? value.split(',') : value);
|
|
36
|
+
|
|
37
|
+
const allColumns = [
|
|
38
|
+
...head.prompts.map((_, idx) => `Prompt ${idx + 1}`),
|
|
39
|
+
...head.vars.map((_, idx) => `Variable ${idx + 1}`),
|
|
40
|
+
];
|
|
41
|
+
const newColumnVisibility: VisibilityState = {};
|
|
42
|
+
allColumns.forEach((col) => {
|
|
43
|
+
newColumnVisibility[col] = (typeof value === 'string' ? value.split(',') : value).includes(
|
|
44
|
+
col,
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
setColumnVisibility(newColumnVisibility);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const columnData = [
|
|
51
|
+
...head.prompts.map((_, idx) => ({
|
|
52
|
+
value: `Prompt ${idx + 1}`,
|
|
53
|
+
label: `Prompt ${idx + 1}`,
|
|
54
|
+
group: 'Prompts',
|
|
55
|
+
})),
|
|
56
|
+
...head.vars.map((_, idx) => ({
|
|
57
|
+
value: `Variable ${idx + 1}`,
|
|
58
|
+
label: `Variable ${idx + 1}`,
|
|
59
|
+
group: 'Variables',
|
|
60
|
+
})),
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
// Set all columns as selected by default
|
|
64
|
+
React.useEffect(() => {
|
|
65
|
+
setSelectedColumns([
|
|
66
|
+
...head.prompts.map((_, idx) => `Prompt ${idx + 1}`),
|
|
67
|
+
...head.vars.map((_, idx) => `Variable ${idx + 1}`),
|
|
68
|
+
]);
|
|
69
|
+
}, [head]);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div>
|
|
73
|
+
<Paper py="md">
|
|
74
|
+
<Stack direction="row" spacing={2} alignItems="center">
|
|
75
|
+
<Box>
|
|
76
|
+
<FormControl sx={{ m: 1, minWidth: 300 }} size="small">
|
|
77
|
+
<InputLabel id="visible-columns-label">Visible columns</InputLabel>
|
|
78
|
+
<Select
|
|
79
|
+
labelId="visible-columns-label"
|
|
80
|
+
id="visible-columns"
|
|
81
|
+
multiple
|
|
82
|
+
value={selectedColumns}
|
|
83
|
+
onChange={handleChange}
|
|
84
|
+
input={<OutlinedInput label="Visible columns" />}
|
|
85
|
+
renderValue={(selected: string[]) => selected.join(', ')}
|
|
86
|
+
>
|
|
87
|
+
{columnData.map((column) => (
|
|
88
|
+
<MenuItem dense key={column.value} value={column.value}>
|
|
89
|
+
<Checkbox checked={selectedColumns.indexOf(column.value) > -1} />
|
|
90
|
+
<ListItemText primary={column.label} />
|
|
91
|
+
</MenuItem>
|
|
92
|
+
))}
|
|
93
|
+
</Select>
|
|
94
|
+
</FormControl>
|
|
95
|
+
</Box>
|
|
96
|
+
<Box>
|
|
97
|
+
<Typography mt={2}>Max text length: {maxTextLength}</Typography>
|
|
98
|
+
<Slider
|
|
99
|
+
min={25}
|
|
100
|
+
max={1000}
|
|
101
|
+
value={maxTextLength}
|
|
102
|
+
onChange={(_, val: number | number[]) => setMaxTextLength(val as number)}
|
|
103
|
+
/>
|
|
104
|
+
</Box>
|
|
105
|
+
</Stack>
|
|
106
|
+
</Paper>
|
|
107
|
+
<ResultsTable maxTextLength={maxTextLength} columnVisibility={columnVisibility} />
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
3
|
+
|
|
4
|
+
font-synthesis: none;
|
|
5
|
+
text-rendering: optimizeLegibility;
|
|
6
|
+
-webkit-font-smoothing: antialiased;
|
|
7
|
+
-moz-osx-font-smoothing: grayscale;
|
|
8
|
+
-webkit-text-size-adjust: 100%;
|
|
9
|
+
|
|
10
|
+
/* Light mode colors */
|
|
11
|
+
--background-color: #ffffff;
|
|
12
|
+
--text-color: #404040;
|
|
13
|
+
--border-color: lightgray;
|
|
14
|
+
--table-border-color: lightgray;
|
|
15
|
+
--pass-color: green;
|
|
16
|
+
--fail-color: #ad0000;
|
|
17
|
+
--smalltext-color: gray;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Dark mode colors */
|
|
21
|
+
@media (prefers-color-scheme: dark) {
|
|
22
|
+
:root {
|
|
23
|
+
--background-color: #1a1a1a;
|
|
24
|
+
--text-color: #f0f0f0;
|
|
25
|
+
--border-color: #444444;
|
|
26
|
+
--table-border-color: #444444;
|
|
27
|
+
--pass-color: #4caf50;
|
|
28
|
+
--fail-color: #f44336;
|
|
29
|
+
--smalltext-color: #888888;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
html {
|
|
34
|
+
font-size: calc(14px + (18 - 14) * ((100vw - 300px) / (1600 - 300)));
|
|
35
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import App from './App.tsx';
|
|
4
|
+
import './index.css';
|
|
5
|
+
|
|
6
|
+
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
|
7
|
+
<React.StrictMode>
|
|
8
|
+
<App />
|
|
9
|
+
</React.StrictMode>,
|
|
10
|
+
);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import create from 'zustand';
|
|
2
|
+
|
|
3
|
+
import type { EvalTable } from './types.js';
|
|
4
|
+
|
|
5
|
+
interface TableState {
|
|
6
|
+
table: EvalTable | null;
|
|
7
|
+
setTable: (table: EvalTable | null) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const useStore = create<TableState>((set) => ({
|
|
11
|
+
table: null,
|
|
12
|
+
setTable: (table: EvalTable | null) => set(() => ({ table })),
|
|
13
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
|
|
8
|
+
/* Bundler mode */
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
|
|
16
|
+
/* Linting */
|
|
17
|
+
"strict": true,
|
|
18
|
+
"noUnusedLocals": true,
|
|
19
|
+
"noUnusedParameters": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true
|
|
21
|
+
},
|
|
22
|
+
"include": ["src"],
|
|
23
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
24
|
+
}
|