feat: 部分适配器支持视频生成

This commit is contained in:
foxhui
2025-12-22 01:53:30 +08:00
Unverified
parent 6f5b0a760e
commit fdd11f9c70
29 changed files with 306 additions and 188 deletions
+80 -31
View File
@@ -71,16 +71,33 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
await fillPrompt(page, inputLocator, prompt, meta);
await sleep(500, 1000);
// 4. 点击 Tools 按钮启用图片生成
// 4. 点击 Tools 按钮启用图片/视频生成
logger.debug('适配器', '点击 Tools 按钮...', meta);
const toolsBtn = page.getByRole('button', { name: 'Tools' });
await safeClick(page, toolsBtn, { bias: 'button' });
await sleep(500, 1000);
// 5. 点击 Create images 按钮
logger.debug('适配器', '点击 Create images 按钮...', meta);
const createImagesBtn = page.getByRole('button', { name: 'Create images' });
await safeClick(page, createImagesBtn, { bias: 'button' });
// 检测是否是视频模型
const isVideoModel = modelId && modelId.startsWith('veo-');
// 5. 点击 Create images / Create videos 按钮
if (isVideoModel) {
logger.debug('适配器', '点击 Create videos 按钮...', meta);
const createVideosBtn = page.getByRole('button', { name: /^Create videos/ });
// 检查按钮是否存在(有些账号可能没有视频生成功能)
const btnCount = await createVideosBtn.count();
if (btnCount === 0) {
logger.error('适配器', '未找到 Create videos 按钮,该账号可能不支持视频生成', meta);
return { error: '该账号不支持视频生成功能 (未找到 Create videos 按钮)' };
}
await safeClick(page, createVideosBtn, { bias: 'button' });
} else {
logger.debug('适配器', '点击 Create images 按钮...', meta);
const createImagesBtn = page.getByRole('button', { name: 'Create images' });
await safeClick(page, createImagesBtn, { bias: 'button' });
}
await sleep(500, 1000);
// 6. 点击发送
@@ -111,33 +128,64 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
return { error: `API 返回错误: ${httpError.error}` };
}
logger.info('适配器', '生成请求成功,等待图片...', meta);
// 8. 等待图片/视频响应
if (isVideoModel) {
// 视频模式:等待视频下载链接
logger.info('适配器', '生成请求成功,等待视频...', meta);
// 8. 等待图片响应
let imageResponse;
try {
imageResponse = await waitApiResponse(page, {
urlMatch: 'googleusercontent.com/rd-gg-dl',
urlContains: '=s1024-rj',
method: 'GET',
timeout: 60000,
meta
});
} catch (e) {
const pageError = normalizePageError(e, meta);
if (pageError) return pageError;
throw e;
let videoResponse;
try {
videoResponse = await waitApiResponse(page, {
urlMatch: 'contribution.usercontent.google.com/download',
urlContains: 'filename=video.mp4',
method: 'GET',
timeout: 180000, // 视频生成可能更慢
meta
});
} catch (e) {
const pageError = normalizePageError(e, meta);
if (pageError) return pageError;
throw e;
}
// 获取视频数据
const buffer = await videoResponse.body();
const base64 = buffer.toString('base64');
const contentType = videoResponse.headers()['content-type'] || 'video/mp4';
const videoData = `data:${contentType};base64,${base64}`;
logger.info('适配器', '已获取视频,任务完成', meta);
return { image: videoData };
} else {
// 图片模式
logger.info('适配器', '生成请求成功,等待图片...', meta);
let imageResponse;
try {
imageResponse = await waitApiResponse(page, {
urlMatch: 'googleusercontent.com/rd-gg-dl',
urlContains: '=s1024-rj',
method: 'GET',
timeout: 60000,
meta
});
} catch (e) {
const pageError = normalizePageError(e, meta);
if (pageError) return pageError;
throw e;
}
// 获取图片数据
const buffer = await imageResponse.body();
const base64 = buffer.toString('base64');
const contentType = imageResponse.headers()['content-type'] || 'image/jpeg';
const imageData = `data:${contentType};base64,${base64}`;
logger.info('适配器', '已获取图片,任务完成', meta);
return { image: imageData };
}
// 获取图片数据
const buffer = await imageResponse.body();
const base64 = buffer.toString('base64');
const contentType = imageResponse.headers()['content-type'] || 'image/jpeg';
const imageData = `data:${contentType};base64,${base64}`;
logger.info('适配器', '已获取图片,任务完成', meta);
return { image: imageData };
} catch (err) {
// 顶层错误处理
const pageError = normalizePageError(err, meta);
@@ -156,7 +204,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
*/
export const manifest = {
id: 'gemini',
displayName: 'Gemini (Consumer)',
displayName: 'Google Gemini (图片、视频生成)',
// 入口 URL
getTargetUrl(config, workerConfig) {
@@ -165,7 +213,8 @@ export const manifest = {
// 模型列表
models: [
{ id: 'gemini-3-pro-image-preview', imagePolicy: 'optional' }
{ id: 'gemini-3-pro-image-preview', imagePolicy: 'optional' },
{ id: 'veo-3.1-generate-preview', imagePolicy: 'optional' }
],
// 无需导航处理器
+18 -6
View File
@@ -171,9 +171,16 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
logger.debug('适配器', '已拦截请求,正在修改...', meta);
if (!postData.streamAssistRequest) postData.streamAssistRequest = {};
if (!postData.streamAssistRequest.assistGenerationConfig) postData.streamAssistRequest.assistGenerationConfig = {};
postData.streamAssistRequest.toolsSpec = { imageGenerationSpec: {} };
logger.info('适配器', '已拦截请求,强制使用 Nano Banana Pro', meta);
// 根据模型 ID 选择 toolsSpec
if (modelId && modelId.startsWith('veo-')) {
postData.streamAssistRequest.toolsSpec = { videoGenerationSpec: {} };
logger.info('适配器', '已拦截请求,使用视频生成规格', meta);
} else {
postData.streamAssistRequest.toolsSpec = { imageGenerationSpec: {} };
logger.info('适配器', '已拦截请求,使用图片生成规格', meta);
}
await route.continue({ postData: JSON.stringify(postData) });
return;
}
@@ -239,8 +246,12 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const base64 = await imageResponse.text();
logger.info('适配器', '已下载图片,任务完成', meta);
const dataUri = `data:image/png;base64,${base64}`;
// 从响应头获取内容类型
const contentType = imageResponse.headers()['x-goog-safety-content-type'] || 'image/png';
logger.info('适配器', `已下载内容,类型: ${contentType}`, meta);
const dataUri = `data:${contentType};base64,${base64}`;
return { image: dataUri };
@@ -264,7 +275,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
*/
export const manifest = {
id: 'gemini_biz',
displayName: 'Gemini Business',
displayName: 'Gemini Business (图片、视频生成)',
// 配置表单定义
configSchema: [
@@ -284,7 +295,8 @@ export const manifest = {
// 模型列表
models: [
{ id: 'gemini-3-pro-image-preview', imagePolicy: 'optional' }
{ id: 'gemini-3-pro-image-preview', imagePolicy: 'optional' },
{ id: 'veo-3.1-generate-preview', imagePolicy: 'optional' },
],
// 导航处理器
+1 -1
View File
@@ -307,7 +307,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
*/
export const manifest = {
id: 'gemini_biz_text',
displayName: 'Gemini Business (Text)',
displayName: 'Gemini Business (文本生成)',
// 配置表单定义(与 gemini_biz 共享配置)
configSchema: [
+1 -1
View File
@@ -215,7 +215,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
*/
export const manifest = {
id: 'gemini_text',
displayName: 'Gemini Text (Consumer)',
displayName: 'Google Gemini (文本生成)',
getTargetUrl(config, workerConfig) {
return TARGET_URL;
+1 -1
View File
@@ -179,7 +179,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
*/
export const manifest = {
id: 'lmarena',
displayName: 'LMArena',
displayName: 'LMArena (图片生成)',
// 配置项模式
configSchema: [
+1 -1
View File
@@ -170,7 +170,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
*/
export const manifest = {
id: 'lmarena_text',
displayName: 'LMArena Text',
displayName: 'LMArena (文本生成)',
// 入口 URL
getTargetUrl(config, workerConfig) {
+1 -1
View File
@@ -135,7 +135,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
*/
export const manifest = {
id: 'nanobananafree_ai',
displayName: 'NanoBananaFree AI',
displayName: 'NanoBananaFree (图片生成)',
// 入口 URL
getTargetUrl(config, workerConfig) {
+1 -1
View File
@@ -196,7 +196,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
*/
export const manifest = {
id: 'turnstile_test',
displayName: 'Cloudflare Turnstile Test',
displayName: 'Cloudflare Turnstile Test (CF人机验证码测试)',
getTargetUrl(config, workerConfig) {
return TARGET_URL;
+1 -1
View File
@@ -348,7 +348,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
*/
export const manifest = {
id: 'zai_is',
displayName: 'zAI (zai.is)',
displayName: 'zAI (图片生成)',
// 入口 URL
getTargetUrl(config, workerConfig) {
+1 -1
View File
@@ -238,7 +238,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
*/
export const manifest = {
id: 'zenmux_ai',
displayName: 'Zenmux AI',
displayName: 'Zenmux AI (文本生成)',
// 无需额外配置
configSchema: [],
+7 -2
View File
@@ -131,11 +131,16 @@ export function createQueueManager(queueConfig, callbacks) {
// 生成成功
let finalContent = '';
if (result.image) {
finalContent = `![generated](${result.image})`;
logger.info('服务器', '图片已准备就绪 (Base64)', { id });
// 只有图片格式才使用 markdown,视频等其他格式直接返回 data URI
if (result.image.startsWith('data:image/')) {
finalContent = `![generated](${result.image})`;
} else {
finalContent = result.image;
}
} else {
finalContent = result.text || '生成失败';
}
logger.info('服务器', '结果已准备就绪', { id });
// 发送成功响应
logger.info('服务器', '准备发送响应...', { id, isStreaming, contentLength: finalContent.length });