修复画布生图交付与素材成本展示
修复 nanobanana 标量尺寸被误判为宽高的问题。 普通生图和快速编辑在内存完成尺寸变换后仅上传一次,变换失败保留 provider 原图。 移除快速编辑重复的原始输出素材,保留角色、图集和 UI 的独立中间产物。 后台将不重复计费的中间产物显示为不单独计费。 补充单次上传、降级交付和成本展示回归测试及相关文档。
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user