#!/usr/bin/env node const fs = require("node:fs"); const path = require("node:path"); const request = require("supertest"); function parseArgs(argv) { const args = { questionsFile: "", runDir: "", rawFileName: "chat20_wave13_raw.json", chatFileName: "Chat20.txt", chatRuFileName: "Чат20.txt", promptsFileName: path.join("prompt_dialogs", "chat20_prompts.md"), useMock: true, promptVersion: "normalizer_v2_0_2", sessionId: "", casePrefix: "q" }; for (let i = 0; i < argv.length; i += 1) { const token = argv[i]; if (token === "--questions-file") { args.questionsFile = String(argv[i + 1] ?? ""); i += 1; continue; } if (token === "--run-dir") { args.runDir = String(argv[i + 1] ?? ""); i += 1; continue; } if (token === "--raw-file") { args.rawFileName = String(argv[i + 1] ?? args.rawFileName); i += 1; continue; } if (token === "--chat-file") { args.chatFileName = String(argv[i + 1] ?? args.chatFileName); i += 1; continue; } if (token === "--chat-ru-file") { args.chatRuFileName = String(argv[i + 1] ?? args.chatRuFileName); i += 1; continue; } if (token === "--prompts-file") { args.promptsFileName = String(argv[i + 1] ?? args.promptsFileName); i += 1; continue; } if (token === "--use-mock") { const value = String(argv[i + 1] ?? "true").toLowerCase(); args.useMock = value !== "0" && value !== "false" && value !== "no"; i += 1; continue; } if (token === "--prompt-version") { args.promptVersion = String(argv[i + 1] ?? args.promptVersion); i += 1; continue; } if (token === "--session-id") { args.sessionId = String(argv[i + 1] ?? ""); i += 1; continue; } if (token === "--case-prefix") { args.casePrefix = String(argv[i + 1] ?? args.casePrefix); i += 1; } } return args; } function ensureDir(dirPath) { fs.mkdirSync(dirPath, { recursive: true }); } function readJson(filePath) { const raw = fs.readFileSync(filePath, "utf8").replace(/^\uFEFF/, ""); return JSON.parse(raw); } function writeUtf8Bom(filePath, content) { ensureDir(path.dirname(filePath)); fs.writeFileSync(filePath, `\uFEFF${content}`, "utf8"); } function asText(value) { return value == null ? "" : String(value); } function makeCaseId(prefix, index) { return `${prefix}${String(index + 1).padStart(2, "0")}`; } function buildPromptsMarkdown(questions) { const lines = []; for (let i = 0; i < questions.length; i += 1) { lines.push(`${i + 1}. ${questions[i]}`); lines.push(""); } return `${lines.join("\n").trim()}\n`; } function buildChatTxt(sessionId, exportedAt, rows) { const lines = []; lines.push("# Assistant conversation export"); lines.push(`session_id: ${sessionId}`); lines.push(`exported_at: ${exportedAt}`); lines.push(""); let messageCounter = 1; for (const row of rows) { lines.push(`## ${messageCounter}. user`); lines.push("message_id: pending"); lines.push("created_at: pending"); lines.push("reply_type: n/a"); lines.push(""); lines.push(asText(row.user_message)); lines.push(""); messageCounter += 1; lines.push(`## ${messageCounter}. assistant`); lines.push(`message_id: ${asText(row.message_id) || "n/a"}`); lines.push(`created_at: ${asText(row.created_at) || "n/a"}`); lines.push(`reply_type: ${asText(row.reply_type) || "n/a"}`); if (row.trace_id) { lines.push(`trace_id: ${asText(row.trace_id)}`); } lines.push(""); lines.push(asText(row.assistant_reply)); lines.push(""); messageCounter += 1; } return `${lines.join("\n").trim()}\n`; } async function main() { const args = parseArgs(process.argv.slice(2)); if (!args.questionsFile) { throw new Error("Missing required argument --questions-file"); } if (!args.runDir) { throw new Error("Missing required argument --run-dir"); } const questionsPath = path.resolve(args.questionsFile); const runDir = path.resolve(args.runDir); const backendRoot = path.resolve(__dirname, ".."); const questions = readJson(questionsPath); if (!Array.isArray(questions) || questions.length === 0) { throw new Error("Questions JSON must be a non-empty array of strings."); } const { createApp } = require(path.join(backendRoot, "dist", "server.js")); const app = createApp(); ensureDir(runDir); ensureDir(path.join(runDir, "prompt_dialogs")); const rows = []; let sessionId = args.sessionId || `wave13-chat20-${Date.now()}`; for (let i = 0; i < questions.length; i += 1) { const userMessage = asText(questions[i]); const response = await request(app).post("/api/assistant/message").send({ useMock: args.useMock, promptVersion: args.promptVersion, session_id: sessionId, user_message: userMessage }); const body = response.body || {}; sessionId = asText(body.session_id) || sessionId; const item = body.conversation_item || {}; rows.push({ case_id: makeCaseId(args.casePrefix, i), user_message: userMessage, assistant_reply: asText(body.assistant_reply), reply_type: asText(body.reply_type), message_id: asText(item.message_id), created_at: asText(item.created_at), trace_id: asText(item.trace_id || body.debug?.trace_id), http_status: response.status, debug: body.debug || {} }); } const exportedAt = new Date().toISOString(); const rawPayload = { session_id: sessionId, exported_at: exportedAt, cases_total: rows.length, rows }; const rawPath = path.join(runDir, args.rawFileName); const chatPath = path.join(runDir, args.chatFileName); const chatRuPath = path.join(runDir, args.chatRuFileName); const promptsPath = path.join(runDir, args.promptsFileName); writeUtf8Bom(rawPath, `${JSON.stringify(rawPayload, null, 2)}\n`); const chatBody = buildChatTxt(sessionId, exportedAt, rows); writeUtf8Bom(chatPath, chatBody); if (args.chatRuFileName) { writeUtf8Bom(chatRuPath, chatBody); } writeUtf8Bom(promptsPath, buildPromptsMarkdown(questions)); process.stdout.write( [ `run_dir=${runDir}`, `session_id=${sessionId}`, `cases_total=${rows.length}`, `raw=${rawPath}`, `chat=${chatPath}`, `chat_ru=${chatRuPath}`, `prompts=${promptsPath}` ].join("\n") ); } main().catch((error) => { process.stderr.write(`${error?.stack || error}\n`); process.exitCode = 1; });