feat(pitch-deck): add AI Presenter mode with LiteLLM migration and FAQ system
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
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>
This commit is contained in:
@@ -5,6 +5,7 @@ import { AnimatePresence } from 'framer-motion'
|
||||
import { useSlideNavigation } from '@/lib/hooks/useSlideNavigation'
|
||||
import { useKeyboard } from '@/lib/hooks/useKeyboard'
|
||||
import { usePitchData } from '@/lib/hooks/usePitchData'
|
||||
import { usePresenterMode } from '@/lib/hooks/usePresenterMode'
|
||||
import { Language, PitchData } from '@/lib/types'
|
||||
|
||||
import ParticleBackground from './ParticleBackground'
|
||||
@@ -14,7 +15,10 @@ import NavigationFAB from './NavigationFAB'
|
||||
import ChatFAB from './ChatFAB'
|
||||
import SlideOverview from './SlideOverview'
|
||||
import SlideContainer from './SlideContainer'
|
||||
import PresenterOverlay from './presenter/PresenterOverlay'
|
||||
import AvatarPlaceholder from './presenter/AvatarPlaceholder'
|
||||
|
||||
import IntroPresenterSlide from './slides/IntroPresenterSlide'
|
||||
import CoverSlide from './slides/CoverSlide'
|
||||
import ProblemSlide from './slides/ProblemSlide'
|
||||
import SolutionSlide from './slides/SolutionSlide'
|
||||
@@ -45,6 +49,13 @@ export default function PitchDeck({ lang, onToggleLanguage }: PitchDeckProps) {
|
||||
const nav = useSlideNavigation()
|
||||
const [fabOpen, setFabOpen] = useState(false)
|
||||
|
||||
const presenter = usePresenterMode({
|
||||
goToSlide: nav.goToSlide,
|
||||
currentSlide: nav.currentIndex,
|
||||
totalSlides: nav.totalSlides,
|
||||
language: lang,
|
||||
})
|
||||
|
||||
const toggleFullscreen = useCallback(() => {
|
||||
if (!document.fullscreenElement) {
|
||||
document.documentElement.requestFullscreen()
|
||||
@@ -66,6 +77,7 @@ export default function PitchDeck({ lang, onToggleLanguage }: PitchDeckProps) {
|
||||
onFullscreen: toggleFullscreen,
|
||||
onLanguageToggle: onToggleLanguage,
|
||||
onMenuToggle: toggleMenu,
|
||||
onPresenterToggle: presenter.toggle,
|
||||
onGoToSlide: nav.goToSlide,
|
||||
enabled: !nav.showOverview,
|
||||
})
|
||||
@@ -96,6 +108,14 @@ export default function PitchDeck({ lang, onToggleLanguage }: PitchDeckProps) {
|
||||
if (!data) return null
|
||||
|
||||
switch (nav.currentSlide) {
|
||||
case 'intro-presenter':
|
||||
return (
|
||||
<IntroPresenterSlide
|
||||
lang={lang}
|
||||
onStartPresenter={presenter.start}
|
||||
isPresenting={presenter.state !== 'idle'}
|
||||
/>
|
||||
)
|
||||
case 'cover':
|
||||
return <CoverSlide lang={lang} onNext={nav.nextSlide} funding={data.funding} />
|
||||
case 'problem':
|
||||
@@ -163,6 +183,8 @@ export default function PitchDeck({ lang, onToggleLanguage }: PitchDeckProps) {
|
||||
currentIndex={nav.currentIndex}
|
||||
visitedSlides={nav.visitedSlides}
|
||||
onGoToSlide={nav.goToSlide}
|
||||
presenterState={presenter.state}
|
||||
onPresenterInterrupt={presenter.pause}
|
||||
/>
|
||||
|
||||
<NavigationFAB
|
||||
@@ -174,6 +196,21 @@ export default function PitchDeck({ lang, onToggleLanguage }: PitchDeckProps) {
|
||||
onToggleLanguage={onToggleLanguage}
|
||||
/>
|
||||
|
||||
{/* Presenter UI */}
|
||||
<AvatarPlaceholder state={presenter.state} />
|
||||
<PresenterOverlay
|
||||
state={presenter.state}
|
||||
currentIndex={nav.currentIndex}
|
||||
totalSlides={nav.totalSlides}
|
||||
progress={presenter.progress}
|
||||
displayText={presenter.displayText}
|
||||
lang={lang}
|
||||
onPause={presenter.pause}
|
||||
onResume={presenter.resume}
|
||||
onStop={presenter.stop}
|
||||
onSkip={presenter.skipSlide}
|
||||
/>
|
||||
|
||||
<AnimatePresence>
|
||||
{nav.showOverview && (
|
||||
<SlideOverview
|
||||
|
||||
Reference in New Issue
Block a user