From 86145941eb98c24b6b7222d5559c1c0f9eb73d37 Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 10 Jun 2026 20:57:25 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Auto-Pipeline-Flow=20(Bild=20freigeben?= =?UTF-8?q?=20=E2=86=92=20Ver=C3=B6ffentlichen-=C3=9Cbersicht)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/components/PairReviewModal.jsx | 54 +--- src/lib/pairRows.js | 53 ++++ src/pages/ContentCreation.jsx | 81 +++++- src/pages/Publish.jsx | 395 +++++++++++++++++++++++------ src/pages/Settings.jsx | 54 ++++ 5 files changed, 499 insertions(+), 138 deletions(-) create mode 100644 src/lib/pairRows.js diff --git a/src/components/PairReviewModal.jsx b/src/components/PairReviewModal.jsx index 481fcc8..778979b 100644 --- a/src/components/PairReviewModal.jsx +++ b/src/components/PairReviewModal.jsx @@ -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); diff --git a/src/lib/pairRows.js b/src/lib/pairRows.js new file mode 100644 index 0000000..afba87d --- /dev/null +++ b/src/lib/pairRows.js @@ -0,0 +1,53 @@ +// Geteilte Anzeige-Helfer für Pair-Inhalte (3-Sprachen-Grid). +// Genutzt von PairReviewModal und der Veröffentlichen-Seite. + +// Platzhalter {{label.type:uuid}} → nur das Label anzeigen. +export 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) +export 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 + if (type === 'question') + rows.push({ kind: 'lang', label: 'Negativ', color: 'text-red-600', + cell: sentenceCell(content.negative, 'negative_sentence') }); + } + return rows; +} diff --git a/src/pages/ContentCreation.jsx b/src/pages/ContentCreation.jsx index 5b67645..5015aa4 100644 --- a/src/pages/ContentCreation.jsx +++ b/src/pages/ContentCreation.jsx @@ -873,7 +873,7 @@ function EditPairForm({ pair, allObjects, onSaved, onCancel, onDeleted, onSavedA // ─── Left panel: Object list ────────────────────────────────────────────────── -function ObjectListPanel({ objects, loadingObjects, mode, selectedObjectId, onAddObject, onSelectObject, currentPicture, onObjectStatusChange }) { +function ObjectListPanel({ objects, loadingObjects, mode, selectedObjectId, onAddObject, onSelectObject, currentPicture, onObjectStatusChange, onPicturesReload }) { return (