mirror of
https://github.com/foxhui/WebAI2API.git
synced 2026-06-16 21:03:59 +08:00
feat: 部分适配器支持视频生成
This commit is contained in:
@@ -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' }
|
||||
],
|
||||
|
||||
// 无需导航处理器
|
||||
|
||||
@@ -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' },
|
||||
],
|
||||
|
||||
// 导航处理器
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -179,7 +179,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
|
||||
*/
|
||||
export const manifest = {
|
||||
id: 'lmarena',
|
||||
displayName: 'LMArena',
|
||||
displayName: 'LMArena (图片生成)',
|
||||
|
||||
// 配置项模式
|
||||
configSchema: [
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -131,11 +131,16 @@ export function createQueueManager(queueConfig, callbacks) {
|
||||
// 生成成功
|
||||
let finalContent = '';
|
||||
if (result.image) {
|
||||
finalContent = ``;
|
||||
logger.info('服务器', '图片已准备就绪 (Base64)', { id });
|
||||
// 只有图片格式才使用 markdown,视频等其他格式直接返回 data URI
|
||||
if (result.image.startsWith('data:image/')) {
|
||||
finalContent = ``;
|
||||
} else {
|
||||
finalContent = result.image;
|
||||
}
|
||||
} else {
|
||||
finalContent = result.text || '生成失败';
|
||||
}
|
||||
logger.info('服务器', '结果已准备就绪', { id });
|
||||
|
||||
// 发送成功响应
|
||||
logger.info('服务器', '准备发送响应...', { id, isStreaming, contentLength: finalContent.length });
|
||||
|
||||
Reference in New Issue
Block a user