Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions apps/api/src/backend.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,26 @@ test('user job DTO applies final prompt visibility without changing the original
assert.equal(visible.finalPrompt, 'optimized'); assert.equal(visible.prompt, 'original')
})

test('user job DTO exposes only the truncated provider failure detail', async () => {
const base = { id: 'job', created_by: 'user', model_id: 'model', model_name: 'Video', prompt: 'p', size: '16:9', count: 1, status: 'failed', error_code: 'PROVIDER_REJECTED', created_at: new Date() }
const rejected = await jobDto({ ...base, provider_error: { status: 400, detail: ' Input image resolution is unsupported ', providerReferenceId: 'nested-ref' } })
assert.equal(rejected.errorCode, 'PROVIDER_REJECTED')
assert.equal(rejected.errorMessage, 'Input image resolution is unsupported')
assert.equal(JSON.stringify(rejected).includes('nested-ref'), false)
assert.equal(JSON.stringify(rejected).includes('"status":400'), false)
const truncated = await jobDto({ ...base, provider_error: { detail: 'x'.repeat(400) } })
assert.equal(truncated.errorMessage?.length, 300)
for (const row of [
base,
{ ...base, provider_error: null },
{ ...base, provider_error: { status: 502 } },
{ ...base, provider_error: { detail: ' ' } },
{ ...base, provider_error: 'not-an-object' },
]) {
assert.equal((await jobDto(row)).errorMessage, undefined)
}
})

test('manual retry resumes from the correct generation phase', () => {
assert.deepEqual(retryPreparation({ optimization_mode: 'disabled' }), { phase: 'image_generating', resetOptimization: false })
assert.deepEqual(retryPreparation({ optimization_mode: 'enabled', prompt_optimization_id: 'po', final_prompt: 'optimized prompt', template_instruction_snapshot: 'template' }), { phase: 'image_generating', resetOptimization: false })
Expand Down
6 changes: 6 additions & 0 deletions apps/api/src/shared/dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ export async function jobDto(row: Record<string, unknown>, outputs: Record<strin
if (rawNormalized && typeof rawNormalized.parameters === 'object' && rawNormalized.parameters !== null) {
normalizedParameters = rawNormalized.parameters as Record<string, unknown>
}
// Only the already-sanitised provider detail is user-visible; never spread the object.
const providerError = row.provider_error && typeof row.provider_error === 'object'
? row.provider_error as { detail?: unknown }
: undefined
const providerDetail = typeof providerError?.detail === 'string' ? providerError.detail.trim() : ''
return {
id: row.id,
createdBy: row.created_by,
Expand Down Expand Up @@ -240,6 +245,7 @@ export async function jobDto(row: Record<string, unknown>, outputs: Record<strin
count: row.count !== null && row.count !== undefined ? Number(row.count) : undefined,
status: row.status,
errorCode: row.error_code || undefined,
errorMessage: providerDetail ? providerDetail.slice(0, 300) : undefined,
createdAt: new Date(row.created_at as string | number | Date).toISOString(),
startedAt: row.started_at ? new Date(row.started_at as string | number | Date).toISOString() : undefined,
completedAt: row.completed_at ? new Date(row.completed_at as string | number | Date).toISOString() : undefined,
Expand Down
4 changes: 2 additions & 2 deletions apps/web-next/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ const nextConfig = {
return [
{
source: '/history',
destination: '/generate?tab=history',
permanent: true,
destination: '/library',
permanent: false,
},
{
source: '/admin/registration',
Expand Down
11 changes: 9 additions & 2 deletions apps/web-next/src/app/(workspace)/generate/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { GenerateConsole } from '@/features/generate/components/generate-console'
import { Suspense } from 'react'

export const dynamic = 'force-dynamic'

export const metadata = {
title: '创作工作台 - MuseCanvas',
description: 'AI 图像生成工作室与控制台',
description: 'AI 图像与视频生成工作室与控制台',
}

export default function GeneratePage() {
return <GenerateConsole />
// The console mirrors its 图像/视频 tab into `?tab=`, so it reads search params
// and needs a boundary like every other `useSearchParams` consumer.
return (
<Suspense fallback={<div className="flex h-full items-center justify-center p-8 text-sm text-muted-foreground">正在加载创作台...</div>}>
<GenerateConsole />
</Suspense>
)
}
11 changes: 11 additions & 0 deletions apps/web-next/src/app/admin/plugins/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { AdminPluginsView } from '@/features/admin/components/admin-plugins-view'

export const dynamic = 'force-dynamic'

export const metadata = {
title: '媒体插件 - MuseCanvas 管理后台',
}

export default function AdminPluginsPage() {
return <AdminPluginsView />
}
11 changes: 11 additions & 0 deletions apps/web-next/src/app/admin/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { AdminSettingsView } from '@/features/admin/components/admin-settings-view'

export const dynamic = 'force-dynamic'

export const metadata = {
title: '系统配置 - MuseCanvas 管理后台',
}

export default function AdminSettingsPage() {
return <AdminSettingsView />
}
Loading
Loading