Files
breakpilot-core/pitch-deck/components/slides/IntroPresenterSlide.tsx
Benjamin Admin 3a2567b44d
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 27s
CI / test-python-voice (push) Successful in 25s
CI / test-bqas (push) Successful in 25s
CI / Deploy (push) Successful in 4s
feat(pitch-deck): add AI Presenter mode with LiteLLM migration and FAQ system
- Migrate chat API from Ollama to LiteLLM (OpenAI-compatible SSE)
- Add 15-min presenter storyline with bilingual scripts for all 20 slides
- Add FAQ system (30 entries) with keyword matching for instant answers
- Add IntroPresenterSlide with avatar placeholder and start button
- Add PresenterOverlay (progress bar, subtitle text, play/pause/stop)
- Add AvatarPlaceholder with pulse animation during speaking
- Add usePresenterMode hook (state machine: idle→presenting→paused→answering→resuming)
- Add 'P' keyboard shortcut to toggle presenter mode
- Support [GOTO:slide-id] markers in chat responses
- Dynamic slide count (was hardcoded 13, now from SLIDE_ORDER)
- TTS stub prepared for future Piper integration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 11:45:55 +01:00

117 lines
4.7 KiB
TypeScript

'use client'
import { motion } from 'framer-motion'
import { Play, MessageCircle, Pause } from 'lucide-react'
import { Language } from '@/lib/types'
import GradientText from '../ui/GradientText'
interface IntroPresenterSlideProps {
lang: Language
onStartPresenter?: () => void
isPresenting?: boolean
}
export default function IntroPresenterSlide({ lang, onStartPresenter, isPresenting }: IntroPresenterSlideProps) {
const isDE = lang === 'de'
return (
<div className="h-full flex flex-col items-center justify-center px-8 text-center">
{/* Avatar Placeholder Circle */}
<motion.div
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ duration: 0.6, ease: 'easeOut' }}
className="relative mb-8"
>
<div className="w-32 h-32 rounded-full bg-gradient-to-br from-indigo-500/30 to-purple-500/30 border-2 border-indigo-400/40 flex items-center justify-center">
{/* Pulse rings */}
<motion.div
className="absolute inset-0 rounded-full border-2 border-indigo-400/20"
animate={{ scale: [1, 1.3, 1], opacity: [0.4, 0, 0.4] }}
transition={{ duration: 2, repeat: Infinity, ease: 'easeInOut' }}
/>
<motion.div
className="absolute inset-0 rounded-full border-2 border-purple-400/20"
animate={{ scale: [1, 1.5, 1], opacity: [0.3, 0, 0.3] }}
transition={{ duration: 2.5, repeat: Infinity, ease: 'easeInOut', delay: 0.3 }}
/>
{/* Bot icon */}
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className="text-indigo-300">
<rect x="3" y="11" width="18" height="10" rx="2" />
<circle cx="12" cy="5" r="2" />
<path d="M12 7v4" />
<circle cx="8" cy="16" r="1" fill="currentColor" />
<circle cx="16" cy="16" r="1" fill="currentColor" />
</svg>
</div>
</motion.div>
{/* Title */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3, duration: 0.5 }}
>
<h1 className="text-4xl md:text-5xl font-bold mb-3">
<GradientText>{isDE ? 'KI-Praesentator' : 'AI Presenter'}</GradientText>
</h1>
<p className="text-lg text-white/60 max-w-lg mx-auto mb-8">
{isDE
? 'Ihr persoenlicher KI-Guide durch das BreakPilot ComplAI Pitch Deck. 15 Minuten, alle Fakten, jederzeit unterbrechbar.'
: 'Your personal AI guide through the BreakPilot ComplAI pitch deck. 15 minutes, all facts, interruptible at any time.'}
</p>
</motion.div>
{/* Start Button */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5, duration: 0.5 }}
>
<button
onClick={onStartPresenter}
className="group relative px-8 py-4 rounded-2xl bg-gradient-to-r from-indigo-600 to-purple-600
hover:from-indigo-500 hover:to-purple-500 transition-all duration-300
text-white font-semibold text-lg shadow-lg shadow-indigo-600/30
hover:shadow-xl hover:shadow-indigo-600/40 hover:scale-105"
>
<span className="flex items-center gap-3">
{isPresenting ? (
<>
<Pause className="w-5 h-5" />
{isDE ? 'Praesentation laeuft...' : 'Presentation running...'}
</>
) : (
<>
<Play className="w-5 h-5" />
{isDE ? 'Praesentation starten' : 'Start Presentation'}
</>
)}
</span>
</button>
</motion.div>
{/* Interaction hints */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.8, duration: 0.5 }}
className="mt-10 flex flex-col md:flex-row gap-4 text-sm text-white/40"
>
<div className="flex items-center gap-2">
<MessageCircle className="w-4 h-4" />
<span>{isDE ? 'Jederzeit Fragen im Chat stellen' : 'Ask questions in chat anytime'}</span>
</div>
<div className="flex items-center gap-2">
<span className="px-1.5 py-0.5 bg-white/10 rounded text-xs font-mono">P</span>
<span>{isDE ? 'Taste P: Presenter An/Aus' : 'Press P: Toggle Presenter'}</span>
</div>
<div className="flex items-center gap-2">
<span className="px-1.5 py-0.5 bg-white/10 rounded text-xs font-mono">ESC</span>
<span>{isDE ? 'Slide-Uebersicht' : 'Slide Overview'}</span>
</div>
</motion.div>
</div>
)
}