返回 oh-my-ppt
generator.ts
根目录 / src / main / styles / preview / generator.ts
1 import fs from 'fs'
2 import os from 'os'
3 import path from 'path'
4 import { FilesystemBackend, createDeepAgent } from 'deepagents'
5 import { resolveModelTimeoutMs } from '@shared/model-timeout'
6 import { resolveModel } from '../../agent-runtime/model'
7 import type { ModelRuntimeConfig } from '../../agent-runtime/model'
8 import { readStylePackage } from '../style-package'
9 import { buildStylePreviewPrompt } from '../../agent-runtime/prompt'
10
11 const MAX_PREVIEW_HTML_BYTES = 1024 * 1024
12
13 type StylePreviewAgentArgs = {
14 provider: string
15 apiKey: string
16 model: string
17 baseUrl: string
18 maxTokens?: number
19 modelRuntime?: ModelRuntimeConfig
20 modelTimeoutMs: number
21 workspaceDir: string
22 prompt: string
23 }
24
25 type GenerateStylePreviewArgs = Omit<StylePreviewAgentArgs, 'workspaceDir' | 'prompt'> & {
26 styleId: string
27 stylePackageDir: string
28 }
29
30 type StylePreviewGeneratorDependencies = {
31 runAgent?: (args: StylePreviewAgentArgs) => Promise<void>
32 }
33
34 async function runStylePreviewAgent(args: StylePreviewAgentArgs): Promise<void> {
35 const model = resolveModel(
36 args.provider,
37 args.apiKey,
38 args.model,
39 args.baseUrl,
40 0.65,
41 args.maxTokens,
42 args.modelRuntime
43 )
44 const agent = createDeepAgent({
45 model,
46 backend: new FilesystemBackend({
47 rootDir: args.workspaceDir,
48 virtualMode: true
49 }),
50 systemPrompt:
51 'You are a presentation style preview designer. Read the provided style package and use write_file to create the requested standalone preview.html. The file, not the chat response, is the deliverable.'
52 })
53
54 const stream = await agent.stream(
55 {
56 messages: [{ role: 'user', content: args.prompt }]
57 },
58 {
59 streamMode: ['updates', 'messages'],
60 subgraphs: true,
61 signal: AbortSignal.timeout(resolveModelTimeoutMs(args.modelTimeoutMs, 'document'))
62 }
63 )
64
65 for await (const _chunk of stream as AsyncIterable<unknown>) {
66 // Consuming the stream drives the agent until preview.html has been written.
67 }
68 }
69
70 export async function generateStylePreviewHtml(
71 args: GenerateStylePreviewArgs,
72 dependencies: StylePreviewGeneratorDependencies = {}
73 ): Promise<string> {
74 const stylePackageDir = path.resolve(args.stylePackageDir)
75 const existingPackage = await readStylePackage(stylePackageDir)
76 if (existingPackage.previewPath) {
77 throw new Error('该风格已有预览,无需重复生成。')
78 }
79
80 const tempRoot = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'ohmyppt-style-preview-'))
81 const workspaceDir = path.join(tempRoot, args.styleId)
82
83 try {
84 await fs.promises.mkdir(workspaceDir, { recursive: true })
85 await Promise.all([
86 fs.promises.copyFile(
87 path.join(stylePackageDir, 'style.json'),
88 path.join(workspaceDir, 'style.json')
89 ),
90 fs.promises.copyFile(
91 path.join(stylePackageDir, 'SKILL.md'),
92 path.join(workspaceDir, 'SKILL.md')
93 )
94 ])
95
96 const prompt = buildStylePreviewPrompt()
97 await (dependencies.runAgent || runStylePreviewAgent)({
98 provider: args.provider,
99 apiKey: args.apiKey,
100 model: args.model,
101 baseUrl: args.baseUrl,
102 maxTokens: args.maxTokens,
103 modelRuntime: args.modelRuntime,
104 modelTimeoutMs: args.modelTimeoutMs,
105 workspaceDir,
106 prompt
107 })
108
109 const generatedPackage = await readStylePackage(workspaceDir)
110 if (!generatedPackage.previewPath) {
111 throw new Error('预览生成失败:模型未创建 preview.html。')
112 }
113 const previewHtml = await fs.promises.readFile(generatedPackage.previewPath, 'utf8')
114 if (Buffer.byteLength(previewHtml, 'utf8') > MAX_PREVIEW_HTML_BYTES) {
115 throw new Error('预览生成失败:preview.html 超过 1MB。')
116 }
117 return previewHtml
118 } finally {
119 await fs.promises.rm(tempRoot, { recursive: true, force: true }).catch(() => undefined)
120 }
121 }
122
122 lines TYPESCRIPT