"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.startServer = exports.BrowserBehavior = void 0;
const compression_1 = __importDefault(require("compression"));
const cors_1 = __importDefault(require("cors"));
const debounce_1 = __importDefault(require("debounce"));
const express_1 = __importDefault(require("express"));
const fs_1 = __importDefault(require("fs"));
const node_http_1 = __importDefault(require("node:http"));
const node_path_1 = __importDefault(require("node:path"));
const node_readline_1 = __importDefault(require("node:readline"));
const opener_1 = __importDefault(require("opener"));
const socket_io_1 = require("socket.io");
const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
const uuid_1 = require("uuid");
const database_1 = require("../database");
const esm_1 = require("../esm");
const index_1 = __importDefault(require("../index"));
const logger_1 = __importDefault(require("../logger"));
const testCases_1 = require("../testCases");
const util_1 = require("../util");
// Running jobs
const evalJobs = new Map();
// Prompts cache
let allPrompts = null;
var BrowserBehavior;
(function (BrowserBehavior) {
    BrowserBehavior[BrowserBehavior["ASK"] = 0] = "ASK";
    BrowserBehavior[BrowserBehavior["OPEN"] = 1] = "OPEN";
    BrowserBehavior[BrowserBehavior["SKIP"] = 2] = "SKIP";
})(BrowserBehavior || (exports.BrowserBehavior = BrowserBehavior = {}));
async function startServer(port = 15500, apiBaseUrl = '', browserBehavior = BrowserBehavior.ASK, filterDescription) {
    const app = (0, express_1.default)();
    const staticDir = node_path_1.default.join((0, esm_1.getDirectory)(), 'web', 'nextui');
    app.use((0, cors_1.default)());
    app.use((0, compression_1.default)());
    app.use(express_1.default.json({ limit: '100mb' }));
    app.use(express_1.default.urlencoded({ limit: '100mb', extended: true }));
    const httpServer = node_http_1.default.createServer(app);
    const io = new socket_io_1.Server(httpServer, {
        cors: {
            origin: '*',
        },
    });
    await (0, util_1.migrateResultsFromFileSystemToDatabase)();
    const watchFilePath = (0, database_1.getDbSignalPath)();
    const watcher = (0, debounce_1.default)(async (curr, prev) => {
        if (curr.mtime !== prev.mtime) {
            io.emit('update', await (0, util_1.readLatestResults)(filterDescription));
            allPrompts = null;
        }
    }, 250);
    fs_1.default.watchFile(watchFilePath, watcher);
    io.on('connection', async (socket) => {
        socket.emit('init', await (0, util_1.readLatestResults)(filterDescription));
    });
    app.get('/api/results', (req, res) => {
        const previousResults = (0, util_1.listPreviousResults)(undefined /* limit */, filterDescription);
        res.json({
            data: previousResults.map((meta) => {
                return {
                    id: meta.evalId,
                    label: meta.description ? `${meta.description} (${meta.evalId})` : meta.evalId,
                };
            }),
        });
    });
    app.post('/api/eval/job', (req, res) => {
        const { evaluateOptions, ...testSuite } = req.body;
        const id = (0, uuid_1.v4)();
        evalJobs.set(id, { status: 'in-progress', progress: 0, total: 0, result: null });
        index_1.default
            .evaluate(Object.assign({}, testSuite, {
            writeLatestResults: true,
            sharing: testSuite.sharing ?? true,
        }), Object.assign({}, evaluateOptions, {
            eventSource: 'web',
            progressCallback: (progress, total) => {
                const job = evalJobs.get(id);
                (0, tiny_invariant_1.default)(job, 'Job not found');
                job.progress = progress;
                job.total = total;
                console.log(`[${id}] ${progress}/${total}`);
            },
        }))
            .then((result) => {
            const job = evalJobs.get(id);
            (0, tiny_invariant_1.default)(job, 'Job not found');
            job.status = 'complete';
            job.result = result;
            console.log(`[${id}] Complete`);
        });
        res.json({ id });
    });
    app.get('/api/eval/job/:id', (req, res) => {
        const id = req.params.id;
        const job = evalJobs.get(id);
        if (!job) {
            res.status(404).json({ error: 'Job not found' });
            return;
        }
        if (job.status === 'complete') {
            res.json({ status: 'complete', result: job.result });
        }
        else {
            res.json({ status: 'in-progress', progress: job.progress, total: job.total });
        }
    });
    app.patch('/api/eval/:id', (req, res) => {
        const id = req.params.id;
        const { table, config } = req.body;
        if (!id) {
            res.status(400).json({ error: 'Missing id' });
            return;
        }
        try {
            (0, util_1.updateResult)(id, config, table);
            res.json({ message: 'Eval updated successfully' });
        }
        catch (error) {
            res.status(500).json({ error: 'Failed to update eval table' });
        }
    });
    app.delete('/api/eval/:id', async (req, res) => {
        const { id } = req.params;
        try {
            await (0, util_1.deleteEval)(id);
            res.json({ message: 'Eval deleted successfully' });
        }
        catch (error) {
            res.status(500).json({ error: 'Failed to delete eval' });
        }
    });
    app.get('/api/results/:id', async (req, res) => {
        const { id } = req.params;
        const file = await (0, util_1.readResult)(id);
        if (!file) {
            res.status(404).send('Result not found');
            return;
        }
        res.json({ data: file.result });
    });
    app.get('/api/prompts', async (req, res) => {
        if (allPrompts == null) {
            allPrompts = await (0, util_1.getPrompts)();
        }
        res.json({ data: allPrompts });
    });
    app.get('/api/progress', async (req, res) => {
        const results = await (0, util_1.getStandaloneEvals)();
        res.json({
            data: results,
        });
    });
    app.get('/api/prompts/:sha256hash', async (req, res) => {
        const sha256hash = req.params.sha256hash;
        const prompts = await (0, util_1.getPromptsForTestCasesHash)(sha256hash);
        res.json({ data: prompts });
    });
    app.get('/api/datasets', async (req, res) => {
        res.json({ data: await (0, util_1.getTestCases)() });
    });
    app.get('/api/config', (req, res) => {
        res.json({
            apiBaseUrl: apiBaseUrl || '',
        });
    });
    app.post('/api/dataset/generate', async (req, res) => {
        const testSuite = {
            prompts: req.body.prompts,
            tests: req.body.tests,
            providers: [],
        };
        const results = await (0, testCases_1.synthesizeFromTestSuite)(testSuite, {});
        return {
            results,
        };
    });
    // Must come after the above routes (particularly /api/config) so it doesn't
    // overwrite dynamic routes.
    app.use(express_1.default.static(staticDir));
    httpServer.listen(port, () => {
        const url = `http://localhost:${port}`;
        logger_1.default.info(`Server running at ${url} and monitoring for new evals.`);
        const openUrl = async () => {
            try {
                logger_1.default.info('Press Ctrl+C to stop the server');
                await (0, opener_1.default)(url);
            }
            catch (err) {
                logger_1.default.error(`Failed to open browser: ${String(err)}`);
            }
        };
        if (browserBehavior === BrowserBehavior.OPEN) {
            openUrl();
        }
        else if (browserBehavior === BrowserBehavior.ASK) {
            const rl = node_readline_1.default.createInterface({
                input: process.stdin,
                output: process.stdout,
            });
            rl.question('Open URL in browser? (y/N): ', async (answer) => {
                if (answer.toLowerCase().startsWith('y')) {
                    openUrl();
                }
                rl.close();
            });
        }
    });
}
exports.startServer = startServer;
//# sourceMappingURL=server.js.map