按后台配置扣除创作泥点

前端创作表单泥点预校验改为读取入口契约配置

拼图和抓大鹅初始生成后端扣费改为解析后台配置

汪汪声浪初始三图生成按入口总成本拆分扣费

创作工作台按钮和确认弹窗展示后台配置泥点成本

补充泥点扣费回归测试并同步文档与共享记忆
This commit is contained in:
2026-06-08 15:47:48 +08:00
parent 3ca5a460f1
commit 5ea9f0a120
21 changed files with 425 additions and 45 deletions

View File

@@ -68,6 +68,7 @@ export function UnifiedCreationWorkspace(props: UnifiedCreationWorkspaceProps) {
showBackButton={false}
title={null}
unifiedChrome
mudPointCost={props.spec.mudPointCost ?? undefined}
/>
</UnifiedCreationPage>
);
@@ -86,6 +87,7 @@ export function UnifiedCreationWorkspace(props: UnifiedCreationWorkspaceProps) {
showBackButton={false}
title={null}
unifiedChrome
mudPointCost={props.spec.mudPointCost ?? undefined}
/>
</UnifiedCreationPage>
);

View File

@@ -65,6 +65,13 @@ function confirmMatch3DPointCost() {
fireEvent.click(within(confirmDialog).getByRole('button', { name: '确定' }));
}
function confirmMatch3DPointCostText(text: string) {
const confirmDialog = screen.getByRole('dialog', {
name: '确认消耗泥点',
});
expect(within(confirmDialog).getByText(text)).toBeTruthy();
}
test('match3d workspace submits derived entry form payload instead of agent chat', () => {
const onCreateFromForm = vi.fn();
const onExecuteAction = vi.fn();
@@ -112,6 +119,26 @@ test('match3d workspace submits derived entry form payload instead of agent chat
expect(onExecuteAction).not.toHaveBeenCalled();
});
test('match3d workspace shows configured mud point cost', () => {
render(
<Match3DCreationWorkspace
session={null}
onBack={() => {}}
onExecuteAction={() => {}}
onCreateFromForm={() => {}}
mudPointCost={12}
/>,
);
expect(screen.getByText('消耗12泥点')).toBeTruthy();
fireEvent.change(screen.getByLabelText('想做一个什么题材的抓大鹅?'), {
target: { value: '陶泥甜品店' },
});
fireEvent.click(screen.getByRole('button', { name: /稿/u }));
confirmMatch3DPointCostText('消耗 12 泥点');
});
test('match3d workspace can defer visible chrome to the unified creation page', () => {
const { container } = render(
<Match3DCreationWorkspace

View File

@@ -20,6 +20,7 @@ type Match3DCreationWorkspaceProps = {
showBackButton?: boolean;
title?: string | null;
unifiedChrome?: boolean;
mudPointCost?: number;
};
type Match3DFormState = {
@@ -117,6 +118,7 @@ export function Match3DCreationWorkspace({
showBackButton = true,
title = '抓大鹅',
unifiedChrome = false,
mudPointCost = 10,
}: Match3DCreationWorkspaceProps) {
const [formState, setFormState] = useState<Match3DFormState>(() =>
resolveInitialFormState(session, initialFormPayload),
@@ -324,7 +326,7 @@ export function Match3DCreationWorkspace({
)}
<span>稿</span>
<span className="rounded-full bg-white/24 px-2 py-0.5 text-[11px] font-bold">
10
{mudPointCost}
</span>
</span>
</button>
@@ -345,7 +347,7 @@ export function Match3DCreationWorkspace({
</div>
<div className="mt-2 text-sm font-semibold leading-6 text-[var(--platform-text-base)]">
10
{mudPointCost}
</div>
<div className="mt-5 grid grid-cols-2 gap-3">
<button

View File

@@ -173,6 +173,13 @@ function confirmPuzzlePointCost() {
fireEvent.click(within(confirmDialog).getByRole('button', { name: '确定' }));
}
function confirmPuzzlePointCostText(text: string) {
const confirmDialog = screen.getByRole('dialog', {
name: '确认消耗泥点',
});
expect(within(confirmDialog).getByText(text)).toBeTruthy();
}
test('puzzle workspace submits the work form instead of agent chat', () => {
const onCreateFromForm = vi.fn();
@@ -216,6 +223,27 @@ test('puzzle workspace submits the work form instead of agent chat', () => {
expect(screen.queryByText('旧会话消息不再渲染为聊天入口。')).toBeNull();
});
test('puzzle workspace shows configured mud point cost', () => {
render(
<PuzzleCreationWorkspace
session={null}
onBack={() => {}}
onSubmitMessage={() => {}}
onExecuteAction={() => {}}
onCreateFromForm={() => {}}
mudPointCost={8}
/>,
);
expect(screen.getByText('消耗8泥点')).toBeTruthy();
fireEvent.change(screen.getByLabelText('画面描述'), {
target: { value: '一座雨后的陶泥小镇。' },
});
fireEvent.click(screen.getByRole('button', { name: /稿/u }));
confirmPuzzlePointCostText('消耗 8 泥点');
});
test('puzzle workspace can defer visible chrome to the unified creation page', () => {
const { container } = render(
<PuzzleCreationWorkspace

View File

@@ -50,6 +50,7 @@ type PuzzleCreationWorkspaceProps = {
showBackButton?: boolean;
title?: string | null;
unifiedChrome?: boolean;
mudPointCost?: number;
};
type PuzzleFormState = {
@@ -248,6 +249,7 @@ export function PuzzleCreationWorkspace({
showBackButton = true,
title = '拼图',
unifiedChrome = false,
mudPointCost = 2,
}: PuzzleCreationWorkspaceProps) {
const [formState, setFormState] = useState<PuzzleFormState>(() =>
resolveInitialFormState(session, initialFormPayload),
@@ -667,7 +669,7 @@ export function PuzzleCreationWorkspace({
inputError={referenceImageError}
error={error}
submitLabel="生成拼图游戏草稿"
submitCostLabel={formState.aiRedraw ? '消耗2泥点' : null}
submitCostLabel={formState.aiRedraw ? `消耗${mudPointCost}泥点` : null}
submitDisabled={!canSubmit}
labels={{
imageField: '拼图画面',
@@ -760,7 +762,7 @@ export function PuzzleCreationWorkspace({
</div>
<div className="mt-2 text-sm font-semibold leading-6 text-[var(--platform-text-base)]">
2
{mudPointCost}
</div>
<div className="mt-5 grid grid-cols-2 gap-3">
<button