2026-05-07 03:22:15 +08:00
|
|
|
|
import Anthropic from '@anthropic-ai/sdk';
|
2026-05-07 23:48:26 +08:00
|
|
|
|
import OpenAI from 'openai';
|
2026-05-07 02:36:28 +08:00
|
|
|
|
import { tools, ToolDefinition } from './tools';
|
2026-05-07 03:22:15 +08:00
|
|
|
|
import { getDb } from '../db';
|
|
|
|
|
|
import fs from 'fs';
|
|
|
|
|
|
import path from 'path';
|
|
|
|
|
|
import { fileURLToPath } from 'url';
|
|
|
|
|
|
|
|
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
|
|
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
|
|
const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
|
|
|
|
|
|
|
2026-05-07 23:48:26 +08:00
|
|
|
|
export type Protocol = 'anthropic' | 'openai';
|
|
|
|
|
|
|
|
|
|
|
|
interface ApiConfig {
|
|
|
|
|
|
protocol: Protocol;
|
|
|
|
|
|
apiKey: string;
|
|
|
|
|
|
baseURL: string | undefined;
|
|
|
|
|
|
model: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getApiConfig(): ApiConfig {
|
2026-05-07 03:22:15 +08:00
|
|
|
|
const configRow = getDb().prepare('SELECT value FROM configs WHERE key = ?').get('api_keys') as { value: string } | undefined;
|
2026-05-07 23:48:26 +08:00
|
|
|
|
|
2026-05-07 03:22:15 +08:00
|
|
|
|
let apiKey = process.env.ANTHROPIC_API_KEY || '';
|
|
|
|
|
|
let baseURL: string | undefined;
|
2026-05-07 23:48:26 +08:00
|
|
|
|
let model = process.env.ANTHROPIC_MODEL || 'claude-sonnet-4-6';
|
|
|
|
|
|
let protocol: Protocol = 'anthropic';
|
2026-05-07 03:22:15 +08:00
|
|
|
|
|
|
|
|
|
|
if (configRow) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const cfg = JSON.parse(configRow.value);
|
|
|
|
|
|
if (cfg.ANTHROPIC_AUTH_TOKEN) apiKey = cfg.ANTHROPIC_AUTH_TOKEN;
|
|
|
|
|
|
if (cfg.ANTHROPIC_BASE_URL) baseURL = cfg.ANTHROPIC_BASE_URL;
|
2026-05-07 23:48:26 +08:00
|
|
|
|
if (cfg.ANTHROPIC_MODEL) model = cfg.ANTHROPIC_MODEL;
|
|
|
|
|
|
if (cfg.PROTOCOL === 'openai') protocol = 'openai';
|
2026-05-07 03:22:15 +08:00
|
|
|
|
} catch {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 23:48:26 +08:00
|
|
|
|
return { protocol, apiKey, baseURL, model };
|
2026-05-07 03:22:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 23:48:26 +08:00
|
|
|
|
function getAnthropicClient(): Anthropic {
|
|
|
|
|
|
const { apiKey, baseURL } = getApiConfig();
|
|
|
|
|
|
return new Anthropic({ apiKey, baseURL });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getOpenAIClient(): OpenAI {
|
|
|
|
|
|
const { apiKey, baseURL } = getApiConfig();
|
|
|
|
|
|
return new OpenAI({ apiKey, baseURL: baseURL || 'https://api.openai.com/v1' });
|
2026-05-07 03:22:15 +08:00
|
|
|
|
}
|
2026-05-07 02:36:28 +08:00
|
|
|
|
|
|
|
|
|
|
export class VideoAgent {
|
|
|
|
|
|
private tools: ToolDefinition[];
|
|
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
|
this.tools = tools;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 23:48:26 +08:00
|
|
|
|
getProtocol(): Protocol {
|
|
|
|
|
|
return getApiConfig().protocol;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getModel(): string {
|
|
|
|
|
|
return getApiConfig().model;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getAnthropicClient(): Anthropic {
|
|
|
|
|
|
return getAnthropicClient();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getOpenAIClient(): OpenAI {
|
|
|
|
|
|
return getOpenAIClient();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 03:22:15 +08:00
|
|
|
|
getAnthropicTools(): Anthropic.Tool[] {
|
2026-05-07 02:36:28 +08:00
|
|
|
|
return this.tools.map((t) => ({
|
|
|
|
|
|
name: t.name,
|
|
|
|
|
|
description: t.description,
|
2026-05-07 03:22:15 +08:00
|
|
|
|
input_schema: t.input_schema,
|
2026-05-07 02:36:28 +08:00
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 23:48:26 +08:00
|
|
|
|
getOpenAITools(): OpenAI.ChatCompletionTool[] {
|
|
|
|
|
|
return this.tools.map((t) => ({
|
|
|
|
|
|
type: 'function' as const,
|
|
|
|
|
|
function: {
|
|
|
|
|
|
name: t.name,
|
|
|
|
|
|
description: t.description,
|
|
|
|
|
|
parameters: t.input_schema,
|
|
|
|
|
|
},
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 02:36:28 +08:00
|
|
|
|
async executeTool(name: string, params: Record<string, unknown>): Promise<string> {
|
|
|
|
|
|
const tool = this.tools.find((t) => t.name === name);
|
|
|
|
|
|
if (!tool) throw new Error(`Unknown tool: ${name}`);
|
|
|
|
|
|
return tool.execute(params);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 03:22:15 +08:00
|
|
|
|
getSystemPrompt(): string {
|
|
|
|
|
|
const accountsDir = path.join(PROJECT_ROOT, 'accounts');
|
|
|
|
|
|
let accountList = '暂无账号';
|
|
|
|
|
|
if (fs.existsSync(accountsDir)) {
|
|
|
|
|
|
const dirs = fs.readdirSync(accountsDir, { withFileTypes: true })
|
|
|
|
|
|
.filter((d) => d.isDirectory() && !d.name.startsWith('_') && !d.name.startsWith('.'));
|
|
|
|
|
|
if (dirs.length > 0) {
|
|
|
|
|
|
accountList = dirs.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}, 画幅:${cfg.defaultFormat})`;
|
|
|
|
|
|
}
|
|
|
|
|
|
return `- ${d.name}`;
|
|
|
|
|
|
}).join('\n');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return `你是美图 Agent,一个专业的短视频创作助手。你可以帮助用户完成从创意到成片的完整流程。
|
|
|
|
|
|
|
|
|
|
|
|
## 当前可用账号
|
|
|
|
|
|
${accountList}
|
|
|
|
|
|
|
|
|
|
|
|
## 你的能力
|
|
|
|
|
|
1. **查看账号** - 使用 list_accounts 列出所有可用账号及其配置
|
|
|
|
|
|
2. **创建账号** - 使用 create_account 创建新的短视频账号,配置生图/视频模型、画幅等
|
|
|
|
|
|
3. **查看账号配置** - 使用 get_account_config 获取账号详细配置
|
|
|
|
|
|
4. **查看 Pipeline 进度** - 使用 pipeline_status 检查创作进度
|
|
|
|
|
|
5. **执行创作阶段** - 使用 run_pipeline_phase 执行 pipeline 阶段
|
2026-05-07 02:36:28 +08:00
|
|
|
|
|
2026-05-07 03:22:15 +08:00
|
|
|
|
## 视频创作流程
|
|
|
|
|
|
1. 确认用户意图(A.幻灯片视频 / B.AI视频)
|
|
|
|
|
|
2. 选择/创建账号
|
|
|
|
|
|
3. 规划分镜脚本
|
|
|
|
|
|
4. 生成图片(images 阶段)
|
|
|
|
|
|
5. 生成视频片段(videos 阶段,仅 B 模式)
|
|
|
|
|
|
6. 配音(tts 阶段)
|
|
|
|
|
|
7. 成片组装(assemble 阶段)
|
2026-05-07 02:36:28 +08:00
|
|
|
|
|
2026-05-07 03:22:15 +08:00
|
|
|
|
## 行为准则
|
|
|
|
|
|
- 用中文回复,友好、专业
|
|
|
|
|
|
- 在用户不清楚时主动询问:成片类型、账号选择、素材来源、画幅等
|
|
|
|
|
|
- 执行 pipeline 前确认 manifest 路径
|
|
|
|
|
|
- 如果用户只是闲聊,就闲聊。如果用户想做视频,引导完成流程
|
|
|
|
|
|
- 不要编造账号或文件路径,使用工具获取真实信息`;
|
|
|
|
|
|
}
|
2026-05-07 02:36:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const videoAgent = new VideoAgent();
|