diff --git a/pitch-deck/app/api/chat/route.ts b/pitch-deck/app/api/chat/route.ts index f274842..8203456 100644 --- a/pitch-deck/app/api/chat/route.ts +++ b/pitch-deck/app/api/chat/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from 'next/server' import pool from '@/lib/db' import { getSessionFromCookie } from '@/lib/auth' -import { SLIDE_ORDER } from '@/lib/slide-order' +import { SLIDE_ORDER, SHOWCASE_HIDDEN_SLIDES } from '@/lib/slide-order' const LITELLM_URL = process.env.LITELLM_URL || 'https://llm-dev.meghsakha.com' const LITELLM_MODEL = process.env.LITELLM_MODEL || 'gpt-oss-120b' @@ -115,6 +115,19 @@ KONKRETES BEISPIEL einer vollständigen Antwort: WICHTIG: Vergiss NIEMALS die Folgefragen! Sie sind PFLICHT.` +// Prepended to system content for showcase (customer demo) sessions +const SHOWCASE_GUARD = `## MODUS: PRODUKT-DEMO (KEIN INVESTOREN-PITCH) +Du bist im Showcase-Modus. Du beantwortest Fragen potenzieller Kunden über das Produkt — NICHT über Investitionsdetails. + +ABSOLUTES VERBOT — niemals erwähnen oder beantworten: +- Finanzierungssumme, Investitionsbedarf, The Ask, Bewertung, Equity, Cap Table +- Finanzprognosen, Umsatzziele, Cashflow, Liquidität, Burn Rate +- Wandeldarlehen, SAFE, Gesellschafteranteile oder Kapitalstruktur +- Irgendwelche Zahlen aus dem Finanzplan oder Finanzierungsrunden + +Wenn danach gefragt wird: "Für geschäftliche Details wenden Sie sich gerne direkt an unser Team." +Fokus: Produkt-Features, Compliance-Module, technische Architektur, Kundennutzen, Markt, Team.` + async function loadFpLiquiditaetSummary(scenarioName: string): Promise { try { const { rows } = await pool.query( @@ -199,7 +212,7 @@ function extractMeta( } } -async function loadPitchContext(versionId?: string | null): Promise { +async function loadPitchContext(versionId?: string | null, isShowcase = false): Promise { try { // Version-specific data path if (versionId) { @@ -225,11 +238,11 @@ async function loadPitchContext(versionId?: string | null): Promise n.toLocaleString('de-DE') @@ -340,40 +360,51 @@ export async function POST(request: NextRequest) { const dynamicFinanzplanKernbotschaft = `9. Finanzplan: "Gründung August 2026. Pre-Seed über ${fundingStr}. ${customersStr} und ${revM} Umsatz bis 2030. ${employeesStr}."` - let systemContent = SYSTEM_PROMPT_PART1 - + '\n' + dynamicFinanzplanKernbotschaft - + SYSTEM_PROMPT_PART2 - + '\n\n' + dynamicVersionIsolation - + SYSTEM_PROMPT_PART3 + let systemContent: string + if (isShowcase) { + // Showcase: product-only context, no financial details anywhere + systemContent = SHOWCASE_GUARD + + '\n\n' + SYSTEM_PROMPT_PART1 + + SYSTEM_PROMPT_PART2 + + SYSTEM_PROMPT_PART3 + } else { + systemContent = SYSTEM_PROMPT_PART1 + + '\n' + dynamicFinanzplanKernbotschaft + + SYSTEM_PROMPT_PART2 + + '\n\n' + dynamicVersionIsolation + + SYSTEM_PROMPT_PART3 + } if (contextString) { systemContent += '\n' + contextString } - // FAQ context: relevant pre-researched answers as basis for the LLM - // IMPORTANT: FAQ entries contain hardcoded numbers written for specific scenarios. - // They are hints only — the version-specific Unternehmensdaten above always take precedence. - if (faqContext && typeof faqContext === 'string') { + // FAQ context — skip for showcase (may contain financial hints) + if (!isShowcase && faqContext && typeof faqContext === 'string') { systemContent += '\n' + faqContext systemContent += '\n\n## Versions-Datenvorrang (ABSOLUT VERBINDLICH)\nWenn die vorrecherchierten Antworten oben Zahlen, Beträge oder Details nennen, die von den "Unternehmensdaten" oder dem "Finanzplan-Liquidität" weiter oben abweichen, haben die Unternehmensdaten IMMER Vorrang. Die FAQ-Antworten sind allgemein formuliert und könnten veraltete oder szenario-fremde Zahlen enthalten. Nutze sie nur für Struktur und Formulierung — die konkreten Zahlen kommen ausschließlich aus den Unternehmensdaten dieses Investors.' } - // Slide context for contextual awareness + // Slide context for contextual awareness — filter hidden slides for showcase + const visibleSlideOrder = isShowcase + ? SLIDE_ORDER.filter(id => !SHOWCASE_HIDDEN_SLIDES.has(id)) + : SLIDE_ORDER + if (slideContext) { const visited: number[] = slideContext.visitedSlides || [] const currentSlideId = slideContext.currentSlide const currentSlideName = SLIDE_DISPLAY_NAMES[currentSlideId]?.[lang] || currentSlideId - const notYetSeen = SLIDE_ORDER + const notYetSeen = visibleSlideOrder .map((id, idx) => ({ id, idx, name: SLIDE_DISPLAY_NAMES[id]?.[lang] || id })) .filter(s => !visited.includes(s.idx)) .map(s => `${s.idx + 1}. ${s.name}`) systemContent += `\n\n## Slide-Kontext (WICHTIG für kontextuelle Antworten) -- Aktuelle Slide: "${currentSlideName}" (Nr. ${slideContext.currentIndex + 1} von ${slideCount}) -- Bereits besuchte Slides: ${visited.map((i: number) => SLIDE_DISPLAY_NAMES[SLIDE_ORDER[i]]?.[lang] || SLIDE_ORDER[i]).filter(Boolean).join(', ')} +- Aktuelle Slide: "${currentSlideName}" (Nr. ${slideContext.currentIndex + 1} von ${visibleSlideOrder.length}) +- Bereits besuchte Slides: ${visited.map((i: number) => SLIDE_DISPLAY_NAMES[visibleSlideOrder[i]]?.[lang] || visibleSlideOrder[i]).filter(Boolean).join(', ')} - Noch nicht gesehene Slides: ${notYetSeen.join(', ')} -- Ist Erstbesuch: ${visited.length <= 1 ? 'JA — Investor hat gerade erst den Pitch geöffnet' : 'Nein'} -- Verfügbare Slide-IDs für [GOTO:id]: ${SLIDE_ORDER.join(', ')} +- Ist Erstbesuch: ${visited.length <= 1 ? 'JA — Besucher hat gerade erst die Präsentation geöffnet' : 'Nein'} +- Verfügbare Slide-IDs für [GOTO:id]: ${visibleSlideOrder.join(', ')} ` } diff --git a/pitch-deck/components/NavigationFAB.tsx b/pitch-deck/components/NavigationFAB.tsx index 2d04d01..06fad47 100644 --- a/pitch-deck/components/NavigationFAB.tsx +++ b/pitch-deck/components/NavigationFAB.tsx @@ -76,12 +76,12 @@ export default function NavigationFAB({ animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.9, y: 20 }} transition={{ duration: 0.2 }} - className="w-[300px] max-h-[80vh] rounded-2xl overflow-hidden + className="w-[300px] max-h-[80vh] flex flex-col rounded-2xl overflow-hidden bg-black/80 backdrop-blur-xl border border-white/10 shadow-2xl shadow-black/50" > {/* Header */} -
+
{i.nav.slides}
{/* Slide List */} -
+
{activeSlideNames.map((name, idx) => { const isActive = idx === currentIndex const isVisited = visitedSlides.has(idx) @@ -134,7 +134,7 @@ export default function NavigationFAB({
{/* Footer */} -
+
{/* Language Toggle */}