| 1 | import { describe, expect, it } from 'vitest' |
| 2 | import fs from 'fs' |
| 3 | import path from 'path' |
| 4 | import { |
| 5 | buildDeckAgentSystemPrompt, |
| 6 | buildEditAgentSystemPrompt |
| 7 | } from '../../../src/main/agent-runtime/prompt' |
| 8 | import { resolveSlideSize } from '../../../src/shared/slide-size' |
| 9 | |
| 10 | const readSource = (relativePath: string): string => |
| 11 | fs.readFileSync(path.join(process.cwd(), relativePath), 'utf-8') |
| 12 | |
| 13 | describe('style fidelity prompt placement', () => { |
| 14 | it('keeps style fidelity as a shared system-level rule', () => { |
| 15 | const shared = readSource('src/main/agent-runtime/prompt/composers/shared.ts') |
| 16 | |
| 17 | expect(shared).toContain('export const STYLE_FIDELITY_RULES') |
| 18 | expect(shared).toContain('尺寸布局与风格合成闸门') |
| 19 | expect(shared).toContain('当前画布尺寸与已注入的 layout skill/catalog 是页面结构的唯一来源') |
| 20 | expect(shared).toContain('视觉语言的唯一来源') |
| 21 | expect(shared).toContain('先依据 layout skill/catalog 选择适合当前尺寸的页面结构') |
| 22 | expect(shared).toContain('不能直接作为页面骨架') |
| 23 | expect(shared).toContain('size-aware layoutMotif') |
| 24 | expect(shared).toContain('单页生成也必须像整套 deck 一样遵守当前 style') |
| 25 | expect(shared).toContain('Size-adapted composition motif') |
| 26 | }) |
| 27 | |
| 28 | it('prescribes content-overload priority in shared content rules', () => { |
| 29 | const shared = readSource('src/main/agent-runtime/prompt/composers/shared.ts') |
| 30 | |
| 31 | // When content oversupply exceeds a canvas's capacity, the model must |
| 32 | // compress/merge/drop first; it must NOT resolve overload by shrinking |
| 33 | // fonts below floors or by overflowing the canvas. |
| 34 | expect(shared).toContain('内容超载时按这个优先级解决') |
| 35 | expect(shared).toContain('绝不靠缩字号到下限以下') |
| 36 | expect(shared).toContain('竖版/小红书/方图本来就是低密度载体') |
| 37 | }) |
| 38 | |
| 39 | it('moves the deck-generation style preset to the end of the system prompt', () => { |
| 40 | const deckSystem = readSource('src/main/agent-runtime/prompt/composers/deck-system.ts') |
| 41 | const prompt = buildDeckAgentSystemPrompt('test-style', { |
| 42 | sessionId: 'session-1', |
| 43 | projectDir: '/tmp/project', |
| 44 | indexPath: '/tmp/project/index.html', |
| 45 | pageFileMap: { 'page-1': '/tmp/project/page-1.html' }, |
| 46 | topic: 'Quarterly report', |
| 47 | deckTitle: 'Quarterly report', |
| 48 | styleId: 'test-style', |
| 49 | styleSkillPrompt: 'Use a clean business style.', |
| 50 | userMessage: 'Create a quarterly report.', |
| 51 | outlineTitles: ['Overview'], |
| 52 | outlineItems: [{ title: 'Overview', contentOutline: 'Summarize the quarter.' }], |
| 53 | slideSize: resolveSlideSize({ id: 'wide-16-9' }), |
| 54 | appLocale: 'en' |
| 55 | }) |
| 56 | |
| 57 | const currentTaskIndex = prompt.indexOf('## Current Task') |
| 58 | const finalStyleIndex = prompt.indexOf('## 最终风格校准(写入前)') |
| 59 | const finalReminderIndex = prompt.indexOf('⛔ FINAL REMINDER') |
| 60 | |
| 61 | expect(deckSystem.slice(0, deckSystem.indexOf("} from './shared'"))).toContain( |
| 62 | 'STYLE_FIDELITY_RULES' |
| 63 | ) |
| 64 | expect(finalStyleIndex).toBeGreaterThan(currentTaskIndex) |
| 65 | expect(finalStyleIndex).toBeLessThan(finalReminderIndex) |
| 66 | expect(prompt.slice(currentTaskIndex)).toContain('风格预设:test-style (test-style)') |
| 67 | expect(prompt.slice(currentTaskIndex)).toContain('尺寸布局与风格合成闸门') |
| 68 | }) |
| 69 | |
| 70 | it('does not duplicate style fidelity into the single-page user prompt', () => { |
| 71 | const generationUser = readSource('src/main/agent-runtime/prompt/composers/generation-user.ts') |
| 72 | |
| 73 | expect(generationUser).not.toContain('STYLE_FIDELITY_RULES') |
| 74 | expect(generationUser).not.toContain('风格预设:') |
| 75 | expect(generationUser).not.toContain('## 最终风格校准(写入前)') |
| 76 | }) |
| 77 | |
| 78 | it('applies the final style gate only to rewrite-capable edit system prompts', () => { |
| 79 | const baseContext = { |
| 80 | sessionId: 'session-1', |
| 81 | projectDir: '/tmp/project', |
| 82 | indexPath: '/tmp/project/index.html', |
| 83 | pageFileMap: { 'page-1': '/tmp/project/page-1.html' }, |
| 84 | topic: 'Quarterly report', |
| 85 | deckTitle: 'Quarterly report', |
| 86 | styleId: 'test-style', |
| 87 | styleSkillPrompt: 'Use a clean business style.', |
| 88 | userMessage: 'Create a quarterly report.', |
| 89 | outlineTitles: ['Overview'], |
| 90 | outlineItems: [{ title: 'Overview', contentOutline: 'Summarize the quarter.' }], |
| 91 | slideSize: resolveSlideSize({ id: 'wide-16-9' }), |
| 92 | appLocale: 'en' as const, |
| 93 | mode: 'edit' as const, |
| 94 | selectedPageId: 'page-1', |
| 95 | selectedPageNumber: 1 |
| 96 | } |
| 97 | const singlePageEdit = buildEditAgentSystemPrompt('test-style', { |
| 98 | ...baseContext, |
| 99 | editScope: 'page' |
| 100 | }) |
| 101 | const deckEdit = buildEditAgentSystemPrompt('test-style', { |
| 102 | ...baseContext, |
| 103 | editScope: 'deck' |
| 104 | }) |
| 105 | const selectorEdit = buildEditAgentSystemPrompt('test-style', { |
| 106 | ...baseContext, |
| 107 | editScope: 'page', |
| 108 | selectedSelector: '.metric' |
| 109 | }) |
| 110 | const containerEdit = buildEditAgentSystemPrompt('test-style', { |
| 111 | ...baseContext, |
| 112 | editScope: 'presentation-container' |
| 113 | }) |
| 114 | |
| 115 | for (const prompt of [singlePageEdit, deckEdit]) { |
| 116 | expect(prompt).toContain('## 最终风格校准(写入前)') |
| 117 | expect(prompt.indexOf('## 最终风格校准(写入前)')).toBeGreaterThan( |
| 118 | prompt.indexOf('## Current Task') |
| 119 | ) |
| 120 | expect(prompt).toContain('尺寸布局与风格合成闸门') |
| 121 | } |
| 122 | |
| 123 | expect(selectorEdit).not.toContain('尺寸布局与风格合成闸门') |
| 124 | expect(containerEdit).not.toContain('尺寸布局与风格合成闸门') |
| 125 | }) |
| 126 | }) |
| 127 |