From 06b1ae0cb8e07dc81abcfb40c888ac8df788d262 Mon Sep 17 00:00:00 2001 From: Phantom Date: Sun, 19 Oct 2025 19:02:16 +0800 Subject: [PATCH 1/9] feat(models): add doubao_after_251015 reasoning model type and support (#10826) * feat(models): add doubao_after_251015 model type and support Add new model type 'doubao_after_251015' with reasoning effort levels and update regex patterns to handle version 251015 and later * fix(sdk): add warning for reasoning_effort field and update reasoning logic Add warning comment about reasoning_effort field being overwritten for openai-compatible providers Update reasoning effort logic to handle Doubao seed models after 251015 and standardize field naming * fix(reasoning): update Doubao model regex patterns and tests Update regex patterns for Doubao model validation to correctly handle version constraints Add comprehensive test cases for model validation functions --- src/renderer/src/aiCore/utils/reasoning.ts | 9 +- .../src/config/__test__/reasoning.test.ts | 166 ++++++++++++++++++ src/renderer/src/config/models/reasoning.ts | 13 +- src/renderer/src/types/index.ts | 1 + src/renderer/src/types/sdk.ts | 1 + 5 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 src/renderer/src/config/__test__/reasoning.test.ts diff --git a/src/renderer/src/aiCore/utils/reasoning.ts b/src/renderer/src/aiCore/utils/reasoning.ts index d7f17e00d5..732fee384d 100644 --- a/src/renderer/src/aiCore/utils/reasoning.ts +++ b/src/renderer/src/aiCore/utils/reasoning.ts @@ -5,6 +5,7 @@ import { GEMINI_FLASH_MODEL_REGEX, getThinkModelType, isDeepSeekHybridInferenceModel, + isDoubaoSeedAfter251015, isDoubaoThinkingAutoModel, isGrok4FastReasoningModel, isGrokReasoningModel, @@ -170,6 +171,10 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin // Doubao 思考模式支持 if (isSupportedThinkingTokenDoubaoModel(model)) { + if (isDoubaoSeedAfter251015(model)) { + return { reasoningEffort } + } + // Comment below this line seems weird. reasoning is high instead of null/undefined. Who wrote this? // reasoningEffort 为空,默认开启 enabled if (reasoningEffort === 'high') { return { thinking: { type: 'enabled' } } @@ -226,12 +231,12 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin const supportedOptions = MODEL_SUPPORTED_REASONING_EFFORT[modelType] if (supportedOptions.includes(reasoningEffort)) { return { - reasoning_effort: reasoningEffort + reasoningEffort } } else { // 如果不支持,fallback到第一个支持的值 return { - reasoning_effort: supportedOptions[0] + reasoningEffort: supportedOptions[0] } } } diff --git a/src/renderer/src/config/__test__/reasoning.test.ts b/src/renderer/src/config/__test__/reasoning.test.ts new file mode 100644 index 0000000000..96fad861f3 --- /dev/null +++ b/src/renderer/src/config/__test__/reasoning.test.ts @@ -0,0 +1,166 @@ +import { describe, expect, it, vi } from 'vitest' + +import { isDoubaoSeedAfter251015, isDoubaoThinkingAutoModel } from '../models/reasoning' + +// FIXME: Idk why it's imported. Maybe circular dependency somewhere +vi.mock('@renderer/services/AssistantService.ts', () => ({ + getDefaultAssistant: () => { + return { + id: 'default', + name: 'default', + emoji: '😀', + prompt: '', + topics: [], + messages: [], + type: 'assistant', + regularPhrases: [], + settings: {} + } + } +})) + +describe('Doubao Models', () => { + describe('isDoubaoThinkingAutoModel', () => { + it('should return false for invalid models', () => { + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-seed-1-6-251015', + name: 'doubao-seed-1-6-251015', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-seed-1-6-lite-251015', + name: 'doubao-seed-1-6-lite-251015', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-seed-1-6-thinking-250715', + name: 'doubao-seed-1-6-thinking-250715', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-seed-1-6-flash', + name: 'doubao-seed-1-6-flash', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-seed-1-6-thinking', + name: 'doubao-seed-1-6-thinking', + provider: '', + group: '' + }) + ).toBe(false) + }) + + it('should return true for valid models', () => { + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-seed-1-6-250615', + name: 'doubao-seed-1-6-250615', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isDoubaoThinkingAutoModel({ + id: 'Doubao-Seed-1.6', + name: 'Doubao-Seed-1.6', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-1-5-thinking-pro-m', + name: 'doubao-1-5-thinking-pro-m', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-seed-1.6-lite', + name: 'doubao-seed-1.6-lite', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isDoubaoThinkingAutoModel({ + id: 'doubao-1-5-thinking-pro-m-12345', + name: 'doubao-1-5-thinking-pro-m-12345', + provider: '', + group: '' + }) + ).toBe(true) + }) + }) + + describe('isDoubaoSeedAfter251015', () => { + it('should return true for models matching the pattern', () => { + expect( + isDoubaoSeedAfter251015({ + id: 'doubao-seed-1-6-251015', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + expect( + isDoubaoSeedAfter251015({ + id: 'doubao-seed-1-6-lite-251015', + name: '', + provider: '', + group: '' + }) + ).toBe(true) + }) + + it('should return false for models not matching the pattern', () => { + expect( + isDoubaoSeedAfter251015({ + id: 'doubao-seed-1-6-250615', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isDoubaoSeedAfter251015({ + id: 'Doubao-Seed-1.6', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isDoubaoSeedAfter251015({ + id: 'doubao-1-5-thinking-pro-m', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + expect( + isDoubaoSeedAfter251015({ + id: 'doubao-seed-1-6-lite-251016', + name: '', + provider: '', + group: '' + }) + ).toBe(false) + }) + }) +}) diff --git a/src/renderer/src/config/models/reasoning.ts b/src/renderer/src/config/models/reasoning.ts index 1ef0303f61..a7e825ef4f 100644 --- a/src/renderer/src/config/models/reasoning.ts +++ b/src/renderer/src/config/models/reasoning.ts @@ -31,6 +31,7 @@ export const MODEL_SUPPORTED_REASONING_EFFORT: ReasoningEffortConfig = { qwen_thinking: ['low', 'medium', 'high'] as const, doubao: ['auto', 'high'] as const, doubao_no_auto: ['high'] as const, + doubao_after_251015: ['minimal', 'low', 'medium', 'high'] as const, hunyuan: ['auto'] as const, zhipu: ['auto'] as const, perplexity: ['low', 'medium', 'high'] as const, @@ -51,6 +52,7 @@ export const MODEL_SUPPORTED_OPTIONS: ThinkingOptionConfig = { qwen_thinking: MODEL_SUPPORTED_REASONING_EFFORT.qwen_thinking, doubao: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao] as const, doubao_no_auto: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.doubao_no_auto] as const, + doubao_after_251015: MODEL_SUPPORTED_REASONING_EFFORT.doubao_after_251015, hunyuan: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.hunyuan] as const, zhipu: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.zhipu] as const, perplexity: MODEL_SUPPORTED_REASONING_EFFORT.perplexity, @@ -85,6 +87,8 @@ export const getThinkModelType = (model: Model): ThinkingModelType => { } else if (isSupportedThinkingTokenDoubaoModel(model)) { if (isDoubaoThinkingAutoModel(model)) { thinkingModelType = 'doubao' + } else if (isDoubaoSeedAfter251015(model)) { + thinkingModelType = 'doubao_after_251015' } else { thinkingModelType = 'doubao_no_auto' } @@ -308,14 +312,21 @@ export const DOUBAO_THINKING_MODEL_REGEX = /doubao-(?:1[.-]5-thinking-vision-pro|1[.-]5-thinking-pro-m|seed-1[.-]6(?:-flash)?(?!-(?:thinking)(?:-|$)))(?:-[\w-]+)*/i // 支持 auto 的 Doubao 模型 doubao-seed-1.6-xxx doubao-seed-1-6-xxx doubao-1-5-thinking-pro-m-xxx +// Auto thinking is no longer supported after version 251015, see https://console.volcengine.com/ark/region:ark+cn-beijing/model/detail?Id=doubao-seed-1-6 export const DOUBAO_THINKING_AUTO_MODEL_REGEX = - /doubao-(1-5-thinking-pro-m|seed-1[.-]6)(?!-(?:flash|thinking)(?:-|$))(?:-[\w-]+)*/i + /doubao-(1-5-thinking-pro-m|seed-1[.-]6)(?!-(?:flash|thinking)(?:-|$))(?:-lite)?(?!-251015)(?:-\d+)?$/i export function isDoubaoThinkingAutoModel(model: Model): boolean { const modelId = getLowerBaseModelName(model.id) return DOUBAO_THINKING_AUTO_MODEL_REGEX.test(modelId) || DOUBAO_THINKING_AUTO_MODEL_REGEX.test(model.name) } +export function isDoubaoSeedAfter251015(model: Model): boolean { + const pattern = new RegExp(/doubao-seed-1-6-(?:lite-)?251015/i) + const result = pattern.test(model.id) + return result +} + export function isSupportedThinkingTokenDoubaoModel(model?: Model): boolean { if (!model) { return false diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 82df2c1f21..750928820f 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -89,6 +89,7 @@ const ThinkModelTypes = [ 'qwen_thinking', 'doubao', 'doubao_no_auto', + 'doubao_after_251015', 'hunyuan', 'zhipu', 'perplexity', diff --git a/src/renderer/src/types/sdk.ts b/src/renderer/src/types/sdk.ts index 54a7896be4..e18891f2bf 100644 --- a/src/renderer/src/types/sdk.ts +++ b/src/renderer/src/types/sdk.ts @@ -78,6 +78,7 @@ export type ReasoningEffortOptionalParams = { thinking?: { type: 'disabled' | 'enabled' | 'auto'; budget_tokens?: number } reasoning?: { max_tokens?: number; exclude?: boolean; effort?: string; enabled?: boolean } | OpenAI.Reasoning reasoningEffort?: OpenAI.Chat.Completions.ChatCompletionCreateParams['reasoning_effort'] | 'none' | 'auto' + // WARN: This field will be overwrite to undefined by aisdk if the provider is openai-compatible. Use reasoningEffort instead. reasoning_effort?: OpenAI.Chat.Completions.ChatCompletionCreateParams['reasoning_effort'] | 'none' | 'auto' enable_thinking?: boolean thinking_budget?: number From 036f61bf120b7e9e3e00357fa67b5ad2f771454c Mon Sep 17 00:00:00 2001 From: Phantom Date: Mon, 20 Oct 2025 00:01:39 +0800 Subject: [PATCH 2/9] fix: use consistent sharp dependencies (#10832) build: update sharp dependencies to version 0.34.3 Update sharp image processing library dependencies to latest version 0.34.3 across all platforms (darwin, linux, win32) to ensure consistent behavior and security fixes --- package.json | 8 +++- yarn.lock | 102 --------------------------------------------------- 2 files changed, 7 insertions(+), 103 deletions(-) diff --git a/package.json b/package.json index 6d59cfff49..d29784342a 100644 --- a/package.json +++ b/package.json @@ -384,7 +384,13 @@ "undici": "6.21.2", "vite": "npm:rolldown-vite@latest", "tesseract.js@npm:*": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch", - "@ai-sdk/google@npm:2.0.20": "patch:@ai-sdk/google@npm%3A2.0.20#~/.yarn/patches/@ai-sdk-google-npm-2.0.20-b9102f9d54.patch" + "@ai-sdk/google@npm:2.0.20": "patch:@ai-sdk/google@npm%3A2.0.20#~/.yarn/patches/@ai-sdk-google-npm-2.0.20-b9102f9d54.patch", + "@img/sharp-darwin-arm64": "0.34.3", + "@img/sharp-darwin-x64": "0.34.3", + "@img/sharp-linux-arm": "0.34.3", + "@img/sharp-linux-arm64": "0.34.3", + "@img/sharp-linux-x64": "0.34.3", + "@img/sharp-win32-x64": "0.34.3" }, "packageManager": "yarn@4.9.1", "lint-staged": { diff --git a/yarn.lock b/yarn.lock index 2d5bffcfd0..171c74decd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5630,18 +5630,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-darwin-arm64@npm:^0.33.5": - version: 0.33.5 - resolution: "@img/sharp-darwin-arm64@npm:0.33.5" - dependencies: - "@img/sharp-libvips-darwin-arm64": "npm:1.0.4" - dependenciesMeta: - "@img/sharp-libvips-darwin-arm64": - optional: true - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@img/sharp-darwin-x64@npm:0.34.3": version: 0.34.3 resolution: "@img/sharp-darwin-x64@npm:0.34.3" @@ -5654,25 +5642,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-darwin-x64@npm:^0.33.5": - version: 0.33.5 - resolution: "@img/sharp-darwin-x64@npm:0.33.5" - dependencies: - "@img/sharp-libvips-darwin-x64": "npm:1.0.4" - dependenciesMeta: - "@img/sharp-libvips-darwin-x64": - optional: true - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@img/sharp-libvips-darwin-arm64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-darwin-arm64@npm:1.0.4" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@img/sharp-libvips-darwin-arm64@npm:1.2.0": version: 1.2.0 resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.0" @@ -5680,13 +5649,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-libvips-darwin-x64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-darwin-x64@npm:1.0.4" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@img/sharp-libvips-darwin-x64@npm:1.2.0": version: 1.2.0 resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.0" @@ -5694,13 +5656,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-libvips-linux-arm64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-linux-arm64@npm:1.0.4" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - "@img/sharp-libvips-linux-arm64@npm:1.2.0": version: 1.2.0 resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.0" @@ -5708,13 +5663,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-libvips-linux-arm@npm:1.0.5": - version: 1.0.5 - resolution: "@img/sharp-libvips-linux-arm@npm:1.0.5" - conditions: os=linux & cpu=arm & libc=glibc - languageName: node - linkType: hard - "@img/sharp-libvips-linux-arm@npm:1.2.0": version: 1.2.0 resolution: "@img/sharp-libvips-linux-arm@npm:1.2.0" @@ -5736,13 +5684,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-libvips-linux-x64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-linux-x64@npm:1.0.4" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - "@img/sharp-libvips-linux-x64@npm:1.2.0": version: 1.2.0 resolution: "@img/sharp-libvips-linux-x64@npm:1.2.0" @@ -5776,18 +5717,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-arm64@npm:^0.33.5": - version: 0.33.5 - resolution: "@img/sharp-linux-arm64@npm:0.33.5" - dependencies: - "@img/sharp-libvips-linux-arm64": "npm:1.0.4" - dependenciesMeta: - "@img/sharp-libvips-linux-arm64": - optional: true - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - "@img/sharp-linux-arm@npm:0.34.3": version: 0.34.3 resolution: "@img/sharp-linux-arm@npm:0.34.3" @@ -5800,18 +5729,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-arm@npm:^0.33.5": - version: 0.33.5 - resolution: "@img/sharp-linux-arm@npm:0.33.5" - dependencies: - "@img/sharp-libvips-linux-arm": "npm:1.0.5" - dependenciesMeta: - "@img/sharp-libvips-linux-arm": - optional: true - conditions: os=linux & cpu=arm & libc=glibc - languageName: node - linkType: hard - "@img/sharp-linux-ppc64@npm:0.34.3": version: 0.34.3 resolution: "@img/sharp-linux-ppc64@npm:0.34.3" @@ -5848,18 +5765,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-x64@npm:^0.33.5": - version: 0.33.5 - resolution: "@img/sharp-linux-x64@npm:0.33.5" - dependencies: - "@img/sharp-libvips-linux-x64": "npm:1.0.4" - dependenciesMeta: - "@img/sharp-libvips-linux-x64": - optional: true - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - "@img/sharp-linuxmusl-arm64@npm:0.34.3": version: 0.34.3 resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.3" @@ -5914,13 +5819,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-win32-x64@npm:^0.33.5": - version: 0.33.5 - resolution: "@img/sharp-win32-x64@npm:0.33.5" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@internationalized/date@npm:3.9.0, @internationalized/date@npm:^3.9.0": version: 3.9.0 resolution: "@internationalized/date@npm:3.9.0" From 8e482a97e5580f9328ed1596a30de84362f30e62 Mon Sep 17 00:00:00 2001 From: Phantom Date: Mon, 20 Oct 2025 00:04:29 +0800 Subject: [PATCH 3/9] style(AgentItem): improve agent item style (#10824) refactor(AgentItem): simplify BotIcon component and adjust styling - Replace absolute positioning with flex layout in BotIcon - Add tooltip for better user experience - Consolidate styling classes for better maintainability --- .../pages/home/Tabs/components/AgentItem.tsx | 44 ++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx b/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx index c67579dcf5..9836ee5891 100644 --- a/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx +++ b/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx @@ -1,4 +1,4 @@ -import { cn } from '@heroui/react' +import { cn, Tooltip } from '@heroui/react' import { DeleteIcon, EditIcon } from '@renderer/components/Icons' import { useSessions } from '@renderer/hooks/agents/useSessions' import { useSettings } from '@renderer/hooks/useSettings' @@ -43,17 +43,13 @@ const AgentItem: FC = ({ agent, isActive, onDelete, onPress }) = + {isActive && ( + + {sessions.length} + + )} + {!isActive && } - {isActive && ( - - {sessions.length} - - )} - {!isActive && ( - - - - )} @@ -110,29 +106,27 @@ export const AgentNameWrapper: React.FC> = export const MenuButton: React.FC> = ({ className, ...props }) => (
) -export const BotIcon: React.FC> = ({ className, ...props }) => ( -
-) +export const BotIcon: React.FC> = ({ ...props }) => { + const { t } = useTranslation() + return ( + + + + + + ) +} export const SessionCount: React.FC> = ({ className, ...props }) => (
) From b26df0e614acdad982dafb6f8e8351911c89c613 Mon Sep 17 00:00:00 2001 From: Pleasure1234 <3196812536@qq.com> Date: Sun, 19 Oct 2025 17:06:34 +0100 Subject: [PATCH 4/9] fix: add continue-on-error & remove unused issue checker (#10821) --- .github/issue-checker.yml | 252 ---------------------------- .github/workflows/delete-branch.yml | 1 + .github/workflows/issue-checker.yml | 25 --- 3 files changed, 1 insertion(+), 277 deletions(-) delete mode 100644 .github/issue-checker.yml delete mode 100644 .github/workflows/issue-checker.yml diff --git a/.github/issue-checker.yml b/.github/issue-checker.yml deleted file mode 100644 index 483e9d966f..0000000000 --- a/.github/issue-checker.yml +++ /dev/null @@ -1,252 +0,0 @@ -default-mode: - add: - remove: [pull_request_target, issues] - -labels: - # 跳过一个 label - # 去掉一个 label - - # skips and removes - - name: skip all - content: - regexes: '[Ss]kip (?:[Aa]ll |)[Ll]abels?' - - name: remove all - content: - regexes: '[Rr]emove (?:[Aa]ll |)[Ll]abels?' - - - name: skip kind/bug - content: - regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/bug(?:`|)' - - name: remove kind/bug - content: - regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/bug(?:`|)' - - - name: skip kind/enhancement - content: - regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/enhancement(?:`|)' - - name: remove kind/enhancement - content: - regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/enhancement(?:`|)' - - - name: skip kind/question - content: - regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/question(?:`|)' - - name: remove kind/question - content: - regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/question(?:`|)' - - - name: skip area/Connectivity - content: - regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)area/Connectivity(?:`|)' - - name: remove area/Connectivity - content: - regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)area/Connectivity(?:`|)' - - - name: skip area/UI/UX - content: - regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)area/UI/UX(?:`|)' - - name: remove area/UI/UX - content: - regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)area/UI/UX(?:`|)' - - - name: skip kind/documentation - content: - regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)kind/documentation(?:`|)' - - name: remove kind/documentation - content: - regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)kind/documentation(?:`|)' - - - name: skip client:linux - content: - regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)client:linux(?:`|)' - - name: remove client:linux - content: - regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)client:linux(?:`|)' - - - name: skip client:mac - content: - regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)client:mac(?:`|)' - - name: remove client:mac - content: - regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)client:mac(?:`|)' - - - name: skip client:win - content: - regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)client:win(?:`|)' - - name: remove client:win - content: - regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)client:win(?:`|)' - - - name: skip sig/Assistant - content: - regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/Assistant(?:`|)' - - name: remove sig/Assistant - content: - regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/Assistant(?:`|)' - - - name: skip sig/Data - content: - regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/Data(?:`|)' - - name: remove sig/Data - content: - regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/Data(?:`|)' - - - name: skip sig/MCP - content: - regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/MCP(?:`|)' - - name: remove sig/MCP - content: - regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/MCP(?:`|)' - - - name: skip sig/RAG - content: - regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)sig/RAG(?:`|)' - - name: remove sig/RAG - content: - regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)sig/RAG(?:`|)' - - - name: skip lgtm - content: - regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)lgtm(?:`|)' - - name: remove lgtm - content: - regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)lgtm(?:`|)' - - - name: skip License - content: - regexes: '[Ss]kip (?:[Ll]abels? |)(?:`|)License(?:`|)' - - name: remove License - content: - regexes: '[Rr]emove (?:[Ll]abels? |)(?:`|)License(?:`|)' - - # `Dev Team` - - name: Dev Team - mode: - add: [pull_request_target, issues] - author_association: - - COLLABORATOR - - # Area labels - - name: area/Connectivity - content: area/Connectivity - regexes: '代理|[Pp]roxy' - skip-if: - - skip all - - skip area/Connectivity - remove-if: - - remove all - - remove area/Connectivity - - - name: area/UI/UX - content: area/UI/UX - regexes: '界面|[Uu][Ii]|重叠|按钮|图标|组件|渲染|菜单|栏目|头像|主题|样式|[Cc][Ss][Ss]' - skip-if: - - skip all - - skip area/UI/UX - remove-if: - - remove all - - remove area/UI/UX - - # Kind labels - - name: kind/documentation - content: kind/documentation - regexes: '文档|教程|[Dd]oc(s|umentation)|[Rr]eadme' - skip-if: - - skip all - - skip kind/documentation - remove-if: - - remove all - - remove kind/documentation - - # Client labels - - name: client:linux - content: client:linux - regexes: '(?:[Ll]inux|[Uu]buntu|[Dd]ebian)' - skip-if: - - skip all - - skip client:linux - remove-if: - - remove all - - remove client:linux - - - name: client:mac - content: client:mac - regexes: '(?:[Mm]ac|[Mm]acOS|[Oo]SX)' - skip-if: - - skip all - - skip client:mac - remove-if: - - remove all - - remove client:mac - - - name: client:win - content: client:win - regexes: '(?:[Ww]in|[Ww]indows)' - skip-if: - - skip all - - skip client:win - remove-if: - - remove all - - remove client:win - - # SIG labels - - name: sig/Assistant - content: sig/Assistant - regexes: '快捷助手|[Aa]ssistant' - skip-if: - - skip all - - skip sig/Assistant - remove-if: - - remove all - - remove sig/Assistant - - - name: sig/Data - content: sig/Data - regexes: '[Ww]ebdav|坚果云|备份|同步|数据|Obsidian|Notion|Joplin|思源' - skip-if: - - skip all - - skip sig/Data - remove-if: - - remove all - - remove sig/Data - - - name: sig/MCP - content: sig/MCP - regexes: '[Mm][Cc][Pp]' - skip-if: - - skip all - - skip sig/MCP - remove-if: - - remove all - - remove sig/MCP - - - name: sig/RAG - content: sig/RAG - regexes: '知识库|[Rr][Aa][Gg]' - skip-if: - - skip all - - skip sig/RAG - remove-if: - - remove all - - remove sig/RAG - - # Other labels - - name: lgtm - content: lgtm - regexes: '(?:[Ll][Gg][Tt][Mm]|[Ll]ooks [Gg]ood [Tt]o [Mm]e)' - skip-if: - - skip all - - skip lgtm - remove-if: - - remove all - - remove lgtm - - - name: License - content: License - regexes: '(?:[Ll]icense|[Cc]opyright|[Mm][Ii][Tt]|[Aa]pache)' - skip-if: - - skip all - - skip License - remove-if: - - remove all - - remove License diff --git a/.github/workflows/delete-branch.yml b/.github/workflows/delete-branch.yml index 033ab4bfa0..99cad5602b 100644 --- a/.github/workflows/delete-branch.yml +++ b/.github/workflows/delete-branch.yml @@ -13,6 +13,7 @@ jobs: steps: - name: Delete merged branch uses: actions/github-script@v8 + continue-on-error: true with: script: | github.rest.git.deleteRef({ diff --git a/.github/workflows/issue-checker.yml b/.github/workflows/issue-checker.yml deleted file mode 100644 index 45da0f6b50..0000000000 --- a/.github/workflows/issue-checker.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: 'Issue Checker' - -on: - issues: - types: [opened, edited] - pull_request_target: - types: [opened, edited] - issue_comment: - types: [created, edited] - -permissions: - contents: read - issues: write - pull-requests: write - -jobs: - triage: - runs-on: ubuntu-latest - steps: - - uses: MaaAssistantArknights/issue-checker@v1.14 - with: - repo-token: '${{ secrets.GITHUB_TOKEN }}' - configuration-path: .github/issue-checker.yml - not-before: 2022-08-05T00:00:00Z - include-title: 1 From 4cca5210b9305bd8498d5846844333ab24e9377f Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Mon, 20 Oct 2025 10:51:53 +0800 Subject: [PATCH 5/9] chore: update @opeoginni/github-copilot-openai-compatible to version 0.1.19 and remove obsolete patch file (#10836) * chore: update @opeoginni/github-copilot-openai-compatible to version 0.1.19 and remove obsolete patch file - Updated the dependency version for @opeoginni/github-copilot-openai-compatible from 0.1.18 to 0.1.19 in package.json and yarn.lock. - Removed the obsolete patch file for the previous version to clean up the project. * recover --- ...nai-compatible-npm-0.1.18-3f65760532.patch | 44 ------------------- package.json | 2 +- yarn.lock | 22 +++------- 3 files changed, 6 insertions(+), 62 deletions(-) delete mode 100644 .yarn/patches/@opeoginni-github-copilot-openai-compatible-npm-0.1.18-3f65760532.patch diff --git a/.yarn/patches/@opeoginni-github-copilot-openai-compatible-npm-0.1.18-3f65760532.patch b/.yarn/patches/@opeoginni-github-copilot-openai-compatible-npm-0.1.18-3f65760532.patch deleted file mode 100644 index cebfdd00a5..0000000000 --- a/.yarn/patches/@opeoginni-github-copilot-openai-compatible-npm-0.1.18-3f65760532.patch +++ /dev/null @@ -1,44 +0,0 @@ -diff --git a/dist/index.js b/dist/index.js -index 53f411e55a4c9a06fd29bb4ab8161c4ad15980cd..71b91f196c8b886ed90dd237dec5625d79d5677e 100644 ---- a/dist/index.js -+++ b/dist/index.js -@@ -12676,10 +12676,13 @@ var OpenAIResponsesLanguageModel = class { - } - }); - } else if (value.item.type === "message") { -- controller.enqueue({ -- type: "text-end", -- id: value.item.id -- }); -+ // Fix for gpt-5-codex: use currentTextId to ensure text-end matches text-start -+ if (currentTextId) { -+ controller.enqueue({ -+ type: "text-end", -+ id: currentTextId -+ }); -+ } - currentTextId = null; - } else if (isResponseOutputItemDoneReasoningChunk(value)) { - const activeReasoningPart = activeReasoning[value.item.id]; -diff --git a/dist/index.mjs b/dist/index.mjs -index 7719264da3c49a66c2626082f6ccaae6e3ef5e89..090fd8cf142674192a826148428ed6a0c4a54e35 100644 ---- a/dist/index.mjs -+++ b/dist/index.mjs -@@ -12670,10 +12670,13 @@ var OpenAIResponsesLanguageModel = class { - } - }); - } else if (value.item.type === "message") { -- controller.enqueue({ -- type: "text-end", -- id: value.item.id -- }); -+ // Fix for gpt-5-codex: use currentTextId to ensure text-end matches text-start -+ if (currentTextId) { -+ controller.enqueue({ -+ type: "text-end", -+ id: currentTextId -+ }); -+ } - currentTextId = null; - } else if (isResponseOutputItemDoneReasoningChunk(value)) { - const activeReasoningPart = activeReasoning[value.item.id]; diff --git a/package.json b/package.json index d29784342a..7c937cc3b3 100644 --- a/package.json +++ b/package.json @@ -154,7 +154,7 @@ "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", - "@opeoginni/github-copilot-openai-compatible": "patch:@opeoginni/github-copilot-openai-compatible@npm%3A0.1.18#~/.yarn/patches/@opeoginni-github-copilot-openai-compatible-npm-0.1.18-3f65760532.patch", + "@opeoginni/github-copilot-openai-compatible": "0.1.19", "@playwright/test": "^1.52.0", "@radix-ui/react-context-menu": "^2.2.16", "@reduxjs/toolkit": "^2.2.5", diff --git a/yarn.lock b/yarn.lock index 171c74decd..5bae743113 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7307,27 +7307,15 @@ __metadata: languageName: node linkType: hard -"@opeoginni/github-copilot-openai-compatible@npm:0.1.18": - version: 0.1.18 - resolution: "@opeoginni/github-copilot-openai-compatible@npm:0.1.18" +"@opeoginni/github-copilot-openai-compatible@npm:0.1.19": + version: 0.1.19 + resolution: "@opeoginni/github-copilot-openai-compatible@npm:0.1.19" dependencies: "@ai-sdk/openai": "npm:^2.0.42" "@ai-sdk/openai-compatible": "npm:^1.0.19" "@ai-sdk/provider": "npm:^2.1.0-beta.4" "@ai-sdk/provider-utils": "npm:^3.0.10" - checksum: 10c0/31b87ed150883bbdd33a0203e45831859560fdf174f0285384fdcb1d01fc4a56ca15f31d648e8d6d3a2d4d5c6e327ddecbf422543eeefaa7e8fdd7dc2f2a3b08 - languageName: node - linkType: hard - -"@opeoginni/github-copilot-openai-compatible@patch:@opeoginni/github-copilot-openai-compatible@npm%3A0.1.18#~/.yarn/patches/@opeoginni-github-copilot-openai-compatible-npm-0.1.18-3f65760532.patch": - version: 0.1.18 - resolution: "@opeoginni/github-copilot-openai-compatible@patch:@opeoginni/github-copilot-openai-compatible@npm%3A0.1.18#~/.yarn/patches/@opeoginni-github-copilot-openai-compatible-npm-0.1.18-3f65760532.patch::version=0.1.18&hash=1cf9d0" - dependencies: - "@ai-sdk/openai": "npm:^2.0.42" - "@ai-sdk/openai-compatible": "npm:^1.0.19" - "@ai-sdk/provider": "npm:^2.1.0-beta.4" - "@ai-sdk/provider-utils": "npm:^3.0.10" - checksum: 10c0/cfffc031d2742068d20baed0e0ade6e9182c29ee7a425fa64262c04023ae75220b8b944ad2c9554255681e325fa1a70ec5e1f961b5f7370c871e70cbaeac0e79 + checksum: 10c0/dfb01832d7c704b2eb080fc09d31b07fc26e5ac4e648ce219dc0d80cf044ef3cae504427781ec2ce3c5a2459c9c81d043046a255642108d5b3de0f83f4a9f20a languageName: node linkType: hard @@ -13903,7 +13891,7 @@ __metadata: "@opentelemetry/sdk-trace-base": "npm:^2.0.0" "@opentelemetry/sdk-trace-node": "npm:^2.0.0" "@opentelemetry/sdk-trace-web": "npm:^2.0.0" - "@opeoginni/github-copilot-openai-compatible": "patch:@opeoginni/github-copilot-openai-compatible@npm%3A0.1.18#~/.yarn/patches/@opeoginni-github-copilot-openai-compatible-npm-0.1.18-3f65760532.patch" + "@opeoginni/github-copilot-openai-compatible": "npm:0.1.19" "@playwright/test": "npm:^1.52.0" "@radix-ui/react-context-menu": "npm:^2.2.16" "@reduxjs/toolkit": "npm:^2.2.5" From 528524b075fbf9d17aed7394a6e859eab932c650 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Mon, 20 Oct 2025 13:16:17 +0800 Subject: [PATCH 6/9] fix: Support right-click to paste file content into inputbar (#10730) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add right-click to paste text file content into input Implemented context menu functionality for text file attachments that allows users to right-click on a text file attachment to paste its content directly into the input field. Changes: - Added onContextMenu prop to CustomTag component for handling right-click events - Extended AttachmentPreview with onAttachmentContextMenu callback - Implemented appendTxtContentToInput function to read and paste text file content - Added clipboard support for copying file content - Integrated context menu handler in Inputbar component 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * use real path * 🐛 fix: clear txt attachment after paste * ✨ fix: improve attachment confirm flow * update i18n * 🎨 refactor: restyle confirm dialog * format code * refactor(ConfirmDialog): replace text buttons with icon buttons and remove i18n - Replace text-based cancel/confirm buttons with icon buttons for better visual clarity - Remove unused i18n translation hook as it's no longer needed - Adjust styling to accommodate new button layout --------- Co-authored-by: Claude Co-authored-by: icarus --- src/renderer/src/components/ConfirmDialog.tsx | 45 +++++++++ .../src/components/Tags/CustomTag.tsx | 5 +- src/renderer/src/i18n/locales/en-us.json | 1 + src/renderer/src/i18n/locales/zh-cn.json | 1 + src/renderer/src/i18n/locales/zh-tw.json | 1 + src/renderer/src/i18n/translate/el-gr.json | 1 + src/renderer/src/i18n/translate/es-es.json | 1 + src/renderer/src/i18n/translate/fr-fr.json | 1 + src/renderer/src/i18n/translate/ja-jp.json | 1 + src/renderer/src/i18n/translate/pt-pt.json | 1 + src/renderer/src/i18n/translate/ru-ru.json | 1 + .../pages/home/Inputbar/AttachmentPreview.tsx | 98 ++++++++++++++++--- .../src/pages/home/Inputbar/Inputbar.tsx | 51 +++++++++- 13 files changed, 192 insertions(+), 16 deletions(-) create mode 100644 src/renderer/src/components/ConfirmDialog.tsx diff --git a/src/renderer/src/components/ConfirmDialog.tsx b/src/renderer/src/components/ConfirmDialog.tsx new file mode 100644 index 0000000000..3f2313b178 --- /dev/null +++ b/src/renderer/src/components/ConfirmDialog.tsx @@ -0,0 +1,45 @@ +import { Button } from '@heroui/react' +import { CheckIcon, XIcon } from 'lucide-react' +import { FC } from 'react' +import { createPortal } from 'react-dom' + +interface Props { + x: number + y: number + message: string + onConfirm: () => void + onCancel: () => void +} + +const ConfirmDialog: FC = ({ x, y, message, onConfirm, onCancel }) => { + if (typeof document === 'undefined') { + return null + } + + return createPortal( + <> +
+
+
+
{message}
+
+ + +
+
+
+ , + document.body + ) +} + +export default ConfirmDialog diff --git a/src/renderer/src/components/Tags/CustomTag.tsx b/src/renderer/src/components/Tags/CustomTag.tsx index 3ecba381d4..4873a3ba7d 100644 --- a/src/renderer/src/components/Tags/CustomTag.tsx +++ b/src/renderer/src/components/Tags/CustomTag.tsx @@ -13,6 +13,7 @@ export interface CustomTagProps { closable?: boolean onClose?: () => void onClick?: MouseEventHandler + onContextMenu?: MouseEventHandler disabled?: boolean inactive?: boolean } @@ -27,6 +28,7 @@ const CustomTag: FC = ({ closable = false, onClose, onClick, + onContextMenu, disabled, inactive }) => { @@ -39,6 +41,7 @@ const CustomTag: FC = ({ $closable={closable} $clickable={!disabled && !!onClick} onClick={disabled ? undefined : onClick} + onContextMenu={disabled ? undefined : onContextMenu} style={{ ...(disabled && { cursor: 'not-allowed' }), ...style @@ -56,7 +59,7 @@ const CustomTag: FC = ({ )} ), - [actualColor, children, closable, disabled, icon, onClick, onClose, size, style] + [actualColor, children, closable, disabled, icon, onClick, onClose, onContextMenu, size, style] ) return tooltip ? ( diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 9dbdd32dae..6b58618ed3 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -538,6 +538,7 @@ "context": "Clear Context {{Command}}" }, "new_topic": "New Topic {{Command}}", + "paste_text_file_confirm": "Paste into input bar?", "pause": "Pause", "placeholder": "Type your message here, press {{key}} to send - @ to Select Model, / to Include Tools", "placeholder_without_triggers": "Type your message here, press {{key}} to send", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 9b146596fc..9156311575 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -538,6 +538,7 @@ "context": "清除上下文 {{Command}}" }, "new_topic": "新话题 {{Command}}", + "paste_text_file_confirm": "粘贴到输入框?", "pause": "暂停", "placeholder": "在这里输入消息,按 {{key}} 发送 - @ 选择模型, / 选择工具", "placeholder_without_triggers": "在这里输入消息,按 {{key}} 发送", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 57dc3480b5..ef4ac80d92 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -538,6 +538,7 @@ "context": "清除上下文 {{Command}}" }, "new_topic": "新話題 {{Command}}", + "paste_text_file_confirm": "[to be translated]:粘贴到输入框?", "pause": "暫停", "placeholder": "在此輸入您的訊息,按 {{key}} 傳送 - @ 選擇模型,/ 包含工具", "placeholder_without_triggers": "在此輸入您的訊息,按 {{key}} 傳送", diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 259d64c910..6bba2e3f38 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -538,6 +538,7 @@ "context": "Καθαρισμός ενδιάμεσων {{Command}}" }, "new_topic": "Νέο θέμα {{Command}}", + "paste_text_file_confirm": "[to be translated]:粘贴到输入框?", "pause": "Παύση", "placeholder": "Εισάγετε μήνυμα εδώ...", "placeholder_without_triggers": "Γράψτε το μήνυμά σας εδώ, πατήστε {{key}} για αποστολή", diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 8b9b863e04..8b0d6cb1e2 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -538,6 +538,7 @@ "context": "Limpiar contexto {{Command}}" }, "new_topic": "Nuevo tema {{Command}}", + "paste_text_file_confirm": "[to be translated]:粘贴到输入框?", "pause": "Pausar", "placeholder": "Escribe aquí tu mensaje...", "placeholder_without_triggers": "Escribe tu mensaje aquí, presiona {{key}} para enviar", diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index f40ffc8163..36a3c01591 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -538,6 +538,7 @@ "context": "Effacer le contexte {{Command}}" }, "new_topic": "Nouveau sujet {{Command}}", + "paste_text_file_confirm": "[to be translated]:粘贴到输入框?", "pause": "Pause", "placeholder": "Entrez votre message ici...", "placeholder_without_triggers": "Tapez votre message ici, appuyez sur {{key}} pour envoyer", diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index 03c5218c31..9d0884b2a3 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -538,6 +538,7 @@ "context": "コンテキストをクリア {{Command}}" }, "new_topic": "新しいトピック {{Command}}", + "paste_text_file_confirm": "[to be translated]:粘贴到输入框?", "pause": "一時停止", "placeholder": "ここにメッセージを入力し、{{key}} を押して送信...", "placeholder_without_triggers": "ここにメッセージを入力し、{{key}} を押して送信...", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 4ea6d25f56..5145f74b14 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -538,6 +538,7 @@ "context": "Limpar contexto {{Command}}" }, "new_topic": "Novo tópico {{Command}}", + "paste_text_file_confirm": "[to be translated]:粘贴到输入框?", "pause": "Pausar", "placeholder": "Digite sua mensagem aqui...", "placeholder_without_triggers": "Escreve a tua mensagem aqui, pressiona {{key}} para enviar", diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 8f751d8d9d..0d2448a923 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -538,6 +538,7 @@ "context": "Очистить контекст {{Command}}" }, "new_topic": "Новый топик {{Command}}", + "paste_text_file_confirm": "[to be translated]:粘贴到输入框?", "pause": "Остановить", "placeholder": "Введите ваше сообщение здесь, нажмите {{key}} для отправки...", "placeholder_without_triggers": "Напишите сообщение здесь, нажмите {{key}} для отправки", diff --git a/src/renderer/src/pages/home/Inputbar/AttachmentPreview.tsx b/src/renderer/src/pages/home/Inputbar/AttachmentPreview.tsx index 27fe399c8e..b6962c0f95 100644 --- a/src/renderer/src/pages/home/Inputbar/AttachmentPreview.tsx +++ b/src/renderer/src/pages/home/Inputbar/AttachmentPreview.tsx @@ -12,6 +12,7 @@ import { GlobalOutlined, LinkOutlined } from '@ant-design/icons' +import ConfirmDialog from '@renderer/components/ConfirmDialog' import CustomTag from '@renderer/components/Tags/CustomTag' import { useAttachment } from '@renderer/hooks/useAttachment' import FileManager from '@renderer/services/FileManager' @@ -19,12 +20,14 @@ import { FileMetadata } from '@renderer/types' import { formatFileSize } from '@renderer/utils' import { Flex, Image, Tooltip } from 'antd' import { isEmpty } from 'lodash' -import { FC, useState } from 'react' +import { FC, MouseEvent, useState } from 'react' +import { useTranslation } from 'react-i18next' import styled from 'styled-components' interface Props { files: FileMetadata[] setFiles: (files: FileMetadata[]) => void + onAttachmentContextMenu?: (file: FileMetadata, event: MouseEvent) => void } const MAX_FILENAME_DISPLAY_LENGTH = 20 @@ -133,24 +136,91 @@ export const FileNameRender: FC<{ file: FileMetadata }> = ({ file }) => { ) } -const AttachmentPreview: FC = ({ files, setFiles }) => { +const AttachmentPreview: FC = ({ files, setFiles, onAttachmentContextMenu }) => { + const { t } = useTranslation() + const [contextMenu, setContextMenu] = useState<{ + file: FileMetadata + x: number + y: number + } | null>(null) + + const handleContextMenu = async (file: FileMetadata, event: MouseEvent) => { + event.preventDefault() + event.stopPropagation() + + // 获取被点击元素的位置 + const target = event.currentTarget as HTMLElement + const rect = target.getBoundingClientRect() + + // 计算对话框位置:附件标签的中心位置 + const x = rect.left + rect.width / 2 + const y = rect.top + + try { + const isText = await window.api.file.isTextFile(file.path) + if (!isText) { + setContextMenu(null) + return + } + + setContextMenu({ + file, + x, + y + }) + } catch (error) { + setContextMenu(null) + } + } + + const handleConfirm = () => { + if (contextMenu && onAttachmentContextMenu) { + // Create a synthetic mouse event for the callback + const syntheticEvent = { + preventDefault: () => {}, + stopPropagation: () => {} + } as MouseEvent + onAttachmentContextMenu(contextMenu.file, syntheticEvent) + } + setContextMenu(null) + } + + const handleCancel = () => { + setContextMenu(null) + } + if (isEmpty(files)) { return null } return ( - - {files.map((file) => ( - setFiles(files.filter((f) => f.id !== file.id))}> - - - ))} - + <> + + {files.map((file) => ( + setFiles(files.filter((f) => f.id !== file.id))} + onContextMenu={(event) => { + void handleContextMenu(file, event) + }}> + + + ))} + + + {contextMenu && ( + + )} + ) } diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 282f62656d..0206053eb8 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -293,6 +293,53 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = } }, [isTranslating, text, getLanguageByLangcode, targetLanguage, setTimeoutTimer, resizeTextArea]) + const appendTxtContentToInput = useCallback( + async (file: FileType, event: React.MouseEvent) => { + event.preventDefault() + event.stopPropagation() + + try { + const targetPath = file.path + const content = await window.api.file.readExternal(targetPath, true) + try { + await navigator.clipboard.writeText(content) + } catch (clipboardError) { + logger.warn('Failed to copy txt attachment content to clipboard:', clipboardError as Error) + } + + setText((prev) => { + if (!prev) { + return content + } + + const needsSeparator = !prev.endsWith('\n') + return needsSeparator ? `${prev}\n${content}` : prev + content + }) + + setFiles((prev) => prev.filter((currentFile) => currentFile.id !== file.id)) + + setTimeoutTimer( + 'appendTxtAttachment', + () => { + const textArea = textareaRef.current?.resizableTextArea?.textArea + if (textArea) { + const end = textArea.value.length + textArea.focus() + textArea.setSelectionRange(end, end) + } + + resizeTextArea(true) + }, + 0 + ) + } catch (error) { + logger.warn('Failed to append txt attachment content:', error as Error) + window.toast.error(t('chat.input.file_error')) + } + }, + [resizeTextArea, setTimeoutTimer, t] + ) + const handleKeyDown = (event: React.KeyboardEvent) => { // 按下Tab键,自动选中${xxx} if (event.key === 'Tab' && inputFocus) { @@ -831,7 +878,9 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = id="inputbar" className={classNames('inputbar-container', inputFocus && 'focus', isFileDragging && 'file-dragging')} ref={containerRef}> - {files.length > 0 && } + {files.length > 0 && ( + + )} {selectedKnowledgeBases.length > 0 && ( Date: Mon, 20 Oct 2025 15:41:34 +0800 Subject: [PATCH 7/9] feat: new painting provider: intel ovms (#10570) * new painting provider: intel ovms Signed-off-by: Ma, Kejiang Signed-off-by: Kejiang Ma * cherryin -> cherryai Signed-off-by: Kejiang Ma * ovms painting only valid when ovms is running Signed-off-by: Kejiang Ma * fix: painting(ovms) still appear while ovms is not running after rebase Signed-off-by: Kejiang Ma * fix warning in PaintingRoute Signed-off-by: Kejiang Ma * add ovms_paintings in migrate config 163 --------- Signed-off-by: Ma, Kejiang Signed-off-by: Kejiang Ma --- src/renderer/src/hooks/usePaintings.ts | 2 + src/renderer/src/pages/paintings/OvmsPage.tsx | 692 ++++++++++++++++++ .../pages/paintings/PaintingsRoutePage.tsx | 36 +- .../src/pages/paintings/config/ovmsConfig.tsx | 129 ++++ src/renderer/src/store/migrate.ts | 4 +- src/renderer/src/store/paintings.ts | 4 +- src/renderer/src/types/index.ts | 16 +- 7 files changed, 868 insertions(+), 15 deletions(-) create mode 100644 src/renderer/src/pages/paintings/OvmsPage.tsx create mode 100644 src/renderer/src/pages/paintings/config/ovmsConfig.tsx diff --git a/src/renderer/src/hooks/usePaintings.ts b/src/renderer/src/hooks/usePaintings.ts index ebf73e97c5..e7c7f22359 100644 --- a/src/renderer/src/hooks/usePaintings.ts +++ b/src/renderer/src/hooks/usePaintings.ts @@ -14,6 +14,7 @@ export function usePaintings() { const aihubmix_image_upscale = useAppSelector((state) => state.paintings.aihubmix_image_upscale) const openai_image_generate = useAppSelector((state) => state.paintings.openai_image_generate) const openai_image_edit = useAppSelector((state) => state.paintings.openai_image_edit) + const ovms_paintings = useAppSelector((state) => state.paintings.ovms_paintings) const dispatch = useAppDispatch() return { @@ -27,6 +28,7 @@ export function usePaintings() { aihubmix_image_upscale, openai_image_generate, openai_image_edit, + ovms_paintings, addPainting: (namespace: keyof PaintingsState, painting: PaintingAction) => { dispatch(addPainting({ namespace, painting })) return painting diff --git a/src/renderer/src/pages/paintings/OvmsPage.tsx b/src/renderer/src/pages/paintings/OvmsPage.tsx new file mode 100644 index 0000000000..5cd42e3052 --- /dev/null +++ b/src/renderer/src/pages/paintings/OvmsPage.tsx @@ -0,0 +1,692 @@ +import { PlusOutlined, RedoOutlined } from '@ant-design/icons' +import { loggerService } from '@logger' +import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar' +import { HStack } from '@renderer/components/Layout' +import Scrollbar from '@renderer/components/Scrollbar' +import { isMac } from '@renderer/config/constant' +import { getProviderLogo } from '@renderer/config/providers' +import { LanguagesEnum } from '@renderer/config/translate' +import { usePaintings } from '@renderer/hooks/usePaintings' +import { useAllProviders } from '@renderer/hooks/useProvider' +import { useRuntime } from '@renderer/hooks/useRuntime' +import { useSettings } from '@renderer/hooks/useSettings' +import { getProviderLabel } from '@renderer/i18n/label' +import FileManager from '@renderer/services/FileManager' +import { translateText } from '@renderer/services/TranslateService' +import { useAppDispatch } from '@renderer/store' +import { setGenerating } from '@renderer/store/runtime' +import type { FileMetadata, OvmsPainting } from '@renderer/types' +import { getErrorMessage, uuid } from '@renderer/utils' +import { Avatar, Button, Input, InputNumber, Select, Slider, Switch, Tooltip } from 'antd' +import TextArea from 'antd/es/input/TextArea' +import { Info } from 'lucide-react' +import type { FC } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useLocation, useNavigate } from 'react-router-dom' +import styled from 'styled-components' + +import SendMessageButton from '../home/Inputbar/SendMessageButton' +import { SettingHelpLink, SettingTitle } from '../settings' +import Artboard from './components/Artboard' +import PaintingsList from './components/PaintingsList' +import { + type ConfigItem, + createDefaultOvmsPainting, + createOvmsConfig, + DEFAULT_OVMS_PAINTING, + getOvmsModels, + OVMS_MODELS +} from './config/ovmsConfig' + +const logger = loggerService.withContext('OvmsPage') + +const OvmsPage: FC<{ Options: string[] }> = ({ Options }) => { + const { addPainting, removePainting, updatePainting, ovms_paintings } = usePaintings() + const ovmsPaintings = useMemo(() => ovms_paintings || [], [ovms_paintings]) + const [painting, setPainting] = useState(ovmsPaintings[0] || DEFAULT_OVMS_PAINTING) + const [currentImageIndex, setCurrentImageIndex] = useState(0) + const [isLoading, setIsLoading] = useState(false) + const [abortController, setAbortController] = useState(null) + const [spaceClickCount, setSpaceClickCount] = useState(0) + const [isTranslating, setIsTranslating] = useState(false) + const [availableModels, setAvailableModels] = useState>([]) + const [ovmsConfig, setOvmsConfig] = useState([]) + + const { t } = useTranslation() + const providers = useAllProviders() + const providerOptions = Options.map((option) => { + const provider = providers.find((p) => p.id === option) + if (provider) { + return { + label: getProviderLabel(provider.id), + value: provider.id + } + } else { + return { + label: 'Unknown Provider', + value: undefined + } + } + }) + const dispatch = useAppDispatch() + const { generating } = useRuntime() + const navigate = useNavigate() + const location = useLocation() + const { autoTranslateWithSpace } = useSettings() + const spaceClickTimer = useRef(null) + const ovmsProvider = providers.find((p) => p.id === 'ovms')! + + const getNewPainting = useCallback(() => { + if (availableModels.length > 0) { + return createDefaultOvmsPainting(availableModels) + } + return { + ...DEFAULT_OVMS_PAINTING, + id: uuid() + } + }, [availableModels]) + + const textareaRef = useRef(null) + + // Load available models on component mount + useEffect(() => { + const loadModels = () => { + try { + // Get OVMS provider to access its models + const ovmsProvider = providers.find((p) => p.id === 'ovms') + const providerModels = ovmsProvider?.models || [] + + // Filter and format models for image generation + const filteredModels = getOvmsModels(providerModels) + setAvailableModels(filteredModels) + setOvmsConfig(createOvmsConfig(filteredModels)) + + // Update painting if it doesn't have a valid model + if (filteredModels.length > 0 && !filteredModels.some((m) => m.value === painting.model)) { + const defaultPainting = createDefaultOvmsPainting(filteredModels) + setPainting(defaultPainting) + } + } catch (error) { + logger.error(`Failed to load OVMS models: ${error}`) + // Use default config if loading fails + setOvmsConfig(createOvmsConfig()) + } + } + + loadModels() + }, [providers, painting.model]) // Re-run when providers change + + const updatePaintingState = (updates: Partial) => { + const updatedPainting = { ...painting, ...updates } + setPainting(updatedPainting) + updatePainting('ovms_paintings', updatedPainting) + } + + const handleError = (error: unknown) => { + if (error instanceof Error && error.name !== 'AbortError') { + window.modal.error({ + content: getErrorMessage(error), + centered: true + }) + } + } + + const downloadImages = async (urls: string[]) => { + const downloadedFiles = await Promise.all( + urls.map(async (url) => { + try { + if (!url?.trim()) { + logger.error('Image URL is empty, possibly due to prohibited prompt') + window.toast.warning(t('message.empty_url')) + return null + } + return await window.api.file.download(url) + } catch (error) { + logger.error(`Failed to download image: ${error}`) + if ( + error instanceof Error && + (error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL')) + ) { + window.toast.warning(t('message.empty_url')) + } + return null + } + }) + ) + + return downloadedFiles.filter((file): file is FileMetadata => file !== null) + } + + const onGenerate = async () => { + if (painting.files.length > 0) { + const confirmed = await window.modal.confirm({ + content: t('paintings.regenerate.confirm'), + centered: true + }) + + if (!confirmed) return + await FileManager.deleteFiles(painting.files) + } + + const prompt = textareaRef.current?.resizableTextArea?.textArea?.value || '' + updatePaintingState({ prompt }) + + if (!painting.model || !painting.prompt) { + return + } + + const controller = new AbortController() + setAbortController(controller) + setIsLoading(true) + dispatch(setGenerating(true)) + + try { + // Prepare request body for OVMS + const requestBody = { + model: painting.model, + prompt: painting.prompt, + size: painting.size || '512x512', + num_inference_steps: painting.num_inference_steps || 4, + rng_seed: painting.rng_seed || 0 + } + + logger.info('OVMS API request:', requestBody) + + const response = await fetch(`${ovmsProvider.apiHost}images/generations`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(requestBody), + signal: controller.signal + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ error: { message: `HTTP ${response.status}` } })) + logger.error('OVMS API error:', errorData) + throw new Error(errorData.error?.message || 'Image generation failed') + } + + const data = await response.json() + logger.info('OVMS API response:', data) + + // Handle base64 encoded images + if (data.data && data.data.length > 0) { + const base64s = data.data.filter((item) => item.b64_json).map((item) => item.b64_json) + + if (base64s.length > 0) { + const validFiles = await Promise.all( + base64s.map(async (base64) => { + return await window.api.file.saveBase64Image(base64) + }) + ) + await FileManager.addFiles(validFiles) + updatePaintingState({ files: validFiles, urls: validFiles.map((file) => file.name) }) + } + + // Handle URL-based images if available + const urls = data.data.filter((item) => item.url).map((item) => item.url) + + if (urls.length > 0) { + const validFiles = await downloadImages(urls) + await FileManager.addFiles(validFiles) + updatePaintingState({ files: validFiles, urls }) + } + } + } catch (error: unknown) { + handleError(error) + } finally { + setIsLoading(false) + dispatch(setGenerating(false)) + setAbortController(null) + } + } + + const handleRetry = async (painting: OvmsPainting) => { + setIsLoading(true) + try { + const validFiles = await downloadImages(painting.urls) + await FileManager.addFiles(validFiles) + updatePaintingState({ files: validFiles, urls: painting.urls }) + } catch (error) { + handleError(error) + } finally { + setIsLoading(false) + } + } + + const onCancel = () => { + abortController?.abort() + } + + const nextImage = () => { + setCurrentImageIndex((prev) => (prev + 1) % painting.files.length) + } + + const prevImage = () => { + setCurrentImageIndex((prev) => (prev - 1 + painting.files.length) % painting.files.length) + } + + const handleAddPainting = () => { + const newPainting = addPainting('ovms_paintings', getNewPainting()) + updatePainting('ovms_paintings', newPainting) + setPainting(newPainting) + return newPainting + } + + const onDeletePainting = (paintingToDelete: OvmsPainting) => { + if (paintingToDelete.id === painting.id) { + const currentIndex = ovmsPaintings.findIndex((p) => p.id === paintingToDelete.id) + + if (currentIndex > 0) { + setPainting(ovmsPaintings[currentIndex - 1]) + } else if (ovmsPaintings.length > 1) { + setPainting(ovmsPaintings[1]) + } + } + + removePainting('ovms_paintings', paintingToDelete) + } + + const translate = async () => { + if (isTranslating) { + return + } + + if (!painting.prompt) { + return + } + + try { + setIsTranslating(true) + const translatedText = await translateText(painting.prompt, LanguagesEnum.enUS) + updatePaintingState({ prompt: translatedText }) + } catch (error) { + logger.error('Translation failed:', error as Error) + } finally { + setIsTranslating(false) + } + } + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (autoTranslateWithSpace && event.key === ' ') { + setSpaceClickCount((prev) => prev + 1) + + if (spaceClickTimer.current) { + clearTimeout(spaceClickTimer.current) + } + + spaceClickTimer.current = setTimeout(() => { + setSpaceClickCount(0) + }, 200) + + if (spaceClickCount === 2) { + setSpaceClickCount(0) + setIsTranslating(true) + translate() + } + } + } + + const handleProviderChange = (providerId: string) => { + const routeName = location.pathname.split('/').pop() + if (providerId !== routeName) { + navigate('../' + providerId, { replace: true }) + } + } + + // Handle random seed generation + const handleRandomSeed = () => { + const randomSeed = Math.floor(Math.random() * 2147483647) + updatePaintingState({ rng_seed: randomSeed }) + return randomSeed + } + + // Render configuration form + const renderConfigForm = (item: ConfigItem) => { + switch (item.type) { + case 'select': { + const isDisabled = typeof item.disabled === 'function' ? item.disabled(item, painting) : item.disabled + const selectOptions = + typeof item.options === 'function' + ? item.options(item, painting).map((option) => ({ + ...option, + label: option.label.startsWith('paintings.') ? t(option.label) : option.label + })) + : item.options?.map((option) => ({ + ...option, + label: option.label.startsWith('paintings.') ? t(option.label) : option.label + })) + + return ( + updatePaintingState({ [item.key!]: e.target.value })} + suffix={ + item.key === 'rng_seed' ? ( + + ) : ( + item.suffix + ) + } + /> + ) + case 'inputNumber': + return ( + updatePaintingState({ [item.key!]: v })} + /> + ) + case 'textarea': + return ( +