diff --git a/src/pages/ImageCanvas/index.tsx b/src/pages/ImageCanvas/index.tsx
index d9e4ffa..83f1c5d 100644
--- a/src/pages/ImageCanvas/index.tsx
+++ b/src/pages/ImageCanvas/index.tsx
@@ -37,6 +37,19 @@ import type {
} from '../../../shared/image-workspace';
const ACTIVE_TASK_STATUSES = new Set(['queued', 'running']);
+const TASK_FAILURE_MESSAGES: Record = {
+ policy_blocked: '内容可能涉及版权或安全风险,请调整设计后重试',
+ budget_denied: '当前设计点不足,无法开始生成',
+ quote_expired: '生成方案已过期,请重新确认',
+ asset_rejected: '生成结果未通过媒体检查,请调整设计后重试',
+ dependency_unavailable: '生成服务暂时不可用,请稍后重试',
+ submission_unknown: '正在确认生成请求状态,请稍后查看',
+ submission_not_accepted: '生成请求未被服务接受,请重新确认后再试',
+ generation_failed: '生成未完成,请调整设计或稍后重试',
+ result_persist_failed: '生成结果保存失败,请稍后重试',
+ cancelled: '生成任务已取消',
+ expired: '生成任务已过期,请重新确认',
+};
const CONFIRMATION_UNAVAILABLE_MESSAGE = (
'当前没有可确认的生成报价,请先让设计 Agent 完成方案与报价。'
);
@@ -61,6 +74,10 @@ function taskStatusLabel(status: DesignTaskStatus): string {
return '失败';
}
+function taskFailureMessage(code: string): string {
+ return TASK_FAILURE_MESSAGES[code] ?? '生成未完成,请稍后重试';
+}
+
function mediumLabel(medium: 'image' | 'video'): string {
return medium === 'video' ? '视频' : '图片';
}
@@ -153,7 +170,7 @@ function TaskCard({ task }: { task: DesignGenerationTask }) {
{task.failureCode ? (
- {task.failureCode}
+ {taskFailureMessage(task.failureCode)}
) : null}
diff --git a/tests/unit/image-canvas-page.test.tsx b/tests/unit/image-canvas-page.test.tsx
index 30d21ab..ffea325 100644
--- a/tests/unit/image-canvas-page.test.tsx
+++ b/tests/unit/image-canvas-page.test.tsx
@@ -482,6 +482,21 @@ describe('ImageCanvas Workspace-first design experience', () => {
expect(confirmImageWorkspaceGenerationMock).not.toHaveBeenCalled();
});
+ it('renders policy-blocked generation tasks with a user-facing design message', async () => {
+ fetchImageWorkspaceTasksMock.mockResolvedValue([{
+ ...taskFixture,
+ status: 'failed',
+ failureCode: 'policy_blocked',
+ resultAssets: [],
+ }]);
+
+ render();
+
+ const alert = await screen.findByRole('alert');
+ expect(alert).toHaveTextContent('内容可能涉及版权或安全风险,请调整设计后重试');
+ expect(alert).not.toHaveTextContent('policy_blocked');
+ });
+
it('renders a new task pushed by the design event stream without repeated polling', async () => {
const queuedTask = {
...taskFixture,