import { NextRequest, NextResponse } from 'next/server'; import { spawn } from 'child_process'; import * as path from 'path'; import * as fs from 'fs'; export async function POST(request: NextRequest) { const { query } = await request.json(); if (!query) { return NextResponse.json({ error: 'Query is required' }, { status: 400 }); } // Create a streaming response const encoder = new TextEncoder(); const stream = new ReadableStream({ async start(controller) { // Send initial status controller.enqueue(encoder.encode(JSON.stringify({ status: 'Starting discovery...' }) + '\n')); // Path to Python script const techscoutDir = path.resolve(process.cwd(), '..'); const pythonScript = path.join(techscoutDir, 'run_discovery.py'); // Create a temporary Python script to run the discovery const scriptContent = ` import sys import json sys.path.insert(0, r'${techscoutDir}') from techscout.pipeline.discovery import DiscoveryPipeline def main(): pipeline = DiscoveryPipeline(model='llama3:8b') # Status updates via print print(json.dumps({"status": "Decomposing capability gap..."}), flush=True) result = pipeline.discover( capability_gap="""${query.replace(/"/g, '\\"')}""", max_results=50, use_llm_scoring=True ) # Output result print(json.dumps({"result": result.to_dict()}), flush=True) if __name__ == '__main__': main() `; // Write temporary script fs.writeFileSync(pythonScript, scriptContent); try { const python = spawn('python', [pythonScript], { cwd: techscoutDir, env: { ...process.env, PYTHONUNBUFFERED: '1' } }); python.stdout.on('data', (data: Buffer) => { const lines = data.toString().split('\n').filter(Boolean); for (const line of lines) { try { // Try to parse as JSON JSON.parse(line); controller.enqueue(encoder.encode(line + '\n')); } catch { // Not JSON, send as status controller.enqueue(encoder.encode(JSON.stringify({ status: line }) + '\n')); } } }); python.stderr.on('data', (data: Buffer) => { const message = data.toString(); // Check if it's a progress message (from logging) if (message.includes('INFO')) { const match = message.match(/- (.+)$/); if (match) { controller.enqueue(encoder.encode(JSON.stringify({ status: match[1] }) + '\n')); } } }); await new Promise((resolve, reject) => { python.on('close', (code: number) => { if (code === 0) { resolve(); } else { reject(new Error(`Python process exited with code ${code}`)); } }); python.on('error', reject); }); } catch (error) { controller.enqueue(encoder.encode(JSON.stringify({ error: `Discovery failed: ${error}` }) + '\n')); } finally { // Cleanup try { fs.unlinkSync(pythonScript); } catch { // Ignore cleanup errors } controller.close(); } } }); return new Response(stream, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', }, }); }