"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createApp = createApp;
exports.startServer = startServer;
const compression_1 = __importDefault(require("compression"));
const cors_1 = __importDefault(require("cors"));
const dedent_1 = __importDefault(require("dedent"));
const express_1 = __importDefault(require("express"));
const node_http_1 = __importDefault(require("node:http"));
const node_path_1 = __importDefault(require("node:path"));
const socket_io_1 = require("socket.io");
const zod_validation_error_1 = require("zod-validation-error");
const share_1 = require("../commands/share");
const constants_1 = require("../constants");
const signal_1 = require("../database/signal");
const esm_1 = require("../esm");
const logger_1 = __importDefault(require("../logger"));
const migrate_1 = require("../migrate");
const eval_1 = __importDefault(require("../models/eval"));
const remoteGeneration_1 = require("../redteam/remoteGeneration");
const telemetry_1 = __importDefault(require("../telemetry"));
const telemetry_2 = require("../telemetry");
const testCases_1 = require("../testCases");
const util_1 = require("../util");
const apiHealth_1 = require("../util/apiHealth");
const invariant_1 = __importDefault(require("../util/invariant"));
const server_1 = require("../util/server");
const server_2 = require("../util/server");
const configs_1 = require("./routes/configs");
const eval_2 = require("./routes/eval");
const providers_1 = require("./routes/providers");
const redteam_1 = require("./routes/redteam");
const user_1 = require("./routes/user");
// Prompts cache
let allPrompts = null;
function createApp() {
    const app = (0, express_1.default)();
    const staticDir = node_path_1.default.join((0, esm_1.getDirectory)(), 'app');
    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 }));
    app.get('/health', (req, res) => {
        res.status(200).json({ status: 'OK', version: constants_1.VERSION });
    });
    app.get('/api/remote-health', async (req, res) => {
        const apiUrl = (0, remoteGeneration_1.getRemoteHealthUrl)();
        if (apiUrl === null) {
            res.json({
                status: 'DISABLED',
                message: 'remote generation and grading are disabled',
            });
            return;
        }
        const result = await (0, apiHealth_1.checkRemoteHealth)(apiUrl);
        res.json(result);
    });
    app.get('/api/results', async (req, res) => {
        const datasetId = req.query.datasetId;
        const previousResults = await (0, util_1.listPreviousResults)(undefined /* limit */, undefined /* offset */, datasetId);
        res.json({
            data: previousResults.map((meta) => {
                return {
                    ...meta,
                    label: meta.description ? `${meta.description} (${meta.evalId})` : meta.evalId,
                };
            }),
        });
    });
    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 { tagName, tagValue, description } = req.query;
        const tag = tagName && tagValue ? { key: tagName, value: tagValue } : undefined;
        const results = await (0, util_1.getStandaloneEvals)({
            tag,
            description: description,
        });
        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)() });
    });
    // This is used by ResultsView.tsx to share an eval with another promptfoo instance
    app.post('/api/results/share', async (req, res) => {
        const { id } = req.body;
        const result = await (0, util_1.readResult)(id);
        if (!result) {
            res.status(404).json({ error: 'Eval not found' });
            return;
        }
        const eval_ = await eval_1.default.findById(id);
        (0, invariant_1.default)(eval_, 'Eval not found');
        const url = await (0, share_1.createPublicUrl)(eval_, true);
        res.json({ url });
    });
    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, {});
        res.json({ results });
    });
    app.use('/api/eval', eval_2.evalRouter);
    app.use('/api/providers', providers_1.providersRouter);
    app.use('/api/redteam', redteam_1.redteamRouter);
    app.use('/api/user', user_1.userRouter);
    app.use('/api/configs', configs_1.configsRouter);
    app.post('/api/telemetry', async (req, res) => {
        try {
            const result = telemetry_2.TelemetryEventSchema.safeParse(req.body);
            if (!result.success) {
                res
                    .status(400)
                    .json({ error: 'Invalid request body', details: (0, zod_validation_error_1.fromError)(result.error).toString() });
                return;
            }
            const { event, properties } = result.data;
            await telemetry_1.default.recordAndSend(event, properties);
            res.status(200).json({ success: true });
        }
        catch (error) {
            console.error('Error processing telemetry request:', error);
            res.status(500).json({ error: 'Failed to process telemetry request' });
        }
    });
    // Must come after the above routes (particularly /api/config) so it doesn't
    // overwrite dynamic routes.
    app.use(express_1.default.static(staticDir));
    // Handle client routing, return all requests to the app
    app.get('*', (req, res) => {
        res.sendFile(node_path_1.default.join(staticDir, 'index.html'));
    });
    return app;
}
async function startServer(port = constants_1.DEFAULT_PORT, browserBehavior = server_1.BrowserBehavior.ASK, filterDescription) {
    const app = createApp();
    const httpServer = node_http_1.default.createServer(app);
    const io = new socket_io_1.Server(httpServer, {
        cors: {
            origin: '*',
        },
    });
    await (0, migrate_1.runDbMigrations)();
    (0, signal_1.setupSignalWatcher)(async () => {
        const latestEval = await (0, util_1.getLatestEval)(filterDescription);
        logger_1.default.info(`Emitting update with eval ID: ${latestEval?.config?.description || 'unknown'}`);
        io.emit('update', latestEval);
        allPrompts = null;
    });
    io.on('connection', async (socket) => {
        socket.emit('init', await (0, util_1.getLatestEval)(filterDescription));
    });
    httpServer
        .listen(port, () => {
        const url = `http://localhost:${port}`;
        logger_1.default.info(`Server running at ${url} and monitoring for new evals.`);
        (0, server_2.openBrowser)(browserBehavior, port).catch((err) => {
            logger_1.default.error(`Failed to handle browser behavior: ${err}`);
        });
    })
        .on('error', (error) => {
        if (error.code === 'EADDRINUSE') {
            logger_1.default.error((0, dedent_1.default) `Port ${port} is already in use. Do you have another Promptfoo instance running?

          To resolve this:
            1. Check if another promptfoo instance is running (try 'ps aux | grep promptfoo')
            2. Kill the process using the port: 'lsof -i :${port}' then 'kill <PID>'
            3. Or specify a different port with --port <number>`);
            process.exit(1);
        }
        else {
            logger_1.default.error(`Failed to start server: ${error.message}`);
            process.exit(1);
        }
    });
}
//# sourceMappingURL=server.js.map