Files
breakpilot-lehrer/admin-lehrer/components/grid-editor/useGridEditorApi.ts
Benjamin Admin b681ddb131 [split-required] Split 58 monoliths across Python, Go, TypeScript (Phases 1-3)
Phase 1 — Python (klausur-service): 5 monoliths → 36 files
- dsfa_corpus_ingestion.py (1,828 LOC → 5 files)
- cv_ocr_engines.py (2,102 LOC → 7 files)
- cv_layout.py (3,653 LOC → 10 files)
- vocab_worksheet_api.py (2,783 LOC → 8 files)
- grid_build_core.py (1,958 LOC → 6 files)

Phase 2 — Go (edu-search-service, school-service): 8 monoliths → 19 files
- staff_crawler.go (1,402 → 4), policy/store.go (1,168 → 3)
- policy_handlers.go (700 → 2), repository.go (684 → 2)
- search.go (592 → 2), ai_extraction_handlers.go (554 → 2)
- seed_data.go (591 → 2), grade_service.go (646 → 2)

Phase 3 — TypeScript (admin-lehrer): 45 monoliths → 220+ files
- sdk/types.ts (2,108 → 16 domain files)
- ai/rag/page.tsx (2,686 → 14 files)
- 22 page.tsx files split into _components/ + _hooks/
- 11 component files split into sub-components
- 10 SDK data catalogs added to loc-exceptions
- Deleted dead backup index_original.ts (4,899 LOC)

All original public APIs preserved via re-export facades.
Zero new errors: Python imports verified, Go builds clean,
TypeScript tsc --noEmit shows only pre-existing errors.

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

242 lines
8.0 KiB
TypeScript

import { useCallback, useEffect, useRef } from 'react'
import type { StructuredGrid } from './types'
import type { IpaMode, SyllableMode } from './gridEditorTypes'
import { KLAUSUR_API } from './gridEditorTypes'
interface ApiDeps {
sessionId: string | null
ipaMode: IpaMode
syllableMode: SyllableMode
ocrEnhance: boolean
ocrMaxCols: number
ocrMinConf: number
visionFusion: boolean
documentCategory: string
setGrid: React.Dispatch<React.SetStateAction<StructuredGrid | null>>
setLoading: React.Dispatch<React.SetStateAction<boolean>>
setSaving: React.Dispatch<React.SetStateAction<boolean>>
setError: React.Dispatch<React.SetStateAction<string | null>>
setDirty: React.Dispatch<React.SetStateAction<boolean>>
grid: StructuredGrid | null
undoStack: React.MutableRefObject<string[]>
redoStack: React.MutableRefObject<string[]>
}
export function useGridEditorApi(deps: ApiDeps) {
const {
sessionId,
ipaMode,
syllableMode,
ocrEnhance,
ocrMaxCols,
ocrMinConf,
visionFusion,
documentCategory,
setGrid,
setLoading,
setSaving,
setError,
setDirty,
grid,
undoStack,
redoStack,
} = deps
// ------------------------------------------------------------------
// Build grid
// ------------------------------------------------------------------
const buildGrid = useCallback(async () => {
if (!sessionId) return
setLoading(true)
setError(null)
try {
const params = new URLSearchParams()
params.set('ipa_mode', ipaMode)
params.set('syllable_mode', syllableMode)
params.set('enhance', String(ocrEnhance))
if (ocrMaxCols > 0) params.set('max_cols', String(ocrMaxCols))
if (ocrMinConf > 0) params.set('min_conf', String(ocrMinConf))
const res = await fetch(
`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/build-grid?${params}`,
{ method: 'POST' },
)
if (!res.ok) {
const data = await res.json().catch(() => ({}))
throw new Error(data.detail || `HTTP ${res.status}`)
}
const data: StructuredGrid = await res.json()
setGrid(data)
setDirty(false)
undoStack.current = []
redoStack.current = []
} catch (e) {
setError(e instanceof Error ? e.message : String(e))
} finally {
setLoading(false)
}
}, [sessionId, ipaMode, syllableMode, ocrEnhance, ocrMaxCols, ocrMinConf, setGrid, setLoading, setError, setDirty, undoStack, redoStack])
// ------------------------------------------------------------------
// Re-run OCR with current quality settings, then rebuild grid
// ------------------------------------------------------------------
const rerunOcr = useCallback(async () => {
if (!sessionId) return
setLoading(true)
setError(null)
try {
const params = new URLSearchParams()
params.set('ipa_mode', ipaMode)
params.set('syllable_mode', syllableMode)
params.set('enhance', String(ocrEnhance))
if (ocrMaxCols > 0) params.set('max_cols', String(ocrMaxCols))
if (ocrMinConf > 0) params.set('min_conf', String(ocrMinConf))
params.set('vision_fusion', String(visionFusion))
if (documentCategory) params.set('doc_category', documentCategory)
const res = await fetch(
`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/rerun-ocr-and-build-grid?${params}`,
{ method: 'POST' },
)
if (!res.ok) {
const data = await res.json().catch(() => ({}))
throw new Error(data.detail || `HTTP ${res.status}`)
}
const data: StructuredGrid = await res.json()
setGrid(data)
setDirty(false)
undoStack.current = []
redoStack.current = []
} catch (e) {
setError(e instanceof Error ? e.message : String(e))
} finally {
setLoading(false)
}
}, [sessionId, ipaMode, syllableMode, ocrEnhance, ocrMaxCols, ocrMinConf, visionFusion, documentCategory, setGrid, setLoading, setError, setDirty, undoStack, redoStack])
// ------------------------------------------------------------------
// Load grid (with auto-build fallback on 404)
// ------------------------------------------------------------------
const loadGrid = useCallback(async () => {
if (!sessionId) return
setLoading(true)
setError(null)
try {
const res = await fetch(
`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/grid-editor`,
)
if (res.status === 404) {
// No grid yet — build it with current modes
const params = new URLSearchParams()
params.set('ipa_mode', ipaMode)
params.set('syllable_mode', syllableMode)
params.set('enhance', String(ocrEnhance))
if (ocrMaxCols > 0) params.set('max_cols', String(ocrMaxCols))
if (ocrMinConf > 0) params.set('min_conf', String(ocrMinConf))
const buildRes = await fetch(
`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/build-grid?${params}`,
{ method: 'POST' },
)
if (buildRes.ok) {
const data: StructuredGrid = await buildRes.json()
setGrid(data)
setDirty(false)
}
return
}
if (!res.ok) {
const data = await res.json().catch(() => ({}))
throw new Error(data.detail || `HTTP ${res.status}`)
}
const data: StructuredGrid = await res.json()
setGrid(data)
setDirty(false)
undoStack.current = []
redoStack.current = []
} catch (e) {
setError(e instanceof Error ? e.message : String(e))
} finally {
setLoading(false)
}
// Only depends on sessionId — mode changes are handled by the
// separate useEffect below, not by re-triggering loadGrid.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sessionId])
// ------------------------------------------------------------------
// Auto-rebuild when IPA or syllable mode changes (skip initial mount)
// ------------------------------------------------------------------
const mountedRef = useRef(false)
useEffect(() => {
if (!mountedRef.current) {
// Skip the first trigger (component mount) — don't rebuild yet
mountedRef.current = true
return
}
if (!sessionId) return
const rebuild = async () => {
setLoading(true)
setError(null)
try {
const params = new URLSearchParams()
params.set('ipa_mode', ipaMode)
params.set('syllable_mode', syllableMode)
const res = await fetch(
`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/build-grid?${params}`,
{ method: 'POST' },
)
if (!res.ok) {
const data = await res.json().catch(() => ({}))
throw new Error(data.detail || `HTTP ${res.status}`)
}
const data: StructuredGrid = await res.json()
setGrid(data)
setDirty(false)
} catch (e) {
setError(e instanceof Error ? e.message : String(e))
} finally {
setLoading(false)
}
}
rebuild()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ipaMode, syllableMode])
// ------------------------------------------------------------------
// Save grid
// ------------------------------------------------------------------
const saveGrid = useCallback(async () => {
if (!sessionId || !grid) return
setSaving(true)
try {
const res = await fetch(
`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/save-grid`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(grid),
},
)
if (!res.ok) {
const data = await res.json().catch(() => ({}))
throw new Error(data.detail || `HTTP ${res.status}`)
}
setDirty(false)
} catch (e) {
setError(e instanceof Error ? e.message : String(e))
} finally {
setSaving(false)
}
}, [sessionId, grid, setSaving, setError, setDirty])
return {
buildGrid,
rerunOcr,
loadGrid,
saveGrid,
}
}