import { NextRequest, NextResponse } from 'next/server' import pool from '@/lib/db' const OLLAMA_URL = process.env.OLLAMA_URL || 'http://host.docker.internal:11434' const OLLAMA_MODEL = process.env.OLLAMA_MODEL || 'qwen2.5:32b' const SYSTEM_PROMPT = `# Investor Agent — BreakPilot ComplAI ## Identitaet Du bist der BreakPilot ComplAI Investor Relations Agent. Du beantwortest Fragen von potenziellen Investoren ueber das Unternehmen, das Produkt, den Markt und die Finanzprognosen. Du hast Zugriff auf alle Unternehmensdaten und zitierst immer konkrete Zahlen. ## Kernprinzipien - **Datengetrieben**: Beziehe dich immer auf die bereitgestellten Unternehmensdaten - **Praezise**: Nenne immer konkrete Zahlen, Prozentsaetze und Zeitraeume - **Begeisternd aber ehrlich**: Stelle das Unternehmen positiv dar, ohne zu uebertreiben - **Zweisprachig**: Antworte in der Sprache, in der die Frage gestellt wird ## Kernbotschaften (IMMER betonen wenn passend) 1. AI-First: "Alles was durch KI loesbar ist, wird durch KI geloest. Kein klassischer Support, kein grosses Sales-Team." 2. Skalierbarkeit: "10x Kunden ≠ 10x Personal. Die KI skaliert mit." 3. Hardware-Differenzierung: "Datensouveraenitaet durch Self-Hosting auf Apple-Hardware." 4. Kostenstruktur: "18 Mitarbeiter in 2030 bei 8.4 Mio EUR Umsatz." 5. Marktchance: "12.4 Mrd EUR TAM, regulatorisch getrieben." ## Kommunikationsstil - Professionell, knapp und ueberzeugend - Strukturierte Antworten mit klaren Abschnitten - Zahlen hervorheben und kontextualisieren - Maximal 3-4 Absaetze pro Antwort ## IP-Schutz-Layer (KRITISCH) NIEMALS offenbaren: Exakte Modellnamen, Frameworks, Code-Architektur, Datenbankschema, Sicherheitsdetails, Cloud-Provider. Stattdessen: "Proprietaere KI-Engine", "Self-Hosted Appliance auf Apple-Hardware", "BSI-zertifizierte Cloud", "Enterprise-Grade Verschluesselung". ## Erlaubt: Geschaeftsmodell, Preise, Marktdaten, Features, Team, Finanzen, Use of Funds, Hardware-Specs (oeffentlich), LLM-Groessen (32b/40b/1000b).` async function loadPitchContext(): Promise { try { const client = await pool.connect() try { const [company, team, financials, market, products, funding, features] = await Promise.all([ client.query('SELECT * FROM pitch_company LIMIT 1'), client.query('SELECT name, role_de, equity_pct, expertise FROM pitch_team ORDER BY sort_order'), client.query('SELECT year, revenue_eur, costs_eur, mrr_eur, customers_count, employees_count, arr_eur FROM pitch_financials ORDER BY year'), client.query('SELECT market_segment, value_eur, growth_rate_pct, source FROM pitch_market'), client.query('SELECT name, hardware, hardware_cost_eur, monthly_price_eur, llm_size, llm_capability_de, operating_cost_eur FROM pitch_products ORDER BY sort_order'), client.query('SELECT round_name, amount_eur, use_of_funds, instrument FROM pitch_funding LIMIT 1'), client.query('SELECT feature_name_de, breakpilot, proliance, dataguard, heydata, is_differentiator FROM pitch_features WHERE is_differentiator = true'), ]) return ` ## Unternehmensdaten (fuer praezise Antworten nutzen) ### Firma ${JSON.stringify(company.rows[0], null, 2)} ### Team ${JSON.stringify(team.rows, null, 2)} ### Finanzprognosen (5-Jahres-Plan) ${JSON.stringify(financials.rows, null, 2)} ### Markt (TAM/SAM/SOM) ${JSON.stringify(market.rows, null, 2)} ### Produkte ${JSON.stringify(products.rows, null, 2)} ### Finanzierung ${JSON.stringify(funding.rows[0], null, 2)} ### Differenzierende Features (nur bei ComplAI) ${JSON.stringify(features.rows, null, 2)} ` } finally { client.release() } } catch (error) { console.warn('Could not load pitch context from DB:', error) return '' } } export async function POST(request: NextRequest) { try { const body = await request.json() const { message, history = [], lang = 'de' } = body if (!message || typeof message !== 'string') { return NextResponse.json({ error: 'Message is required' }, { status: 400 }) } const pitchContext = await loadPitchContext() let systemContent = SYSTEM_PROMPT if (pitchContext) { systemContent += '\n' + pitchContext } systemContent += `\n\n## Aktuelle Sprache: ${lang === 'de' ? 'Deutsch' : 'English'}\nAntworte in ${lang === 'de' ? 'Deutsch' : 'English'}.` const messages = [ { role: 'system', content: systemContent }, ...history.slice(-10).map((h: { role: string; content: string }) => ({ role: h.role === 'user' ? 'user' : 'assistant', content: h.content, })), { role: 'user', content: message }, ] const ollamaResponse = await fetch(`${OLLAMA_URL}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: OLLAMA_MODEL, messages, stream: true, options: { temperature: 0.4, num_predict: 4096, }, }), signal: AbortSignal.timeout(120000), }) if (!ollamaResponse.ok) { const errorText = await ollamaResponse.text() console.error('Ollama error:', ollamaResponse.status, errorText) return NextResponse.json( { error: `LLM nicht erreichbar (Status ${ollamaResponse.status}).` }, { status: 502 } ) } const encoder = new TextEncoder() const stream = new ReadableStream({ async start(controller) { const reader = ollamaResponse.body!.getReader() const decoder = new TextDecoder() try { while (true) { const { done, value } = await reader.read() if (done) break const chunk = decoder.decode(value, { stream: true }) const lines = chunk.split('\n').filter((l) => l.trim()) for (const line of lines) { try { const json = JSON.parse(line) if (json.message?.content) { controller.enqueue(encoder.encode(json.message.content)) } } catch { // Partial JSON line, skip } } } } catch (error) { console.error('Stream read error:', error) } finally { controller.close() } }, }) return new NextResponse(stream, { headers: { 'Content-Type': 'text/plain; charset=utf-8', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', }, }) } catch (error) { console.error('Investor agent chat error:', error) return NextResponse.json( { error: 'Verbindung zum LLM fehlgeschlagen.' }, { status: 503 } ) } }