121 lines
3.4 KiB
TypeScript
121 lines
3.4 KiB
TypeScript
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<void>((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',
|
|
},
|
|
});
|
|
}
|