feat: BreakPilot PWA - Full codebase (clean push without large binaries)
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed

All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
This commit is contained in:
BreakPilot Dev
2026-02-11 13:25:58 +01:00
commit 19855efacc
2512 changed files with 933814 additions and 0 deletions

View File

@@ -0,0 +1,255 @@
/**
* RAGSearchPanel Component
*
* Enhanced RAG search panel for teachers to query their Erwartungshorizonte.
* Features:
* - search_info display (which RAG features were active)
* - Confidence indicator based on re-ranking scores
* - Toggle for "Enhanced Search" with advanced options
*/
import { useState, useCallback } from 'react'
import { ehApi, EHRAGResult } from '../services/api'
interface RAGSearchPanelProps {
onClose: () => void
defaultSubject?: string
passphrase: string
}
interface SearchOptions {
rerank: boolean
limit: number
}
// Confidence level based on score
type ConfidenceLevel = 'high' | 'medium' | 'low'
function getConfidenceLevel(score: number): ConfidenceLevel {
if (score >= 0.8) return 'high'
if (score >= 0.5) return 'medium'
return 'low'
}
function getConfidenceColor(level: ConfidenceLevel): string {
switch (level) {
case 'high': return 'var(--bp-success, #22c55e)'
case 'medium': return 'var(--bp-warning, #f59e0b)'
case 'low': return 'var(--bp-danger, #ef4444)'
}
}
function getConfidenceLabel(level: ConfidenceLevel): string {
switch (level) {
case 'high': return 'Hohe Relevanz'
case 'medium': return 'Mittlere Relevanz'
case 'low': return 'Geringe Relevanz'
}
}
export default function RAGSearchPanel({ onClose, defaultSubject, passphrase }: RAGSearchPanelProps) {
const [query, setQuery] = useState('')
const [searching, setSearching] = useState(false)
const [results, setResults] = useState<EHRAGResult | null>(null)
const [error, setError] = useState<string | null>(null)
// Enhanced search options
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false)
const [options, setOptions] = useState<SearchOptions>({
rerank: true, // Default: enabled for better results
limit: 5
})
const handleSearch = useCallback(async () => {
if (!query.trim() || !passphrase) return
setSearching(true)
setError(null)
try {
const result = await ehApi.ragQuery({
query_text: query,
passphrase: passphrase,
subject: defaultSubject,
limit: options.limit,
rerank: options.rerank
})
setResults(result)
} catch (err) {
console.error('RAG search failed:', err)
setError(err instanceof Error ? err.message : 'Suche fehlgeschlagen')
} finally {
setSearching(false)
}
}, [query, passphrase, defaultSubject, options])
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
handleSearch()
}
}
return (
<div className="rag-search-overlay">
<div className="rag-search-modal">
{/* Header */}
<div className="rag-search-header">
<h2>Erwartungshorizont durchsuchen</h2>
<button className="rag-search-close" onClick={onClose}>&times;</button>
</div>
{/* Search Input */}
<div className="rag-search-input-container">
<textarea
className="rag-search-input"
placeholder="Suchen Sie nach relevanten Abschnitten im Erwartungshorizont..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
rows={2}
/>
<button
className="rag-search-btn"
onClick={handleSearch}
disabled={searching || !query.trim()}
>
{searching ? 'Sucht...' : 'Suchen'}
</button>
</div>
{/* Advanced Options Toggle */}
<div className="rag-advanced-toggle">
<button
className="rag-toggle-btn"
onClick={() => setShowAdvancedOptions(!showAdvancedOptions)}
>
{showAdvancedOptions ? '▼' : '▶'} Erweiterte Optionen
</button>
</div>
{/* Advanced Options Panel */}
{showAdvancedOptions && (
<div className="rag-advanced-options">
<div className="rag-option-row">
<label className="rag-option-label">
<input
type="checkbox"
checked={options.rerank}
onChange={(e) => setOptions(prev => ({ ...prev, rerank: e.target.checked }))}
/>
<span className="rag-option-text">
Re-Ranking aktivieren
<span className="rag-option-hint">
(Cross-Encoder fuer hoehere Genauigkeit)
</span>
</span>
</label>
</div>
<div className="rag-option-row">
<label className="rag-option-label">
Anzahl Ergebnisse:
<select
value={options.limit}
onChange={(e) => setOptions(prev => ({ ...prev, limit: Number(e.target.value) }))}
className="rag-option-select"
>
<option value={3}>3</option>
<option value={5}>5</option>
<option value={10}>10</option>
</select>
</label>
</div>
</div>
)}
{/* Error Display */}
{error && (
<div className="rag-error">
{error}
</div>
)}
{/* Results */}
{results && (
<div className="rag-results">
{/* Search Info Badge */}
{results.search_info && (
<div className="rag-search-info">
<span className="rag-info-badge">
{results.search_info.rerank_applied && (
<span className="rag-info-tag rag-info-rerank">Re-Ranked</span>
)}
{results.search_info.hybrid_search_applied && (
<span className="rag-info-tag rag-info-hybrid">Hybrid Search</span>
)}
{results.search_info.embedding_model && (
<span className="rag-info-tag rag-info-model">
{results.search_info.embedding_model.split('/').pop()}
</span>
)}
</span>
<span className="rag-info-count">
{results.search_info.total_candidates} Kandidaten gefiltert
</span>
</div>
)}
{/* Context Summary */}
{results.context && (
<div className="rag-context-summary">
<h4>Zusammengefasster Kontext</h4>
<p>{results.context}</p>
</div>
)}
{/* Source Results */}
<div className="rag-sources">
<h4>Relevante Abschnitte ({results.sources.length})</h4>
{results.sources.map((source, index) => {
const confidence = getConfidenceLevel(source.score)
return (
<div key={`${source.eh_id}-${source.chunk_index}`} className="rag-source-item">
<div className="rag-source-header">
<span className="rag-source-rank">#{index + 1}</span>
<span className="rag-source-title">{source.eh_title}</span>
<span
className="rag-confidence-badge"
style={{
backgroundColor: getConfidenceColor(confidence),
color: 'white'
}}
title={`Score: ${(source.score * 100).toFixed(1)}%`}
>
{getConfidenceLabel(confidence)}
{source.reranked && <span className="rag-reranked-icon" title="Re-ranked">&#x2713;</span>}
</span>
</div>
<div className="rag-source-text">
{source.text}
</div>
<div className="rag-source-meta">
<span>Chunk #{source.chunk_index}</span>
<span>Score: {(source.score * 100).toFixed(1)}%</span>
</div>
</div>
)
})}
</div>
</div>
)}
{/* Empty State */}
{!results && !error && !searching && (
<div className="rag-empty-state">
<div className="rag-empty-icon">&#128269;</div>
<p>Geben Sie eine Suchanfrage ein, um relevante Abschnitte aus Ihren Erwartungshorizonten zu finden.</p>
<p className="rag-empty-hint">
Tipp: Aktivieren Sie "Re-Ranking" fuer praezisere Ergebnisse.
</p>
</div>
)}
</div>
</div>
)
}