refactor: auto pairs now loops all objects, no selection needed

Button moves to ObjectListPanel (visible when ≥1 object exists).
One Claude call per object — image + that object's words & coordinates.
Progress shows "Objekt 2/3 — Pair 12/30". Pair saving logic extracted
into shared savePairsForObject() helper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 21:15:01 +02:00
parent d7ba2c2c47
commit b39a3cca9f

View File

@@ -744,10 +744,10 @@ function EditPairForm({ pair, allObjects, onSaved, onCancel, onDeleted }) {
// ─── Left panel: Object list ────────────────────────────────────────────────── // ─── Left panel: Object list ──────────────────────────────────────────────────
function ObjectListPanel({ objects, loadingObjects, mode, selectedObjectId, onAddObject, onSelectObject }) { function ObjectListPanel({ objects, loadingObjects, mode, selectedObjectId, onAddObject, onSelectObject, currentPicture }) {
return ( return (
<aside className="w-1/5 min-w-[180px] border-r border-slate-200 bg-white flex flex-col overflow-hidden"> <aside className="w-1/5 min-w-[180px] border-r border-slate-200 bg-white flex flex-col overflow-hidden">
<div className="px-3 py-2.5 border-b border-slate-100 bg-slate-50 flex-shrink-0"> <div className="px-3 py-2.5 border-b border-slate-100 bg-slate-50 flex-shrink-0 space-y-1.5">
<button <button
onClick={onAddObject} onClick={onAddObject}
className={`w-full flex items-center justify-center gap-1.5 text-xs font-medium py-1.5 rounded-lg transition-colors className={`w-full flex items-center justify-center gap-1.5 text-xs font-medium py-1.5 rounded-lg transition-colors
@@ -757,6 +757,9 @@ function ObjectListPanel({ objects, loadingObjects, mode, selectedObjectId, onAd
> >
<span className="text-base leading-none"></span> Objekt hinzufügen <span className="text-base leading-none"></span> Objekt hinzufügen
</button> </button>
{objects.length > 0 && (
<AutoCreateAllButton currentPicture={currentPicture} objects={objects} />
)}
</div> </div>
<div className="flex-1 overflow-y-auto p-3 space-y-2"> <div className="flex-1 overflow-y-auto p-3 space-y-2">
{loadingObjects && ( {loadingObjects && (
@@ -887,20 +890,7 @@ async function callClaudeForPairs(picture, allObjects, selectedObjectId) {
return res.pairs; return res.pairs;
} }
function AutoCreateButton({ currentPicture, allObjects, selectedObject, onPairSaved }) { async function savePairsForObject(pairs, objectId, lang, onPairSaved, onProgress) {
const lang = getUserLang();
const [state, setState] = useState('idle'); // idle | loading | done | error
const [progress, setProgress] = useState({ done: 0, total: 0 });
const [errorMsg, setErrorMsg] = useState('');
async function handleAutoCreate() {
setState('loading');
setProgress({ done: 0, total: 0 });
setErrorMsg('');
try {
const pairs = await callClaudeForPairs(currentPicture, allObjects, selectedObject.id);
setProgress({ done: 0, total: pairs.length });
for (let i = 0; i < pairs.length; i++) { for (let i = 0; i < pairs.length; i++) {
const p = pairs[i]; const p = pairs[i];
@@ -932,13 +922,48 @@ function AutoCreateButton({ currentPicture, allObjects, selectedObject, onPairSa
negative_statement_id: negStmtId, negative_statement_id: negStmtId,
status: 'draft', status: 'draft',
}); });
await apiLink(`/objects/${selectedObject.id}/pairs/${created.id}`); await apiLink(`/objects/${objectId}/pairs/${created.id}`);
onPairSaved(created); onPairSaved?.(created, objectId);
setProgress({ done: i + 1, total: pairs.length }); onProgress?.(i + 1, pairs.length);
}
}
function AutoCreateAllButton({ currentPicture, objects }) {
const lang = getUserLang();
const [state, setState] = useState('idle'); // idle | loading | done | error
const [progress, setProgress] = useState({ objIdx: 0, objTotal: 0, pairDone: 0, pairTotal: 0 });
const [totalCreated, setTotalCreated] = useState(0);
const [errorMsg, setErrorMsg] = useState('');
async function handleAutoCreateAll() {
if (!currentPicture?.picture_link) {
alert('Dieses Bild hat keinen Link für die KI-Analyse');
return;
}
setState('loading');
setTotalCreated(0);
setProgress({ objIdx: 0, objTotal: objects.length, pairDone: 0, pairTotal: 0 });
setErrorMsg('');
let total = 0;
try {
for (let oi = 0; oi < objects.length; oi++) {
const obj = objects[oi];
setProgress({ objIdx: oi, objTotal: objects.length, pairDone: 0, pairTotal: 0 });
const pairs = await callClaudeForPairs(currentPicture, objects, obj.id);
setProgress(prev => ({ ...prev, pairTotal: pairs.length }));
await savePairsForObject(pairs, obj.id, lang, null, (done, total) => {
setProgress(prev => ({ ...prev, pairDone: done, pairTotal: total }));
});
total += pairs.length;
setTotalCreated(total);
} }
setState('done'); setState('done');
setTimeout(() => setState('idle'), 4000); setTimeout(() => setState('idle'), 5000);
} catch (e) { } catch (e) {
setErrorMsg(e.message); setErrorMsg(e.message);
setState('error'); setState('error');
@@ -946,15 +971,18 @@ function AutoCreateButton({ currentPicture, allObjects, selectedObject, onPairSa
} }
if (state === 'loading') { if (state === 'loading') {
const { objIdx, objTotal, pairDone, pairTotal } = progress;
return ( return (
<div className="w-full py-2 px-3 text-xs text-violet-700 bg-violet-50 border border-violet-200 rounded-lg flex items-center gap-2"> <div className="w-full py-2 px-3 text-xs text-violet-700 bg-violet-50 border border-violet-200 rounded-lg flex items-center gap-2">
<svg className="animate-spin h-3 w-3 text-violet-500 shrink-0" viewBox="0 0 24 24" fill="none"> <svg className="animate-spin h-3 w-3 text-violet-500 shrink-0" viewBox="0 0 24 24" fill="none">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"/> <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"/>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"/> <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"/>
</svg> </svg>
{progress.total === 0 <span>
? 'KI analysiert Bild' {pairTotal === 0
: `Erstelle Pairs… ${progress.done}/${progress.total}`} ? `Objekt ${objIdx + 1}/${objTotal} — KI analysiert…`
: `Objekt ${objIdx + 1}/${objTotal} — Pair ${pairDone}/${pairTotal}`}
</span>
</div> </div>
); );
} }
@@ -962,7 +990,7 @@ function AutoCreateButton({ currentPicture, allObjects, selectedObject, onPairSa
if (state === 'done') { if (state === 'done') {
return ( return (
<div className="w-full py-2 px-3 text-xs text-green-700 bg-green-50 border border-green-200 rounded-lg font-medium text-center"> <div className="w-full py-2 px-3 text-xs text-green-700 bg-green-50 border border-green-200 rounded-lg font-medium text-center">
✓ {progress.total} Pairs automatisch erstellt ✓ {totalCreated} Pairs für {objects.length} Objekte erstellt
</div> </div>
); );
} }
@@ -982,7 +1010,7 @@ function AutoCreateButton({ currentPicture, allObjects, selectedObject, onPairSa
} }
return ( return (
<button onClick={handleAutoCreate} <button onClick={handleAutoCreateAll}
className="w-full py-1.5 text-xs font-medium rounded-lg bg-violet-600 hover:bg-violet-700 text-white transition-colors flex items-center justify-center gap-1.5"> className="w-full py-1.5 text-xs font-medium rounded-lg bg-violet-600 hover:bg-violet-700 text-white transition-colors flex items-center justify-center gap-1.5">
✨ Auto Pairs erstellen ✨ Auto Pairs erstellen
</button> </button>
@@ -991,7 +1019,7 @@ function AutoCreateButton({ currentPicture, allObjects, selectedObject, onPairSa
// ─── Right panel: Pairs ─────────────────────────────────────────────────────── // ─── Right panel: Pairs ───────────────────────────────────────────────────────
function PairsPanel({ selectedObject, allObjects, currentPicture, objectPairs, loadingPairs, onPairSaved, onPairsReload }) { function PairsPanel({ selectedObject, allObjects, objectPairs, loadingPairs, onPairSaved, onPairsReload }) {
const [editingId, setEditingId] = useState(null); const [editingId, setEditingId] = useState(null);
if (!selectedObject) { if (!selectedObject) {
@@ -1012,13 +1040,7 @@ function PairsPanel({ selectedObject, allObjects, currentPicture, objectPairs, l
</h2> </h2>
</div> </div>
<div className="flex-1 overflow-y-auto"> <div className="flex-1 overflow-y-auto">
<div className="p-3 border-b border-slate-100 space-y-2"> <div className="p-3 border-b border-slate-100">
<AutoCreateButton
currentPicture={currentPicture}
allObjects={allObjects}
selectedObject={selectedObject}
onPairSaved={onPairSaved}
/>
<PairForm objectId={selectedObject.id} allObjects={allObjects} onPairSaved={pair => onPairSaved(pair)} /> <PairForm objectId={selectedObject.id} allObjects={allObjects} onPairSaved={pair => onPairSaved(pair)} />
</div> </div>
<div className="p-3 space-y-3"> <div className="p-3 space-y-3">
@@ -1381,6 +1403,7 @@ export default function ContentCreation() {
selectedObjectId={selectedObjectId} selectedObjectId={selectedObjectId}
onAddObject={handleAddObject} onAddObject={handleAddObject}
onSelectObject={handleSelectObject} onSelectObject={handleSelectObject}
currentPicture={currentPicture}
/> />
<ImageCanvas <ImageCanvas
@@ -1412,7 +1435,6 @@ export default function ContentCreation() {
<PairsPanel <PairsPanel
selectedObject={selectedObjectWithIndex} selectedObject={selectedObjectWithIndex}
allObjects={objects} allObjects={objects}
currentPicture={currentPicture}
objectPairs={objectPairs} objectPairs={objectPairs}
loadingPairs={loadingPairs} loadingPairs={loadingPairs}
onPairSaved={pair => setObjectPairs(prev => [pair, ...prev])} onPairSaved={pair => setObjectPairs(prev => [pair, ...prev])}