diff --git a/docs/QWEN_CHAT_MODELS.md b/docs/QWEN_CHAT_MODELS.md index b83036e..6f488e1 100644 --- a/docs/QWEN_CHAT_MODELS.md +++ b/docs/QWEN_CHAT_MODELS.md @@ -1,18 +1,19 @@ # Синхронизация моделей Qwen Chat -Сгенерировано: 2026-06-05T13:56:59.888Z +Сгенерировано: 2026-07-22T02:03:42.778Z Источник: prerendered-метаданные моделей с https://chat.qwen.ai/. ## Модели, которые сейчас видны в Qwen Chat - `qwen3.7-plus` — thinking-режим, аудио, видео, документы, зрение, поиск +- `qwen3.8-max-preview` — thinking-режим, видео, документы, зрение, поиск - `qwen3.7-max` — thinking-режим, документы - `qwen3.6-plus` — thinking-режим, аудио, видео, документы, зрение, поиск ## Добавлено последней синхронизацией -- Новых моделей нет. +- `qwen3.8-max-preview` ## Модели эндпоинта, которых нет в текущих landing-метаданных Qwen Chat @@ -45,6 +46,7 @@ ## Итоговый объединённый список моделей эндпоинта - `qwen3.7-plus` +- `qwen3.8-max-preview` - `qwen3.7-max` - `qwen3.6-plus` - `qwen3.5-plus` diff --git a/src/AvailableModels.txt b/src/AvailableModels.txt index 6b8bb19..cfe661d 100644 --- a/src/AvailableModels.txt +++ b/src/AvailableModels.txt @@ -1,4 +1,5 @@ qwen3.7-plus +qwen3.8-max-preview qwen3.7-max qwen3.6-plus qwen3.5-plus diff --git a/src/api/chat.js b/src/api/chat.js index 7f0fc0c..4a62885 100644 --- a/src/api/chat.js +++ b/src/api/chat.js @@ -822,6 +822,10 @@ async function executeApiRequestWithNodeStreaming(apiUrl, payload, token, onChun try { const chunk = JSON.parse(jsonStr); + if (process.env.DEBUG_SSE_CHUNKS === 'true') { + logWarn(`[SSE-DEBUG] ${jsonStr.substring(0, 800)}`); + } + if (chunk.code === 'RateLimited' || (chunk.code && chunk.detail)) { streamError = { status: 429, errorBody: JSON.stringify(chunk) }; finished = true; @@ -1084,7 +1088,7 @@ async function handleApiError(response, tokenObj, requestContext) { const { chatId, retryCount, fileAccountId } = requestContext; logRaw(JSON.stringify(response)); logError(`Ошибка при получении ответа: ${response.error || response.statusText || `HTTP ${response.status || 'unknown'}`}`); - if (response.errorBody) logDebug(`Тело ответа с ошибкой: ${response.errorBody}`); + if (response.errorBody) logWarn(`Тело ответа с ошибкой: ${response.errorBody}`); if (response.html && response.html.includes('Verification')) { setAuthenticationStatus(false); @@ -1131,8 +1135,10 @@ async function handleApiError(response, tokenObj, requestContext) { let hours = 24; try { const rateInfo = JSON.parse(response.errorBody); - const parsedHours = Number(rateInfo.num); - hours = Number.isFinite(parsedHours) && parsedHours > 0 ? parsedHours : 24; + // Qwen returns the wait time nested at data.num, in MINUTES + // (see data.template: "Please wait {{num}} minutes before trying again.") + const parsedMinutes = Number(rateInfo?.data?.num); + hours = Number.isFinite(parsedMinutes) && parsedMinutes > 0 ? parsedMinutes / 60 : 24; } catch { /* errorBody might not be valid JSON */ } markRateLimitedByToken(tokenObj?.token, hours); @@ -1166,7 +1172,8 @@ async function handleApiError(response, tokenObj, requestContext) { return { error: `Все токены заблокированы по лимиту (${hours}ч)`, chatId }; } - return { error: response.error || response.statusText, details: response.errorBody || 'Нет дополнительных деталей', chatId }; + const fallbackError = response.error || response.statusText || (response.status ? `HTTP ${response.status}` : 'Неизвестная ошибка ответа'); + return { error: fallbackError, details: response.errorBody || 'Нет дополнительных деталей', chatId }; } // ─── Main public API ───────────────────────────────────────────────────────── @@ -1583,8 +1590,10 @@ export async function createChatV2(model = DEFAULT_MODEL, title = 'Новый ч if (isRateLimited) { let hours = 24; try { - const parsedHours = Number(JSON.parse(structuredErrorBody).num); - hours = Number.isFinite(parsedHours) && parsedHours > 0 ? parsedHours : 24; + // Qwen returns the wait time nested at data.num, in MINUTES + // (see data.template: "Please wait {{num}} minutes before trying again.") + const parsedMinutes = Number(JSON.parse(structuredErrorBody)?.data?.num); + hours = Number.isFinite(parsedMinutes) && parsedMinutes > 0 ? parsedMinutes / 60 : 24; } catch { /* non-JSON body */ } markRateLimitedByToken(tokenObj.token, hours); if (isBrowserAccountId(tokenObj.id) || browserAuthToken === tokenObj.token) { diff --git a/src/api/routes.js b/src/api/routes.js index 75862b3..cb83957 100644 --- a/src/api/routes.js +++ b/src/api/routes.js @@ -1,7 +1,7 @@ import express from 'express'; import { sendMessage, getAllModels, getApiKeys, createChatV2, pollQwenTaskStatus, extractMediaUrl, pagePool, extractAuthToken, preflightFileRequest, testToken } from './chat.js'; import { sendApiResultError } from './apiErrors.js'; -import { getAuthenticationStatus, getBrowserContext } from '../browser/browser.js'; +import { getAuthenticationStatus, getBrowserContext, restartBrowserInHeadlessMode } from '../browser/browser.js'; import { checkAuthentication } from '../browser/auth.js'; import { logInfo, logError, logDebug } from '../logger/index.js'; import { getMappedModel } from './modelMapping.js'; @@ -1066,6 +1066,24 @@ router.post('/chat', async (req, res) => { return res.status(400).json({ error: 'Сообщение не указано' }); } + // Handle /new command - restart browser session + const normalizedMessage = typeof messageContent === 'string' ? messageContent.trim() : ''; + if (normalizedMessage === '/new') { + logInfo('Получена команда /new - перезапуск сессии браузера'); + try { + await restartBrowserInHeadlessMode(); + return res.json({ + success: true, + message: 'Browser session restarted', + chatId: null, + parentId: null + }); + } catch (error) { + logError('Ошибка при перезапуске браузера', error); + return res.status(500).json({ error: 'Failed to restart browser session' }); + } + } + const filePreflight = preflightFileRequest(messageContent, null, getSessionKey(req)); if (filePreflight.error) return sendApiResultError(res, filePreflight); @@ -1397,6 +1415,32 @@ router.post('/chat/completions', async (req, res) => { return res.status(400).json({ error: 'Сообщения не указаны' }); } + // Handle /new command - restart browser session + const lastUserMessage = messages.filter(msg => msg.role === 'user').pop(); + const lastMessageContent = lastUserMessage?.content; + const normalizedLastMessage = typeof lastMessageContent === 'string' ? lastMessageContent.trim() : ''; + if (normalizedLastMessage === '/new') { + logInfo('Получена команда /new - перезапуск сессии браузера'); + try { + await restartBrowserInHeadlessMode(); + return res.json({ + id: 'chatcmpl-' + Date.now(), + object: 'chat.completion', + created: Math.floor(Date.now() / 1000), + model: model || DEFAULT_MODEL, + choices: [{ + index: 0, + message: { role: 'assistant', content: 'Browser session restarted successfully.' }, + finish_reason: 'stop' + }], + usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } + }); + } catch (error) { + logError('Ошибка при перезапуске браузера', error); + return res.status(500).json({ error: 'Failed to restart browser session' }); + } + } + const isMeta = isOpenWebUiMetaRequest(messages); // Используем переданный chatId ИЛИ восстанавливаем из сессии diff --git a/src/config.js b/src/config.js index 0917383..49453b2 100644 --- a/src/config.js +++ b/src/config.js @@ -82,7 +82,7 @@ export const USER_AGENT = process.env.USER_AGENT || 'Mozilla/5.0 (Windows NT 10. // ─── Сервер ────────────────────────────────────────────────────────────────── export const PORT = Number(process.env.PORT) || 3264; -export const HOST = process.env.HOST || '127.0.0.1'; +export const HOST = process.env.HOST || '0.0.0.0'; export const DEFAULT_MODEL = process.env.DEFAULT_MODEL || 'qwen3.7-max'; export const ALLOW_UNSCOPED_SESSION_CHAT_RESTORE = toBoolean(process.env.ALLOW_UNSCOPED_SESSION_CHAT_RESTORE);