This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

431 lines
14 KiB
JavaScript

/**
* BreakPilot Studio - Cloze (Lückentext) Module
*
* Lückentext-Funktionalität mit Übersetzung:
* - Generierung von Lückentexten aus Arbeitsblättern
* - Mehrsprachige Übersetzungen (TR, AR, RU, UK, PL, EN)
* - Interaktives Übungsmodul mit Auswertung
* - Druckfunktion (mit/ohne Lösungen)
*
* Refactored: 2026-01-19
*/
import { t } from './i18n.js';
import { setStatus } from './api-helpers.js';
// State
let currentClozeData = null;
let clozeAnswers = {};
// DOM References
let clozePreview = null;
let clozeBadge = null;
let clozeLanguageSelect = null;
let clozeModal = null;
let clozeModalBody = null;
let clozeModalClose = null;
let btnClozeGenerate = null;
let btnClozeShow = null;
let btnClozePrint = null;
// Callbacks
let getEingangFilesCallback = null;
let getCurrentIndexCallback = null;
/**
* Initialisiert das Cloze-Modul
* @param {Object} options - Konfiguration
*/
export function initClozeModule(options = {}) {
getEingangFilesCallback = options.getEingangFiles || (() => []);
getCurrentIndexCallback = options.getCurrentIndex || (() => 0);
// DOM References
clozePreview = document.getElementById('cloze-preview') || options.previewEl;
clozeBadge = document.getElementById('cloze-badge') || options.badgeEl;
clozeLanguageSelect = document.getElementById('cloze-language') || options.languageSelectEl;
clozeModal = document.getElementById('cloze-modal') || options.modalEl;
clozeModalBody = document.getElementById('cloze-modal-body') || options.modalBodyEl;
clozeModalClose = document.getElementById('cloze-modal-close') || options.modalCloseEl;
btnClozeGenerate = document.getElementById('btn-cloze-generate') || options.generateBtn;
btnClozeShow = document.getElementById('btn-cloze-show') || options.showBtn;
btnClozePrint = document.getElementById('btn-cloze-print') || options.printBtn;
// Event-Listener
if (btnClozeGenerate) {
btnClozeGenerate.addEventListener('click', generateClozeTexts);
}
if (btnClozeShow) {
btnClozeShow.addEventListener('click', openClozeModal);
}
if (btnClozePrint) {
btnClozePrint.addEventListener('click', openClozePrintDialog);
}
if (clozeModalClose) {
clozeModalClose.addEventListener('click', closeClozeModal);
}
if (clozeModal) {
clozeModal.addEventListener('click', (ev) => {
if (ev.target === clozeModal) {
closeClozeModal();
}
});
}
// Event für Datei-Wechsel
window.addEventListener('fileSelected', () => {
loadClozePreviewForCurrent();
});
}
/**
* Generiert Lückentexte für alle Arbeitsblätter
*/
export async function generateClozeTexts() {
const targetLang = clozeLanguageSelect ? clozeLanguageSelect.value : 'tr';
try {
setStatus(t('cloze_generating') || 'Generiere Lückentexte …', t('ai_working') || 'Bitte warten, KI arbeitet.', 'busy');
if (clozeBadge) clozeBadge.textContent = t('generating') || 'Generiert...';
const resp = await fetch('/api/generate-cloze?target_language=' + targetLang, { method: 'POST' });
if (!resp.ok) {
throw new Error('HTTP ' + resp.status);
}
const result = await resp.json();
if (result.status === 'OK' && result.generated.length > 0) {
setStatus(t('cloze_generated') || 'Lückentexte generiert', result.generated.length + ' ' + (t('files_created') || 'Dateien erstellt'));
if (clozeBadge) clozeBadge.textContent = t('ready') || 'Fertig';
if (btnClozeShow) btnClozeShow.style.display = 'inline-block';
if (btnClozePrint) btnClozePrint.style.display = 'inline-block';
// Lade Vorschau für aktuelle Datei
await loadClozePreviewForCurrent();
} else if (result.errors && result.errors.length > 0) {
setStatus(t('cloze_error') || 'Fehler bei Lückentext-Generierung', result.errors[0].error, 'error');
if (clozeBadge) clozeBadge.textContent = t('error') || 'Fehler';
} else {
setStatus(t('no_cloze_generated') || 'Keine Lückentexte generiert', t('analysis_missing') || 'Möglicherweise fehlen Analyse-Daten.', 'error');
if (clozeBadge) clozeBadge.textContent = t('ready') || 'Bereit';
}
} catch (e) {
console.error('Lückentext-Generierung fehlgeschlagen:', e);
setStatus(t('cloze_error') || 'Fehler bei Lückentext-Generierung', String(e), 'error');
if (clozeBadge) clozeBadge.textContent = t('error') || 'Fehler';
}
}
/**
* Lädt Cloze-Vorschau für die aktuelle Datei
*/
export async function loadClozePreviewForCurrent() {
const eingangFiles = getEingangFilesCallback();
const currentIndex = getCurrentIndexCallback();
if (!eingangFiles.length) {
if (clozePreview) clozePreview.innerHTML = '<div style="font-size:11px;color:var(--bp-text-muted);">' + (t('no_worksheets') || 'Keine Arbeitsblätter vorhanden.') + '</div>';
return;
}
const currentFile = eingangFiles[currentIndex];
if (!currentFile) return;
try {
const resp = await fetch('/api/cloze-data/' + encodeURIComponent(currentFile));
const result = await resp.json();
if (result.status === 'OK' && result.data) {
currentClozeData = result.data;
renderClozePreview(result.data);
if (btnClozeShow) btnClozeShow.style.display = 'inline-block';
if (btnClozePrint) btnClozePrint.style.display = 'inline-block';
} else {
if (clozePreview) clozePreview.innerHTML = '<div style="font-size:11px;color:var(--bp-text-muted);">' + (t('no_cloze_for_worksheet') || 'Noch keine Lückentexte für dieses Arbeitsblatt generiert.') + '</div>';
currentClozeData = null;
if (btnClozeShow) btnClozeShow.style.display = 'none';
if (btnClozePrint) btnClozePrint.style.display = 'none';
}
} catch (e) {
console.error('Fehler beim Laden der Lückentext-Daten:', e);
if (clozePreview) clozePreview.innerHTML = '';
}
}
/**
* Rendert die Cloze-Vorschau
* @param {Object} clozeData - Cloze-Daten
*/
function renderClozePreview(clozeData) {
if (!clozePreview) return;
if (!clozeData || !clozeData.cloze_items || clozeData.cloze_items.length === 0) {
clozePreview.innerHTML = '<div style="font-size:11px;color:var(--bp-text-muted);">' + (t('no_cloze_texts') || 'Keine Lückentexte vorhanden.') + '</div>';
return;
}
const items = clozeData.cloze_items;
const metadata = clozeData.metadata || {};
let html = '';
// Statistiken
html += '<div class="cloze-stats">';
if (metadata.subject) {
html += '<div><strong>' + (t('subject') || 'Fach') + ':</strong> ' + escapeHtml(metadata.subject) + '</div>';
}
if (metadata.grade_level) {
html += '<div><strong>' + (t('grade') || 'Stufe') + ':</strong> ' + escapeHtml(metadata.grade_level) + '</div>';
}
html += '<div><strong>' + (t('sentences') || 'Sätze') + ':</strong> ' + items.length + '</div>';
if (metadata.total_gaps) {
html += '<div><strong>' + (t('gaps') || 'Lücken') + ':</strong> ' + metadata.total_gaps + '</div>';
}
html += '</div>';
// Zeige erste 2 Sätze als Vorschau
const previewItems = items.slice(0, 2);
previewItems.forEach((item, idx) => {
html += '<div class="cloze-item">';
html += '<div class="cloze-sentence">' + (idx + 1) + '. ' + item.sentence_with_gaps.replace(/___/g, '<span class="cloze-gap">___</span>') + '</div>';
// Übersetzung anzeigen
if (item.translation && item.translation.full_sentence) {
html += '<div class="cloze-translation">';
html += '<div class="cloze-translation-label">' + (item.translation.language_name || t('translation') || 'Übersetzung') + ':</div>';
html += escapeHtml(item.translation.full_sentence);
html += '</div>';
}
html += '</div>';
});
if (items.length > 2) {
html += '<div style="font-size:11px;color:var(--bp-text-muted);text-align:center;margin-top:8px;">+ ' + (items.length - 2) + ' ' + (t('more_sentences') || 'weitere Sätze') + '</div>';
}
clozePreview.innerHTML = html;
}
/**
* Öffnet das Cloze-Modal
*/
export function openClozeModal() {
if (!currentClozeData || !currentClozeData.cloze_items) {
alert(t('no_cloze_texts') || 'Keine Lückentexte vorhanden. Bitte zuerst generieren.');
return;
}
clozeAnswers = {}; // Reset Antworten
renderClozeModal(currentClozeData);
if (clozeModal) clozeModal.classList.remove('hidden');
}
/**
* Schließt das Cloze-Modal
*/
export function closeClozeModal() {
if (clozeModal) clozeModal.classList.add('hidden');
}
/**
* Rendert den Modal-Inhalt
* @param {Object} clozeData - Cloze-Daten
*/
function renderClozeModal(clozeData) {
if (!clozeModalBody) return;
const items = clozeData.cloze_items;
const metadata = clozeData.metadata || {};
let html = '';
// Header
html += '<div class="cloze-stats" style="margin-bottom:16px;">';
if (metadata.source_title) {
html += '<div><strong>' + (t('worksheet') || 'Arbeitsblatt') + ':</strong> ' + escapeHtml(metadata.source_title) + '</div>';
}
if (metadata.total_gaps) {
html += '<div><strong>' + (t('total_gaps') || 'Lücken gesamt') + ':</strong> ' + metadata.total_gaps + '</div>';
}
html += '</div>';
html += '<div style="font-size:12px;color:var(--bp-text-muted);margin-bottom:12px;">' + (t('cloze_instruction') || 'Fülle die Lücken aus und klicke auf "Prüfen".') + '</div>';
// Alle Sätze mit Eingabefeldern
items.forEach((item, idx) => {
html += '<div class="cloze-item" data-cid="' + item.id + '">';
// Satz mit Eingabefeldern statt ___
let sentenceHtml = item.sentence_with_gaps;
const gaps = item.gaps || [];
// Ersetze ___ durch Eingabefelder
let gapIndex = 0;
sentenceHtml = sentenceHtml.replace(/___/g, () => {
const gap = gaps[gapIndex] || { id: 'g' + gapIndex, word: '' };
const inputId = item.id + '_' + gap.id;
gapIndex++;
return '<input type="text" class="cloze-gap-input" data-cid="' + item.id + '" data-gid="' + gap.id + '" data-answer="' + escapeHtml(gap.word) + '" id="input_' + inputId + '" autocomplete="off">';
});
html += '<div class="cloze-sentence">' + (idx + 1) + '. ' + sentenceHtml + '</div>';
// Übersetzung als Hilfe
if (item.translation && item.translation.sentence_with_gaps) {
html += '<div class="cloze-translation">';
html += '<div class="cloze-translation-label">' + (item.translation.language_name || t('translation') || 'Übersetzung') + ' (' + (t('with_gaps') || 'mit Lücken') + '):</div>';
html += escapeHtml(item.translation.sentence_with_gaps);
html += '</div>';
}
html += '</div>';
});
// Buttons
html += '<div style="margin-top:16px;text-align:center;display:flex;gap:8px;justify-content:center;">';
html += '<button class="btn btn-primary" id="btn-cloze-check">' + (t('check') || 'Prüfen') + '</button>';
html += '<button class="btn btn-ghost" id="btn-cloze-show-answers">' + (t('show_answers') || 'Lösungen zeigen') + '</button>';
html += '</div>';
clozeModalBody.innerHTML = html;
// Event-Listener für Prüfen-Button
const btnCheck = document.getElementById('btn-cloze-check');
if (btnCheck) {
btnCheck.addEventListener('click', checkClozeAnswers);
}
// Event-Listener für Lösungen zeigen
const btnShowAnswers = document.getElementById('btn-cloze-show-answers');
if (btnShowAnswers) {
btnShowAnswers.addEventListener('click', showClozeAnswers);
}
// Enter-Taste zum Prüfen
clozeModalBody.querySelectorAll('.cloze-gap-input').forEach(input => {
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
checkClozeAnswers();
}
});
});
}
/**
* Überprüft die Cloze-Antworten
*/
function checkClozeAnswers() {
if (!clozeModalBody) return;
let correct = 0;
let total = 0;
clozeModalBody.querySelectorAll('.cloze-gap-input').forEach(input => {
const userAnswer = input.value.trim().toLowerCase();
const correctAnswer = input.getAttribute('data-answer').toLowerCase();
total++;
// Entferne vorherige Klassen
input.classList.remove('correct', 'incorrect');
if (userAnswer === correctAnswer) {
input.classList.add('correct');
correct++;
} else if (userAnswer !== '') {
input.classList.add('incorrect');
}
});
// Zeige Ergebnis
let existingResult = clozeModalBody.querySelector('.cloze-result');
if (existingResult) existingResult.remove();
const percentage = Math.round(correct / total * 100);
const resultHtml = '<div class="cloze-result" style="margin-top:16px;padding:12px;background:rgba(15,23,42,0.6);border-radius:8px;text-align:center;">' +
'<div style="font-size:18px;font-weight:600;">' + correct + ' ' + (t('of') || 'von') + ' ' + total + ' ' + (t('correct_answers') || 'richtig') + '</div>' +
'<div style="font-size:12px;color:var(--bp-text-muted);margin-top:4px;">' + percentage + '% ' + (t('percent_correct') || 'korrekt') + '</div>' +
'</div>';
const resultDiv = document.createElement('div');
resultDiv.innerHTML = resultHtml;
clozeModalBody.appendChild(resultDiv.firstChild);
}
/**
* Zeigt alle Cloze-Lösungen
*/
function showClozeAnswers() {
if (!clozeModalBody) return;
clozeModalBody.querySelectorAll('.cloze-gap-input').forEach(input => {
const correctAnswer = input.getAttribute('data-answer');
input.value = correctAnswer;
input.classList.remove('incorrect');
input.classList.add('correct');
});
}
/**
* Öffnet den Druck-Dialog
*/
export function openClozePrintDialog() {
if (!currentClozeData) {
alert(t('no_cloze_texts') || 'Keine Lückentexte vorhanden.');
return;
}
const eingangFiles = getEingangFilesCallback();
const currentIndex = getCurrentIndexCallback();
const currentFile = eingangFiles[currentIndex];
const confirmMsg = (t('cloze_print_with_answers') || 'Mit Lösungen drucken?') +
'\n\nOK = ' + (t('with_filled_gaps') || 'Mit ausgefüllten Lücken') +
'\n' + (t('cancel') || 'Abbrechen') + ' = ' + (t('exercise_with_wordbank') || 'Übungsblatt mit Wortbank');
const choice = confirm(confirmMsg);
const url = '/api/print-cloze/' + encodeURIComponent(currentFile) + '?show_answers=' + choice;
window.open(url, '_blank');
}
// === Getter und Setter ===
/**
* Gibt die aktuellen Cloze-Daten zurück
* @returns {Object|null}
*/
export function getClozeData() {
return currentClozeData;
}
/**
* Setzt die Cloze-Daten
* @param {Object} data
*/
export function setClozeData(data) {
currentClozeData = data;
if (data) {
renderClozePreview(data);
}
}
/**
* Gibt die aktuelle Zielsprache zurück
* @returns {string}
*/
export function getTargetLanguage() {
return clozeLanguageSelect ? clozeLanguageSelect.value : 'tr';
}
/**
* Helper: HTML-Escape
*/
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}