feat: 支持自定义生成等待超时时间 (closes #21)

This commit is contained in:
foxhui
2026-03-05 03:43:41 +08:00
Unverified
parent d5e0d92598
commit a9240c2375
21 changed files with 103 additions and 52 deletions
+4 -3
View File
@@ -31,7 +31,8 @@ const INPUT_SELECTOR = '.ProseMirror';
* @returns {Promise<{image?: string, error?: string}>}
*/
async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const { page } = context;
const { page, config } = context;
const waitTimeout = config?.backend?.pool?.waitTimeout ?? 120000;
const sendBtnLocator = page.getByRole('button', { name: 'Send prompt' });
try {
@@ -94,7 +95,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
conversationResponse = await waitApiResponse(page, {
urlMatch: 'backend-api/f/conversation',
method: 'POST',
timeout: 120000, // 图片生成可能较慢
timeout: waitTimeout, // 图片生成可能较慢
meta
});
} catch (e) {
@@ -144,7 +145,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
} catch {
return false;
}
}, { timeout: 120000 });
}, { timeout: waitTimeout });
} catch (e) {
const pageError = normalizePageError(e, meta);
if (pageError) return pageError;
+3 -2
View File
@@ -80,7 +80,8 @@ async function selectModel(page, codeName, meta = {}) {
* @returns {Promise<{text?: string, error?: string}>}
*/
async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const { page } = context;
const { page, config } = context;
const waitTimeout = config?.backend?.pool?.waitTimeout ?? 120000;
const sendBtnLocator = page.getByRole('button', { name: 'Send prompt' });
try {
@@ -223,7 +224,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
} catch {
return false;
}
}, { timeout: 120000 });
}, { timeout: waitTimeout });
} catch (e) {
const pageError = normalizePageError(e, meta);
if (pageError) return pageError;
+3 -2
View File
@@ -82,7 +82,8 @@ async function configureModel(page, modelConfig, meta = {}) {
* @returns {Promise<{text?: string, error?: string}>}
*/
async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const { page } = context;
const { page, config } = context;
const waitTimeout = config?.backend?.pool?.waitTimeout ?? 120000;
try {
logger.info('适配器', '开启新会话...', meta);
@@ -208,7 +209,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
} catch {
return false;
}
}, { timeout: 120000 });
}, { timeout: waitTimeout });
// 5. 发送提示词
logger.debug('适配器', '发送提示词...', meta);
+4 -3
View File
@@ -32,7 +32,8 @@ const TARGET_URL = 'https://gemini.google.com/app?hl=en';
* @returns {Promise<{image?: string, error?: string}>}
*/
async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const { page } = context;
const { page, config } = context;
const waitTimeout = config?.backend?.pool?.waitTimeout ?? 120000;
const inputLocator = page.getByRole('textbox');
const sendBtnLocator = page.getByRole('button', { name: 'Send message' });
@@ -100,7 +101,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const streamApiResponsePromise = waitApiResponse(page, {
urlMatch: 'assistant.lamda.BardFrontendService/StreamGenerate',
method: 'POST',
timeout: 120000,
timeout: waitTimeout,
meta
});
@@ -138,7 +139,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
urlMatch: 'contribution.usercontent.google.com/download',
urlContains: 'filename=video.mp4',
method: 'GET',
timeout: 180000, // 视频生成可能更慢
timeout: waitTimeout,
meta
});
} catch (e) {
+3 -2
View File
@@ -98,6 +98,7 @@ async function handleAccountChooser(page) {
*/
async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const { page, config } = context;
const waitTimeout = config?.backend?.pool?.waitTimeout ?? 120000;
try {
// 支持新路径 adapter.gemini_biz.entryUrl,向下兼容旧路径 geminiBiz.entryUrl
@@ -180,7 +181,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const apiResponsePromise = waitApiResponse(page, {
urlMatch: 'global/widgetStreamAssist',
method: 'POST',
timeout: 120000,
timeout: waitTimeout,
errorText: ['modelArmorViolation'],
meta
});
@@ -217,7 +218,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const imageResponsePromise = waitApiResponse(page, {
urlMatch: 'download/v1alpha/projects',
method: 'GET',
timeout: 120000,
timeout: waitTimeout,
errorText: ['is unable to reply as the prompt'],
meta
});
+2 -1
View File
@@ -97,6 +97,7 @@ async function handleAccountChooser(page) {
*/
async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const { page, config } = context;
const waitTimeout = config?.backend?.pool?.waitTimeout ?? 120000;
try {
// 支持新路径 adapter.gemini_biz.entryUrl,向下兼容旧路径 geminiBiz.entryUrl
@@ -190,7 +191,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const apiResponsePromise = waitApiResponse(page, {
urlMatch: 'global/widgetStreamAssist',
method: 'POST',
timeout: 120000,
timeout: waitTimeout,
errorText: ['modelArmorViolation'],
meta
});
+3 -2
View File
@@ -30,7 +30,8 @@ const TARGET_URL = 'https://gemini.google.com/app?hl=en';
* @returns {Promise<{text?: string, error?: string}>}
*/
async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const { page } = context;
const { page, config } = context;
const waitTimeout = config?.backend?.pool?.waitTimeout ?? 120000;
const inputLocator = page.getByRole('textbox');
const sendBtnLocator = page.getByRole('button', { name: 'Send message' });
@@ -151,7 +152,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const apiResponsePromise = waitApiResponse(page, {
urlMatch: 'assistant.lamda.BardFrontendService/StreamGenerate',
method: 'POST',
timeout: 120000,
timeout: waitTimeout,
meta
});
+3 -2
View File
@@ -48,7 +48,8 @@ async function detectImageAspect(imgPath) {
* @returns {Promise<{image?: string, error?: string}>}
*/
async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const { page } = context;
const { page, config } = context;
const waitTimeout = config?.backend?.pool?.waitTimeout ?? 120000;
// 获取模型配置
const modelConfig = manifest.models.find(m => m.id === modelId) || manifest.models[0];
@@ -197,7 +198,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const apiResponsePromise = waitApiResponse(page, {
urlMatch: 'flowMedia:batchGenerateImages',
method: 'POST',
timeout: 120000,
timeout: waitTimeout,
meta
});
+2 -1
View File
@@ -52,6 +52,7 @@ function extractImage(text) {
*/
async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const { page, config } = context;
const waitTimeout = config?.backend?.pool?.waitTimeout ?? 120000;
const textareaSelector = 'textarea';
try {
@@ -117,7 +118,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const responsePromise = waitApiResponse(page, {
urlMatch: '/nextjs-api/stream',
method: 'POST',
timeout: 120000,
timeout: waitTimeout,
meta
});
+3 -2
View File
@@ -31,7 +31,8 @@ const TARGET_URL_SEARCH = 'https://lmarena.ai/zh/c/new?mode=direct&chat-modality
* @returns {Promise<{image?: string, text?: string, error?: string}>} 生成结果
*/
async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const { page } = context;
const { page, config } = context;
const waitTimeout = config?.backend?.pool?.waitTimeout ?? 120000;
const textareaSelector = 'textarea';
// Worker 已验证,直接解析模型配置
@@ -101,7 +102,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const responsePromise = waitApiResponse(page, {
urlMatch: '/nextjs-api/stream',
method: 'POST',
timeout: 120000,
timeout: waitTimeout,
meta
});
+3 -2
View File
@@ -31,7 +31,8 @@ const TARGET_URL = 'https://nanobananafree.ai/';
* @returns {Promise<{image?: string, text?: string, error?: string}>} 生成结果
*/
async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const { page } = context;
const { page, config } = context;
const waitTimeout = config?.backend?.pool?.waitTimeout ?? 120000;
const textareaSelector = 'textarea';
try {
@@ -63,7 +64,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const responsePromise = waitApiResponse(page, {
urlMatch: 'v1/generateContent',
method: 'POST',
timeout: 120000,
timeout: waitTimeout,
meta
});
+3 -2
View File
@@ -116,6 +116,7 @@ async function handleDiscordAuth(page) {
*/
async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const { page, config } = context;
const waitTimeout = config?.backend?.pool?.waitTimeout ?? 120000;
try {
// 开启新对话 - 先等待可能正在进行的登录处理完成
@@ -276,7 +277,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
completionsResponse = await waitApiResponse(page, {
urlMatch: 'chat/completions',
method: 'POST',
timeout: 120000,
timeout: waitTimeout,
errorText: ['Model is unable to process your request', 'Rate limit reached'],
meta
});
@@ -312,7 +313,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
completedResponse = await waitApiResponse(page, {
urlMatch: 'chat/completed',
method: 'POST',
timeout: 120000,
timeout: waitTimeout,
errorText: ['Model is unable to process your request', 'Rate limit reached'],
meta
});
+3 -2
View File
@@ -133,6 +133,7 @@ function extractTextContent(content) {
*/
async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const { page, config } = context;
const waitTimeout = config?.backend?.pool?.waitTimeout ?? 120000;
try {
// 开启新对话 - 先等待可能正在进行的登录处理完成
@@ -245,7 +246,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
completionsResponse = await waitApiResponse(page, {
urlMatch: 'chat/completions',
method: 'POST',
timeout: 120000,
timeout: waitTimeout,
errorText: ['Model is unable to process your request', 'Rate limit reached'],
meta
});
@@ -281,7 +282,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
completedResponse = await waitApiResponse(page, {
urlMatch: 'chat/completed',
method: 'POST',
timeout: 120000,
timeout: waitTimeout,
errorText: ['Model is unable to process your request', 'Rate limit reached'],
meta
});
+3 -2
View File
@@ -30,7 +30,8 @@ const SEND_BUTTON_SELECTOR = '.input-actions-send button';
* @returns {Promise<{text?: string, error?: string}>} 生成结果
*/
async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const { page } = context;
const { page, config } = context;
const waitTimeout = config?.backend?.pool?.waitTimeout ?? 120000;
try {
const targetUrl = 'https://zenmux.ai/settings/chat';
@@ -137,7 +138,7 @@ async function generate(context, prompt, imgPaths, modelId, meta = {}) {
const apiResponsePromise = waitApiResponse(page, {
urlMatch: 'v1/chat/completions',
method: 'POST',
timeout: 120000,
timeout: waitTimeout,
meta
});
+7
View File
@@ -281,6 +281,7 @@ export function getPoolConfig() {
return {
strategy: pool.strategy || 'least_busy',
waitTimeout: pool.waitTimeout != null ? Math.round(pool.waitTimeout / 1000) : 120,
failover: {
enabled: failover.enabled !== false, // 默认 true
maxRetries: failover.maxRetries ?? 2
@@ -302,6 +303,12 @@ export function savePoolConfig(data) {
config.backend.pool.strategy = data.strategy;
}
if (data.waitTimeout !== undefined) {
// 前端传入秒,写入 YAML 为毫秒
const ms = Number(data.waitTimeout) * 1000;
if (ms > 0) config.backend.pool.waitTimeout = ms;
}
if (data.failover) {
if (!config.backend.pool.failover) config.backend.pool.failover = {};
if (data.failover.enabled !== undefined) {