From 3a36da1bf94c58882c46f7e3f01e96dbe8b1b0ea Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Fri, 9 May 2025 22:12:16 +0800 Subject: [PATCH 1/2] feat: add inspect option to context menu with localization support (#5807) * implemented a new inspect menu item in the context menu that toggles developer tools * added localization for the inspect option in English, Japanese, Russian, and Chinese (both simplified and traditional) --- src/main/services/ContextMenu.ts | 19 ++++++++++++++++++- src/renderer/src/i18n/locales/en-us.json | 1 + src/renderer/src/i18n/locales/ja-jp.json | 1 + src/renderer/src/i18n/locales/ru-ru.json | 1 + src/renderer/src/i18n/locales/zh-cn.json | 1 + src/renderer/src/i18n/locales/zh-tw.json | 1 + 6 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/services/ContextMenu.ts b/src/main/services/ContextMenu.ts index 503af88db2..2f4f5aa20f 100644 --- a/src/main/services/ContextMenu.ts +++ b/src/main/services/ContextMenu.ts @@ -9,12 +9,29 @@ class ContextMenu { const template: MenuItemConstructorOptions[] = this.createEditMenuItems(properties) const filtered = template.filter((item) => item.visible !== false) if (filtered.length > 0) { - const menu = Menu.buildFromTemplate(filtered) + const menu = Menu.buildFromTemplate([...filtered, ...this.createInspectMenuItems(w)]) menu.popup() } }) } + private createInspectMenuItems(w: Electron.BrowserWindow): MenuItemConstructorOptions[] { + const locale = locales[configManager.getLanguage()] + const { common } = locale.translation + const template: MenuItemConstructorOptions[] = [ + { + id: 'inspect', + label: common.inspect, + click: () => { + w.webContents.toggleDevTools() + }, + enabled: true + } + ] + + return template + } + private createEditMenuItems(properties: Electron.ContextMenuParams): MenuItemConstructorOptions[] { const locale = locales[configManager.getLanguage()] const { common } = locale.translation diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 3d8ab74b96..0dffe56035 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -304,6 +304,7 @@ "confirm": "Confirm", "copied": "Copied", "copy": "Copy", + "inspect": "Inspect", "cut": "Cut", "default": "Default", "delete": "Delete", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index f1b12358d8..b813ededfc 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -304,6 +304,7 @@ "confirm": "確認", "copied": "コピーされました", "copy": "コピー", + "inspect": "検査", "cut": "切り取り", "default": "デフォルト", "delete": "削除", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 2692e1c270..803be36eac 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -304,6 +304,7 @@ "confirm": "Подтверждение", "copied": "Скопировано", "copy": "Копировать", + "inspect": "Осмотреть", "cut": "Вырезать", "default": "По умолчанию", "delete": "Удалить", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index e68409a642..86c9ac34b8 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -304,6 +304,7 @@ "confirm": "确认", "copied": "已复制", "copy": "复制", + "inspect": "检查", "cut": "剪切", "default": "默认", "delete": "删除", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 893a7b75a1..4bec777b83 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -304,6 +304,7 @@ "confirm": "確認", "copied": "已複製", "copy": "複製", + "inspect": "檢查", "cut": "剪下", "default": "預設", "delete": "刪除", From ac0651a9f34712594e910ab4d178162d10fcad18 Mon Sep 17 00:00:00 2001 From: jwcrystal <121911854+jwcrystal@users.noreply.github.com> Date: Fri, 9 May 2025 22:21:04 +0800 Subject: [PATCH 2/2] fix(Qwen3): Add Qwen3 Model Thinking Mode Switch in Thinking Button(#5781) * feat(Qwen3): Add Qwen3 Model Thinking Mode Switch - Add a thinking mode switch for the Qwen3 model on the settings page, - Enabled: Generates thinking content. Disabled: Does not generate thinking content. This feature is implemented by adding new settings items and corresponding logic. * docs(i18n): Add multilingual translations for Qwen3 model's thinking mode * refactor(Qwen3): Remove Qwen thinking mode related code from SettingTab - Remove Qwen thinking mode related state and logic from the SettingsTab component - Integarte Qwen 3 thinking mode switch login the ThinkingButton component to simplify the code and improve maintainability * refactor(OpenAICompatibleProvider): Extract qwen3 handling logic to ModelMessageService Move the postsuffix handling logic in OpenAICompatibleProvider to ModelMessageService to improve code maintainability and reusability * docs(i18n): Remove Qwen3 model-related translations Remove the translation content of the unused Qwen3 model's thinking mode and its prompts --- .../pages/home/Inputbar/ThinkingButton.tsx | 22 +++++-- .../AiProvider/OpenAICompatibleProvider.ts | 16 ++++- .../src/services/ModelMessageService.ts | 62 ++++++++++++++++++- src/renderer/src/types/index.ts | 1 + 4 files changed, 93 insertions(+), 8 deletions(-) diff --git a/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx b/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx index 623b4a1de2..702c1d2823 100644 --- a/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/ThinkingButton.tsx @@ -6,7 +6,11 @@ import { MdiLightbulbOn90 } from '@renderer/components/Icons/SVGIcon' import { useQuickPanel } from '@renderer/components/QuickPanel' -import { isSupportedReasoningEffortGrokModel, isSupportedThinkingTokenGeminiModel } from '@renderer/config/models' +import { + isSupportedReasoningEffortGrokModel, + isSupportedThinkingTokenGeminiModel, + isSupportedThinkingTokenQwenModel +} from '@renderer/config/models' import { useAssistant } from '@renderer/hooks/useAssistant' import { Assistant, Model, ReasoningEffortOptions } from '@renderer/types' import { Tooltip } from 'antd' @@ -30,7 +34,8 @@ interface Props { const MODEL_SUPPORTED_OPTIONS: Record = { default: ['off', 'low', 'medium', 'high'], grok: ['off', 'low', 'high'], - gemini: ['off', 'low', 'medium', 'high', 'auto'] + gemini: ['off', 'low', 'medium', 'high', 'auto'], + qwen: ['off', 'low', 'medium', 'high', 'auto'] } // 选项转换映射表:当选项不支持时使用的替代选项 @@ -49,6 +54,7 @@ const ThinkingButton: FC = ({ ref, model, assistant, ToolbarButton }): Re const isGrokModel = isSupportedReasoningEffortGrokModel(model) const isGeminiModel = isSupportedThinkingTokenGeminiModel(model) + const isQwenModel = isSupportedThinkingTokenQwenModel(model) const currentReasoningEffort = useMemo(() => { return assistant.settings?.reasoning_effort || 'off' @@ -58,8 +64,9 @@ const ThinkingButton: FC = ({ ref, model, assistant, ToolbarButton }): Re const modelType = useMemo(() => { if (isGeminiModel) return 'gemini' if (isGrokModel) return 'grok' + if (isQwenModel) return 'qwen' return 'default' - }, [isGeminiModel, isGrokModel]) + }, [isGeminiModel, isGrokModel, isQwenModel]) // 获取当前模型支持的选项 const supportedOptions = useMemo(() => { @@ -73,7 +80,8 @@ const ThinkingButton: FC = ({ ref, model, assistant, ToolbarButton }): Re const fallbackOption = OPTION_FALLBACK[currentReasoningEffort as ThinkingOption] updateAssistantSettings({ - reasoning_effort: fallbackOption === 'off' ? undefined : fallbackOption + reasoning_effort: fallbackOption === 'off' ? undefined : fallbackOption, + qwenThinkMode: fallbackOption === 'off' }) } }, [currentReasoningEffort, supportedOptions, updateAssistantSettings, model.id]) @@ -103,12 +111,14 @@ const ThinkingButton: FC = ({ ref, model, assistant, ToolbarButton }): Re // 然后更新设置 if (!isEnabled) { updateAssistantSettings({ - reasoning_effort: undefined + reasoning_effort: undefined, + qwenThinkMode: false }) return } updateAssistantSettings({ - reasoning_effort: option + reasoning_effort: option, + qwenThinkMode: true }) return }, diff --git a/src/renderer/src/providers/AiProvider/OpenAICompatibleProvider.ts b/src/renderer/src/providers/AiProvider/OpenAICompatibleProvider.ts index 444612eab0..eb1809797f 100644 --- a/src/renderer/src/providers/AiProvider/OpenAICompatibleProvider.ts +++ b/src/renderer/src/providers/AiProvider/OpenAICompatibleProvider.ts @@ -25,7 +25,7 @@ import { filterEmptyMessages, filterUserRoleStartMessages } from '@renderer/services/MessagesService' -import { processReqMessages } from '@renderer/services/ModelMessageService' +import { processPostsuffixQwen3Model, processReqMessages } from '@renderer/services/ModelMessageService' import store from '@renderer/store' import { Assistant, @@ -401,6 +401,20 @@ export default class OpenAICompatibleProvider extends BaseOpenAiProvider { const { signal } = abortController await this.checkIsCopilot() + const lastUserMsg = userMessages.findLast((m) => m.role === 'user') + if (lastUserMsg) { + const postsuffix = '/no_think' + // qwenThinkMode === true 表示思考模式啓用,此時不應添加 /no_think,如果存在則移除 + const qwenThinkModeEnabled = assistant.settings?.qwenThinkMode === true + const currentContent = lastUserMsg.content // content 類型:string | ChatCompletionContentPart[] | null + + lastUserMsg.content = processPostsuffixQwen3Model( + currentContent, + postsuffix, + qwenThinkModeEnabled + ) as ChatCompletionContentPart[] + } + //当 systemMessage 内容为空时不发送 systemMessage let reqMessages: ChatCompletionMessageParam[] if (!systemMessage.content) { diff --git a/src/renderer/src/services/ModelMessageService.ts b/src/renderer/src/services/ModelMessageService.ts index 0ef3001f20..4e9c1d5729 100644 --- a/src/renderer/src/services/ModelMessageService.ts +++ b/src/renderer/src/services/ModelMessageService.ts @@ -1,5 +1,5 @@ import { Model } from '@renderer/types' -import { ChatCompletionMessageParam } from 'openai/resources' +import { ChatCompletionContentPart, ChatCompletionContentPartText, ChatCompletionMessageParam } from 'openai/resources' export function processReqMessages( model: Model, @@ -40,3 +40,63 @@ function interleaveUserAndAssistantMessages(messages: ChatCompletionMessageParam return processedMessages } + +// Process postsuffix for Qwen3 model +export function processPostsuffixQwen3Model( + // content 類型:string | ChatCompletionContentPart[] | null + content: string | ChatCompletionContentPart[] | null, + postsuffix: string, + qwenThinkModeEnabled: boolean +): string | ChatCompletionContentPart[] | null { + if (typeof content === 'string') { + if (qwenThinkModeEnabled) { + // 思考模式启用,移除 postsuffix + if (content.endsWith(postsuffix)) { + return content.substring(0, content.length - postsuffix.length).trimEnd() + } + } else { + // 思考模式未启用,添加 postsuffix + if (!content.endsWith(postsuffix)) { + return content + postsuffix + } + } + } else if (Array.isArray(content)) { + let lastTextPartIndex = -1 + for (let i = content.length - 1; i >= 0; i--) { + if (content[i].type === 'text') { + lastTextPartIndex = i + break + } + } + + if (lastTextPartIndex !== -1) { + const textPart = content[lastTextPartIndex] as ChatCompletionContentPartText + if (qwenThinkModeEnabled) { + // 思考模式启用,移除 postsuffix + if (textPart.text.endsWith(postsuffix)) { + textPart.text = textPart.text.substring(0, textPart.text.length - postsuffix.length).trimEnd() + // 可選:如果 textPart.text 變為空,可以考慮是否移除該 part + } + } else { + // 思考模式未启用,添加 postsuffix + if (!textPart.text.endsWith(postsuffix)) { + textPart.text += postsuffix + } + } + } else { + // 數組中沒有文本部分 + if (!qwenThinkModeEnabled) { + // 思考模式未啓用,需要添加 postsuffix + // 如果沒有文本部分,則添加一個新的文本部分 + content.push({ type: 'text', text: postsuffix }) + } + } + } else { + // currentContent 是 null + if (!qwenThinkModeEnabled) { + // 思考模式未启用,需要添加 postsuffix + return content + } + } + return content +} diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 95b3756980..db716beb3d 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -60,6 +60,7 @@ export type AssistantSettings = { defaultModel?: Model customParameters?: AssistantSettingCustomParameters[] reasoning_effort?: ReasoningEffortOptions + qwenThinkMode?: boolean } export type Agent = Omit & {