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
- 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>
117 lines
4.7 KiB
TypeScript
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>
|
|
)
|
|
}
|