修复画布生图交付与素材成本展示

修复 nanobanana 标量尺寸被误判为宽高的问题。

普通生图和快速编辑在内存完成尺寸变换后仅上传一次,变换失败保留 provider 原图。

移除快速编辑重复的原始输出素材,保留角色、图集和 UI 的独立中间产物。

后台将不重复计费的中间产物显示为不单独计费。

补充单次上传、降级交付和成本展示回归测试及相关文档。
This commit is contained in:
2026-07-15 01:36:09 +08:00
parent 5c1434511b
commit 3f3a6c4ab2
7 changed files with 272 additions and 98 deletions
@@ -245,6 +245,40 @@ test('后台素材查询不展示分类筛选和分类列', async () => {
expect(screen.queryByRole('columnheader', { name: '分类' })).toBeNull();
});
test('后台素材查询将中间产物标记为不单独计费', async () => {
vi.mocked(listAdminEditorAssets).mockResolvedValueOnce({
entries: [
{
...generatedAsset,
assetId: 'asset-source',
label: '角色形象原图',
assetKind: 'editor_green_screen_source',
generationCostMudPoints: 0,
},
generatedAsset,
],
nextCursor: null,
});
render(
<AdminEditorAssetQueryPage token="admin-token" onUnauthorized={vi.fn()} />,
);
const sourceRow = (await screen.findByText('角色形象原图')).closest('tr');
const finalRow = screen.getByText('角色形象 1').closest('tr');
expect(sourceRow).not.toBeNull();
expect(finalRow).not.toBeNull();
expect(within(sourceRow!).getByText('不单独计费')).toBeTruthy();
expect(within(finalRow!).getByText('12 泥点')).toBeTruthy();
fireEvent.click(within(sourceRow!).getByRole('button', { name: '详情' }));
expect(
within(await screen.findByRole('dialog', { name: '素材详情' })).getByText(
'不单独计费',
),
).toBeTruthy();
});
test('后台素材查询缩略图使用 objectKey 换签后展示', async () => {
render(
<AdminEditorAssetQueryPage token="admin-token" onUnauthorized={vi.fn()} />,
@@ -24,6 +24,10 @@ const ADMIN_ASSET_READ_EXPIRE_SECONDS = 300;
const ADMIN_ASSET_READ_DISPATCH_SPACING_MS = 40;
const ADMIN_ASSET_READ_RETRY_DELAYS_MS = [400, 1_200, 3_000] as const;
const ADMIN_ASSET_THUMBNAIL_ROOT_MARGIN = '240px 0px';
const NON_BILLED_INTERMEDIATE_ASSET_KINDS = new Set([
'editor_green_screen_source',
'editor_image_edit_source',
]);
const AUDIO_ASSET_COVER_SRC = `${import.meta.env.DEV ? import.meta.env.BASE_URL : '/'}creation-home/audio-asset-cover.png`;
let adminAssetReadDispatchTail = Promise.resolve();
@@ -217,7 +221,7 @@ export function AdminEditorAssetQueryPage({
{promptText}
</button>
</td>
<td>{entry.generationCostMudPoints} 泥点</td>
<td>{generationCostLabel(entry)}</td>
<td>
<button
className="admin-secondary-button"
@@ -516,7 +520,7 @@ function AdminAssetDetailDialog({
{entry.width} x {entry.height}
</AdminInfoItem>
<AdminInfoItem label="成本">
{entry.generationCostMudPoints} 泥点
{generationCostLabel(entry)}
</AdminInfoItem>
<AdminInfoItem label="模型">{entry.model || '-'}</AdminInfoItem>
<AdminInfoItem label="Provider">
@@ -558,6 +562,13 @@ function AdminAssetDetailDialog({
);
}
function generationCostLabel(entry: AdminEditorAssetPayload) {
return entry.assetKind &&
NON_BILLED_INTERMEDIATE_ASSET_KINDS.has(entry.assetKind)
? '不单独计费'
: `${entry.generationCostMudPoints} 泥点`;
}
function AdminAssetPreviewDialog({
entry,
token,