diff --git a/migrations/003_settings_gallery.sql b/migrations/003_settings_gallery.sql new file mode 100644 index 0000000..84a51eb --- /dev/null +++ b/migrations/003_settings_gallery.sql @@ -0,0 +1,2 @@ +-- Konfigurierbare Standard-Picdrop-Galerie (statt fest verdrahtet). +ALTER TABLE settings ADD COLUMN IF NOT EXISTS picdrop_default_gallery text; diff --git a/src/components/AdminApp.tsx b/src/components/AdminApp.tsx index 7649060..4898be4 100644 --- a/src/components/AdminApp.tsx +++ b/src/components/AdminApp.tsx @@ -37,7 +37,7 @@ export default function AdminApp() { const saveSettings = async () => { const body: any = { picdrop_host: s.picdrop_host, picdrop_protocol: s.picdrop_protocol, picdrop_port: s.picdrop_port, - picdrop_user: s.picdrop_user, picdrop_base_path: s.picdrop_base_path, + picdrop_user: s.picdrop_user, picdrop_base_path: s.picdrop_base_path, picdrop_default_gallery: s.picdrop_default_gallery, default_dpi: s.default_dpi, default_crop_mode: s.default_crop_mode, concurrency: s.concurrency, cricut_sheet_cm: s.cricut_sheet_cm, monthly_budget: s.monthly_budget, n8n_webhook_url: s.n8n_webhook_url, }; @@ -126,7 +126,11 @@ export default function AdminApp() {
setPdPw(e.target.value)} />
-
field('picdrop_base_path', e.target.value)} />
+
+
field('picdrop_base_path', e.target.value)} />
+
field('picdrop_default_gallery', e.target.value)} />
+
+
Bilder landen in Basisordner / Galerie — z. B. / + POSTER LEA. Pro Ordner lässt sich später eine eigene Galerie mappen.
diff --git a/src/components/LibraryApp.tsx b/src/components/LibraryApp.tsx index 681f665..3fdf788 100644 --- a/src/components/LibraryApp.tsx +++ b/src/components/LibraryApp.tsx @@ -1,22 +1,25 @@ import React, { useState, useEffect, useCallback } from 'react'; interface Item { id: string; filename: string; output_px: string; has_alpha: boolean; - folder_id: string | null; by_name: string; tasks: string[]; delivery_status: string; } + folder_id: string | null; by_name: string; tasks: string[]; delivery_status: string; model_used: string; } export default function LibraryApp() { const [items, setItems] = useState([]); const [folders, setFolders] = useState([]); + const [models, setModels] = useState([]); + const modelLabel = (id: string) => models.find((m) => m.model_id === id)?.label || null; const [sel, setSel] = useState>(new Set()); const [compare, setCompare] = useState(null); const [toast, setToast] = useState(null); const notify = (t: string) => { setToast(t); setTimeout(() => setToast(null), 2400); }; const load = useCallback(async () => { - const [i, f] = await Promise.all([ + const [i, f, m] = await Promise.all([ fetch('/api/items').then((r) => r.json()).catch(() => ({ items: [] })), fetch('/api/folders').then((r) => r.json()).catch(() => ({ folders: [] })), + fetch('/api/models').then((r) => r.json()).catch(() => ({ models: [] })), ]); - setItems(i.items || []); setFolders(f.folders || []); + setItems(i.items || []); setFolders(f.folders || []); setModels(m.models || []); }, []); useEffect(() => { load(); }, [load]); @@ -67,7 +70,7 @@ export default function LibraryApp() { {sel.has(v.id) ? '✓' : ''} rename(v.id, e.target.value)} /> -
{(v.tasks || []).join(' · ')}{v.output_px ? ` · ${v.output_px}px` : ''}{v.has_alpha ? ' · transparent' : ''} +
{(v.tasks || []).join(' · ')}{v.output_px ? ` · ${v.output_px}px` : ''}{v.has_alpha ? ' · transparent' : ''}{modelLabel(v.model_used) ? ` · ${modelLabel(v.model_used)}` : ''} {v.delivery_status === 'delivered' && Picdrop ✓} {v.delivery_status === 'pending' && Picdrop …} {v.delivery_status === 'failed' && Picdrop ✕} diff --git a/src/components/StudioApp.tsx b/src/components/StudioApp.tsx index 0e3d542..57cbb4a 100644 --- a/src/components/StudioApp.tsx +++ b/src/components/StudioApp.tsx @@ -44,6 +44,8 @@ export default function StudioApp({ recipes }: { recipes: any[] }) { const [contourMm, setContourMm] = useState(3); const [custom, setCustom] = useState(''); const [delivery, setDelivery] = useState<'library' | 'picdrop' | 'both'>('library'); + const [models, setModels] = useState([]); + const [modelKey, setModelKey] = useState(''); const [dragging, setDragging] = useState(false); const [busy, setBusy] = useState(false); const [toast, setToast] = useState(null); @@ -92,6 +94,14 @@ export default function StudioApp({ recipes }: { recipes: any[] }) { return () => window.removeEventListener('paste', onPaste); }, [upload]); + useEffect(() => { + fetch('/api/models').then((r) => r.json()).then((j) => { + const ms = j.models || []; setModels(ms); + const def = ms.find((m: any) => m.is_default) || ms[0]; + if (def) setModelKey(def.model_id); + }).catch(() => {}); + }, []); + function toggleTask(id: string) { setTasks((t) => { let next = t.includes(id) ? t.filter((x) => x !== id) : [...t, id]; @@ -110,6 +120,7 @@ export default function StudioApp({ recipes }: { recipes: any[] }) { setContourMm(Number(r.contour_mm) || 3); setCustom(r.custom_instruction || ''); setDelivery(r.delivery || 'library'); + if (r.model_key) setModelKey(r.model_key); } const ready = pics.filter((p) => p.source_path && !p.error); @@ -125,7 +136,7 @@ export default function StudioApp({ recipes }: { recipes: any[] }) { orientation: portrait ? 'portrait' : 'landscape', crop_mode: crop, dpi: 300, contour_mm: tasks.includes('contour') ? contourMm : null, - custom_instruction: custom || null, delivery, + custom_instruction: custom || null, delivery, model_key: modelKey || null, }; try { const res = await fetch('/api/jobs', { @@ -234,6 +245,19 @@ export default function StudioApp({ recipes }: { recipes: any[] }) {
)} + {models.length > 0 && ( +
+ +
+ {models.map((m) => ( + + ))} +
+
{models.find((m) => m.model_id === modelKey)?.description || ''}
+
+ )} +