Add SmartSpellChecker + refactor vocab-worksheet page.tsx
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 45s
CI / test-go-edu-search (push) Successful in 43s
CI / test-python-klausur (push) Failing after 2m51s
CI / test-python-agent-core (push) Successful in 36s
CI / test-nodejs-website (push) Successful in 37s
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 45s
CI / test-go-edu-search (push) Successful in 43s
CI / test-python-klausur (push) Failing after 2m51s
CI / test-python-agent-core (push) Successful in 36s
CI / test-nodejs-website (push) Successful in 37s
SmartSpellChecker (klausur-service): - Language-aware OCR post-correction without LLMs - Dual-dictionary heuristic for EN/DE language detection - Context-based a/I disambiguation via bigram lookup - Multi-digit substitution (sch00l→school) - Cross-language guard (don't false-correct DE words in EN column) - Umlaut correction (Schuler→Schüler, uber→über) - Integrated into spell_review_entries_sync() pipeline - 31 tests, 9ms/100 corrections Vocab-worksheet refactoring (studio-v2): - Split 2337-line page.tsx into 14 files - Custom hook useVocabWorksheet.ts (all state + logic) - 9 components in components/ directory - types.ts, constants.ts for shared definitions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
125
studio-v2/app/vocab-worksheet/components/OcrSettingsPanel.tsx
Normal file
125
studio-v2/app/vocab-worksheet/components/OcrSettingsPanel.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import type { VocabWorksheetHook } from '../types'
|
||||
import { defaultOcrPrompts } from '../constants'
|
||||
|
||||
export function OcrSettingsPanel({ h }: { h: VocabWorksheetHook }) {
|
||||
const { isDark, glassCard, glassInput } = h
|
||||
|
||||
return (
|
||||
<div className={`${glassCard} rounded-2xl p-6 mb-6`}>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className={`text-lg font-semibold ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||||
OCR-Filter Einstellungen
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => h.setShowSettings(false)}
|
||||
className={`p-1 rounded-lg ${isDark ? 'hover:bg-white/10 text-white/60' : 'hover:bg-black/5 text-slate-500'}`}
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={`p-4 rounded-xl mb-4 ${isDark ? 'bg-blue-500/20 text-blue-200' : 'bg-blue-100 text-blue-800'}`}>
|
||||
<p className="text-sm">
|
||||
Diese Einstellungen helfen, unerwuenschte Elemente wie Seitenzahlen, Kapitelnamen oder Kopfzeilen aus dem OCR-Ergebnis zu filtern.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Checkboxes */}
|
||||
<div className="space-y-3">
|
||||
<label className={`flex items-center gap-3 cursor-pointer ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={h.ocrPrompts.filterHeaders}
|
||||
onChange={(e) => h.saveOcrPrompts({ ...h.ocrPrompts, filterHeaders: e.target.checked })}
|
||||
className="w-5 h-5 rounded border-2 border-purple-500 text-purple-500 focus:ring-purple-500"
|
||||
/>
|
||||
<span>Kopfzeilen filtern (z.B. Kapitelnamen)</span>
|
||||
</label>
|
||||
|
||||
<label className={`flex items-center gap-3 cursor-pointer ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={h.ocrPrompts.filterFooters}
|
||||
onChange={(e) => h.saveOcrPrompts({ ...h.ocrPrompts, filterFooters: e.target.checked })}
|
||||
className="w-5 h-5 rounded border-2 border-purple-500 text-purple-500 focus:ring-purple-500"
|
||||
/>
|
||||
<span>Fusszeilen filtern</span>
|
||||
</label>
|
||||
|
||||
<label className={`flex items-center gap-3 cursor-pointer ${isDark ? 'text-white' : 'text-slate-900'}`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={h.ocrPrompts.filterPageNumbers}
|
||||
onChange={(e) => h.saveOcrPrompts({ ...h.ocrPrompts, filterPageNumbers: e.target.checked })}
|
||||
className="w-5 h-5 rounded border-2 border-purple-500 text-purple-500 focus:ring-purple-500"
|
||||
/>
|
||||
<span>Seitenzahlen filtern (auch ausgeschrieben: "zweihundertzwoelf")</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Patterns */}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className={`block text-sm font-medium mb-2 ${isDark ? 'text-white/70' : 'text-slate-600'}`}>
|
||||
Kopfzeilen-Muster (kommagetrennt)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={h.ocrPrompts.headerPatterns.join(', ')}
|
||||
onChange={(e) => h.saveOcrPrompts({
|
||||
...h.ocrPrompts,
|
||||
headerPatterns: e.target.value.split(',').map(s => s.trim()).filter(Boolean)
|
||||
})}
|
||||
placeholder="Unit, Chapter, Lesson..."
|
||||
className={`w-full px-4 py-2 rounded-xl border ${glassInput} focus:outline-none focus:ring-2 focus:ring-purple-500`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className={`block text-sm font-medium mb-2 ${isDark ? 'text-white/70' : 'text-slate-600'}`}>
|
||||
Fusszeilen-Muster (kommagetrennt)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={h.ocrPrompts.footerPatterns.join(', ')}
|
||||
onChange={(e) => h.saveOcrPrompts({
|
||||
...h.ocrPrompts,
|
||||
footerPatterns: e.target.value.split(',').map(s => s.trim()).filter(Boolean)
|
||||
})}
|
||||
placeholder="zweihundert, Page, Seite..."
|
||||
className={`w-full px-4 py-2 rounded-xl border ${glassInput} focus:outline-none focus:ring-2 focus:ring-purple-500`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<label className={`block text-sm font-medium mb-2 ${isDark ? 'text-white/70' : 'text-slate-600'}`}>
|
||||
Zusaetzlicher Filter-Prompt (optional)
|
||||
</label>
|
||||
<textarea
|
||||
value={h.ocrPrompts.customFilter}
|
||||
onChange={(e) => h.saveOcrPrompts({ ...h.ocrPrompts, customFilter: e.target.value })}
|
||||
placeholder="z.B.: Ignoriere alle Zeilen, die nur Zahlen oder Buchstaben enthalten..."
|
||||
rows={2}
|
||||
className={`w-full px-4 py-2 rounded-xl border ${glassInput} focus:outline-none focus:ring-2 focus:ring-purple-500 resize-none`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex justify-end">
|
||||
<button
|
||||
onClick={() => h.saveOcrPrompts(defaultOcrPrompts)}
|
||||
className={`px-4 py-2 rounded-xl text-sm ${isDark ? 'text-white/60 hover:text-white' : 'text-slate-500 hover:text-slate-700'}`}
|
||||
>
|
||||
Auf Standard zuruecksetzen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user