promptfoo 0.17.6 → 0.17.8

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.
Files changed (50) hide show
  1. package/dist/package.json +2 -2
  2. package/dist/src/assertions.js +2 -2
  3. package/dist/src/assertions.js.map +1 -1
  4. package/dist/src/evaluator.d.ts.map +1 -1
  5. package/dist/src/evaluator.js +37 -6
  6. package/dist/src/evaluator.js.map +1 -1
  7. package/dist/src/main.js +4 -0
  8. package/dist/src/main.js.map +1 -1
  9. package/dist/src/providers/azureopenai.d.ts +4 -0
  10. package/dist/src/providers/azureopenai.d.ts.map +1 -1
  11. package/dist/src/providers/azureopenai.js +15 -0
  12. package/dist/src/providers/azureopenai.js.map +1 -1
  13. package/dist/src/providers/openai.d.ts +5 -0
  14. package/dist/src/providers/openai.d.ts.map +1 -1
  15. package/dist/src/providers/openai.js +21 -2
  16. package/dist/src/providers/openai.js.map +1 -1
  17. package/dist/src/providers/replicate.d.ts +8 -1
  18. package/dist/src/providers/replicate.d.ts.map +1 -1
  19. package/dist/src/providers/replicate.js +9 -6
  20. package/dist/src/providers/replicate.js.map +1 -1
  21. package/dist/src/providers/shared.d.ts.map +1 -1
  22. package/dist/src/providers/shared.js.map +1 -1
  23. package/dist/src/providers.js +1 -1
  24. package/dist/src/providers.js.map +1 -1
  25. package/dist/src/types.d.ts +6 -1
  26. package/dist/src/types.d.ts.map +1 -1
  27. package/dist/src/util.d.ts +8 -1
  28. package/dist/src/util.d.ts.map +1 -1
  29. package/dist/src/util.js +81 -26
  30. package/dist/src/util.js.map +1 -1
  31. package/dist/src/web/client/assets/{index-13198388.js → index-0c6f887d.js} +25 -25
  32. package/dist/src/web/client/index.html +1 -1
  33. package/dist/src/web/server.d.ts.map +1 -1
  34. package/dist/src/web/server.js +26 -3
  35. package/dist/src/web/server.js.map +1 -1
  36. package/package.json +2 -2
  37. package/src/assertions.ts +2 -2
  38. package/src/evaluator.ts +42 -6
  39. package/src/main.ts +6 -0
  40. package/src/providers/azureopenai.ts +24 -0
  41. package/src/providers/openai.ts +33 -3
  42. package/src/providers/replicate.ts +20 -7
  43. package/src/providers/shared.ts +3 -1
  44. package/src/providers.ts +1 -1
  45. package/src/types.ts +10 -1
  46. package/src/util.ts +95 -27
  47. package/src/web/client/src/App.tsx +24 -1
  48. package/src/web/client/src/ResultsView.tsx +42 -3
  49. package/src/web/server.ts +33 -10
  50. package/src/web/client/package-lock.json +0 -5726
@@ -14,6 +14,7 @@ function App() {
14
14
  const { table, setTable, setConfig } = useStore();
15
15
  const [loaded, setLoaded] = React.useState<boolean>(false);
16
16
  const loadedFromApi = React.useRef(false);
17
+ const [recentFiles, setRecentFiles] = React.useState<string[]>([]);
17
18
 
18
19
  const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
19
20
  const [darkMode, setDarkMode] = React.useState(prefersDarkMode);
@@ -43,6 +44,22 @@ function App() {
43
44
  }
44
45
  }, [prefersDarkMode]);
45
46
 
47
+ const fetchRecentFiles = async () => {
48
+ if (!window.location.href.includes('localhost')) {
49
+ return;
50
+ }
51
+ const resp = await fetch(`http://localhost:15500/results`);
52
+ const body = await resp.json();
53
+ setRecentFiles(body.data);
54
+ };
55
+
56
+ const handleRecentFileSelection = async (file: string) => {
57
+ const resp = await fetch(`http://localhost:15500/results/${file}`);
58
+ const body = await resp.json();
59
+ setTable(body.data.results.table);
60
+ setConfig(body.data.config);
61
+ };
62
+
46
63
  React.useEffect(() => {
47
64
  const fetchEvalData = async (id: string) => {
48
65
  if (loadedFromApi.current) {
@@ -72,12 +89,14 @@ function App() {
72
89
  setLoaded(true);
73
90
  setTable(data.results.table);
74
91
  setConfig(data.config);
92
+ fetchRecentFiles();
75
93
  });
76
94
 
77
95
  socket.on('update', (data) => {
78
96
  console.log('Received data update', data);
79
97
  setTable(data.results.table);
80
98
  setConfig(data.config);
99
+ fetchRecentFiles();
81
100
  });
82
101
  }
83
102
 
@@ -89,7 +108,11 @@ function App() {
89
108
  return (
90
109
  <ThemeProvider theme={theme}>
91
110
  <NavBar darkMode={darkMode} onToggleDarkMode={toggleDarkMode} />
92
- {loaded && table ? <ResultsView /> : <div>Loading...</div>}
111
+ {loaded && table ? (
112
+ <ResultsView recentFiles={recentFiles} onRecentFileSelected={handleRecentFileSelection} />
113
+ ) : (
114
+ <div>Loading...</div>
115
+ )}
93
116
  </ThemeProvider>
94
117
  );
95
118
  }
@@ -37,7 +37,26 @@ const ResponsiveStack = styled(Stack)(({ theme }) => ({
37
37
  },
38
38
  }));
39
39
 
40
- export default function ResultsView() {
40
+ function filenameToDate(filename: string) {
41
+ const dateString = filename.slice('eval-'.length, filename.length - '.json'.length);
42
+ const date = new Date(dateString);
43
+ return date.toLocaleDateString('en-US', {
44
+ year: 'numeric',
45
+ month: 'long',
46
+ day: 'numeric',
47
+ hour: '2-digit',
48
+ minute: '2-digit',
49
+ second: '2-digit',
50
+ timeZoneName: 'short',
51
+ });
52
+ }
53
+
54
+ interface ResultsViewProps {
55
+ recentFiles: string[];
56
+ onRecentFileSelected: (file: string) => void;
57
+ }
58
+
59
+ export default function ResultsView({ recentFiles, onRecentFileSelected }: ResultsViewProps) {
41
60
  const { table, config } = useStore();
42
61
  const [maxTextLength, setMaxTextLength] = React.useState(250);
43
62
  const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
@@ -148,10 +167,30 @@ export default function ResultsView() {
148
167
  return (
149
168
  <div>
150
169
  <Paper py="md">
151
- <ResponsiveStack direction="row" spacing={8} alignItems="center">
170
+ <ResponsiveStack direction="row" spacing={4} alignItems="center">
171
+ <Box>
172
+ {recentFiles && recentFiles.length > 0 && (
173
+ <FormControl sx={{ m: 1, minWidth: 200 }} size="small">
174
+ <InputLabel>View run</InputLabel>
175
+ <Select
176
+ key={recentFiles.join(',')}
177
+ className="recent-files"
178
+ label="Previous runs"
179
+ defaultValue={recentFiles[0]}
180
+ onChange={(e: SelectChangeEvent) => onRecentFileSelected(e.target.value)}
181
+ >
182
+ {recentFiles.map((file) => (
183
+ <MenuItem key={file} value={file}>
184
+ {filenameToDate(file)}
185
+ </MenuItem>
186
+ ))}
187
+ </Select>
188
+ </FormControl>
189
+ )}
190
+ </Box>
152
191
  <Box>
153
192
  <FormControl sx={{ m: 1, minWidth: 200 }} size="small">
154
- <InputLabel id="visible-columns-label">Visible columns</InputLabel>
193
+ <InputLabel id="visible-columns-label">Show columns</InputLabel>
155
194
  <Select
156
195
  labelId="visible-columns-label"
157
196
  id="visible-columns"
package/src/web/server.ts CHANGED
@@ -1,4 +1,4 @@
1
- import fs from 'fs';
1
+ import fs, { Stats } from 'fs';
2
2
  import path from 'node:path';
3
3
  import readline from 'node:readline';
4
4
  import http from 'node:http';
@@ -11,7 +11,7 @@ import { Server as SocketIOServer } from 'socket.io';
11
11
 
12
12
  import logger from '../logger';
13
13
  import { getDirectory } from '../esm';
14
- import { getLatestResultsPath } from '../util';
14
+ import { getLatestResultsPath, listPreviousResults, readResult } from '../util';
15
15
 
16
16
  export function init(port = 15500) {
17
17
  const app = express();
@@ -40,14 +40,37 @@ export function init(port = 15500) {
40
40
  socket.emit('init', readLatestJson());
41
41
 
42
42
  // Watch for changes to latest.json and emit the update event
43
- fs.watch(
44
- latestJsonPath,
45
- debounce((event: string) => {
46
- if (event === 'change') {
47
- socket.emit('update', readLatestJson());
48
- }
49
- }, 250),
50
- );
43
+ const watcher = debounce((curr: Stats, prev: Stats) => {
44
+ if (curr.mtime !== prev.mtime) {
45
+ socket.emit('update', readLatestJson());
46
+ }
47
+ }, 250);
48
+ fs.watchFile(latestJsonPath, watcher);
49
+
50
+ // Stop watching the file when the socket connection is closed
51
+ socket.on('disconnect', () => {
52
+ fs.unwatchFile(latestJsonPath, watcher);
53
+ });
54
+ });
55
+
56
+ app.get('/results', (req, res) => {
57
+ const previousResults = listPreviousResults();
58
+ res.json({ data: previousResults });
59
+ });
60
+
61
+ app.get('/results/:filename', (req, res) => {
62
+ const filename = req.params.filename;
63
+ const safeFilename = path.basename(filename);
64
+ if (safeFilename !== filename || !listPreviousResults().includes(safeFilename)) {
65
+ res.status(400).send('Invalid filename');
66
+ return;
67
+ }
68
+ const result = readResult(safeFilename);
69
+ if (!result) {
70
+ res.status(404).send('Result not found');
71
+ return;
72
+ }
73
+ res.json({ data: result });
51
74
  });
52
75
 
53
76
  httpServer.listen(port, () => {