fix: stabilize psd template admin and export render
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-04-10 21:16:57 +08:00
parent db46934025
commit 8c8b1794e4
2 changed files with 60 additions and 55 deletions

View File

@ -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

View File

@ -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;
}