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
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:
255
klausur-service/frontend/src/components/RAGSearchPanel.tsx
Normal file
255
klausur-service/frontend/src/components/RAGSearchPanel.tsx
Normal 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}>×</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">✓</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">🔍</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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user