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:
72
pitch-deck/lib/presenter/faq-matcher.ts
Normal file
72
pitch-deck/lib/presenter/faq-matcher.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { Language } from '../types'
|
||||
import { FAQEntry } from './types'
|
||||
import { PRESENTER_FAQ } from './presenter-faq'
|
||||
|
||||
/**
|
||||
* Match a user query against pre-cached FAQ entries.
|
||||
* Returns the best match if score exceeds threshold, or null for LLM fallback.
|
||||
*/
|
||||
export function matchFAQ(query: string, lang: Language): FAQEntry | null {
|
||||
const normalized = query.toLowerCase().trim()
|
||||
const queryWords = normalized.split(/\s+/)
|
||||
|
||||
let bestMatch: FAQEntry | null = null
|
||||
let bestScore = 0
|
||||
|
||||
for (const entry of PRESENTER_FAQ) {
|
||||
let score = 0
|
||||
|
||||
// Check keyword matches
|
||||
for (const keyword of entry.keywords) {
|
||||
const kwLower = keyword.toLowerCase()
|
||||
if (kwLower.includes(' ')) {
|
||||
// Multi-word keyword: check if phrase appears in query
|
||||
if (normalized.includes(kwLower)) {
|
||||
score += 3 * entry.priority / 10
|
||||
}
|
||||
} else {
|
||||
// Single keyword: check word-level match
|
||||
if (queryWords.some(w => w === kwLower || w.startsWith(kwLower) || kwLower.startsWith(w))) {
|
||||
score += 1
|
||||
}
|
||||
// Also check if keyword appears anywhere in query (partial match)
|
||||
if (normalized.includes(kwLower)) {
|
||||
score += 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if query matches the question text closely
|
||||
const questionText = lang === 'de' ? entry.question_de : entry.question_en
|
||||
const questionWords = questionText.toLowerCase().split(/\s+/)
|
||||
const overlapCount = queryWords.filter(w =>
|
||||
w.length > 2 && questionWords.some(qw => qw.includes(w) || w.includes(qw))
|
||||
).length
|
||||
if (overlapCount >= 2) {
|
||||
score += overlapCount * 0.5
|
||||
}
|
||||
|
||||
// Weight by priority
|
||||
score *= (entry.priority / 10)
|
||||
|
||||
if (score > bestScore) {
|
||||
bestScore = score
|
||||
bestMatch = entry
|
||||
}
|
||||
}
|
||||
|
||||
// Threshold: need meaningful match to avoid false positives
|
||||
// Require at least 2 keyword hits or strong phrase match
|
||||
if (bestScore < 1.5) {
|
||||
return null
|
||||
}
|
||||
|
||||
return bestMatch
|
||||
}
|
||||
|
||||
/**
|
||||
* Get FAQ answer text in the requested language
|
||||
*/
|
||||
export function getFAQAnswer(entry: FAQEntry, lang: Language): string {
|
||||
return lang === 'de' ? entry.answer_de : entry.answer_en
|
||||
}
|
||||
Reference in New Issue
Block a user