'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 { OasisCanvas } from '@/components/login/OasisCanvas'; import type { GenImage, GenSession, GenerateAllPacksResponse, GeneratePackResponse, GenerateResponse, LockCharacterResponse, PackKind, ProjectFromUploadResponse, RegenerateAssetResponse, UploadImageResponse, UploadedImageRole, VideoGenerationResponse, } from '@/lib/types'; import type { VIDEO_TEMPLATES } from '@/lib/templates'; 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 [uploadLoading, setUploadLoading] = 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 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', { 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, background: true }), }); 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); scheduleSessionRefresh(current.id); } 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; } function scheduleSessionRefresh(sessionId: string, remaining = 90) { if (remaining <= 0) return; window.setTimeout(async () => { const updated = await reloadCurrent(sessionId); const hasDraftPack = updated?.packs?.some(pack => pack.status === 'draft') ?? false; if (hasDraftPack || remaining > 84) scheduleSessionRefresh(sessionId, remaining - 1); }, 5000); } 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, background: true }), }); if (!r.ok) { alert('完整三包生成失败:' + (await r.text())); return; } const d: GenerateAllPacksResponse = await r.json(); setProvider(d.provider); await reloadCurrent(current.id); scheduleSessionRefresh(current.id); } finally { setAllLoading(false); } } async function handleRegenerateAsset(assetId: string, userRefinement?: string) { if (!current) return; const r = await fetch(`/api/assets/${encodeURIComponent(assetId)}/regenerate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: current.id, userRefinement, confirmCost: true }), }); if (!r.ok) { alert('单张重做失败:' + (await r.text())); return; } const d: RegenerateAssetResponse = await r.json(); setProvider(d.provider); await reloadCurrent(current.id); } function resolveVideoAnchor(image: GenImage) { const packs = current?.packs ?? []; const mktFront = packs.find(pack => pack.kind === 'marketing')?.assets.find(asset => asset.templateId === 'mkt_white_front'); const patentFront = packs.find(pack => pack.kind === 'patent')?.assets.find(asset => asset.templateId === 'patent_front'); const cleanReference = current?.characterSpec?.cleanReferenceImageUrl; if (mktFront) return { url: mktFront.url, label: '宣发白底图' }; if (patentFront) return { url: patentFront.url, label: '专利主图' }; if (cleanReference) return { url: cleanReference, label: 'L1 白底锚图' }; return { url: image.url, label: '意向图' }; } async function handleGenerateVideo(image: GenImage, template: typeof VIDEO_TEMPLATES[number]) { if (!current || videoLoading) return; setVideoLoading(true); try { const character = current.characterSpec ? `${current.characterSpec.name},${current.characterSpec.oneLiner}` : current.prompt; const prompt = template.promptTemplate.replace('{character}', character); const anchor = resolveVideoAnchor(image); const r = await fetch('/api/video/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt, imageUrl: anchor.url, duration: template.duration, ratio: template.ratio, generateAudio: true, watermark: false, }), }); if (!r.ok) { alert('Seedance 视频提交失败:' + (await r.text())); return; } const d: VideoGenerationResponse = await r.json(); alert(`Seedance 任务已提交:${d.taskId ?? d.status};参考:${anchor.label}`); } finally { setVideoLoading(false); } } async function handleLogout() { await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' }); window.location.href = '/login'; } 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 Patent 把一句想法变成专利 / 生产 / 宣发素材包

批量出意向 · 快筛 · 锁定角色 · 一键四包 · Seedance 视频

{current && ( <> 图库 记录 )} {provider === 'gpt' ? 'GPT · gpt-image-2' : provider === 'mock' ? 'Mock · 占位图' : provider === '?' ? '待连接' : provider}
{current && (
Step · 02 · Quick Screen

本次生成

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

{current.id}
)}
); }