Files
breakpilot-lehrer/studio-v2/app/vocab-worksheet/useSessionHandlers.ts
Benjamin Admin b6983ab1dc [split-required] Split 500-1000 LOC files across all services
backend-lehrer (5 files):
- alerts_agent/db/repository.py (992 → 5), abitur_docs_api.py (956 → 3)
- teacher_dashboard_api.py (951 → 3), services/pdf_service.py (916 → 3)
- mail/mail_db.py (987 → 6)

klausur-service (5 files):
- legal_templates_ingestion.py (942 → 3), ocr_pipeline_postprocess.py (929 → 4)
- ocr_pipeline_words.py (876 → 3), ocr_pipeline_ocr_merge.py (616 → 2)
- KorrekturPage.tsx (956 → 6)

website (5 pages):
- mail (985 → 9), edu-search (958 → 8), mac-mini (950 → 7)
- ocr-labeling (946 → 7), audit-workspace (871 → 4)

studio-v2 (5 files + 1 deleted):
- page.tsx (946 → 5), MessagesContext.tsx (925 → 4)
- korrektur (914 → 6), worksheet-cleanup (899 → 6)
- useVocabWorksheet.ts (888 → 3)
- Deleted dead page-original.tsx (934 LOC)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 23:35:37 +02:00

157 lines
6.8 KiB
TypeScript

import type {
VocabularyEntry, Session, StoredDocument, OcrPrompts, IpaMode, SyllableMode,
} from './types'
import { getApiBase } from './constants'
/**
* Start a new session: create on server, upload document, process first page or PDF.
*/
export async function startSessionFlow(params: {
sessionName: string
selectedDocumentId: string | null
directFile: File | null
selectedMobileFile: { dataUrl: string; type: string; name: string } | null
storedDocuments: StoredDocument[]
ocrPrompts: OcrPrompts
startActivity: (type: string, meta: any) => void
setSession: (s: Session | null | ((prev: Session | null) => Session | null)) => void
setWorksheetTitle: (t: string) => void
setExtractionStatus: (s: string) => void
setPdfPageCount: (n: number) => void
setSelectedPages: (p: number[]) => void
setPagesThumbnails: (t: string[]) => void
setIsLoadingThumbnails: (l: boolean) => void
setVocabulary: (v: VocabularyEntry[]) => void
setActiveTab: (t: 'upload' | 'pages' | 'vocabulary' | 'spreadsheet' | 'worksheet' | 'export' | 'settings') => void
setError: (e: string | null) => void
}): Promise<Session | null> {
const {
sessionName, selectedDocumentId, directFile, selectedMobileFile, storedDocuments,
ocrPrompts, startActivity, setSession, setWorksheetTitle, setExtractionStatus,
setPdfPageCount, setSelectedPages, setPagesThumbnails, setIsLoadingThumbnails,
setVocabulary, setActiveTab, setError,
} = params
setError(null)
setExtractionStatus('Session wird erstellt...')
const API_BASE = getApiBase()
const sessionRes = await fetch(`${API_BASE}/api/v1/vocab/sessions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: sessionName, ocr_prompts: ocrPrompts }),
})
if (!sessionRes.ok) throw new Error('Session konnte nicht erstellt werden')
const sessionData = await sessionRes.json()
setSession(sessionData)
setWorksheetTitle(sessionName)
startActivity('vocab_extraction', { description: sessionName })
let file: File
let isPdf = false
if (directFile) {
file = directFile
isPdf = directFile.type === 'application/pdf'
} else if (selectedMobileFile) {
isPdf = selectedMobileFile.type === 'application/pdf'
const base64Data = selectedMobileFile.dataUrl.split(',')[1]
const byteCharacters = atob(base64Data)
const byteNumbers = new Array(byteCharacters.length)
for (let i = 0; i < byteCharacters.length; i++) byteNumbers[i] = byteCharacters.charCodeAt(i)
const blob = new Blob([new Uint8Array(byteNumbers)], { type: selectedMobileFile.type })
file = new File([blob], selectedMobileFile.name, { type: selectedMobileFile.type })
} else {
const selectedDoc = storedDocuments.find(d => d.id === selectedDocumentId)
if (!selectedDoc || !selectedDoc.url) throw new Error('Das ausgewaehlte Dokument ist nicht verfuegbar.')
isPdf = selectedDoc.type === 'application/pdf'
const base64Data = selectedDoc.url.split(',')[1]
const byteCharacters = atob(base64Data)
const byteNumbers = new Array(byteCharacters.length)
for (let i = 0; i < byteCharacters.length; i++) byteNumbers[i] = byteCharacters.charCodeAt(i)
const blob = new Blob([new Uint8Array(byteNumbers)], { type: selectedDoc.type })
file = new File([blob], selectedDoc.name, { type: selectedDoc.type })
}
if (isPdf) {
setExtractionStatus('PDF wird hochgeladen...')
const formData = new FormData()
formData.append('file', file)
const pdfInfoRes = await fetch(`${API_BASE}/api/v1/vocab/sessions/${sessionData.id}/upload-pdf-info`, {
method: 'POST', body: formData,
})
if (!pdfInfoRes.ok) throw new Error('PDF konnte nicht verarbeitet werden')
const pdfInfo = await pdfInfoRes.json()
setPdfPageCount(pdfInfo.page_count)
setSelectedPages(Array.from({ length: pdfInfo.page_count }, (_, i) => i))
setActiveTab('pages')
setExtractionStatus(`${pdfInfo.page_count} Seiten erkannt. Vorschau wird geladen...`)
setIsLoadingThumbnails(true)
const thumbnails: string[] = []
for (let i = 0; i < pdfInfo.page_count; i++) {
try {
const thumbRes = await fetch(`${API_BASE}/api/v1/vocab/sessions/${sessionData.id}/pdf-thumbnail/${i}?hires=true`)
if (thumbRes.ok) { const blob = await thumbRes.blob(); thumbnails.push(URL.createObjectURL(blob)) }
} catch (e) { console.error(`Failed to load thumbnail for page ${i}`) }
}
setPagesThumbnails(thumbnails)
setIsLoadingThumbnails(false)
setExtractionStatus(`${pdfInfo.page_count} Seiten bereit. Waehlen Sie die zu verarbeitenden Seiten.`)
} else {
setExtractionStatus('KI analysiert das Bild... (kann 30-60 Sekunden dauern)')
const formData = new FormData()
formData.append('file', file)
const uploadRes = await fetch(`${API_BASE}/api/v1/vocab/sessions/${sessionData.id}/upload`, {
method: 'POST', body: formData,
})
if (!uploadRes.ok) throw new Error('Bild konnte nicht verarbeitet werden')
const uploadData = await uploadRes.json()
setSession(prev => prev ? { ...prev, status: 'extracted', vocabulary_count: uploadData.vocabulary_count } : null)
const vocabRes = await fetch(`${API_BASE}/api/v1/vocab/sessions/${sessionData.id}/vocabulary`)
if (vocabRes.ok) {
const vocabData = await vocabRes.json()
setVocabulary(vocabData.vocabulary || [])
setExtractionStatus(`${vocabData.vocabulary?.length || 0} Vokabeln gefunden!`)
}
await new Promise(r => setTimeout(r, 1000))
setActiveTab('vocabulary')
}
return sessionData
}
/**
* Resume an existing session from the API.
*/
export async function resumeSessionFlow(
existingSession: Session,
setSession: (s: Session) => void,
setWorksheetTitle: (t: string) => void,
setVocabulary: (v: VocabularyEntry[]) => void,
setActiveTab: (t: 'upload' | 'pages' | 'vocabulary' | 'spreadsheet' | 'worksheet' | 'export' | 'settings') => void,
setExtractionStatus: (s: string) => void,
): Promise<void> {
const API_BASE = getApiBase()
const sessionRes = await fetch(`${API_BASE}/api/v1/vocab/sessions/${existingSession.id}`)
if (!sessionRes.ok) throw new Error('Session nicht gefunden')
const sessionData = await sessionRes.json()
setSession(sessionData)
setWorksheetTitle(sessionData.name)
if (sessionData.status === 'extracted' || sessionData.status === 'completed') {
const vocabRes = await fetch(`${API_BASE}/api/v1/vocab/sessions/${existingSession.id}/vocabulary`)
if (vocabRes.ok) { const vd = await vocabRes.json(); setVocabulary(vd.vocabulary || []) }
setActiveTab('vocabulary')
setExtractionStatus('')
} else if (sessionData.status === 'pending') {
setActiveTab('upload')
setExtractionStatus('Diese Session hat noch keine Vokabeln. Bitte laden Sie ein Dokument hoch.')
} else {
setActiveTab('vocabulary')
setExtractionStatus('')
}
}