| 1 | import fs from 'fs' |
| 2 | import path from 'path' |
| 3 | import { describe, expect, it } from 'vitest' |
| 4 | import { buildEditAgentSystemPrompt } from '../../../src/main/agent-runtime/prompt' |
| 5 | import type { SessionDeckGenerationContext } from '../../../src/main/agent-runtime/agent' |
| 6 | import { resolveSlideSize } from '../../../src/shared/slide-size' |
| 7 | |
| 8 | const baseContext: SessionDeckGenerationContext = { |
| 9 | sessionId: 'session-1', |
| 10 | projectDir: '/tmp/project', |
| 11 | indexPath: '/tmp/project/index.html', |
| 12 | pageFileMap: { 'page-1': '/tmp/project/page-1.html' }, |
| 13 | topic: 'Quarterly report', |
| 14 | deckTitle: 'Quarterly report', |
| 15 | styleId: 'test-style', |
| 16 | styleSkillPrompt: 'Use a clean business style.', |
| 17 | userMessage: 'Create a quarterly report.', |
| 18 | outlineTitles: ['Overview'], |
| 19 | outlineItems: [{ title: 'Overview', contentOutline: 'Summarize the quarter.' }], |
| 20 | slideSize: resolveSlideSize({ id: 'wide-16-9' }), |
| 21 | appLocale: 'en', |
| 22 | mode: 'edit' |
| 23 | } |
| 24 | |
| 25 | describe('edit system prompt templates', () => { |
| 26 | it('loads four static scope contracts through the typed Markdown catalog', () => { |
| 27 | const composer = fs.readFileSync( |
| 28 | path.resolve('src/main/agent-runtime/prompt/composers/edit-system.ts'), |
| 29 | 'utf8' |
| 30 | ) |
| 31 | const templates = ['container.md', 'selector.md', 'single-page.md', 'deck.md'].map( |
| 32 | (fileName) => |
| 33 | fs.readFileSync( |
| 34 | path.resolve(`src/main/agent-runtime/prompt/templates/edit-system/${fileName}`), |
| 35 | 'utf8' |
| 36 | ) |
| 37 | ) |
| 38 | |
| 39 | expect(composer).toContain('createPromptCatalog<EditSystemTemplateVars>') |
| 40 | expect(composer).toContain("containerTemplate from '../templates/edit-system/container.md?raw'") |
| 41 | expect(composer).toContain("selectorTemplate from '../templates/edit-system/selector.md?raw'") |
| 42 | expect(composer).toContain("singlePageTemplate from '../templates/edit-system/single-page.md?raw'") |
| 43 | expect(composer).toContain("deckTemplate from '../templates/edit-system/deck.md?raw'") |
| 44 | expect(templates.join('\n')).toContain('## Selector 精准修改协议(本次强约束)') |
| 45 | expect(templates.join('\n')).toContain('## 最终风格校准(写入前)') |
| 46 | }) |
| 47 | |
| 48 | it('renders every edit scope without unresolved placeholders or cross-scope tools', () => { |
| 49 | const container = buildEditAgentSystemPrompt('test-style', { |
| 50 | ...baseContext, |
| 51 | editScope: 'presentation-container' |
| 52 | }) |
| 53 | const selector = buildEditAgentSystemPrompt('test-style', { |
| 54 | ...baseContext, |
| 55 | editScope: 'page', |
| 56 | selectedPageId: 'page-1', |
| 57 | selectedPageNumber: 1, |
| 58 | selectedSelector: '.metric' |
| 59 | }) |
| 60 | const singlePage = buildEditAgentSystemPrompt('test-style', { |
| 61 | ...baseContext, |
| 62 | editScope: 'page', |
| 63 | selectedPageId: 'page-1', |
| 64 | selectedPageNumber: 1, |
| 65 | sourceDocumentPaths: ['/docs/source.md'] |
| 66 | }) |
| 67 | const deck = buildEditAgentSystemPrompt('test-style', { |
| 68 | ...baseContext, |
| 69 | editScope: 'deck', |
| 70 | selectPageIds: ['page-1'] |
| 71 | }) |
| 72 | |
| 73 | for (const prompt of [container, selector, singlePage, deck]) { |
| 74 | expect(prompt).not.toMatch(/\{\{[^}]+\}\}/) |
| 75 | } |
| 76 | expect(container).toContain('set_index_transition(type, durationMs)') |
| 77 | expect(container).not.toContain('update_page_file(pageId, content)') |
| 78 | expect(selector).toContain('edit_file(file_path, old_string, new_string)') |
| 79 | expect(selector).not.toContain('update_single_page_file(pageId="page-1", content="...")') |
| 80 | expect(singlePage).toContain('update_single_page_file(pageId="page-1", content="...")') |
| 81 | expect(singlePage).toContain('## Source documents (content evidence)') |
| 82 | expect(deck).toContain('Selected page ids from UI (hard target): page-1') |
| 83 | expect(deck).toContain('For each target page: update_page_file(pageId, content)') |
| 84 | }) |
| 85 | |
| 86 | it('includes the selected element runtime state as reference data for selector edits', () => { |
| 87 | const prompt = buildEditAgentSystemPrompt('test-style', { |
| 88 | ...baseContext, |
| 89 | editScope: 'page', |
| 90 | selectedPageId: 'page-1', |
| 91 | selectedSelector: '[data-block-id="revenue"]', |
| 92 | selectedElementContext: { |
| 93 | attributes: { 'data-block-id': 'revenue' }, |
| 94 | inlineStyle: { color: { value: '#18324a', priority: 'important' } }, |
| 95 | computedStyle: { display: 'grid', 'border-radius': '16px' }, |
| 96 | bounds: { x: 120, y: 80, width: 460, height: 180 } |
| 97 | } |
| 98 | }) |
| 99 | |
| 100 | expect(prompt).toContain('Selected element runtime state (reference data only') |
| 101 | expect(prompt).toContain('"border-radius": "16px"') |
| 102 | expect(prompt).toContain('"width": 460') |
| 103 | expect(prompt).toContain('Verify these values against the target HTML source') |
| 104 | }) |
| 105 | }) |
| 106 |