改造时隐藏系统提示词

改造和重绘面板只从用户可见输入快照恢复提示词。

没有用户输入快照的旧资源打开改造时保持输入框为空。

补充图片、音频、视频、规范和宣发素材改造提示词恢复测试。

更新图片画布编辑器文档中的系统提示词隐藏规则。
This commit is contained in:
2026-06-22 20:13:23 +08:00
parent 97edb5cb1a
commit d9cd2edf92
4 changed files with 183 additions and 46 deletions
File diff suppressed because one or more lines are too long
@@ -22,6 +22,7 @@ import {
createIconGenerationDialogDraft,
createPublicationGenerationDialogDraft,
createQuickEditPanelDraft,
createRedrawPanelDraft,
createSoundEffectGenerationDialogDraft,
createSpecDialogDraft,
createUiDesignGenerationDialogDraft,
@@ -263,7 +264,7 @@ describe('ImageCanvasGenerationDialogModel', () => {
title: '游戏音效',
mediaType: 'audio',
assetKind: 'sound-effect',
prompt: '金币收集音',
prompt: '【系统内置】完整音效提示词:金币收集音,自动混音,内部参数。',
generationInputs: {
fields: [
{ title: 'prompt', value: '金币收集音' },
@@ -315,20 +316,19 @@ describe('ImageCanvasGenerationDialogModel', () => {
expect(createAudioRedrawGenerationDialogDraft(createLayer())).toBeNull();
});
it('uses audio prompt templates when remodeling old audio layers without prompts', () => {
it('keeps audio remodel prompts blank when no user input snapshot exists', () => {
expect(
createAudioRedrawGenerationDialogDraft(
createLayer({
mediaType: 'audio',
assetKind: 'sound-effect',
prompt: '',
prompt: '【系统内置】完整音效提示词',
generationInputs: { fields: [], references: [] },
}),
),
).toMatchObject({
mode: 'audio-sound-effect',
prompt:
'短促清脆的游戏交互音效,带轻微闪光质感,干净收尾,适合按钮确认或收集奖励。',
prompt: '',
});
expect(
createAudioRedrawGenerationDialogDraft(
@@ -336,26 +336,33 @@ describe('ImageCanvasGenerationDialogModel', () => {
mediaType: 'audio',
assetKind: 'background-music',
title: '游戏背景音乐',
prompt: '',
generationInputs: { fields: [], references: [] },
prompt: '【系统内置】完整背景音乐提示词',
generationInputs: {
fields: [{ title: 'duration', value: '10秒' }],
references: [],
},
}),
),
).toMatchObject({
mode: 'audio-background-music',
prompt: '适合轻松冒险游戏的循环背景音乐,温暖明亮,旋律简洁,不包含人声。',
prompt: '',
});
});
it('creates edit, quick-edit, and character animation panel drafts', () => {
const sourceLayer = createLayer({
prompt: '原图提示',
prompt: '【系统内置】完整拼接提示词:原图提示,自动画质优化。',
model: 'gpt-image-2',
assetKind: 'character',
generationInputs: {
fields: [{ title: '生成提示词', value: '原图提示' }],
references: [],
},
});
expect(createEditDialogDraft(sourceLayer)).toEqual({
mode: 'edit',
prompt: '原图提示,在保持主体结构的基础上优化画面细节',
prompt: '原图提示',
status: 'idle',
composerOpen: true,
sourceLayerId: 'layer-source',
@@ -371,6 +378,7 @@ describe('ImageCanvasGenerationDialogModel', () => {
quickEditReferences: [],
status: 'idle',
});
expect(createRedrawPanelDraft(sourceLayer).prompt).toBe('原图提示');
expect(createCharacterAnimationPanelDraft(sourceLayer)).toEqual({
sourceLayerId: 'layer-source',
promptText: '',
@@ -383,6 +391,86 @@ describe('ImageCanvasGenerationDialogModel', () => {
expect(createCharacterAnimationPanelDraft(createLayer())).toBeNull();
});
it('restores remodel prompts from user-visible generation input snapshots only', () => {
const cases: Array<{
fields: NonNullable<CanvasLayer['generationInputs']>['fields'];
expectedPrompt: string;
}> = [
{
fields: [{ title: '视频描述', value: '用户的视频描述' }],
expectedPrompt: '用户的视频描述',
},
{
fields: [{ title: '角色设定', value: '用户的角色设定' }],
expectedPrompt: '用户的角色设定',
},
{
fields: [{ title: '用户输入', value: '用户的 UI 需求' }],
expectedPrompt: '用户的 UI 需求',
},
{
fields: [{ title: '素材描述', value: '返回按钮\n设置按钮' }],
expectedPrompt: '返回按钮\n设置按钮',
},
{
fields: [{ title: '自定义规范提示词', value: '用户自定义规范' }],
expectedPrompt: '用户自定义规范',
},
{
fields: [
{ title: '玩法设定', value: '平台跳跃' },
{ title: '美术风格', value: '像素风' },
],
expectedPrompt: '玩法设定:平台跳跃\n美术风格:像素风',
},
{
fields: [
{ title: '游戏名', value: '星海远征' },
{ title: '游戏分类', value: '冒险' },
{ title: '一句话描述游戏', value: '驾驶飞船探索星云' },
],
expectedPrompt:
'游戏名:星海远征\n游戏分类:冒险\n一句话描述游戏:驾驶飞船探索星云',
},
{
fields: [{ title: '动作描述', value: '待机动作' }],
expectedPrompt: '待机动作',
},
];
for (const testCase of cases) {
expect(
createRedrawPanelDraft(
createLayer({
prompt: '【系统内置】完整拼接提示词,包含默认规则和模型约束。',
generationInputs: {
fields: testCase.fields,
references: [],
},
}),
).prompt,
).toBe(testCase.expectedPrompt);
}
expect(
createRedrawPanelDraft(
createLayer({
prompt: '【系统内置】固定素材提取提示词',
generationInputs: {
fields: [
{
title: '提取提示词',
value: '仅提取被红色框框选的素材并整理成spritesheet',
},
{ title: 'model', value: 'gpt-image-2' },
],
references: [],
},
}),
).prompt,
).toBe('');
});
it('normalizes legacy nanobanana model aliases in quick edit drafts', () => {
expect(
createQuickEditPanelDraft(
@@ -416,37 +416,62 @@ function findGenerationInputFieldValue(
);
}
function resolveDefaultAudioPrompt(
mode: Extract<
GenerateDialogState['mode'],
'audio-sound-effect' | 'audio-background-music'
>,
) {
return mode === 'audio-background-music'
? DEFAULT_BACKGROUND_MUSIC_PROMPT
: DEFAULT_SOUND_EFFECT_PROMPT;
const USER_PROMPT_INPUT_TITLES = new Set(
[
'生成提示词',
'视频描述',
'prompt',
'sound',
'gpt_description_prompt',
'音效提示词',
'背景音乐提示词',
'角色设定',
'用户输入',
'素材描述',
'自定义规范提示词',
'修改要求',
'快速编辑提示词',
'重绘提示词',
'动作描述',
].map((title) => title.toLowerCase()),
);
const STRUCTURED_USER_INPUT_TITLES = new Set(
[
'玩法设定',
'美术风格',
'头身比',
'角色视角',
'游戏名',
'游戏分类',
'一句话描述游戏',
].map((title) => title.toLowerCase()),
);
function resolveUserGenerationPromptSnapshot(sourceLayer: CanvasLayer) {
const fields = sourceLayer.generationInputs?.fields ?? [];
const promptField = fields.find((field) =>
USER_PROMPT_INPUT_TITLES.has(field.title.trim().toLowerCase()),
);
const promptValue = promptField?.value.trim();
if (promptValue) {
return promptValue;
}
const structuredLines = fields.flatMap((field) => {
if (!STRUCTURED_USER_INPUT_TITLES.has(field.title.trim().toLowerCase())) {
return [];
}
const value = field.value.trim();
return value ? [`${field.title.trim()}${value}`] : [];
});
return structuredLines.join('\n');
}
function resolveAudioRedrawPrompt(
sourceLayer: CanvasLayer,
mode: Extract<
GenerateDialogState['mode'],
'audio-sound-effect' | 'audio-background-music'
>,
) {
return (
sourceLayer.prompt?.trim() ||
findGenerationInputFieldValue(sourceLayer, [
'prompt',
'sound',
'gpt_description_prompt',
'生成提示词',
'音效提示词',
'背景音乐提示词',
])?.trim() ||
sourceLayer.generationInputs?.fields[0]?.value?.trim() ||
resolveDefaultAudioPrompt(mode)
);
return resolveUserGenerationPromptSnapshot(sourceLayer);
}
function resolveAudioRedrawMode(sourceLayer: CanvasLayer) {
@@ -493,7 +518,7 @@ export function createAudioRedrawGenerationDialogDraft(
const baseDraft = {
mode,
sourceLayerId: sourceLayer.id,
prompt: resolveAudioRedrawPrompt(sourceLayer, mode),
prompt: resolveAudioRedrawPrompt(sourceLayer),
status: 'idle' as const,
composerOpen: true,
placeholder: {
@@ -562,9 +587,7 @@ export function createEditDialogDraft(
): GenerateDialogState {
return {
mode: 'edit',
prompt: sourceLayer.prompt
? `${sourceLayer.prompt},在保持主体结构的基础上优化画面细节`
: '',
prompt: resolveUserGenerationPromptSnapshot(sourceLayer),
status: 'idle',
composerOpen: true,
sourceLayerId: sourceLayer.id,
@@ -646,10 +669,7 @@ export function createRedrawPanelDraft(
return {
...createQuickEditPanelDraft(sourceLayer),
mode: 'redraw',
prompt:
sourceLayer.prompt?.trim() ||
sourceLayer.generationInputs?.fields[0]?.value?.trim() ||
'',
prompt: resolveUserGenerationPromptSnapshot(sourceLayer),
};
}
@@ -168,6 +168,7 @@ function GenerationWorkflowHarness({
? `${activeDialog.mode}:${activeDialog.status}:${activeDialog.composerOpen !== false ? 'open' : 'closed'}:${activeDialog.generatedLayerId ?? '-'}:${activeDialog.placeholder ? 'placeholder' : '-'}`
: '-'}
</span>
<span data-testid="dialog-prompt">{activeDialog?.prompt || '-'}</span>
<span data-testid="generation-dialogs">
{dialogs.canvasGenerationDialogs
.map(
@@ -256,7 +257,7 @@ function GenerationWorkflowHarness({
height: 120,
originalWidth: 420,
originalHeight: 120,
prompt: '金币跳出音',
prompt: '【系统内置】完整音效提示词:金币跳出音,内部混音参数。',
generationInputs: {
fields: [
{ title: 'prompt', value: '金币跳出音' },
@@ -296,6 +297,23 @@ function GenerationWorkflowHarness({
>
</button>
<button
type="button"
onClick={() =>
workflow.openRedrawPanel(
createLayer({
id: 'layer-internal-prompt',
prompt: '【系统内置】完整拼接提示词,带默认画质和模型约束。',
generationInputs: {
fields: [{ title: '视频描述', value: '用户的视频描述' }],
references: [],
},
}),
)
}
>
</button>
<button
type="button"
onClick={() => workflow.openCropExpandPanel(layers[0]!)}
@@ -678,11 +696,22 @@ describe('useImageCanvasGenerationWorkflow', () => {
expect(screen.getByTestId('dialog').textContent).toBe(
'audio-sound-effect:idle:open:-:placeholder',
);
expect(screen.getByTestId('dialog-prompt').textContent).toBe('金币跳出音');
expect(screen.getByTestId('placeholder').textContent).toBe(
'572:140:420:120',
);
});
it('opens generated layer remodel with user input snapshot instead of internal prompt', () => {
render(<GenerationWorkflowHarness />);
fireEvent.click(screen.getByRole('button', { name: '打开用户快照改造' }));
expect(screen.getByTestId('quick-edit').textContent).toBe(
'layer-internal-prompt:idle:用户的视频描述',
);
});
it('keeps the active canvas generator when opening quick edit for another layer', () => {
render(<GenerationWorkflowHarness />);