diff --git a/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts b/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts index 1a19724c9f..eeaa1c7bad 100644 --- a/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts +++ b/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts @@ -6,6 +6,8 @@ import { getOpenAIWebSearchParams, isDoubaoThinkingAutoModel, isGrokReasoningModel, + isNotSupportSystemMessageModel, + isQwenMTModel, isQwenReasoningModel, isReasoningModel, isSupportedReasoningEffortGrokModel, @@ -32,6 +34,7 @@ import { Model, Provider, ToolCallResponse, + TranslateAssistant, WebSearchSource } from '@renderer/types' import { ChunkType, TextStartChunk, ThinkingStartChunk } from '@renderer/types/chunk' @@ -44,6 +47,7 @@ import { OpenAISdkRawOutput, ReasoningEffortOptionalParams } from '@renderer/types/sdk' +import { mapLanguageToQwenMTModel } from '@renderer/utils' import { addImageFileToContents } from '@renderer/utils/formats' import { isEnabledToolUse, @@ -472,6 +476,16 @@ export class OpenAIAPIClient extends OpenAIBaseClient< streamOutput = true } + const extra_body: Record = {} + + if (isQwenMTModel(model)) { + const targetLanguage = (assistant as TranslateAssistant).targetLanguage + extra_body.translation_options = { + source_lang: 'auto', + target_lang: mapLanguageToQwenMTModel(targetLanguage!) + } + } + // 1. 处理系统消息 let systemMessage = { role: 'system', content: assistant.prompt || '' } @@ -515,7 +529,7 @@ export class OpenAIAPIClient extends OpenAIBaseClient< // 4. 最终请求消息 let reqMessages: OpenAISdkMessageParam[] - if (!systemMessage.content) { + if (!systemMessage.content || isNotSupportSystemMessageModel(model)) { reqMessages = [...userMessages] } else { reqMessages = [systemMessage, ...userMessages].filter(Boolean) as OpenAISdkMessageParam[] @@ -541,7 +555,8 @@ export class OpenAIAPIClient extends OpenAIBaseClient< // 只在对话场景下应用自定义参数,避免影响翻译、总结等其他业务逻辑 ...(coreRequest.callType === 'chat' ? this.getCustomParameters(assistant) : {}), // OpenRouter usage tracking - ...(this.provider.id === 'openrouter' ? { usage: { include: true } } : {}) + ...(this.provider.id === 'openrouter' ? { usage: { include: true } } : {}), + ...(isQwenMTModel(model) ? extra_body : {}) } // Create the appropriate parameters object based on whether streaming is enabled diff --git a/src/renderer/src/aiCore/middleware/core/TextChunkMiddleware.ts b/src/renderer/src/aiCore/middleware/core/TextChunkMiddleware.ts index 82e8a7cdaa..41157bf504 100644 --- a/src/renderer/src/aiCore/middleware/core/TextChunkMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/core/TextChunkMiddleware.ts @@ -45,8 +45,11 @@ export const TextChunkMiddleware: CompletionsMiddleware = transform(chunk: GenericChunk, controller) { logger.silly('chunk', chunk) if (chunk.type === ChunkType.TEXT_DELTA) { - accumulatedTextContent += chunk.text - + if (model.supported_text_delta === false) { + accumulatedTextContent = chunk.text + } else { + accumulatedTextContent += chunk.text + } // 处理 onResponse 回调 - 发送增量文本更新 if (params.onResponse) { params.onResponse(accumulatedTextContent, false) diff --git a/src/renderer/src/components/ModelList/AddModelPopup.tsx b/src/renderer/src/components/ModelList/AddModelPopup.tsx index d11db2f4cd..0d8932aba3 100644 --- a/src/renderer/src/components/ModelList/AddModelPopup.tsx +++ b/src/renderer/src/components/ModelList/AddModelPopup.tsx @@ -1,4 +1,5 @@ import { TopView } from '@renderer/components/TopView' +import { isNotSupportedTextDelta } from '@renderer/config/models' import { useProvider } from '@renderer/hooks/useProvider' import { Model, Provider } from '@renderer/types' import { getDefaultGroupName } from '@renderer/utils' @@ -56,7 +57,7 @@ const PopupContainer: React.FC = ({ title, provider, resolve }) => { group: values.group ?? getDefaultGroupName(id) } - addModel(model) + addModel({ ...model, supported_text_delta: !isNotSupportedTextDelta(model) }) return true } diff --git a/src/renderer/src/components/ModelList/EditModelsPopup.tsx b/src/renderer/src/components/ModelList/EditModelsPopup.tsx index 00a6ad479f..01c3ea3a70 100644 --- a/src/renderer/src/components/ModelList/EditModelsPopup.tsx +++ b/src/renderer/src/components/ModelList/EditModelsPopup.tsx @@ -13,6 +13,7 @@ import { groupQwenModels, isEmbeddingModel, isFunctionCallingModel, + isNotSupportedTextDelta, isReasoningModel, isRerankModel, isVisionModel, @@ -144,13 +145,14 @@ const PopupContainer: React.FC = ({ provider: _provider, resolve }) => { if (model.supported_endpoint_types && model.supported_endpoint_types.length > 0) { addModel({ ...model, - endpoint_type: model.supported_endpoint_types[0] + endpoint_type: model.supported_endpoint_types[0], + supported_text_delta: !isNotSupportedTextDelta(model) }) } else { NewApiAddModelPopup.show({ title: t('settings.models.add.add_model'), provider, model }) } } else { - addModel(model) + addModel({ ...model, supported_text_delta: !isNotSupportedTextDelta(model) }) } } }, diff --git a/src/renderer/src/components/ModelList/ModelEditContent.tsx b/src/renderer/src/components/ModelList/ModelEditContent.tsx index eb1d198fab..f4d8811309 100644 --- a/src/renderer/src/components/ModelList/ModelEditContent.tsx +++ b/src/renderer/src/components/ModelList/ModelEditContent.tsx @@ -11,7 +11,7 @@ import { import { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth' import { Model, ModelCapability, ModelType, Provider } from '@renderer/types' import { getDefaultGroupName, getDifference, getUnion } from '@renderer/utils' -import { Button, Checkbox, Divider, Flex, Form, Input, InputNumber, message, Modal, Select } from 'antd' +import { Button, Checkbox, Divider, Flex, Form, Input, InputNumber, message, Modal, Select, Switch } from 'antd' import { ChevronDown, ChevronUp } from 'lucide-react' import { FC, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -33,6 +33,7 @@ const ModelEditContent: FC = ({ provider, model, onUpdate const [currencySymbol, setCurrencySymbol] = useState(model.pricing?.currencySymbol || '$') const [isCustomCurrency, setIsCustomCurrency] = useState(!symbols.includes(model.pricing?.currencySymbol || '$')) const [modelCapabilities, setModelCapabilities] = useState(model.capabilities || []) + const [supportedTextDelta, setSupportedTextDelta] = useState(model.supported_text_delta) const labelWidth = useDynamicLabelWidth([t('settings.models.add.endpoint_type.label')]) @@ -45,6 +46,7 @@ const ModelEditContent: FC = ({ provider, model, onUpdate group: values.group || model.group, endpoint_type: provider.id === 'new-api' ? values.endpointType : model.endpoint_type, capabilities: modelCapabilities, + supported_text_delta: supportedTextDelta, pricing: { input_per_million_tokens: Number(values.input_per_million_tokens) || 0, output_per_million_tokens: Number(values.output_per_million_tokens) || 0, @@ -338,6 +340,12 @@ const ModelEditContent: FC = ({ provider, model, onUpdate ) })()} + + setSupportedTextDelta(checked)} /> + {t('models.price.price')}