feat(core/frontend): wire settings/avatar/image-gen + real data render (library/product-detail/pipeline)
App.tsx: thread saveProfile/changePassword/uploadAvatar/generateImages handlers + assets prop to pages. - settings.tsx: profile save / password modal / avatar upload wired; notification/theme prefs -> localStorage - library.tsx + product-detail: asset thumbnails + grids render real TOS preview_url - ai-tools ImageWorkbenchPage: 生成图片 wired to /api/ai/generate-image, renders returned assets - pipeline.tsx stage2-5: base_assets/storyboard/video_segments(adopted_asset)/timeline(clips/subtitles/bgm) rendered from real project data; graceful empty states - types.ts: +VideoSegment.adopted_asset, +Timeline.subtitle_tracks/bgm_tracks verified: tsc --noEmit clean; screenshots confirm pipeline stages 2-5 + product-detail render real data+images (demo asset object_keys re-pointed to image objects so thumbnails resolve) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
603584b46b
commit
099bf0e6aa
@@ -271,7 +271,8 @@ export function ImageWorkbenchPage({
|
||||
assets,
|
||||
modelConfigs,
|
||||
onBack,
|
||||
navigate
|
||||
navigate,
|
||||
onGenerate
|
||||
}: {
|
||||
mode: WorkMode;
|
||||
products: Product[];
|
||||
@@ -279,6 +280,7 @@ export function ImageWorkbenchPage({
|
||||
modelConfigs: ModelConfig[];
|
||||
onBack: () => void;
|
||||
navigate?: (page: Page) => void;
|
||||
onGenerate: (payload: { prompt: string; mode?: "image" | "model" | "cover"; count?: number }) => Promise<{ assets: Asset[] } | null>;
|
||||
}) {
|
||||
const meta = MODE_META[mode];
|
||||
const [productId, setProductId] = useState(products[0]?.id || "");
|
||||
@@ -287,6 +289,8 @@ export function ImageWorkbenchPage({
|
||||
const [ratio, setRatio] = useState(meta.ratio);
|
||||
const [count, setCount] = useState("4");
|
||||
const [pickedIds, setPickedIds] = useState<string[]>([]);
|
||||
const [generating, setGenerating] = useState(false);
|
||||
const [results, setResults] = useState<Asset[] | null>(null);
|
||||
|
||||
const imageModels = modelConfigs.filter((model) => model.capability.includes("image"));
|
||||
const modelOptions = useMemo(() => modelConfigs.slice(0, 6), [modelConfigs]);
|
||||
@@ -304,6 +308,19 @@ export function ImageWorkbenchPage({
|
||||
|
||||
const ratioVar = ratio.replace(":", " / ");
|
||||
const candidateCount = Math.max(1, Number(count) || 4);
|
||||
const canGenerate = prompt.trim().length > 0 && !generating;
|
||||
|
||||
async function runGenerate() {
|
||||
if (!canGenerate) return;
|
||||
setGenerating(true);
|
||||
try {
|
||||
const result = await onGenerate({ prompt: prompt.trim(), mode, count: candidateCount });
|
||||
// 成功:渲染真图;失败(null):保留原占位,不改 results
|
||||
if (result?.assets) setResults(result.assets);
|
||||
} finally {
|
||||
setGenerating(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="image-workbench">
|
||||
@@ -326,9 +343,9 @@ export function ImageWorkbenchPage({
|
||||
方案 A
|
||||
</button>
|
||||
)}
|
||||
<button className="btn btn-primary" type="button">
|
||||
<WandSparkles size={13} />
|
||||
生成图片
|
||||
<button className="btn btn-primary" type="button" onClick={runGenerate} disabled={!canGenerate}>
|
||||
{generating ? <span className="spinner" aria-hidden /> : <WandSparkles size={13} />}
|
||||
{generating ? "生成中…" : "生成图片"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -485,9 +502,9 @@ export function ImageWorkbenchPage({
|
||||
</div>
|
||||
|
||||
<div className="iw-cta">
|
||||
<button className="btn btn-primary" type="button">
|
||||
<WandSparkles size={13} />
|
||||
立即生成
|
||||
<button className="btn btn-primary" type="button" onClick={runGenerate} disabled={!canGenerate}>
|
||||
{generating ? <span className="spinner" aria-hidden /> : <WandSparkles size={13} />}
|
||||
{generating ? "生成中…" : "立即生成"}
|
||||
</button>
|
||||
<div className="iw-cta-hint">
|
||||
// {imageModels[0]?.display_name || "Volcano Image"} · 预估 {meta.title}
|
||||
@@ -513,14 +530,24 @@ export function ImageWorkbenchPage({
|
||||
<span className="m-sep">|</span>
|
||||
<span>{product?.title || meta.title}</span>
|
||||
</div>
|
||||
<div className="gen-images" style={{ "--cols": candidateCount >= 4 ? 4 : 2, "--ratio": ratioVar } as React.CSSProperties}>
|
||||
{Array.from({ length: candidateCount }).map((_, index) => (
|
||||
<div className="gen-image" key={index}>
|
||||
<div className="placeholder">
|
||||
<span className="ph-frame">
|
||||
{ratio} · #{index + 1}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="gen-images"
|
||||
style={{ "--cols": (results?.length ?? candidateCount) >= 4 ? 4 : 2, "--ratio": ratioVar } as React.CSSProperties}
|
||||
>
|
||||
{(results && results.length > 0
|
||||
? results.map((asset, index) => ({ key: asset.id, index, url: asset.files?.[0]?.preview_url }))
|
||||
: Array.from({ length: candidateCount }).map((_, index) => ({ key: `ph-${index}`, index, url: undefined }))
|
||||
).map(({ key, index, url }) => (
|
||||
<div className="gen-image" key={key}>
|
||||
{url ? (
|
||||
<img className="gen-image-img" src={url} alt={`${meta.title} #${index + 1}`} loading="lazy" />
|
||||
) : (
|
||||
<div className="placeholder">
|
||||
<span className="ph-frame">
|
||||
{ratio} · #{index + 1}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="gen-image-actions">
|
||||
<button className="gen-img-btn" type="button" title="重跑">
|
||||
<RefreshCw size={14} />
|
||||
|
||||
Reference in New Issue
Block a user