fix: stabilize psd template admin and export render
This commit is contained in:
parent
db46934025
commit
8c8b1794e4
@ -66,6 +66,26 @@ import type { AgentWorkflowUiConfig } from "@/types/agent";
|
||||
|
||||
const fetcher = (url: string) => fetch(url, { cache: "no-store" }).then((res) => res.json());
|
||||
|
||||
async function fetchPsdTemplateList(url: string): Promise<AgentPsdTemplateListResponse> {
|
||||
const response = await fetch(url, { cache: "no-store" });
|
||||
const payload = (await response
|
||||
.json()
|
||||
.catch(() => null)) as Partial<AgentPsdTemplateListResponse> | null;
|
||||
|
||||
if (
|
||||
!response.ok ||
|
||||
!payload ||
|
||||
payload.success !== true ||
|
||||
!Array.isArray(payload.templates)
|
||||
) {
|
||||
throw new Error(
|
||||
String(payload?.error || "").trim() || "PSD 模板读取失败"
|
||||
);
|
||||
}
|
||||
|
||||
return payload as AgentPsdTemplateListResponse;
|
||||
}
|
||||
|
||||
const SYSTEM_FONT_OPTIONS = [
|
||||
{ id: "__system_sans", label: "系统无衬线", family: "sans-serif" },
|
||||
{ id: "__system_serif", label: "系统衬线", family: "serif" },
|
||||
@ -227,9 +247,19 @@ export function PsdTemplatesSettingsClient({
|
||||
backHref: string;
|
||||
backLabel: string;
|
||||
}) {
|
||||
const { data, mutate, isLoading } = useSWR<AgentPsdTemplateListResponse>(
|
||||
const {
|
||||
data,
|
||||
error: templatesRequestError,
|
||||
mutate,
|
||||
isLoading,
|
||||
} = useSWR<AgentPsdTemplateListResponse>(
|
||||
"/api/admin/psd-templates",
|
||||
fetcher
|
||||
fetchPsdTemplateList,
|
||||
{
|
||||
keepPreviousData: true,
|
||||
revalidateOnFocus: false,
|
||||
dedupingInterval: 10_000,
|
||||
}
|
||||
);
|
||||
const { data: siteSettingsData, mutate: mutateSiteSettings } = useSWR<{
|
||||
agentWorkflowUiConfig?: AgentWorkflowUiConfig;
|
||||
@ -244,11 +274,15 @@ export function PsdTemplatesSettingsClient({
|
||||
|
||||
const templates = useMemo(() => (data?.success ? data.templates : []), [data]);
|
||||
const templateListErrorMessage = useMemo(() => {
|
||||
if (templatesRequestError instanceof Error) {
|
||||
return templatesRequestError.message.trim() || "PSD 模板读取失败";
|
||||
}
|
||||
|
||||
if (!data || data.success) {
|
||||
return "";
|
||||
}
|
||||
return String(data.error || "").trim() || "PSD 模板读取失败";
|
||||
}, [data]);
|
||||
}, [data, templatesRequestError]);
|
||||
const fonts = useMemo(() => (fontsData?.success ? fontsData.fonts : []), [fontsData]);
|
||||
const brandOptions = useMemo(
|
||||
() =>
|
||||
@ -437,6 +471,10 @@ export function PsdTemplatesSettingsClient({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (templateListErrorMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!templates.length) {
|
||||
setSelectedTemplateId(null);
|
||||
return;
|
||||
@ -445,7 +483,7 @@ export function PsdTemplatesSettingsClient({
|
||||
if (!selectedTemplateId || !templates.some((template) => template.id === selectedTemplateId)) {
|
||||
setSelectedTemplateId(templates[0].id);
|
||||
}
|
||||
}, [selectedTemplateId, templates]);
|
||||
}, [selectedTemplateId, templateListErrorMessage, templates]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedTemplate) {
|
||||
@ -1721,7 +1759,24 @@ export function PsdTemplatesSettingsClient({
|
||||
<CardDescription>按品牌查看模板,选择一个模板继续配置槽位。</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{isLoading ? (
|
||||
{templateListErrorMessage && templates.length > 0 ? (
|
||||
<div className="flex items-start gap-3 rounded-xl border border-amber-300/30 bg-amber-500/5 p-4 text-sm text-amber-100">
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div>{`${templateListErrorMessage},当前先显示上次成功加载的模板列表。`}</div>
|
||||
<Button
|
||||
className="mt-3"
|
||||
onClick={() => void mutate()}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="outline"
|
||||
>
|
||||
重新加载
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{isLoading && templates.length === 0 ? (
|
||||
<div className="space-y-3">
|
||||
{Array.from({ length: 3 }).map((_, index) => (
|
||||
<div
|
||||
|
||||
@ -94,15 +94,6 @@ function sanitizeFileSegment(value: string, fallback: string) {
|
||||
return normalized || fallback;
|
||||
}
|
||||
|
||||
function hasDedicatedRenderBase(template: AgentPsdTemplate) {
|
||||
const renderBaseUrl = String(template.renderBaseUrl || "").trim();
|
||||
const previewUrl = String(template.previewUrl || "").trim();
|
||||
return Boolean(String(template.renderBasePath || "").trim()) || (
|
||||
Boolean(renderBaseUrl) &&
|
||||
renderBaseUrl !== previewUrl
|
||||
);
|
||||
}
|
||||
|
||||
function resolveTextOverlayFrame(params: {
|
||||
layerWidth: number;
|
||||
layerHeight: number;
|
||||
@ -327,7 +318,6 @@ export async function renderAgentPsdTemplate(params: {
|
||||
const canvasHeight = Math.max(1, Math.round(Number(template.height || 1)));
|
||||
const remoteBufferCache = new Map<string, Buffer>();
|
||||
const fontMap = new Map(fonts.map((font) => [font.id, font]));
|
||||
const shouldUseDedicatedRenderBase = hasDedicatedRenderBase(template);
|
||||
|
||||
async function readCachedBuffer(url: string, label = "PSD 模板资源") {
|
||||
const normalizedUrl = String(url || "").trim();
|
||||
@ -350,7 +340,6 @@ export async function renderAgentPsdTemplate(params: {
|
||||
}
|
||||
|
||||
const compositeInputs: Array<{ input: Buffer; left: number; top: number }> = [];
|
||||
let didAppendDedicatedRenderBase = false;
|
||||
|
||||
async function appendBufferOverlay(params: {
|
||||
input: Buffer;
|
||||
@ -408,47 +397,8 @@ export async function renderAgentPsdTemplate(params: {
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldUseDedicatedRenderBase && template.renderBaseUrl) {
|
||||
try {
|
||||
const renderBaseBuffer = await readCachedBuffer(
|
||||
template.renderBaseUrl,
|
||||
"PSD 模板底图"
|
||||
);
|
||||
const normalizedRenderBaseBuffer = await sharp(renderBaseBuffer)
|
||||
.rotate()
|
||||
.resize({
|
||||
width: canvasWidth,
|
||||
height: canvasHeight,
|
||||
fit: "fill",
|
||||
background: {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
alpha: 0,
|
||||
},
|
||||
})
|
||||
.png()
|
||||
.toBuffer();
|
||||
didAppendDedicatedRenderBase = await appendBufferOverlay({
|
||||
input: normalizedRenderBaseBuffer,
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: canvasWidth,
|
||||
height: canvasHeight,
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("[PSD Template] failed to load dedicated render base during render:", {
|
||||
templateId: template.id,
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const layer of [...template.layers].reverse()) {
|
||||
if (!layer.isEditable) {
|
||||
if (didAppendDedicatedRenderBase) {
|
||||
continue;
|
||||
}
|
||||
await appendStoredLayer(layer);
|
||||
continue;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user