feat: Auto-Pipeline-Flow (Bild freigeben → Veröffentlichen-Übersicht)
- ContentCreation: '🚀 Bild freigeben'-Button startet die serverseitige Pipeline (Pairs → Übersetzung → Audio); Status-Chip pro Bild - Veröffentlichen-Seite neu: Live-Fortschritt (Polling), pro Bild eine flache Karte mit Objekten, Pairs (3 Sprachspalten), Audio-Indikatoren, 🚩-Flag zum Ausschließen, Ein-Klick-Publish des ganzen Bildes - Settings: Pipeline-Sektion (Pairs pro Objekt) - lib/pairRows.js: geteilte 3-Sprachen-Anzeige-Helfer (aus PairReviewModal) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { apiPost, apiPatch } from '../lib/api';
|
||||
import { buildRows } from '../lib/pairRows';
|
||||
|
||||
const LANGS = [
|
||||
{ code: 'de', flag: '🇩🇪' },
|
||||
@@ -7,59 +8,6 @@ const LANGS = [
|
||||
{ code: 'sv', flag: '🇸🇪' },
|
||||
];
|
||||
|
||||
// Platzhalter {{label.type:uuid}} → nur das Label anzeigen.
|
||||
function strip(text) {
|
||||
if (!text) return '';
|
||||
return text.replace(/\{\{([^}]+)\}\}/g, (_, inner) => {
|
||||
const m = inner.match(/^(.+?)\.[a-z]+:[0-9a-f-]{36}$/i);
|
||||
return m ? m[1] : inner;
|
||||
});
|
||||
}
|
||||
|
||||
// Baut die Anzeigezeilen je nach answer_type. Jede Zeile ist entweder
|
||||
// - { kind: 'lang', label, color, cell(l) } → eine Zelle pro Sprache (übersetzbar)
|
||||
// - { kind: 'single', label, color, value } → ein einzelner Wert (nicht sprachabhängig)
|
||||
function buildRows(content) {
|
||||
if (!content) return [];
|
||||
const type = content?.answer_type;
|
||||
const rows = [];
|
||||
const wordsCell = (stmt) => (l) =>
|
||||
(stmt?.words || []).map(w => w[`titel_${l}`] || '—').join(', ');
|
||||
const sentenceCell = (stmt, prefix) => (l) =>
|
||||
strip(stmt?.sentence?.[`${prefix}_${l}`] || '');
|
||||
|
||||
// Frage (yes_no / question / word)
|
||||
if (content.question) {
|
||||
rows.push({ kind: 'lang', label: 'Frage', color: 'text-slate-700',
|
||||
cell: l => strip(content.question[`sentence_${l}`] || '') });
|
||||
}
|
||||
|
||||
if (type === 'yes_no') {
|
||||
// Ja/Nein-Antwort ist ein boolescher Wert, keine Übersetzung
|
||||
const a = content.positive?.answer;
|
||||
rows.push({ kind: 'single', label: 'Antwort', color: 'text-green-700',
|
||||
value: a === true ? '✓ Ja' : a === false ? '✗ Nein' : null });
|
||||
} else if (type === 'word') {
|
||||
rows.push({ kind: 'lang', label: 'Positiv-Wörter', color: 'text-green-700',
|
||||
cell: wordsCell(content.positive) });
|
||||
// 'word' braucht laut Datenmodell Negativ-Wörter → Zeile immer zeigen, fehlende sichtbar machen
|
||||
rows.push({ kind: 'lang', label: 'Negativ-Wörter', color: 'text-red-600',
|
||||
cell: wordsCell(content.negative) });
|
||||
} else {
|
||||
// text / question → Sätze
|
||||
if (content.positive)
|
||||
rows.push({ kind: 'lang', label: 'Positiv', color: 'text-green-700',
|
||||
cell: sentenceCell(content.positive, 'positive_sentence') });
|
||||
// 'question' = Frage + Positiv + Negativ → Negativ-Zeile immer zeigen, auch wenn leer
|
||||
// ('fehlt' statt stilles Ausblenden, damit eine fehlende Negativ-Antwort auffällt).
|
||||
// 'text' hat per Definition kein Negativ.
|
||||
if (type === 'question')
|
||||
rows.push({ kind: 'lang', label: 'Negativ', color: 'text-red-600',
|
||||
cell: sentenceCell(content.negative, 'negative_sentence') });
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
export default function PairReviewModal({ pair, content, onClose, onDone, onRetranslate }) {
|
||||
const [busy, setBusy] = useState(null); // 'review' | 'block' | 'retranslate'
|
||||
const [missing, setMissing] = useState(null);
|
||||
|
||||
Reference in New Issue
Block a user