feat: model selection+visibility, better filenames, picdrop direct upload + configurable gallery
- studio: quality/model selector (friendly names from active models); library shows model used - seed 2 models (Schnell/Beste Qualitaet); GET /api/models for studio+library - filename: YYYY-MM-DD_motif_format.png (was redundant slug, no date) - picdrop: direct upload to final name (fix .tmp- leftover from failed rename on picdrop); cleanupTmp helper - configurable default gallery (migration 003, settings + admin field); base_path=SFTP root, gallery=subfolder
This commit is contained in:
@@ -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<Item[]>([]);
|
||||
const [folders, setFolders] = useState<any[]>([]);
|
||||
const [models, setModels] = useState<any[]>([]);
|
||||
const modelLabel = (id: string) => models.find((m) => m.model_id === id)?.label || null;
|
||||
const [sel, setSel] = useState<Set<string>>(new Set());
|
||||
const [compare, setCompare] = useState<Item | null>(null);
|
||||
const [toast, setToast] = useState<string | null>(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() {
|
||||
<span className="haken">{sel.has(v.id) ? '✓' : ''}</span>
|
||||
</div>
|
||||
<input className="name" defaultValue={v.filename || ''} onBlur={(e) => rename(v.id, e.target.value)} />
|
||||
<div className="meta">{(v.tasks || []).join(' · ')}{v.output_px ? ` · ${v.output_px}px` : ''}{v.has_alpha ? ' · transparent' : ''}
|
||||
<div className="meta">{(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' && <span className="dbadge ok">Picdrop ✓</span>}
|
||||
{v.delivery_status === 'pending' && <span className="dbadge">Picdrop …</span>}
|
||||
{v.delivery_status === 'failed' && <span className="dbadge err">Picdrop ✕</span>}
|
||||
|
||||
Reference in New Issue
Block a user