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)} />
+
+ 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 || ''}
+
+ )}
+