'use client'; import { useCallback, useEffect, useState } from 'react'; import PromptPanel from '@/components/PromptPanel'; import ResultGrid from '@/components/ResultGrid'; import Sidebar from '@/components/Sidebar'; import PackPanel from '@/components/PackPanel'; import type { GenImage, GenSession, GenerateAllPacksResponse, GeneratePackResponse, GenerateResponse, LockCharacterResponse, PackKind, VideoGenerationResponse, } from '@/lib/types'; export default function Home() { const [sessions, setSessions] = useState([]); const [current, setCurrent] = useState(null); const [loading, setLoading] = useState(false); const [loadingKind, setLoadingKind] = useState(null); const [allLoading, setAllLoading] = useState(false); const [characterLoading, setCharacterLoading] = useState(false); const [videoLoading, setVideoLoading] = useState(false); const [provider, setProvider] = useState('?'); const [sidebarOpen, setSidebarOpen] = useState(true); const refreshSessions = useCallback(async () => { const r = await fetch('/api/sessions'); const d = await r.json(); setSessions(d.sessions); return d.sessions as GenSession[]; }, []); useEffect(() => { refreshSessions(); }, [refreshSessions]); async function handleGenerate(opts: { prompt: string; refImages: string[]; count: number; style?: string }) { setLoading(true); try { const r = await fetch('/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(opts), }); if (!r.ok) { alert('生成失败:' + (await r.text())); return; } const d: GenerateResponse = await r.json(); setProvider(d.provider); const all = await refreshSessions(); const s = all.find(x => x.id === d.sessionId) ?? null; setCurrent(s); } finally { setLoading(false); } } async function handleAction(imageId: string, action: 'select' | 'reject' | 'reset') { if (!current) return; const r = await fetch('/api/select', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: current.id, imageId, action }), }); if (!r.ok) return; const d: { image: GenImage } = await r.json(); setCurrent(prev => prev ? { ...prev, images: prev.images.map(i => i.id === imageId ? d.image : i), } : prev); refreshSessions(); } async function handleGeneratePack(image: GenImage, kind: PackKind) { if (!current || loadingKind) return; setLoadingKind(kind); try { const r = await fetch('/api/packs/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: current.id, imageId: image.id, kind }), }); if (!r.ok) { alert('素材包生成失败:' + (await r.text())); return; } const d: GeneratePackResponse = await r.json(); setProvider(d.provider); const all = await refreshSessions(); const updated = all.find(x => x.id === current.id) ?? null; setCurrent(updated); } finally { setLoadingKind(null); } } async function reloadCurrent(sessionId: string) { const all = await refreshSessions(); const updated = all.find(x => x.id === sessionId) ?? null; setCurrent(updated); return updated; } async function handleLockCharacter(image: GenImage) { if (!current || characterLoading) return; setCharacterLoading(true); try { const r = await fetch('/api/character/lock', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: current.id, imageId: image.id, force: true }), }); if (!r.ok) { alert('角色锁定失败:' + (await r.text())); return; } const d: LockCharacterResponse = await r.json(); setProvider(d.provider); await reloadCurrent(current.id); } finally { setCharacterLoading(false); } } async function handleGenerateAll(image: GenImage) { if (!current || allLoading) return; setAllLoading(true); try { const r = await fetch('/api/packs/generate-all', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: current.id, imageId: image.id }), }); if (!r.ok) { alert('完整三包生成失败:' + (await r.text())); return; } const d: GenerateAllPacksResponse = await r.json(); setProvider(d.provider); await reloadCurrent(current.id); } finally { setAllLoading(false); } } async function handleGenerateVideo(image: GenImage, promptTemplate: string) { if (!current || videoLoading) return; setVideoLoading(true); try { const character = current.characterSpec ? `${current.characterSpec.name},${current.characterSpec.oneLiner}` : current.prompt; const r = await fetch('/api/video/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: promptTemplate.replace('{character}', character), imageUrl: image.url, duration: 6, ratio: '16:9', resolution: '1080p', }), }); if (!r.ok) { alert('Seedance 视频提交失败:' + (await r.text())); return; } const d: VideoGenerationResponse = await r.json(); alert(`Seedance 任务已提交:${d.taskId ?? d.status}`); } finally { setVideoLoading(false); } } return (
setSidebarOpen(v => !v)} sessions={sessions} currentId={current?.id ?? null} onPick={id => setCurrent(sessions.find(s => s.id === id) ?? null)} onNew={() => setCurrent(null)} />
AI Toy Workflow

把一句想法 变成专利、生产、宣发的素材包

批量出意向 · 九宫格快筛 · 锁定角色 · 一键三包 · Seedance 视频

{provider === 'gpt' ? 'GPT · 最高规格' : provider === 'mock' ? 'Mock · 占位图' : provider === '?' ? '待连接' : provider}
{current && (
Step · 02 · Quick Screen

本次生成

{new Date(current.createdAt).toLocaleString('zh-CN')}

{current.id}
)}
); }