feat: clarify editable PSD slots in agent dialog
This commit is contained in:
parent
e562e48c13
commit
510b0c13a6
@ -88,6 +88,25 @@ export function AgentPsdTemplateEditorDialog({
|
||||
[selectedTemplateId, templates]
|
||||
);
|
||||
|
||||
const editableImageLayers = useMemo(
|
||||
() =>
|
||||
(selectedTemplate?.layers || []).filter((layer) =>
|
||||
isAgentPsdImageLayerSlot(layer)
|
||||
),
|
||||
[selectedTemplate]
|
||||
);
|
||||
const editableTextLayers = useMemo(
|
||||
() =>
|
||||
(selectedTemplate?.layers || []).filter((layer) =>
|
||||
isAgentPsdTextLayerSlot(layer)
|
||||
),
|
||||
[selectedTemplate]
|
||||
);
|
||||
const fixedLayerCount = useMemo(() => {
|
||||
if (!selectedTemplate) return 0;
|
||||
return Math.max(0, selectedTemplate.layerCount - selectedTemplate.layers.length);
|
||||
}, [selectedTemplate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
|
||||
@ -123,7 +142,7 @@ export function AgentPsdTemplateEditorDialog({
|
||||
...current,
|
||||
[slotId]: payload.url,
|
||||
}));
|
||||
toast.success("素材已上传");
|
||||
toast.success("替换图片已上传");
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "素材上传失败");
|
||||
} finally {
|
||||
@ -159,7 +178,7 @@ export function AgentPsdTemplateEditorDialog({
|
||||
}
|
||||
|
||||
setRenderedResultUrl(payload.result.url);
|
||||
toast.success("套版结果已生成");
|
||||
toast.success("模板替换结果已生成");
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : "PSD 模板渲染失败");
|
||||
} finally {
|
||||
@ -176,7 +195,7 @@ export function AgentPsdTemplateEditorDialog({
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
overlayClassName="bg-black/85 backdrop-blur-sm"
|
||||
className="max-h-[90vh] max-w-6xl overflow-hidden border border-white/10 bg-[#09090b] p-0 text-white shadow-[0_30px_120px_rgba(0,0,0,0.65)]"
|
||||
className="flex h-[92vh] w-[96vw] max-w-[1600px] flex-col overflow-hidden border border-white/10 bg-[#09090b] p-0 text-white shadow-[0_30px_120px_rgba(0,0,0,0.65)]"
|
||||
>
|
||||
<DialogHeader className="border-b border-white/10 bg-[#09090b] px-6 py-5">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
@ -184,266 +203,300 @@ export function AgentPsdTemplateEditorDialog({
|
||||
套用 PSD 模板
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
当前结果图会自动填入默认图片槽位,文字槽位可直接输入,其他图片槽位可继续上传素材替换。
|
||||
这里是直接把当前图片和输入文字替换进 PSD 模板,不会再次调用 AI 生图模型。
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="grid min-h-0 gap-0 lg:grid-cols-[360px,minmax(0,1fr),minmax(0,1fr)]">
|
||||
<div className="border-r border-white/10 bg-[#09090b] p-5">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>选择模板</Label>
|
||||
<Select value={selectedTemplateId} onValueChange={setSelectedTemplateId}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={isLoading ? "加载模板中..." : "请选择模板"} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{templates.map((template) => (
|
||||
<SelectItem key={template.id} value={template.id}>
|
||||
{template.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{selectedTemplate ? (
|
||||
<>
|
||||
<div className="mx-auto w-full max-w-[240px] overflow-hidden rounded-2xl border bg-muted/20">
|
||||
<div
|
||||
style={{
|
||||
aspectRatio: `${Math.max(1, selectedTemplate.width)} / ${Math.max(1, selectedTemplate.height)}`,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={selectedTemplate.previewUrl}
|
||||
alt={selectedTemplate.name}
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Badge variant="secondary">{selectedTemplate.brandName}</Badge>
|
||||
<Badge variant="secondary">
|
||||
{selectedTemplate.width} x {selectedTemplate.height}
|
||||
</Badge>
|
||||
<Badge variant="outline">
|
||||
{
|
||||
selectedTemplate.layers.filter((layer) => layer.slotType === "image").length
|
||||
}{" "}
|
||||
个图片槽位
|
||||
</Badge>
|
||||
<Badge variant="outline">
|
||||
{
|
||||
selectedTemplate.layers.filter((layer) => layer.slotType === "text").length
|
||||
}{" "}
|
||||
个文字槽位
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{selectedTemplate.description ? (
|
||||
<div className="rounded-xl border p-3 text-sm text-muted-foreground">
|
||||
{selectedTemplate.description}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Button onClick={handleRender} disabled={isRendering} className="w-full">
|
||||
{isRendering ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Sparkles className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
生成套版结果
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<div className="rounded-2xl border border-dashed p-5 text-sm text-muted-foreground">
|
||||
{isLoading
|
||||
? "正在加载可用模板..."
|
||||
: normalizedBrandName
|
||||
? `当前品牌“${normalizedBrandName}”下还没有可用 PSD 模板,请先去后台配置。`
|
||||
: "当前还没有启用的 PSD 模板,请先去后台配置。"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-r border-white/10 bg-[#09090b] p-5">
|
||||
<div className="mb-4">
|
||||
<div className="text-sm font-medium">槽位内容</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
图片槽位支持上传和套用当前结果图,文字槽位直接输入即可。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="h-[65vh] pr-4">
|
||||
<div className="grid min-h-0 flex-1 grid-cols-1 overflow-hidden lg:grid-cols-[340px_minmax(0,1fr)] xl:grid-cols-[340px_minmax(0,1fr)_360px]">
|
||||
<div className="min-h-0 border-b border-white/10 bg-[#09090b] p-5 lg:border-b-0 lg:border-r">
|
||||
<ScrollArea className="h-full pr-4">
|
||||
<div className="space-y-4">
|
||||
{selectedTemplate?.layers.length ? (
|
||||
selectedTemplate.layers.map((layer) => {
|
||||
if (isAgentPsdImageLayerSlot(layer)) {
|
||||
const assignedUrl = imageAssignments[layer.id] || "";
|
||||
const slotUploadInputId = `psd-slot-upload-${layer.id}`;
|
||||
<div className="space-y-2">
|
||||
<Label>选择模板</Label>
|
||||
<Select value={selectedTemplateId} onValueChange={setSelectedTemplateId}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={isLoading ? "加载模板中..." : "请选择模板"} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{templates.map((template) => (
|
||||
<SelectItem key={template.id} value={template.id}>
|
||||
{template.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div key={layer.id} className="rounded-2xl border p-4">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="text-sm font-medium">{layer.layerName}</div>
|
||||
<Badge variant="outline">图片槽位</Badge>
|
||||
{layer.isRequired ? <Badge variant="destructive">必传</Badge> : null}
|
||||
{layer.isDefaultResultSource ? (
|
||||
<Badge variant="secondary">默认结果图</Badge>
|
||||
) : null}
|
||||
<Badge variant="outline">
|
||||
{layer.width} x {layer.height}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">{layer.layerPath}</div>
|
||||
|
||||
{assignedUrl ? (
|
||||
<div className="mt-3 overflow-hidden rounded-xl border bg-muted/20">
|
||||
<div className="aspect-[4/3]">
|
||||
<img
|
||||
src={assignedUrl}
|
||||
alt={layer.layerName}
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-3 flex aspect-[4/3] items-center justify-center rounded-xl border border-dashed bg-muted/10 text-xs text-muted-foreground">
|
||||
暂未分配素材
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{sourceImageUrl ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setImageAssignments((current) => ({
|
||||
...current,
|
||||
[layer.id]: sourceImageUrl,
|
||||
}))
|
||||
}
|
||||
>
|
||||
使用当前结果图
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{layer.allowUserUploadOverride ? (
|
||||
<>
|
||||
<Input
|
||||
id={slotUploadInputId}
|
||||
className="hidden"
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/webp"
|
||||
onChange={(event) => handleSlotUpload(layer.id, event)}
|
||||
/>
|
||||
<Label htmlFor={slotUploadInputId} className="inline-flex">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={uploadingSlotId === layer.id}
|
||||
onClick={(event) => {
|
||||
if (uploadingSlotId === layer.id) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{uploadingSlotId === layer.id ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Upload className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
上传替换图
|
||||
</Button>
|
||||
</Label>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{assignedUrl ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setImageAssignments((current) => {
|
||||
const nextAssignments = { ...current };
|
||||
delete nextAssignments[layer.id];
|
||||
return nextAssignments;
|
||||
})
|
||||
}
|
||||
>
|
||||
清空
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const textValue = textAssignments[layer.id] || "";
|
||||
return (
|
||||
<div key={layer.id} className="rounded-2xl border p-4">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="text-sm font-medium">{layer.layerName}</div>
|
||||
<Badge variant="default">文字槽位</Badge>
|
||||
{layer.isRequired ? <Badge variant="destructive">必填</Badge> : null}
|
||||
<Badge variant="outline">
|
||||
{layer.width} x {layer.height}
|
||||
</Badge>
|
||||
{layer.fontLabel || layer.fontFamily ? (
|
||||
<Badge variant="secondary">{layer.fontLabel || layer.fontFamily}</Badge>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">{layer.layerPath}</div>
|
||||
|
||||
<div className="mt-3 grid gap-3">
|
||||
<Textarea
|
||||
rows={4}
|
||||
value={textValue}
|
||||
placeholder={layer.placeholder || "请输入文字"}
|
||||
onChange={(event) =>
|
||||
setTextAssignments((current) => ({
|
||||
...current,
|
||||
[layer.id]: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<div className="flex flex-wrap gap-2 text-xs text-muted-foreground">
|
||||
<span>字体:{layer.fontLabel || layer.fontFamily || "系统字体"}</span>
|
||||
<span>字号:{layer.fontSize}px</span>
|
||||
<span>行高:{layer.lineHeight}</span>
|
||||
<span>对齐:{layer.textAlign}</span>
|
||||
</div>
|
||||
</div>
|
||||
{selectedTemplate ? (
|
||||
<>
|
||||
<div className="mx-auto w-full max-w-[240px] overflow-hidden rounded-2xl border border-white/10 bg-white/5">
|
||||
<div
|
||||
style={{
|
||||
aspectRatio: `${Math.max(1, selectedTemplate.width)} / ${Math.max(1, selectedTemplate.height)}`,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={selectedTemplate.previewUrl}
|
||||
alt={selectedTemplate.name}
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Badge variant="secondary">{selectedTemplate.brandName}</Badge>
|
||||
<Badge variant="secondary">
|
||||
{selectedTemplate.width} x {selectedTemplate.height}
|
||||
</Badge>
|
||||
<Badge variant="outline">{editableImageLayers.length} 个可替换图片</Badge>
|
||||
<Badge variant="outline">{editableTextLayers.length} 个可编辑文字</Badge>
|
||||
{fixedLayerCount > 0 ? (
|
||||
<Badge variant="outline">{fixedLayerCount} 个固定图层</Badge>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{selectedTemplate.description ? (
|
||||
<div className="rounded-xl border border-white/10 bg-white/[0.03] p-3 text-sm text-muted-foreground">
|
||||
{selectedTemplate.description}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="rounded-xl border border-dashed border-white/15 bg-white/[0.02] p-3 text-xs leading-6 text-muted-foreground">
|
||||
用户侧这里只会显示后台已经开放的槽位:
|
||||
图片图层显示为“可替换图片”,文字图层显示为“可编辑文字”,其余固定图层不会出现在这里。
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleRender}
|
||||
disabled={isRendering}
|
||||
className="w-full bg-gradient-to-r from-orange-600 to-rose-600 hover:from-orange-500 hover:to-rose-500"
|
||||
>
|
||||
{isRendering ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Sparkles className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
应用模板并导出
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<div className="rounded-2xl border border-dashed p-5 text-sm text-muted-foreground">
|
||||
{normalizedBrandName ? "暂无可编辑槽位。" : "请先在工作流中选择品牌后再使用 PSD 模板。"}
|
||||
<div className="rounded-2xl border border-dashed border-white/15 p-5 text-sm text-muted-foreground">
|
||||
{isLoading
|
||||
? "正在加载可用模板..."
|
||||
: normalizedBrandName
|
||||
? `当前品牌“${normalizedBrandName}”下还没有可用 PSD 模板,请先去后台配置。`
|
||||
: "当前还没有启用的 PSD 模板,请先去后台配置。"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#09090b] p-5">
|
||||
<div className="min-h-0 border-b border-white/10 bg-[#09090b] p-5 xl:border-b-0 xl:border-r">
|
||||
<div className="mb-4 flex flex-wrap items-center gap-2">
|
||||
<div className="text-sm font-medium">槽位内容</div>
|
||||
{selectedTemplate ? (
|
||||
<>
|
||||
<Badge variant="outline">{editableImageLayers.length} 个可替换图片</Badge>
|
||||
<Badge variant="outline">{editableTextLayers.length} 个可编辑文字</Badge>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mb-4 text-xs text-muted-foreground">
|
||||
图片槽位直接替换图片,文字槽位直接修改文案,不会再次调用 AI 生图。
|
||||
</div>
|
||||
|
||||
<ScrollArea className="h-full min-h-0 pr-4">
|
||||
{selectedTemplate ? (
|
||||
<div className="space-y-6">
|
||||
<section className="space-y-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="text-sm font-medium">可替换图片</div>
|
||||
<Badge variant="secondary">{editableImageLayers.length}</Badge>
|
||||
</div>
|
||||
{editableImageLayers.length > 0 ? (
|
||||
editableImageLayers.map((layer) => {
|
||||
const assignedUrl = imageAssignments[layer.id] || "";
|
||||
const slotUploadInputId = `psd-slot-upload-${layer.id}`;
|
||||
|
||||
return (
|
||||
<div key={layer.id} className="rounded-2xl border border-white/10 bg-white/[0.02] p-4">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="text-sm font-medium">{layer.layerName}</div>
|
||||
<Badge variant="default">可替换图片</Badge>
|
||||
{layer.isRequired ? <Badge variant="destructive">必传</Badge> : null}
|
||||
{layer.isDefaultResultSource ? (
|
||||
<Badge variant="secondary">默认使用当前图片</Badge>
|
||||
) : null}
|
||||
<Badge variant="outline">
|
||||
{layer.width} x {layer.height}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">{layer.layerPath}</div>
|
||||
|
||||
{assignedUrl ? (
|
||||
<div className="mt-3 overflow-hidden rounded-xl border border-white/10 bg-white/5">
|
||||
<div className="aspect-[4/3]">
|
||||
<img
|
||||
src={assignedUrl}
|
||||
alt={layer.layerName}
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-3 flex aspect-[4/3] items-center justify-center rounded-xl border border-dashed border-white/15 bg-white/[0.02] text-xs text-muted-foreground">
|
||||
暂未替换图片
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{sourceImageUrl ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setImageAssignments((current) => ({
|
||||
...current,
|
||||
[layer.id]: sourceImageUrl,
|
||||
}))
|
||||
}
|
||||
>
|
||||
使用当前图片
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{layer.allowUserUploadOverride ? (
|
||||
<>
|
||||
<Input
|
||||
id={slotUploadInputId}
|
||||
className="hidden"
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/webp"
|
||||
onChange={(event) => handleSlotUpload(layer.id, event)}
|
||||
/>
|
||||
<Label htmlFor={slotUploadInputId} className="inline-flex">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={uploadingSlotId === layer.id}
|
||||
onClick={(event) => {
|
||||
if (uploadingSlotId === layer.id) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{uploadingSlotId === layer.id ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Upload className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
上传替换图片
|
||||
</Button>
|
||||
</Label>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{assignedUrl ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setImageAssignments((current) => {
|
||||
const nextAssignments = { ...current };
|
||||
delete nextAssignments[layer.id];
|
||||
return nextAssignments;
|
||||
})
|
||||
}
|
||||
>
|
||||
清空
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="rounded-2xl border border-dashed border-white/15 p-5 text-sm text-muted-foreground">
|
||||
这个模板当前没有开放可替换图片槽位。
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="space-y-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="text-sm font-medium">可编辑文字</div>
|
||||
<Badge variant="secondary">{editableTextLayers.length}</Badge>
|
||||
</div>
|
||||
{editableTextLayers.length > 0 ? (
|
||||
editableTextLayers.map((layer) => {
|
||||
const textValue = textAssignments[layer.id] || "";
|
||||
return (
|
||||
<div key={layer.id} className="rounded-2xl border border-white/10 bg-white/[0.02] p-4">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="text-sm font-medium">{layer.layerName}</div>
|
||||
<Badge variant="default">可编辑文字</Badge>
|
||||
{layer.isRequired ? <Badge variant="destructive">必填</Badge> : null}
|
||||
<Badge variant="outline">
|
||||
{layer.width} x {layer.height}
|
||||
</Badge>
|
||||
{layer.fontLabel || layer.fontFamily ? (
|
||||
<Badge variant="secondary">{layer.fontLabel || layer.fontFamily}</Badge>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">{layer.layerPath}</div>
|
||||
|
||||
<div className="mt-3 grid gap-3">
|
||||
<Textarea
|
||||
rows={4}
|
||||
value={textValue}
|
||||
placeholder={layer.placeholder || "请输入文字"}
|
||||
onChange={(event) =>
|
||||
setTextAssignments((current) => ({
|
||||
...current,
|
||||
[layer.id]: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<div className="flex flex-wrap gap-2 text-xs text-muted-foreground">
|
||||
<span>字体:{layer.fontLabel || layer.fontFamily || "系统字体"}</span>
|
||||
<span>字号:{layer.fontSize}px</span>
|
||||
<span>行高:{layer.lineHeight}</span>
|
||||
<span>对齐:{layer.textAlign}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="rounded-2xl border border-dashed border-white/15 p-5 text-sm text-muted-foreground">
|
||||
这个模板当前没有开放可编辑文字槽位。
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-2xl border border-dashed border-white/15 p-5 text-sm text-muted-foreground">
|
||||
{normalizedBrandName ? "请先选择一个模板。" : "请先在工作流中选择品牌后再使用 PSD 模板。"}
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 bg-[#09090b] p-5">
|
||||
<div className="mb-4">
|
||||
<div className="text-sm font-medium">套版结果</div>
|
||||
<div className="text-sm font-medium">导出结果</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
生成后可直接预览并下载结果图。
|
||||
直接完成模板替换后,这里会显示最终导出的成品图。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{renderedResultUrl ? (
|
||||
<div className="space-y-4">
|
||||
<div className="mx-auto w-full max-w-[280px] overflow-hidden rounded-2xl border bg-muted/20">
|
||||
<div className="flex h-full min-h-0 flex-col justify-between gap-4">
|
||||
<div className="mx-auto w-full max-w-[280px] overflow-hidden rounded-2xl border border-white/10 bg-white/5">
|
||||
<div
|
||||
style={{
|
||||
aspectRatio: selectedTemplate
|
||||
@ -453,7 +506,7 @@ export function AgentPsdTemplateEditorDialog({
|
||||
>
|
||||
<img
|
||||
src={renderedResultUrl}
|
||||
alt="PSD 模板渲染结果"
|
||||
alt="PSD 模板导出结果"
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
@ -465,18 +518,18 @@ export function AgentPsdTemplateEditorDialog({
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-[65vh] flex-col items-center justify-center rounded-2xl border border-dashed bg-muted/10 px-6 text-center">
|
||||
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-muted">
|
||||
{selectedTemplate?.layers.some((layer) => layer.slotType === "text") ? (
|
||||
<div className="flex h-full min-h-0 flex-col items-center justify-center rounded-2xl border border-dashed border-white/15 bg-white/[0.02] px-6 text-center">
|
||||
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-white/5">
|
||||
{editableTextLayers.length > 0 ? (
|
||||
<Type className="h-8 w-8 text-muted-foreground" />
|
||||
) : (
|
||||
<ImageIcon className="h-8 w-8 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-4 text-sm font-medium">结果还没生成</div>
|
||||
<div className="mt-4 text-sm font-medium">还未导出成品图</div>
|
||||
<div className="mt-2 text-xs leading-6 text-muted-foreground">
|
||||
{normalizedBrandName
|
||||
? "选好模板和槽位内容后点击“生成套版结果”,这里会显示导出的图片。"
|
||||
? "确认替换图片和文字后,点击“应用模板并导出”,这里会显示最终图片。"
|
||||
: "请先返回工作流区域选择品牌,再打开 PSD 模板。"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user