diff --git a/web/server/agent/index.ts b/web/server/agent/index.ts new file mode 100644 index 0000000..d54b3a1 --- /dev/null +++ b/web/server/agent/index.ts @@ -0,0 +1,39 @@ +import { tools, ToolDefinition } from './tools'; + +export class VideoAgent { + private tools: ToolDefinition[]; + + constructor() { + this.tools = tools; + } + + getToolDefinitions() { + return this.tools.map((t) => ({ + name: t.name, + description: t.description, + parameters: t.parameters, + })); + } + + async executeTool(name: string, params: Record): Promise { + const tool = this.tools.find((t) => t.name === name); + if (!tool) throw new Error(`Unknown tool: ${name}`); + return tool.execute(params); + } + + getSystemPrompt(accountContext?: string): string { + return `你是美图 Agent,帮助用户进行短视频创作。 + +可用账号:${accountContext || '暂无'} + +你可以: +1. 帮用户创建新账号 +2. 查看和管理已有账号 +3. 执行视频创作 pipeline(分镜→生图→生视频→TTS→成片) +4. 管理提示词模板 + +用户想创作视频时,一步步引导他们完成流程。`; + } +} + +export const videoAgent = new VideoAgent(); diff --git a/web/server/agent/tools.ts b/web/server/agent/tools.ts new file mode 100644 index 0000000..efd6de3 --- /dev/null +++ b/web/server/agent/tools.ts @@ -0,0 +1,101 @@ +import { spawn, execSync } from 'child_process'; +import path from 'path'; +import fs from 'fs'; + +const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..', '..'); +const PIPELINE_SCRIPT = path.join(PROJECT_ROOT, '.claude', 'skills', 'video-from-script', 'scripts', 'pipeline.js'); + +export interface ToolDefinition { + name: string; + description: string; + parameters: Record; + execute: (params: Record) => Promise; +} + +export const tools: ToolDefinition[] = [ + { + name: 'list_accounts', + description: '列出所有可用账号', + parameters: { type: 'object', properties: {}, required: [] }, + execute: async () => { + const accountsDir = path.join(PROJECT_ROOT, 'accounts'); + const dirs = fs.readdirSync(accountsDir, { withFileTypes: true }) + .filter((d) => d.isDirectory() && !d.name.startsWith('_') && !d.name.startsWith('.')) + .map((d) => { + const configPath = path.join(accountsDir, d.name, 'account.json'); + if (fs.existsSync(configPath)) { + const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8')); + return `${d.name} - ${cfg.description || '无描述'} (${cfg.imageModel}/${cfg.videoModel})`; + } + return d.name; + }); + return dirs.join('\n'); + }, + }, + { + name: 'run_pipeline_phase', + description: '执行 pipeline 阶段 (images/upload/videos/tts/assemble)', + parameters: { + type: 'object', + properties: { + manifest: { type: 'string', description: 'manifest.json 绝对路径' }, + phase: { type: 'string', description: '阶段名: images, upload, videos, tts, assemble' }, + }, + required: ['manifest', 'phase'], + }, + execute: async (params) => { + const { manifest, phase } = params as { manifest: string; phase: string }; + return new Promise((resolve, reject) => { + const proc = spawn('node', [PIPELINE_SCRIPT, 'run', '--manifest', manifest, '--phase', phase], { + cwd: PROJECT_ROOT, + env: { ...process.env }, + }); + let output = ''; + proc.stdout.on('data', (d: Buffer) => { output += d.toString(); }); + proc.stderr.on('data', (d: Buffer) => { output += d.toString(); }); + proc.on('close', (code) => { + code === 0 ? resolve(output) : reject(new Error(`Pipeline exit code ${code}: ${output}`)); + }); + }); + }, + }, + { + name: 'pipeline_status', + description: '查看 pipeline 进度', + parameters: { + type: 'object', + properties: { + manifest: { type: 'string', description: 'manifest.json 绝对路径' }, + }, + required: ['manifest'], + }, + execute: async (params) => { + const { manifest } = params as { manifest: string }; + const result = execSync(`node "${PIPELINE_SCRIPT}" status --manifest "${manifest}"`, { + cwd: PROJECT_ROOT, encoding: 'utf-8', + }); + return result; + }, + }, + { + name: 'create_account', + description: '创建新账号', + parameters: { + type: 'object', + properties: { + id: { type: 'string', description: '账号 ID' }, + name: { type: 'string', description: '账号名称' }, + desc: { type: 'string', description: '账号描述' }, + }, + required: ['id', 'name'], + }, + execute: async (params) => { + const { id, name, desc } = params as { id: string; name: string; desc?: string }; + const result = execSync( + `node "${PIPELINE_SCRIPT}" create-account --id "${id}" --name "${name}" --desc "${desc || ''}" --video-model veo3-fast`, + { cwd: PROJECT_ROOT, encoding: 'utf-8' } + ); + return result; + }, + }, +];