auto-save 2026-05-19 10:40 (+1, ~3)

This commit is contained in:
2026-05-19 10:40:30 +08:00
parent a3481e798d
commit 12159ca366
4 changed files with 636 additions and 1 deletions

View File

@@ -13,7 +13,10 @@ import type {
GenerateResponse,
LockCharacterResponse,
PackKind,
ProjectFromUploadResponse,
RegenerateAssetResponse,
UploadImageResponse,
UploadedImageRole,
VideoGenerationResponse,
} from '@/lib/types';
import type { VIDEO_TEMPLATES } from '@/lib/templates';
@@ -26,6 +29,7 @@ export default function Home() {
const [allLoading, setAllLoading] = useState(false);
const [characterLoading, setCharacterLoading] = useState(false);
const [videoLoading, setVideoLoading] = useState(false);
const [uploadLoading, setUploadLoading] = useState(false);
const [provider, setProvider] = useState<string>('?');
const [sidebarOpen, setSidebarOpen] = useState(true);
@@ -60,6 +64,55 @@ export default function Home() {
}
}
async function uploadImage(file: File, role: UploadedImageRole) {
const form = new FormData();
form.set('image', file);
form.set('role', role);
const r = await fetch('/api/uploads', { method: 'POST', body: form });
if (!r.ok) throw new Error(await r.text());
const d: UploadImageResponse = await r.json();
return d.uploadedImage;
}
async function handleUploadProject(opts: {
mode: 'remix' | 'replicate';
files: Array<{ file: File; role: 'reference' | 'subject' }>;
prompt?: string;
styleId?: string;
count?: number;
}) {
if (uploadLoading) return;
setUploadLoading(true);
try {
const uploadedImages = await Promise.all(opts.files.map(item => uploadImage(item.file, item.role)));
const r = await fetch('/api/projects/from-upload', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
uploadedImages,
mode: opts.mode,
remixPrompt: opts.mode === 'remix' ? opts.prompt : undefined,
userHint: opts.mode === 'replicate' ? opts.prompt : undefined,
styleId: opts.styleId,
count: opts.count,
}),
});
if (!r.ok) {
alert('上传项目创建失败:' + (await r.text()));
return;
}
const d: ProjectFromUploadResponse = await r.json();
setProvider(d.provider);
const all = await refreshSessions();
const s = all.find(x => x.id === d.sessionId) ?? null;
setCurrent(s);
} catch (error) {
alert('上传失败:' + String(error));
} finally {
setUploadLoading(false);
}
}
async function handleAction(imageId: string, action: 'select' | 'reject' | 'reset') {
if (!current) return;
const r = await fetch('/api/select', {
@@ -243,7 +296,12 @@ export default function Home() {
</header>
<div className="space-y-8">
<PromptPanel onGenerate={handleGenerate} loading={loading} />
<PromptPanel
onGenerate={handleGenerate}
onUploadProject={handleUploadProject}
loading={loading}
uploadLoading={uploadLoading}
/>
{current && (
<section className="space-y-5">
<div className="flex items-end justify-between">