This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/pitch-deck/app/api/chat/route.ts
BreakPilot Dev 557305db5d
Some checks failed
ci/woodpecker/push/integration Pipeline failed
ci/woodpecker/push/main Pipeline failed
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
feat: Add Academy, Whistleblower, Incidents SDK modules, pitch-deck, blog and CI/CD config
- Academy, Whistleblower, Incidents frontend pages with API proxies and types
- Vendor compliance API proxy route
- Go backend handlers and models for all new SDK modules
- Investor pitch-deck app with interactive slides
- Blog section with DSGVO, AI Act, NIS2, glossary articles
- MkDocs documentation site
- CI/CD pipelines (Woodpecker, GitHub Actions), security scanning config
- Planning and implementation documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 21:12:16 +01:00

184 lines
6.6 KiB
TypeScript

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<string> {
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 }
)
}
}