From e5bb31037c563370932ffabe1b457684da5d15b4 Mon Sep 17 00:00:00 2001 From: yyhhyyyyyy Date: Wed, 6 Aug 2025 15:18:13 +0800 Subject: [PATCH 01/22] fix: add AlertDialogDescription to resolve accessibility warning (#706) --- src/renderer/src/components/settings/PromptSetting.vue | 4 ++++ src/renderer/src/i18n/en-US/promptSetting.json | 1 + src/renderer/src/i18n/fa-IR/promptSetting.json | 1 + src/renderer/src/i18n/fr-FR/promptSetting.json | 1 + src/renderer/src/i18n/ja-JP/promptSetting.json | 1 + src/renderer/src/i18n/ko-KR/promptSetting.json | 1 + src/renderer/src/i18n/ru-RU/promptSetting.json | 1 + src/renderer/src/i18n/zh-CN/promptSetting.json | 1 + src/renderer/src/i18n/zh-HK/promptSetting.json | 1 + src/renderer/src/i18n/zh-TW/promptSetting.json | 1 + 10 files changed, 13 insertions(+) diff --git a/src/renderer/src/components/settings/PromptSetting.vue b/src/renderer/src/components/settings/PromptSetting.vue index f69cb0211..777fcbe46 100644 --- a/src/renderer/src/components/settings/PromptSetting.vue +++ b/src/renderer/src/components/settings/PromptSetting.vue @@ -135,6 +135,9 @@ {{ t('promptSetting.confirmDelete', { name: prompt.name }) }} + {{ + t('promptSetting.confirmDeleteDescription') + }} {{ t('common.cancel') }} @@ -514,6 +517,7 @@ import { AlertDialogContent, AlertDialogHeader, AlertDialogTitle, + AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction diff --git a/src/renderer/src/i18n/en-US/promptSetting.json b/src/renderer/src/i18n/en-US/promptSetting.json index 12dfd44ed..ddf4c556f 100644 --- a/src/renderer/src/i18n/en-US/promptSetting.json +++ b/src/renderer/src/i18n/en-US/promptSetting.json @@ -45,6 +45,7 @@ "uploadSuccess": "Upload successful", "uploadedCount": "Uploaded {count} files", "confirmDelete": "Are you sure you want to delete prompt \"{name}\"?", + "confirmDeleteDescription": "This action cannot be undone. The prompt will be permanently deleted.", "deleteSuccess": "Prompt Deleted", "deleteFailed": "Failed to Delete Prompt", "inactive": "Inactive", diff --git a/src/renderer/src/i18n/fa-IR/promptSetting.json b/src/renderer/src/i18n/fa-IR/promptSetting.json index b6a8a61d6..0f1a04aa6 100644 --- a/src/renderer/src/i18n/fa-IR/promptSetting.json +++ b/src/renderer/src/i18n/fa-IR/promptSetting.json @@ -45,6 +45,7 @@ "uploadSuccess": "بارگذاری موفق", "uploadedCount": "{count} پرونده بارگذاری شد", "confirmDelete": "آیا مطمئن هستید که می‌خواهید دستورکار \"{name}\" را پاک کنید؟", + "confirmDeleteDescription": "این عمل قابل بازگشت نیست. دستورکار به طور دائم پاک خواهد شد.", "deleteSuccess": "پاک کردن موفق", "deleteFailed": "پاک کردن ناموفق", "inactive": "غیرفعال", diff --git a/src/renderer/src/i18n/fr-FR/promptSetting.json b/src/renderer/src/i18n/fr-FR/promptSetting.json index 72a77814e..144bd5249 100644 --- a/src/renderer/src/i18n/fr-FR/promptSetting.json +++ b/src/renderer/src/i18n/fr-FR/promptSetting.json @@ -45,6 +45,7 @@ "uploadSuccess": "Téléchargement réussi", "uploadedCount": "{count} fichiers téléchargés", "confirmDelete": "Êtes-vous sûr de vouloir supprimer le prompt \"{name}\" ?", + "confirmDeleteDescription": "Cette action ne peut pas être annulée. Le prompt sera définitivement supprimé.", "deleteSuccess": "Suppression réussie", "deleteFailed": "Échec de la suppression", "inactive": "Inactif", diff --git a/src/renderer/src/i18n/ja-JP/promptSetting.json b/src/renderer/src/i18n/ja-JP/promptSetting.json index 2300db011..3e68cac7e 100644 --- a/src/renderer/src/i18n/ja-JP/promptSetting.json +++ b/src/renderer/src/i18n/ja-JP/promptSetting.json @@ -45,6 +45,7 @@ "uploadSuccess": "アップロード成功", "uploadedCount": "{count}個のファイルがアップロードされました", "confirmDelete": "プロンプト「{name}」を削除してもよろしいですか?", + "confirmDeleteDescription": "この操作は元に戻すことができません。プロンプトは完全に削除されます。", "deleteSuccess": "削除に成功しました", "deleteFailed": "削除に失敗しました", "inactive": "無効", diff --git a/src/renderer/src/i18n/ko-KR/promptSetting.json b/src/renderer/src/i18n/ko-KR/promptSetting.json index 8029a5747..6745afdf7 100644 --- a/src/renderer/src/i18n/ko-KR/promptSetting.json +++ b/src/renderer/src/i18n/ko-KR/promptSetting.json @@ -45,6 +45,7 @@ "uploadSuccess": "업로드 성공", "uploadedCount": "{count}개 파일 업로드됨", "confirmDelete": "프롬프트 \"{name}\"을(를) 삭제하시겠습니까?", + "confirmDeleteDescription": "이 작업은 되돌릴 수 없습니다. 프롬프트가 영구적으로 삭제됩니다.", "deleteSuccess": "삭제 성공", "deleteFailed": "삭제 실패", "inactive": "비활성", diff --git a/src/renderer/src/i18n/ru-RU/promptSetting.json b/src/renderer/src/i18n/ru-RU/promptSetting.json index b239ba06a..d1d028a94 100644 --- a/src/renderer/src/i18n/ru-RU/promptSetting.json +++ b/src/renderer/src/i18n/ru-RU/promptSetting.json @@ -45,6 +45,7 @@ "uploadSuccess": "Загрузка успешна", "uploadedCount": "Загружено {count} файлов", "confirmDelete": "Вы уверены, что хотите удалить промпт \"{name}\"?", + "confirmDeleteDescription": "Это действие нельзя отменить. Промпт будет удален навсегда.", "deleteSuccess": "Удаление успешно", "deleteFailed": "Ошибка удаления", "inactive": "Неактивен", diff --git a/src/renderer/src/i18n/zh-CN/promptSetting.json b/src/renderer/src/i18n/zh-CN/promptSetting.json index 81d016658..31991f383 100644 --- a/src/renderer/src/i18n/zh-CN/promptSetting.json +++ b/src/renderer/src/i18n/zh-CN/promptSetting.json @@ -45,6 +45,7 @@ "uploadSuccess": "上传成功", "uploadedCount": "已上传 {count} 个文件", "confirmDelete": "确定要删除提示词「{name}」吗?", + "confirmDeleteDescription": "此操作不可撤销,删除后将无法恢复该提示词。", "deleteSuccess": "删除成功", "deleteFailed": "删除失败", "inactive": "已禁用", diff --git a/src/renderer/src/i18n/zh-HK/promptSetting.json b/src/renderer/src/i18n/zh-HK/promptSetting.json index a5f828d77..1fbc254fa 100644 --- a/src/renderer/src/i18n/zh-HK/promptSetting.json +++ b/src/renderer/src/i18n/zh-HK/promptSetting.json @@ -45,6 +45,7 @@ "uploadSuccess": "上傳成功", "uploadedCount": "已上傳 {count} 個文件", "confirmDelete": "確定要刪除提示詞「{name}」嗎?", + "confirmDeleteDescription": "此操作不可撤銷,刪除後將無法恢復該提示詞。", "deleteSuccess": "刪除成功", "deleteFailed": "刪除失敗", "inactive": "已停用", diff --git a/src/renderer/src/i18n/zh-TW/promptSetting.json b/src/renderer/src/i18n/zh-TW/promptSetting.json index a5f828d77..1fbc254fa 100644 --- a/src/renderer/src/i18n/zh-TW/promptSetting.json +++ b/src/renderer/src/i18n/zh-TW/promptSetting.json @@ -45,6 +45,7 @@ "uploadSuccess": "上傳成功", "uploadedCount": "已上傳 {count} 個文件", "confirmDelete": "確定要刪除提示詞「{name}」嗎?", + "confirmDeleteDescription": "此操作不可撤銷,刪除後將無法恢復該提示詞。", "deleteSuccess": "刪除成功", "deleteFailed": "刪除失敗", "inactive": "已停用", From 0c528c808e4f37452bea298fc77cf00ca976c250 Mon Sep 17 00:00:00 2001 From: yyhhyyyyyy Date: Wed, 6 Aug 2025 20:59:17 +0800 Subject: [PATCH 02/22] fix: resolve focus flicker when creating new windows with Ctrl+Shift+N (#707) --- src/main/presenter/tabPresenter.ts | 7 ++ src/main/presenter/windowPresenter/index.ts | 77 +++++++++++++++++++-- 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/src/main/presenter/tabPresenter.ts b/src/main/presenter/tabPresenter.ts index 3b5d2504a..27053a0e7 100644 --- a/src/main/presenter/tabPresenter.ts +++ b/src/main/presenter/tabPresenter.ts @@ -10,6 +10,7 @@ import { getContextMenuLabels } from '@shared/i18n' import { app } from 'electron' import { addWatermarkToNativeImage } from '@/lib/watermark' import { stitchImagesVertically } from '@/lib/scrollCapture' +import { presenter } from './' export class TabPresenter implements ITabPresenter { // 全局标签页实例存储 @@ -533,6 +534,12 @@ export class TabPresenter implements ITabPresenter { // Once did-finish-load happens, emit first content loaded webContents.once('did-finish-load', () => { eventBus.sendToMain(WINDOW_EVENTS.FIRST_CONTENT_LOADED, windowId) + setTimeout(() => { + const windowPresenter = presenter.windowPresenter as any + if (windowPresenter && typeof windowPresenter.focusActiveTab === 'function') { + windowPresenter.focusActiveTab(windowId, 'initial') + } + }, 300) }) } diff --git a/src/main/presenter/windowPresenter/index.ts b/src/main/presenter/windowPresenter/index.ts index a041f2f26..60686d86d 100644 --- a/src/main/presenter/windowPresenter/index.ts +++ b/src/main/presenter/windowPresenter/index.ts @@ -26,6 +26,16 @@ export class WindowPresenter implements IWindowPresenter { private isQuitting: boolean = false // 当前获得焦点的窗口 ID (内部记录) private focusedWindowId: number | null = null + // 窗口聚焦状态管理 + private windowFocusStates = new Map< + number, + { + lastFocusTime: number + shouldFocus: boolean + isNewWindow: boolean + hasInitialFocus: boolean + } + >() constructor(configPresenter: ConfigPresenter) { this.windows = new Map() @@ -310,7 +320,6 @@ export class WindowPresenter implements IWindowPresenter { this.handleWindowRestore(targetWindow.id).catch((error) => { console.error(`Error handling restore logic after showing window ${targetWindow!.id}:`, error) }) - this.focusActiveTab(targetWindow.id) } /** @@ -375,19 +384,69 @@ export class WindowPresenter implements IWindowPresenter { return focusedWindow ? focusedWindow.id === windowId : false } + /** + * 检查是否应该聚焦标签页 + * @param windowId 窗口 ID + * @param reason 聚焦原因 + */ + private shouldFocusTab( + windowId: number, + reason: 'focus' | 'restore' | 'show' | 'initial' + ): boolean { + const state = this.windowFocusStates.get(windowId) + if (!state) { + return true + } + const now = Date.now() + if (now - state.lastFocusTime < 100) { + console.log(`Skipping focus for window ${windowId}, too frequent (${reason})`) + return false + } + switch (reason) { + case 'initial': + return !state.hasInitialFocus + case 'focus': + return state.shouldFocus + case 'restore': + case 'show': + return state.isNewWindow || state.shouldFocus + default: + return false + } + } + /** * 将焦点传递给指定窗口的活动标签页 * @param windowId 窗口 ID + * @param reason 聚焦原因 */ - private focusActiveTab(windowId: number): void { + public focusActiveTab( + windowId: number, + reason: 'focus' | 'restore' | 'show' | 'initial' = 'focus' + ): void { + if (!this.shouldFocusTab(windowId, reason)) { + return + } try { setTimeout(async () => { const tabPresenterInstance = presenter.tabPresenter as TabPresenter const tabsData = await tabPresenterInstance.getWindowTabsData(windowId) const activeTab = tabsData.find((tab) => tab.isActive) if (activeTab) { - console.log(`Focusing active tab ${activeTab.id} in window ${windowId}`) + console.log( + `Focusing active tab ${activeTab.id} in window ${windowId} (reason: ${reason})` + ) await tabPresenterInstance.switchTab(activeTab.id) + const state = this.windowFocusStates.get(windowId) + if (state) { + state.lastFocusTime = Date.now() + if (reason === 'initial') { + state.hasInitialFocus = true + } + if (reason === 'focus' || reason === 'initial') { + state.isNewWindow = false + } + } } }, 50) } catch (error) { @@ -531,6 +590,13 @@ export class WindowPresenter implements IWindowPresenter { const windowId = shellWindow.id this.windows.set(windowId, shellWindow) // 将窗口实例存入 Map + this.windowFocusStates.set(windowId, { + lastFocusTime: 0, + shouldFocus: true, + isNewWindow: true, + hasInitialFocus: false + }) + shellWindowState.manage(shellWindow) // 管理窗口状态 // 应用内容保护设置 @@ -558,7 +624,7 @@ export class WindowPresenter implements IWindowPresenter { if (!shellWindow.isDestroyed()) { shellWindow.webContents.send('window-focused', windowId) } - this.focusActiveTab(windowId) + this.focusActiveTab(windowId, 'focus') }) // 窗口失去焦点 @@ -606,7 +672,7 @@ export class WindowPresenter implements IWindowPresenter { this.handleWindowRestore(windowId).catch((error) => { console.error(`Error handling restore logic for window ${windowId}:`, error) }) - this.focusActiveTab(windowId) + this.focusActiveTab(windowId, 'restore') eventBus.sendToMain(WINDOW_EVENTS.WINDOW_RESTORED, windowId) } shellWindow.on('restore', handleRestore) @@ -717,6 +783,7 @@ export class WindowPresenter implements IWindowPresenter { shellWindow.removeListener('restore', handleRestore) this.windows.delete(windowIdBeingClosed) // 从 Map 中移除 + this.windowFocusStates.delete(windowIdBeingClosed) shellWindowState.unmanage() // 停止管理窗口状态 eventBus.sendToMain(WINDOW_EVENTS.WINDOW_CLOSED, windowIdBeingClosed) console.log( From 68fc0e2c2fafc8a010927feed64221de16ac8ed6 Mon Sep 17 00:00:00 2001 From: hllshiro <40970081+hllshiro@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:34:18 +0800 Subject: [PATCH 03/22] feat: enhance window management by implementing main window ID handling (#709) --- src/main/presenter/windowPresenter/index.ts | 24 ++++++++++----------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/main/presenter/windowPresenter/index.ts b/src/main/presenter/windowPresenter/index.ts index 60686d86d..c35c25262 100644 --- a/src/main/presenter/windowPresenter/index.ts +++ b/src/main/presenter/windowPresenter/index.ts @@ -26,6 +26,8 @@ export class WindowPresenter implements IWindowPresenter { private isQuitting: boolean = false // 当前获得焦点的窗口 ID (内部记录) private focusedWindowId: number | null = null + // 主窗口 id + private mainWindowId: number | null = null // 窗口聚焦状态管理 private windowFocusStates = new Map< number, @@ -722,18 +724,11 @@ export class WindowPresenter implements IWindowPresenter { // 如果应用不是正在退出过程中... if (!this.isQuitting) { // 实现隐藏到托盘逻辑: - // 在非 macOS 平台,或在 macOS 上且未配置关闭时退出 (或还有其他窗口),阻止默认关闭行为,仅隐藏窗口。 - const isLastWindow = this.windows.size === 1 - // 检查 macOS 配置:关闭时是否退出应用 - const shouldQuitOnClose = - process.platform === 'darwin' ? this.configPresenter.getCloseToQuit() : false - - // 是否应该阻止默认关闭并隐藏: - // - 非 macOS 平台总是阻止 (实现隐藏到托盘)。 - // - macOS 平台:如果不是最后一个窗口,或虽然是最后一个窗口但配置为不退出时,阻止。 - const shouldPreventDefault = - process.platform !== 'darwin' || - (process.platform === 'darwin' && (!isLastWindow || !shouldQuitOnClose)) + // 1. 如果是其他窗口,直接关闭 + // 2. 如果是主窗口,判断配置是否允许关闭 + // shouldPreventDefault: true隐藏, false关闭 + const shouldQuitOnClose = this.configPresenter.getCloseToQuit() + const shouldPreventDefault = windowId === this.mainWindowId && !shouldQuitOnClose if (shouldPreventDefault) { console.log(`Window ${windowId}: Preventing default close behavior, hiding instead.`) @@ -760,7 +755,6 @@ export class WindowPresenter implements IWindowPresenter { shellWindow.hide() } } else { - // 如果是 macOS,且是最后一个窗口,且配置为关闭时退出,或者 isQuitting 为 true // 允许默认关闭行为。这将触发 'closed' 事件。 console.log( `Window ${windowId}: Allowing default close behavior (app is quitting or macOS last window configured to quit).` @@ -880,6 +874,10 @@ export class WindowPresenter implements IWindowPresenter { } console.log(`Shell window ${windowId} created successfully.`) + + if (this.mainWindowId == null) { + this.mainWindowId = windowId // 如果这是第一个窗口,设置为主窗口 ID + } return windowId // 返回新创建窗口的 ID } From 0d650093f7860124443efc14d9159d7d5b881dee Mon Sep 17 00:00:00 2001 From: tomsun28 Date: Fri, 8 Aug 2025 18:26:15 +0800 Subject: [PATCH 04/22] docs: update zhipu developer doc website link (#715) Co-authored-by: gongchao --- src/main/presenter/configPresenter/providers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/presenter/configPresenter/providers.ts b/src/main/presenter/configPresenter/providers.ts index 69320a80c..32c2df736 100644 --- a/src/main/presenter/configPresenter/providers.ts +++ b/src/main/presenter/configPresenter/providers.ts @@ -314,7 +314,7 @@ export const DEFAULT_PROVIDERS: LLM_PROVIDER_BASE[] = [ websites: { official: 'https://open.bigmodel.cn/', apiKey: 'https://open.bigmodel.cn/usercenter/apikeys', - docs: 'https://open.bigmodel.cn/dev/howuse/introduction', + docs: 'https://docs.bigmodel.cn', models: 'https://open.bigmodel.cn/modelcenter/square', defaultBaseUrl: 'https://open.bigmodel.cn/api/paas/v4/' } From 1909d8bbb2985cfe1bfaae684a95f1cb0c03c27a Mon Sep 17 00:00:00 2001 From: duskzhen Date: Fri, 8 Aug 2025 22:08:50 +0800 Subject: [PATCH 05/22] refactor: better translate (#716) * chore: en-us i18n * chore(i18n): polish ja-JP translations across UI; keep chat.input.placeholder unchanged * chore(i18n): polish fr-FR translations; keep chat.input.placeholder unchanged * chore(i18n): refine fr-FR MCP & Settings copy; idiomatic, concise, brand-consistent * chore(i18n): polish ru-RU translations across UI; keep chat.input.placeholder unchanged * chore(i18n): polish fa-IR translations across UI; keep chat.input.placeholder unchanged * chore: fix format * chore: fix i18n * chore: lock rolldown-vite version --- package.json | 4 +- src/renderer/src/i18n/en-US/chat.json | 6 +- src/renderer/src/i18n/en-US/components.json | 12 ++-- src/renderer/src/i18n/en-US/contextMenu.json | 2 +- src/renderer/src/i18n/en-US/dialog.json | 4 +- src/renderer/src/i18n/en-US/mcp.json | 56 +++++++-------- .../src/i18n/en-US/promptSetting.json | 2 +- src/renderer/src/i18n/en-US/settings.json | 50 ++++++------- src/renderer/src/i18n/en-US/thread.json | 12 ++-- src/renderer/src/i18n/en-US/update.json | 2 +- src/renderer/src/i18n/fa-IR/about.json | 2 +- src/renderer/src/i18n/fa-IR/artifacts.json | 6 +- src/renderer/src/i18n/fa-IR/chat.json | 6 +- src/renderer/src/i18n/fa-IR/common.json | 6 +- src/renderer/src/i18n/fa-IR/components.json | 14 ++-- src/renderer/src/i18n/fa-IR/contextMenu.json | 4 +- src/renderer/src/i18n/fa-IR/dialog.json | 18 ++--- src/renderer/src/i18n/fa-IR/routes.json | 2 +- src/renderer/src/i18n/fa-IR/thread.json | 14 ++-- src/renderer/src/i18n/fa-IR/toolCall.json | 2 +- src/renderer/src/i18n/fa-IR/update.json | 2 +- src/renderer/src/i18n/fr-FR/about.json | 2 +- src/renderer/src/i18n/fr-FR/artifacts.json | 6 +- src/renderer/src/i18n/fr-FR/chat.json | 6 +- src/renderer/src/i18n/fr-FR/common.json | 6 +- src/renderer/src/i18n/fr-FR/components.json | 14 ++-- src/renderer/src/i18n/fr-FR/contextMenu.json | 4 +- src/renderer/src/i18n/fr-FR/dialog.json | 16 ++--- src/renderer/src/i18n/fr-FR/mcp.json | 72 +++++++++---------- src/renderer/src/i18n/fr-FR/routes.json | 2 +- src/renderer/src/i18n/fr-FR/settings.json | 18 ++--- src/renderer/src/i18n/fr-FR/thread.json | 16 ++--- src/renderer/src/i18n/fr-FR/toolCall.json | 2 +- src/renderer/src/i18n/fr-FR/update.json | 2 +- src/renderer/src/i18n/ja-JP/about.json | 8 +-- src/renderer/src/i18n/ja-JP/artifacts.json | 6 +- src/renderer/src/i18n/ja-JP/chat.json | 6 +- src/renderer/src/i18n/ja-JP/common.json | 6 +- src/renderer/src/i18n/ja-JP/components.json | 16 ++--- src/renderer/src/i18n/ja-JP/contextMenu.json | 4 +- src/renderer/src/i18n/ja-JP/dialog.json | 16 ++--- src/renderer/src/i18n/ja-JP/mcp.json | 58 +++++++-------- src/renderer/src/i18n/ja-JP/routes.json | 2 +- src/renderer/src/i18n/ja-JP/settings.json | 18 ++--- src/renderer/src/i18n/ja-JP/thread.json | 20 +++--- src/renderer/src/i18n/ja-JP/toolCall.json | 2 +- src/renderer/src/i18n/ja-JP/update.json | 4 +- src/renderer/src/i18n/ru-RU/about.json | 4 +- src/renderer/src/i18n/ru-RU/artifacts.json | 6 +- src/renderer/src/i18n/ru-RU/chat.json | 6 +- src/renderer/src/i18n/ru-RU/common.json | 6 +- src/renderer/src/i18n/ru-RU/components.json | 16 ++--- src/renderer/src/i18n/ru-RU/contextMenu.json | 22 +++--- src/renderer/src/i18n/ru-RU/dialog.json | 18 ++--- src/renderer/src/i18n/ru-RU/routes.json | 2 +- src/renderer/src/i18n/ru-RU/thread.json | 18 ++--- src/renderer/src/i18n/ru-RU/toolCall.json | 2 +- src/renderer/src/i18n/ru-RU/update.json | 4 +- 58 files changed, 331 insertions(+), 331 deletions(-) diff --git a/package.json b/package.json index dfc5d4f99..65e657daf 100644 --- a/package.json +++ b/package.json @@ -148,7 +148,7 @@ "tailwindcss-animate": "^1.0.7", "tippy.js": "^6.3.7", "typescript": "^5.8.3", - "vite": "npm:rolldown-vite@latest", + "vite": "npm:rolldown-vite@7.0.12", "vite-plugin-monaco-editor-esm": "^2.0.2", "vite-plugin-vue-devtools": "^8.0.0", "vite-svg-loader": "^5.1.0", @@ -178,7 +178,7 @@ }, "pnpm": { "overrides": { - "vite": "npm:rolldown-vite@latest" + "vite": "npm:rolldown-vite@7.0.12" }, "onlyBuiltDependencies": [ "@tailwindcss/oxide", diff --git a/src/renderer/src/i18n/en-US/chat.json b/src/renderer/src/i18n/en-US/chat.json index 346ba2ab8..d647a1773 100644 --- a/src/renderer/src/i18n/en-US/chat.json +++ b/src/renderer/src/i18n/en-US/chat.json @@ -4,14 +4,14 @@ "fileArea": "File Area", "inputArea": "Input Area", "functionSwitch": "Function Switch", - "fileSelect": "File Select", - "pasteFiles": "Support copy and paste files", + "fileSelect": "Select File", + "pasteFiles": "Paste files from clipboard", "dropFiles": "Drop files here", "promptFilesAdded": "Prompt Files Added", "promptFilesAddedDesc": "Successfully added {count} files", "promptFilesError": "File Processing Error", "promptFilesErrorDesc": "{count} files failed to process", - "historyPlaceholder": "(Press Tab to fill)", + "historyPlaceholder": "(Press Tab to autocomplete)", "rateLimitQueue": "Queue {count}", "rateLimitWait": "Wait {seconds}s", "rateLimitQueueTooltip": "{count} requests in queue, {interval}s interval", diff --git a/src/renderer/src/i18n/en-US/components.json b/src/renderer/src/i18n/en-US/components.json index d0754b7fd..19c5b84c7 100644 --- a/src/renderer/src/i18n/en-US/components.json +++ b/src/renderer/src/i18n/en-US/components.json @@ -23,14 +23,14 @@ "granted": "Permission granted", "denied": "Permission denied", "type": { - "read": "Read permissions", - "write": "Write permissions", - "all": "Full permissions" + "read": "Read Access", + "write": "Write Access", + "all": "Full Access" }, "description": { - "read": "Allow '{toolName}' of '{serverName}' to perform read operations?", - "write": "Allow '{toolName}' of '{serverName}' to perform write operations?", - "all": "Allow '{toolName}' of '{serverName}' to perform read and write operations?" + "read": "Allow '{toolName}' from '{serverName}' to perform read operations?", + "write": "Allow '{toolName}' from '{serverName}' to perform write operations?", + "all": "Allow '{toolName}' from '{serverName}' to perform read and write operations?" } } } diff --git a/src/renderer/src/i18n/en-US/contextMenu.json b/src/renderer/src/i18n/en-US/contextMenu.json index 58caa7ea6..7b418f2ef 100644 --- a/src/renderer/src/i18n/en-US/contextMenu.json +++ b/src/renderer/src/i18n/en-US/contextMenu.json @@ -9,7 +9,7 @@ "title": "Ask AI", "question": "Question", "answer": "Answer", - "error": "AI answer failed" + "error": "AI response failed" }, "copy": "Copy", "paste": "Paste", diff --git a/src/renderer/src/i18n/en-US/dialog.json b/src/renderer/src/i18n/en-US/dialog.json index c65da5c18..90e6623fe 100644 --- a/src/renderer/src/i18n/en-US/dialog.json +++ b/src/renderer/src/i18n/en-US/dialog.json @@ -18,10 +18,10 @@ "confirm": "Clear" }, "fork": { - "title": "Create Branch Conversation", + "title": "Create Branch", "description": "This will copy all messages from the first message to the current message into a new conversation where you can continue the dialogue.", "confirm": "Create Branch", - "tag": "Branches" + "tag": "Branch" }, "error": { "title": "Error" diff --git a/src/renderer/src/i18n/en-US/mcp.json b/src/renderer/src/i18n/en-US/mcp.json index eb8dd8572..2e328d3e2 100644 --- a/src/renderer/src/i18n/en-US/mcp.json +++ b/src/renderer/src/i18n/en-US/mcp.json @@ -4,7 +4,7 @@ "noToolsAvailable": "No tools available", "selectToolToDebug": "Select a tool to debug", "dialogDescription": "Debug and test tools provided by MCP servers", - "toolsCount": "tools", + "toolsCount": "{count} tools", "availableTools": "Available Tools", "toolList": "Tool List", "functionDescription": "Function Description", @@ -41,25 +41,25 @@ "annotations": "Annotations", "invalidJsonFormat": "JSON format is incorrect" }, - "addServer": "Add a server", + "addServer": "Add Server", "addServerDialog": { "description": "Configure a new MCP server", - "title": "Add a server" + "title": "Add Server" }, "confirmDelete": { "cancel": "Cancel", - "confirm": "delete", + "confirm": "Delete", "description": "Are you sure you want to delete the server {name}? \nThis operation cannot be cancelled.", "title": "Confirm deletion" }, "confirmRemoveServer": "Are you sure you want to delete the server {name}? \nThis operation cannot be cancelled.", "default": "default", - "deleteServer": "Delete the server", + "deleteServer": "Delete Server", "description": "Manage and configure MCP (Model Context Protocol) servers and tools", - "editServer": "Edit the server", + "editServer": "Edit Server", "editServerDialog": { "description": "Edit MCP server configuration", - "title": "Edit the server" + "title": "Edit Server" }, "enableToAccess": "Please enable MCP to access configuration options.", "enabledDescription": "Enable or disable MCP functionality and tools.", @@ -67,17 +67,17 @@ "isDefault": "Default server", "noServersFound": "Server not found", "removeDefault": "Remove default", - "removeServer": "Delete the server", + "removeServer": "Remove Server", "removeServerDialog": { - "title": "Delete the server" + "title": "Delete Server" }, - "resetConfirm": "recover", + "resetConfirm": "Restore", "resetConfirmDescription": "This action restores all default servers while retaining your customized servers. \nAny modifications to the default server will be lost.", "resetConfirmTitle": "Restore default service", "resetToDefault": "Restore default service", "running": "Running", "serverForm": { - "add": "Add to", + "add": "Add", "args": "Arguments", "argsPlaceholder": "Enter parameters, separated by spaces", "argsRequired": "Parameters cannot be empty", @@ -102,8 +102,8 @@ "envPlaceholder": "Enter environment variables in JSON format", "icon": "Icon", "iconPlaceholder": "Enter icon", - "icons": "Icon", - "iconsPlaceholder": "Enter icon", + "icons": "Icons", + "iconsPlaceholder": "Enter icons", "jsonConfig": "JSON configuration", "jsonConfigExample": "JSON configuration example", "jsonConfigIntro": "You can directly paste the JSON configuration or choose to configure the server manually.", @@ -115,7 +115,7 @@ "parseError": "Parsing error", "parseSuccess": "Configuration parsed successfully", "skipToManual": "Skip to manual configuration", - "submit": "submit", + "submit": "Submit", "folders": "Folder List", "addFolder": "Add Folder", "selectFolder": "Select Folder", @@ -141,7 +141,7 @@ "title": "MCP Settings", "inmemory": { "Artifacts": { - "desc": "Make your DeepChat output diversified Artifacts content", + "desc": "Produce richer Artifacts in DeepChat", "name": "Artifacts" }, "bochaSearch": { @@ -149,11 +149,11 @@ "name": "Bocha Search" }, "buildInFileSystem": { - "desc": "Allows DeepChat to interact with the local file system.", + "desc": "Allow DeepChat to interact with the local file system.", "name": "File System" }, "imageServer": { - "desc": "Enables any model in DeepChat to understand and generate images.", + "desc": "Enable any model in DeepChat to understand and generate images.", "name": "Image Service" }, "braveSearch": { @@ -161,7 +161,7 @@ "name": "Brave Search" }, "powerpack": { - "desc": "Provides any large model with enhanced capabilities like time queries, web scraping, and secure code execution.", + "desc": "Provide models with time queries, web browsing, and secure code execution.", "name": "Power Pack" }, "difyKnowledge": { @@ -177,36 +177,36 @@ "desc": "FastGPT knowledge base search service, can search FastGPT knowledge base content" }, "deepchat-inmemory/custom-prompts-server": { - "desc": "DeepChat built-in custom prompt word service", + "desc": "DeepChat built-in custom prompts service", "name": "Custom Prompts" }, "deepchat-inmemory/deep-research-server": { - "desc": "DeepChat built-in in-depth research service based on Bocha Search (note that long context models are required to be used, and models with insufficient context may fail)", + "desc": "DeepChat built-in deep research powered by Bocha Search. Long-context models are recommended.", "name": "DeepResearch" }, "deepchat-inmemory/auto-prompting-server": { "name": "Auto Template Prompting", - "desc": "Automatically selects the most suitable custom prompt based on user input and intelligently fills the prompt template." + "desc": "Automatically select the best custom prompt based on input and fill the template intelligently." }, "deepchat-inmemory/conversation-search-server": { "name": "Conversation History Search", - "desc": "DeepChat built-in conversation history search service, can search historical conversation records and message contents" + "desc": "DeepChat built-in conversation history search for past chats and messages." }, "builtinKnowledge": { - "desc": "DeepChat built-in knowledge base search service, which can search the content of DeepChat built-in knowledge base", + "desc": "DeepChat built-in knowledge base search for DeepChat docs and guides.", "name": "Built-in knowledge base search" }, "deepchat-inmemory/meeting-server": { "name": "Multi-Agent Meetings", - "desc": "DeepChat's built-in meeting service enables hosting and facilitating multi-agent discussions." + "desc": "DeepChat built-in meetings to host multi-agent discussions." }, "deepchat/apple-server": { - "desc": "Let the model operate macOS's system functions such as calendar, contacts, emails, maps, memos, reminders and other systems", - "name": "macOS system assistant" + "desc": "Let models operate macOS apps like Calendar, Contacts, Mail, Maps, Notes, and Reminders.", + "name": "macOS System Assistant" } }, "prompts": { - "noPromptsAvailable": "No Prompts available", + "noPromptsAvailable": "No Prompts Available", "noDescription": "No description yet", "selectPrompt": "Details for the selected prompt will be shown here.", "parameters": "Parameters", @@ -223,7 +223,7 @@ "noResourcesAvailable": "No Resources Available", "selectResource": "Select a resource to view its content.", "loading": "Loading...", - "loadContent": "View Content", + "loadContent": "Load Content", "pleaseSelect": "Click to view resource details.", "dialogDescription": "Browse and view resources provided by MCP servers" }, diff --git a/src/renderer/src/i18n/en-US/promptSetting.json b/src/renderer/src/i18n/en-US/promptSetting.json index ddf4c556f..a7969fb1b 100644 --- a/src/renderer/src/i18n/en-US/promptSetting.json +++ b/src/renderer/src/i18n/en-US/promptSetting.json @@ -37,7 +37,7 @@ "required": "Required", "characters": "Characters", "fileManagement": "File Management", - "uploadFromDevice": "Upload from Your Device", + "uploadFromDevice": "Upload from device", "uploadFromDeviceDesc": "Supports text, documents, CSV, and more.", "uploadedFiles": "Uploaded Files", "noFiles": "No files", diff --git a/src/renderer/src/i18n/en-US/settings.json b/src/renderer/src/i18n/en-US/settings.json index 6c3e8cb1a..913be048f 100644 --- a/src/renderer/src/i18n/en-US/settings.json +++ b/src/renderer/src/i18n/en-US/settings.json @@ -33,13 +33,12 @@ "theme": "Theme", "themeSelect": "Select theme", "closeToQuit": "Exit app when closing window", - "contentProtection": "Screen Protection", "contentProtectionDialogTitle": "Confirm Screen Protection Change", - "contentProtectionEnableDesc": "Enabling screen protection prevents screen sharing software from capturing the DeepChat window, protecting your content privacy. Note that this feature will not completely hide all interfaces. Please use this feature responsibly and in compliance with regulations. Additionally, not all screen sharing software supports this feature. Additionally, some environments may leave a black window.", - "contentProtectionDisableDesc": "Disabling screen protection will allow screen sharing software to capture the DeepChat window.", + "contentProtectionEnableDesc": "Prevent screen sharing apps from capturing the DeepChat window to help protect your privacy. Not all apps honor this setting; in some environments a black window may remain.", + "contentProtectionDisableDesc": "Allow screen sharing apps to capture the DeepChat window.", "contentProtectionRestartNotice": "Changing this setting will restart the application. Do you want to continue?", - "soundEnabled": "Enable Sound effects", - "copyWithCotEnabled": "Copy including COT details", + "soundEnabled": "Enable Sound Effects", + "copyWithCotEnabled": "Copy COT Details", "loggingEnabled": "Enable Logging", "loggingDialogTitle": "Confirm Logging Setting Change", "loggingEnableDesc": "Enabling logging will help us diagnose issues and improve the application. Log files may contain sensitive information.", @@ -50,8 +49,9 @@ "newChat": "Create a new chat", "title": "Shortcut key settings" }, - "notifications": "System Notification", - "notificationsDesc": "When DeepChat is not in the foreground, if a session is generated, a system notification will be sent" + "notifications": "System Notifications", + "notificationsDesc": "When DeepChat is not in the foreground, if a session is generated, a system notification will be sent", + "contentProtection": "Screen capture protection" }, "data": { "title": "Data Settings", @@ -108,10 +108,10 @@ }, "artifacts": { "description": "Enabling the Artifacts feature allows the AI to generate richer content", - "title": "Artifacts effects" + "title": "Artifacts" }, - "addModel": "Add a model", - "configureModel": "Configuration model", + "addModel": "Add Model", + "configureModel": "Configure Model", "modelList": "Model List", "provider": "Service provider", "providerSetting": "Service provider settings", @@ -120,7 +120,7 @@ "cancel": "Cancel", "contextLength": { "description": "Set the context length that the model can handle", - "label": "Context length" + "label": "Context Length" }, "description": "Please note that this configuration is only valid for the current model and will not affect other models. Please modify it with caution. Incorrect parameters may cause the model to not work properly.", "functionCall": { @@ -172,8 +172,8 @@ "useModelDefault": "Use model default configuration", "currentUsingModelDefault": "Currently using model default configuration", "temperature": { - "description": "Control the randomness of the output. Most models are 0-1, and some support between 0-2. The higher the higher the randomness.", - "label": "temperature" + "description": "Control the randomness of the output. Most models are 0-1, and some support between 0-2. Higher values increase randomness.", + "label": "Temperature" }, "title": "Custom model parameters", "type": { @@ -181,9 +181,9 @@ "label": "Model Type", "options": { "chat": "Language Model", - "embedding": "Embed Model", - "imageGeneration": "Image generation model", - "rerank": "Reorder the model" + "embedding": "Embedding Model", + "imageGeneration": "Image Generation Model", + "rerank": "Rerank Model" } }, "validation": { @@ -303,7 +303,7 @@ "serverList": "Server List", "totalServers": "Total Servers", "addServer": "Add Server", - "autoStart": "Self-start", + "autoStart": "Auto Start", "githubCopilotAuth": "GitHub Copilot Auth", "githubCopilotConnected": "GitHub Copilot Connected", "githubCopilotNotConnected": "GitHub Copilot Not Connected", @@ -318,7 +318,7 @@ "disconnected": "Disconnected successfully", "disconnectFailed": "Disconnect failed", "keyStatus": { - "remaining": "Remaining amount", + "remaining": "Remaining Quota", "usage": "Used" }, "refreshingModels": "Refreshing...", @@ -328,10 +328,10 @@ }, "anthropicApiKeyTip": "Please go to Anthropic Console to get your API Key", "anthropicConnected": "Anthropic connected", - "anthropicNotConnected": "Anthropic Not connected", - "anthropicOAuthTip": "Click Authorize DeepChat to access your Anthropic account", + "anthropicNotConnected": "Anthropic not connected", + "anthropicOAuthTip": "Click to authorize DeepChat to access your Anthropic account", "oauthLogin": "OAuth Login", - "authMethod": "Certification method", + "authMethod": "Authentication Method", "authMethodPlaceholder": "Select authentication method", "apiKeyLabel": "API Key", "apiUrlLabel": "API URL", @@ -493,7 +493,7 @@ "type": "Server Type", "typePlaceholder": "Select server type", "typeStdio": "Standard input and output (Stdio)", - "typeSse": "Server Send Events (SSE)", + "typeSse": "Server-Sent Events (SSE)", "typeInMemory": "In-Memory", "typeHttp": "Streaming HTTP Requests (HTTP)", "baseUrl": "Base URL", @@ -537,14 +537,14 @@ "browseMarketplace": "Browse MCP Marketplace", "imageModel": "Choose a vision model", "customHeadersParseError": "Custom Header parsing failed", - "customHeaders": "Custom request header", + "customHeaders": "Custom Request Headers", "invalidKeyValueFormat": "Incorrect request header format, please check whether the input is correct.", "npmRegistry": "Custom NPM Registry", "npmRegistryPlaceholder": "Set up a custom NPM Registry, leave the system to automatically select the fastest one", "browseHigress": "View Higress MCP Marketplace", "selectFolderError": "Folder selection error", - "folders": "Folders allowed to access", - "addFolder": "Add folder", + "folders": "Allowed Folders", + "addFolder": "Add Folder", "noFoldersSelected": "No folders were selected", "useE2B": "Enable E2B Sandbox", "e2bDescription": "Execute Python code using E2B sandbox", diff --git a/src/renderer/src/i18n/en-US/thread.json b/src/renderer/src/i18n/en-US/thread.json index f429d8531..92a170bf6 100644 --- a/src/renderer/src/i18n/en-US/thread.json +++ b/src/renderer/src/i18n/en-US/thread.json @@ -11,21 +11,21 @@ "toolbar": { "save": "Save", "cancel": "Cancel", - "previousVariant": "Previous Response", - "nextVariant": "Next Response", - "copy": "Copy Response as Markdown", + "previousVariant": "Previous Variant", + "nextVariant": "Next Variant", + "copy": "Copy as Markdown", "copyImage": "Copy as Image", - "copyImageWithLongPress": "Copy as image (long press to capture from top)", + "copyImageWithLongPress": "Copy as Image (long-press to capture from top)", "copyFromTopSuccess": "Full conversation image copied", "capturing": "Capturing...", "retry": "Regenerate", - "fork": "Fork Conversation", + "fork": "Branch to New Chat", "edit": "Edit message", "delete": "Delete message" }, "message": { "toolbar": { - "save": "save" + "save": "Save" } }, "export": { diff --git a/src/renderer/src/i18n/en-US/update.json b/src/renderer/src/i18n/en-US/update.json index aaeb60f66..ab8b5b475 100644 --- a/src/renderer/src/i18n/en-US/update.json +++ b/src/renderer/src/i18n/en-US/update.json @@ -4,7 +4,7 @@ "releaseDate": "Release Date", "releaseNotes": "Release Notes", "later": "Later", - "githubDownload": "Github Download", + "githubDownload": "GitHub Download", "netdiskDownload": "Cloud Download", "checkUpdate": "Check for Updates", "downloading": "Downloading", diff --git a/src/renderer/src/i18n/fa-IR/about.json b/src/renderer/src/i18n/fa-IR/about.json index 1b965e7e2..1c6138e0b 100644 --- a/src/renderer/src/i18n/fa-IR/about.json +++ b/src/renderer/src/i18n/fa-IR/about.json @@ -1,7 +1,7 @@ { "title": "دیپ‌چت", "description": "دیپ‌چت یک کارخواه هوش مصنوعی چندسکویی است که به دسترسی آسان‌تر مردم به هوش مصنوعی اختصاص دارد.", - "website": "بازدید از وبگاه ما", + "website": "باز کردن وب‌سایت", "deviceInfo": { "title": "اطلاعات دستگاه", "platform": "سکو", diff --git a/src/renderer/src/i18n/fa-IR/artifacts.json b/src/renderer/src/i18n/fa-IR/artifacts.json index f75226889..b80b0010a 100644 --- a/src/renderer/src/i18n/fa-IR/artifacts.json +++ b/src/renderer/src/i18n/fa-IR/artifacts.json @@ -30,11 +30,11 @@ "htmlPreviewTitle": "پیش‌نمایش اچ‌تی‌ام‌ال", "svgPreviewTitle": "پیش‌نمایش اس‌وی‌جی", "copy": "رونوشت", - "copySuccess": "رونوشت موفق", + "copySuccess": "رونوشت شد", "copySuccessDesc": "محتوا به بریده‌دان رونویسی شد", - "copyFailed": "رونوشت ناموفق", + "copyFailed": "رونوشت نشد", "copyFailedDesc": "رونویسی محتوا به بریده‌دان انجام نشد", "copyAsImage": "رونوشت به‌صورت تصویر", "copyImageSuccessDesc": "تصویر به بریده‌دان رونویسی شد", - "copyImageFailedDesc": "رونویسی تصویر به بریده‌‌‌‌دان انجام نشد" + "copyImageFailedDesc": "رونویسی تصویر به بریده‌دان انجام نشد" } diff --git a/src/renderer/src/i18n/fa-IR/chat.json b/src/renderer/src/i18n/fa-IR/chat.json index 508897b54..a409eb803 100644 --- a/src/renderer/src/i18n/fa-IR/chat.json +++ b/src/renderer/src/i18n/fa-IR/chat.json @@ -4,14 +4,14 @@ "fileArea": "ناحیه پرونده", "inputArea": "ناحیه ورودی", "functionSwitch": "کلید کارکرد", - "fileSelect": "انتخاب پرونده", - "pasteFiles": "پشتیبانی از رونوشت و چسباندن پرونده‌ها", + "fileSelect": "انتخاب فایل", + "pasteFiles": "چسباندن فایل‌ها از بریده‌دان", "dropFiles": "پرونده‌ها را اینجا رها کنید", "promptFilesAdded": "پرونده‌ها افزوده شد", "promptFilesAddedDesc": "{count} پرونده با موفقیت افزوده شد", "promptFilesError": "خطا در پردازش پرونده", "promptFilesErrorDesc": "پردازش {count} پرونده ناموفق بود", - "historyPlaceholder": "(برای پر کردن برگه را فشار دهید)", + "historyPlaceholder": "(برای تکمیل، Tab را فشار دهید)", "rateLimitQueue": "صف {count}", "rateLimitWait": "انتظار {seconds}ث", "rateLimitQueueTooltip": "{count} درخواست در صف، فاصله {interval} ثانیه", diff --git a/src/renderer/src/i18n/fa-IR/common.json b/src/renderer/src/i18n/fa-IR/common.json index 350c51318..1c587ab57 100644 --- a/src/renderer/src/i18n/fa-IR/common.json +++ b/src/renderer/src/i18n/fa-IR/common.json @@ -2,11 +2,11 @@ "enabled": "روشن", "disabled": "خاموش", "loading": "در حال بارگذاری...", - "copySuccess": "رونوشت موفق", + "copySuccess": "رونوشت شد", "copySuccessDesc": "محتوا در بریده‌دان رونویسی شد", - "copyImageSuccess": "رونوشت موفق", + "copyImageSuccess": "رونوشت شد", "copyImageSuccessDesc": "تصویر در بریده‌دان رونویسی شد", - "copyFailed": "رونوشت ناموفق", + "copyFailed": "رونوشت نشد", "copyFailedDesc": "رونویسی محتوا در بریده‌دان انجام نشد", "copyCode": "رونوشت کد", "copy": "رونوشت", diff --git a/src/renderer/src/i18n/fa-IR/components.json b/src/renderer/src/i18n/fa-IR/components.json index c2831ee41..1566148ff 100644 --- a/src/renderer/src/i18n/fa-IR/components.json +++ b/src/renderer/src/i18n/fa-IR/components.json @@ -13,7 +13,7 @@ }, "messageBlockAction": { "continue": "ادامه", - "continued": "ادامه یافت" + "continued": "ادامه شد" }, "messageBlockPermissionRequest": { "title": "مجوز مورد نیاز", @@ -23,14 +23,14 @@ "granted": "مجوز داده شد", "denied": "مجوز رد شد", "type": { - "read": "مجوزهای خواندن", - "write": "مجوزهای نوشتن", - "all": "تمام مجوزها" + "read": "دسترسی خواندن", + "write": "دسترسی نوشتن", + "all": "دسترسی کامل" }, "description": { - "all": "اجازه دهید \"{نام ابزار\" از \"{servername}\" عملیات خواندن و نوشتن را انجام دهد؟", - "read": "اجازه دهید \"{نام ابزار\" از \"{servername}\" انجام عملیات خواندن را انجام دهد؟", - "write": "اجازه می دهد \"{ابزار\" از \"{servername}\" انجام عملیات نوشتن؟" + "read": "به '{toolName}' از '{serverName}' اجازه می‌دهید عملیات خواندن انجام دهد؟", + "write": "به '{toolName}' از '{serverName}' اجازه می‌دهید عملیات نوشتن انجام دهد؟", + "all": "به '{toolName}' از '{serverName}' اجازه می‌دهید بخواند و بنویسد؟" } } } diff --git a/src/renderer/src/i18n/fa-IR/contextMenu.json b/src/renderer/src/i18n/fa-IR/contextMenu.json index 4eeae17a3..6a79eda01 100644 --- a/src/renderer/src/i18n/fa-IR/contextMenu.json +++ b/src/renderer/src/i18n/fa-IR/contextMenu.json @@ -1,8 +1,8 @@ { "translate": { "title": "ترجمه", - "original": "اصلی", - "translated": "ترجمه", + "original": "متن اصلی", + "translated": "ترجمه‌شده", "error": "ترجمه ناموفق بود" }, "askAI": { diff --git a/src/renderer/src/i18n/fa-IR/dialog.json b/src/renderer/src/i18n/fa-IR/dialog.json index df5765d9c..732797aae 100644 --- a/src/renderer/src/i18n/fa-IR/dialog.json +++ b/src/renderer/src/i18n/fa-IR/dialog.json @@ -4,8 +4,8 @@ "close": "بستن", "ok": "خوب", "delete": { - "title": "آیا مطمئن هستید که می‌خواهید این گفت‌وگو را پاک کنید؟", - "description": "این کنش بازگشت‌پذیر نیست.", + "title": "این گفت‌وگو پاک شود؟", + "description": "این کار برگشت‌ناپذیر است.", "confirm": "پاک کردن" }, "rename": { @@ -13,15 +13,15 @@ "description": "لطفاً نام جدیدی برای گفت‌وگو وارد کنید." }, "cleanMessages": { - "title": "تمیز کردن همه پیام‌ها", - "description": "این کار همه پیام‌ها و پرونده‌ها را در این گفت‌وگو پاک می‌کند. آیا مطمئن هستید که می‌خواهید ادامه دهید؟", - "confirm": "تمیز کردن" + "title": "پاک کردن پیام‌ها", + "description": "همه پیام‌ها و پرونده‌های این گفت‌وگو حذف می‌شوند. ادامه می‌دهید؟", + "confirm": "پاک کردن" }, "fork": { - "title": "ساختن گفت‌وگوی شاخه‌ای", - "description": "این کار همه پیام‌ها از پیام نخست تا پیام کنونی را در گفت‌وگویی جدید رونویسی می‌کند که می‌توانید گفت‌وگو را در آن ادامه دهید.", - "confirm": "ساختن شاخه", - "tag": "شاخه‌ها" + "title": "ساخت شاخه", + "description": "پیام‌ها از ابتدا تا پیام کنونی در گفت‌وگویی جدید کپی می‌شوند.", + "confirm": "ساخت شاخه", + "tag": "شاخه" }, "error": { "title": "خطا" diff --git a/src/renderer/src/i18n/fa-IR/routes.json b/src/renderer/src/i18n/fa-IR/routes.json index 6e146e87f..1754b6102 100644 --- a/src/renderer/src/i18n/fa-IR/routes.json +++ b/src/renderer/src/i18n/fa-IR/routes.json @@ -10,5 +10,5 @@ "settings-shortcut": "میان‌برها", "settings-display": "تنظیمات نمایش", "settings-knowledge-base": "پایگاه دانش", - "settings-prompt": "مدیریت پرامپت" + "settings-prompt": "مدیریت پرامپت‌ها" } diff --git a/src/renderer/src/i18n/fa-IR/thread.json b/src/renderer/src/i18n/fa-IR/thread.json index 8ce2530c9..04869ed5a 100644 --- a/src/renderer/src/i18n/fa-IR/thread.json +++ b/src/renderer/src/i18n/fa-IR/thread.json @@ -9,23 +9,23 @@ "exportText": "متن ساده" }, "toolbar": { - "save": "نگه‌داری", + "save": "ذخیره", "cancel": "رد کردن", - "previousVariant": "رفتن به نگارش پیشین", - "nextVariant": "رفتن به نگارش بعدی", - "copy": "رونوشت پاسخ به مارک‌داون", + "previousVariant": "گونه قبلی", + "nextVariant": "گونه بعدی", + "copy": "رونوشت به Markdown", "copyImage": "رونوشت در قالب تصویر", "copyImageWithLongPress": "رونوشت در قالب تصویر (فشار طولانی برای گرفتن از بالا)", "copyFromTopSuccess": "تصویر کامل گفتگو رونویسی شد", "capturing": "در حال گرفتن...", - "retry": "ساخت دوباره", - "fork": "شاخه‌سازی به نشست جدید", + "retry": "تولید دوباره", + "fork": "ساخت شاخه", "edit": "ویرایش پیام", "delete": "پاک کردن پیام" }, "message": { "toolbar": { - "save": "نگه‌داری" + "save": "ذخیره" } }, "export": { diff --git a/src/renderer/src/i18n/fa-IR/toolCall.json b/src/renderer/src/i18n/fa-IR/toolCall.json index 1aa678ece..4dac9f0f2 100644 --- a/src/renderer/src/i18n/fa-IR/toolCall.json +++ b/src/renderer/src/i18n/fa-IR/toolCall.json @@ -4,7 +4,7 @@ "end": "پایان یافت", "error": "خطا", "title": "فراخوانی ابزار", - "clickToView": "برای دیدن جزئیات اینجا بزنید", + "clickToView": "برای دیدن جزئیات بزنید", "functionName": "نام تابع", "permission": "درخواست دسترسی", "params": "ورودی‌های تابع", diff --git a/src/renderer/src/i18n/fa-IR/update.json b/src/renderer/src/i18n/fa-IR/update.json index b3e2656ef..d86f50cc7 100644 --- a/src/renderer/src/i18n/fa-IR/update.json +++ b/src/renderer/src/i18n/fa-IR/update.json @@ -4,7 +4,7 @@ "releaseDate": "تاریخ انتشار", "releaseNotes": "یادداشت‌های انتشار", "later": "زمانی دیگر", - "githubDownload": "بارگیری از گیت‌هاب", + "githubDownload": "بارگیری از GitHub", "netdiskDownload": "بارگیری از فضای ابری", "checkUpdate": "بررسی به‌روزرسانی‌ها", "downloading": "در حال بارگیری", diff --git a/src/renderer/src/i18n/fr-FR/about.json b/src/renderer/src/i18n/fr-FR/about.json index 2dd3d3cf4..a0338f3a7 100644 --- a/src/renderer/src/i18n/fr-FR/about.json +++ b/src/renderer/src/i18n/fr-FR/about.json @@ -1,7 +1,7 @@ { "title": "DeepChat", "description": "DeepChat est un client IA multiplateforme, dédié à rendre l'IA accessible à plus de personnes.", - "website": "Visitez notre site web", + "website": "Ouvrir le site web", "deviceInfo": { "title": "Informations sur l'appareil", "platform": "Plateforme", diff --git a/src/renderer/src/i18n/fr-FR/artifacts.json b/src/renderer/src/i18n/fr-FR/artifacts.json index c2304264d..1074df564 100644 --- a/src/renderer/src/i18n/fr-FR/artifacts.json +++ b/src/renderer/src/i18n/fr-FR/artifacts.json @@ -30,11 +30,11 @@ "htmlPreviewTitle": "Aperçu HTML", "svgPreviewTitle": "Aperçu SVG", "copy": "Copier", - "copySuccess": "Copie réussie", + "copySuccess": "Copié", "copySuccessDesc": "Contenu copié dans le presse-papiers", - "copyFailed": "Échec de la copie", + "copyFailed": "Impossible de copier", "copyFailedDesc": "Impossible de copier le contenu dans le presse-papiers", "copyAsImage": "Copier en tant qu'image", "copyImageSuccessDesc": "Image copiée dans le presse-papiers", - "copyImageFailedDesc": "Impossible de copier des images dans le presse-papiers" + "copyImageFailedDesc": "Impossible de copier l'image dans le presse-papiers" } diff --git a/src/renderer/src/i18n/fr-FR/chat.json b/src/renderer/src/i18n/fr-FR/chat.json index 8da88981d..d4862eb53 100644 --- a/src/renderer/src/i18n/fr-FR/chat.json +++ b/src/renderer/src/i18n/fr-FR/chat.json @@ -4,14 +4,14 @@ "fileArea": "Zone de fichiers", "inputArea": "Zone de saisie", "functionSwitch": "Commutateur de fonction", - "fileSelect": "Sélection de fichier", - "pasteFiles": "Support du copier-coller de fichiers", + "fileSelect": "Sélectionner un fichier", + "pasteFiles": "Coller des fichiers depuis le presse-papiers", "dropFiles": "Déposez les fichiers ici", "promptFilesAdded": "Fichiers de prompt ajoutés", "promptFilesAddedDesc": "{count} fichiers ajoutés avec succès", "promptFilesError": "Erreur de traitement des fichiers", "promptFilesErrorDesc": "Échec du traitement de {count} fichiers", - "historyPlaceholder": "(Appuyez sur l'onglet pour remplir)", + "historyPlaceholder": "(Appuyez sur Tab pour compléter)", "rateLimitQueue": "File {count}", "rateLimitWait": "Attendre {seconds}s", "rateLimitQueueTooltip": "{count} requêtes en file, intervalle {interval} secondes", diff --git a/src/renderer/src/i18n/fr-FR/common.json b/src/renderer/src/i18n/fr-FR/common.json index 708c9a11a..90a35bdee 100644 --- a/src/renderer/src/i18n/fr-FR/common.json +++ b/src/renderer/src/i18n/fr-FR/common.json @@ -2,11 +2,11 @@ "enabled": "Activé", "disabled": "Désactivé", "loading": "Chargement...", - "copySuccess": "Copie réussie", + "copySuccess": "Copié", "copySuccessDesc": "Contenu copié dans le presse-papiers", - "copyImageSuccess": "Copie réussie", + "copyImageSuccess": "Copié", "copyImageSuccessDesc": "Image copiée dans le presse-papiers", - "copyFailed": "Échec de la copie", + "copyFailed": "Impossible de copier", "copyFailedDesc": "Impossible de copier le contenu dans le presse-papiers", "copyCode": "Copier le code", "copy": "Copier", diff --git a/src/renderer/src/i18n/fr-FR/components.json b/src/renderer/src/i18n/fr-FR/components.json index a1aa7134a..081560f65 100644 --- a/src/renderer/src/i18n/fr-FR/components.json +++ b/src/renderer/src/i18n/fr-FR/components.json @@ -13,7 +13,7 @@ }, "messageBlockAction": { "continue": "Continuer", - "continued": "Suite" + "continued": "Poursuivi" }, "messageBlockPermissionRequest": { "title": "Autorisation requise", @@ -23,14 +23,14 @@ "granted": "Autorisation accordée", "denied": "Autorisation refusée", "type": { - "read": "Autorisations de lecture", - "write": "Autorisations d'écriture", - "all": "Toutes les autorisations" + "read": "Accès en lecture", + "write": "Accès en écriture", + "all": "Accès complet" }, "description": { - "all": "Autoriser '{ToolName}' de '{servername}' pour effectuer des opérations de lecture et d'écriture?", - "read": "Autoriser '{ToolName}' de '{servername}' pour effectuer des opérations de lecture?", - "write": "Autoriser '{ToolName}' de '{servername}' pour effectuer des opérations d'écriture?" + "read": "Autoriser '{toolName}' de '{serverName}' à effectuer des opérations de lecture ?", + "write": "Autoriser '{toolName}' de '{serverName}' à effectuer des opérations d'écriture ?", + "all": "Autoriser '{toolName}' de '{serverName}' à lire et écrire ?" } } } diff --git a/src/renderer/src/i18n/fr-FR/contextMenu.json b/src/renderer/src/i18n/fr-FR/contextMenu.json index 04a645b4b..2ba55412f 100644 --- a/src/renderer/src/i18n/fr-FR/contextMenu.json +++ b/src/renderer/src/i18n/fr-FR/contextMenu.json @@ -6,10 +6,10 @@ "error": "Échec de la traduction" }, "askAI": { - "title": "Demander à l'IA", + "title": "Demander à l’IA", "question": "Question", "answer": "Réponse", - "error": "Échec de la réponse de l'IA" + "error": "Échec de la réponse de l’IA" }, "copy": "Copier", "paste": "Coller", diff --git a/src/renderer/src/i18n/fr-FR/dialog.json b/src/renderer/src/i18n/fr-FR/dialog.json index f34c2c4b4..f16846b77 100644 --- a/src/renderer/src/i18n/fr-FR/dialog.json +++ b/src/renderer/src/i18n/fr-FR/dialog.json @@ -4,8 +4,8 @@ "close": "Fermer", "ok": "OK", "delete": { - "title": "Êtes-vous sûr de vouloir supprimer cette conversation ?", - "description": "Cette action ne peut pas être annulée, veuillez procéder avec prudence.", + "title": "Supprimer cette conversation ?", + "description": "Cette action est irréversible.", "confirm": "Supprimer" }, "rename": { @@ -16,14 +16,14 @@ "title": "Erreur" }, "cleanMessages": { - "title": "Nettoyer les messages de la conversation", - "description": "Le nettoyage supprimera tous les messages et fichiers de cette conversation. Êtes-vous sûr de vouloir continuer ?", - "confirm": "Nettoyer" + "title": "Effacer les messages", + "description": "Cela supprimera tous les messages et fichiers de cette conversation. Continuer ?", + "confirm": "Effacer" }, "fork": { - "tag": "Branches", + "tag": "Branche", "confirm": "Créer une branche", - "description": "Copiez le message du premier message au message actuellement sélectionné dans une nouvelle session où vous pouvez continuer la conversation.", - "title": "Créer une branche de session" + "description": "Copier les messages du début jusqu’au message actuel dans une nouvelle conversation.", + "title": "Créer une branche" } } diff --git a/src/renderer/src/i18n/fr-FR/mcp.json b/src/renderer/src/i18n/fr-FR/mcp.json index 18e16c623..09bbb0566 100644 --- a/src/renderer/src/i18n/fr-FR/mcp.json +++ b/src/renderer/src/i18n/fr-FR/mcp.json @@ -34,7 +34,7 @@ "annotations": "Annotations", "selectToolToDebug": "Sélectionnez un outil à déboguer", "dialogDescription": "Déboguer et tester les outils fournis par les serveurs MCP", - "toolsCount": "outils", + "toolsCount": "{count} outils", "availableTools": "Outils disponibles", "invalidJson": "Format JSON invalide", "inputHint": "Veuillez entrer les paramètres au format JSON", @@ -48,12 +48,12 @@ }, "confirmDelete": { "cancel": "Annuler", - "confirm": "supprimer", + "confirm": "Supprimer", "description": "Êtes-vous sûr de vouloir supprimer le serveur {name} ? \nCette opération ne peut pas être annulée.", "title": "Confirmer la suppression" }, "confirmRemoveServer": "Êtes-vous sûr de vouloir supprimer le serveur {name} ? \nCette opération ne peut pas être annulée.", - "default": "par défaut", + "default": "Par défaut", "deleteServer": "Supprimer le serveur", "description": "Gérer et configurer les serveurs et outils MCP (Modèle de Contrôle de Protocole)", "editServer": "Modifier le serveur", @@ -66,26 +66,26 @@ "enabledTitle": "Activer MCP", "isDefault": "Serveur par défaut", "noServersFound": "Serveur non trouvé", - "removeDefault": "Supprimer par défaut", + "removeDefault": "Retirer le statut par défaut", "removeServer": "Supprimer le serveur", "removeServerDialog": { "title": "Supprimer le serveur" }, - "resetConfirm": "récupérer", + "resetConfirm": "Restaurer", "resetConfirmDescription": "Cette action restaure tous les serveurs par défaut tout en conservant vos serveurs personnalisés. \nToute modification du serveur par défaut sera perdue.", "resetConfirmTitle": "Restaurer le service par défaut", "resetToDefault": "Restaurer le service par défaut", "running": "En cours d'exécution", "serverForm": { - "add": "Ajouter à", - "args": "paramètre", + "add": "Ajouter", + "args": "Arguments", "argsPlaceholder": "Entrez les paramètres, séparés par des espaces", "argsRequired": "Les paramètres ne peuvent pas être vides", "autoApprove": "Autorisation automatique", - "autoApproveAll": "tout", + "autoApproveAll": "Tout", "autoApproveHelp": "Sélectionnez le type d'opération nécessitant une autorisation automatique et exécutez sans confirmation de l'utilisateur", - "autoApproveRead": "Lire", - "autoApproveWrite": "Écrire", + "autoApproveRead": "Lecture", + "autoApproveWrite": "Écriture", "baseUrl": "URL de base", "baseUrlPlaceholder": "Entrez l'URL de base du serveur (par exemple : http://localhost:3000)", "cancel": "Annuler", @@ -93,17 +93,17 @@ "commandPlaceholder": "Entrez une commande", "commandRequired": "La commande ne peut pas être vide", "configImported": "Importation de la configuration réussie", - "description": "décrire", + "description": "Description", "descriptionPlaceholder": "Entrez la description du serveur", - "descriptions": "décrire", + "descriptions": "Description", "descriptionsPlaceholder": "Entrez la description du serveur", "env": "Variables d'environnement", "envInvalid": "Les variables d'environnement doivent être au format JSON valide", "envPlaceholder": "Entrez les variables d'environnement au format JSON", - "icon": "icône", + "icon": "Icône", "iconPlaceholder": "Entrez l'icône", - "icons": "icône", - "iconsPlaceholder": "Entrez l'icône", + "icons": "Icônes", + "iconsPlaceholder": "Entrez les icônes", "jsonConfig": "Configuration JSON", "jsonConfigExample": "Exemple de configuration JSON", "jsonConfigIntro": "Vous pouvez coller directement la configuration JSON ou choisir de configurer le serveur manuellement.", @@ -113,17 +113,17 @@ "nameRequired": "Le nom du serveur ne peut pas être vide", "parseAndContinue": "Analyser et continuer", "parseError": "Erreur d'analyse", - "parseSuccess": "Analyse de la configuration réussie", + "parseSuccess": "Configuration analysée avec succès", "skipToManual": "Passer à la configuration manuelle", - "submit": "soumettre", + "submit": "Soumettre", "type": "Type de serveur", "typeInMemory": "Mémoire", "typePlaceholder": "Sélectionnez un type de serveur", - "typeSse": "Le serveur envoie des événements", + "typeSse": "Server‑Sent Events (SSE)", "typeStdio": "Entrée et sortie standard", - "update": "renouveler", + "update": "Mettre à jour", "addFolder": "Ajouter un dossier", - "folders": "Liste de dossiers", + "folders": "Dossiers autorisés", "noFoldersSelected": "Aucun dossier n'a été sélectionné", "selectFolder": "Sélectionnez un dossier", "selectFolderError": "Échec de la sélection d'un dossier" @@ -157,8 +157,8 @@ "name": "Service d'images" }, "braveSearch": { - "desc": "Brave Search API https://brave.com/search/API/", - "name": "Recherche courageuse" + "desc": "Brave Search API https://brave.com/search/api/", + "name": "Brave Search" }, "powerpack": { "desc": "Fournissez des capacités améliorées telles que la requête temporelle, l'acquisition d'informations Web et l'exécution de code sécurisé pour tout grand modèle, afin que le modèle ait des capacités d'acquisition d'informations plus puissantes et précises.", @@ -177,28 +177,28 @@ "desc": "Service de recherche dans la base de connaissances FastGPT, peut rechercher le contenu de la base de connaissances FastGPT" }, "deepchat-inmemory/custom-prompts-server": { - "desc": "Service de mot invite personnalisé intégré Deepchat intégré", - "name": "Mots rapides personnalisés" + "desc": "Service de prompts personnalisés intégré à DeepChat", + "name": "Prompts personnalisés" }, "deepchat-inmemory/deep-research-server": { - "desc": "Un service de recherche approfondi de Deepchat basé sur la recherche de bocha (notez que des modèles de contexte longs doivent être utilisés, et les modèles avec un contexte insuffisant peuvent échouer)", - "name": "Recherche en profondeur" + "desc": "Fonction de recherche approfondie intégrée, basée sur Bocha Search. Modèles à long contexte recommandés.", + "name": "DeepResearch" }, "deepchat-inmemory/auto-prompting-server": { - "name": "Génération Automatique de Prompt par Modèle", - "desc": "Sélectionne automatiquement le prompt personnalisé le plus adapté en fonction de l'entrée de l'utilisateur et remplit intelligemment le modèle de prompt." + "name": "Auto‑prompt par modèle", + "desc": "Sélection automatique du prompt le plus adapté selon l’entrée, et remplissage intelligent du gabarit." }, "deepchat-inmemory/conversation-search-server": { - "name": "Recherche d'Historique de Conversation", - "desc": "Service de recherche d'historique de conversation intégré DeepChat, peut rechercher les enregistrements de conversation historiques et le contenu des messages" + "name": "Recherche d’historique", + "desc": "Rechercher les conversations et messages passés (intégré à DeepChat)" }, "deepchat-inmemory/meeting-server": { "name": "Réunion Multi-Agent", "desc": "Le service de réunion intégré de DeepChat permet d’organiser et d’animer des discussions multi-agents." }, "builtinKnowledge": { - "desc": "Service de recherche de base de connaissances intégré Deepchat, qui peut rechercher le contenu de la base de connaissances intégrée Deepchat", - "name": "Recherche de base de connaissances intégrée" + "desc": "Recherche dans la base de connaissances intégrée de DeepChat (docs, contenus intégrés).", + "name": "Base de connaissances intégrée" }, "deepchat/apple-server": { "desc": "Laissez le modèle d'exploitation des fonctions du système de MacOS telles que le calendrier, les contacts, les e-mails, les cartes, les mémos, les rappels et autres systèmes", @@ -221,10 +221,10 @@ }, "resources": { "noResourcesAvailable": "Aucune ressource", - "selectResource": "Afficher le contenu des ressources ici", - "loading": "chargement", - "loadContent": "Obtenez du contenu de ressources", - "pleaseSelect": "Cliquez pour obtenir des détails sur les ressources d'affichage", + "selectResource": "Sélectionnez une ressource pour afficher le contenu", + "loading": "Chargement...", + "loadContent": "Charger le contenu", + "pleaseSelect": "Cliquer pour afficher les détails de la ressource", "dialogDescription": "Parcourir et afficher les ressources fournies par les serveurs MCP" }, "errors": { diff --git a/src/renderer/src/i18n/fr-FR/routes.json b/src/renderer/src/i18n/fr-FR/routes.json index 244cf4eef..b38a9f6fe 100644 --- a/src/renderer/src/i18n/fr-FR/routes.json +++ b/src/renderer/src/i18n/fr-FR/routes.json @@ -8,7 +8,7 @@ "settings-database": "Paramètres des données", "settings-about": "À propos", "settings-shortcut": "Raccourcis", - "settings-display": "Paramètres d'affichage", + "settings-display": "Affichage", "settings-knowledge-base": "Base de connaissances", "settings-prompt": "Gestion des Prompts" } diff --git a/src/renderer/src/i18n/fr-FR/settings.json b/src/renderer/src/i18n/fr-FR/settings.json index 6e019f4eb..2d98f45b3 100644 --- a/src/renderer/src/i18n/fr-FR/settings.json +++ b/src/renderer/src/i18n/fr-FR/settings.json @@ -50,8 +50,8 @@ "newChat": "Créer une nouvelle conversation", "title": "Paramètres des raccourcis clavier" }, - "notifications": "Notification système", - "notificationsDesc": "Lorsque Deepchat n'est pas au premier plan, si une session est générée, une notification système sera envoyée" + "notifications": "Notifications système", + "notificationsDesc": "Quand DeepChat est en arrière‑plan, envoyer une notification à la fin d’une réponse" }, "data": { "title": "Paramètres des données", @@ -107,11 +107,11 @@ "description": "Définir la longueur maximale de la réponse de l'IA" }, "artifacts": { - "description": "L'activation de la fonction Artifacts permet à l'IA de générer un contenu plus riche", - "title": "Effets des artefacts" + "description": "Activer Artifacts pour permettre à l’IA de générer un contenu plus riche", + "title": "Artifacts" }, "addModel": "Ajouter un modèle", - "configureModel": "Configuration du modèle", + "configureModel": "Configurer le modèle", "modelList": "Liste des modèles", "provider": "Fournisseur de service", "providerSetting": "Paramètres du fournisseur", @@ -120,7 +120,7 @@ "cancel": "Annuler", "contextLength": { "description": "Définir la longueur de contexte que le modèle peut gérer", - "label": "Durée du contexte" + "label": "Longueur du contexte" }, "description": "Veuillez noter que cette configuration n'est valable que pour le modèle actuel et n'affectera pas d'autres modèles. Veuillez le modifier avec prudence. Des paramètres incorrects peuvent faire en sorte que le modèle ne fonctionne pas correctement.", "functionCall": { @@ -172,8 +172,8 @@ "useModelDefault": "Utiliser la configuration par défaut du modèle", "currentUsingModelDefault": "Utilise actuellement la configuration par défaut du modèle", "temperature": { - "description": "Contrôlez le caractère aléatoire de la sortie. La plupart des modèles sont de 0-1 et certains soutiennent entre 0-2. Plus l'aléatoire est élevé.", - "label": "température" + "description": "Contrôle l’aléatoire de la sortie. Valeur plus élevée = plus aléatoire.", + "label": "Température" }, "title": "Paramètres du modèle personnalisé", "type": { @@ -183,7 +183,7 @@ "chat": "Modèle de langue", "embedding": "Modèle d'intégration", "imageGeneration": "Modèle de génération d'images", - "rerank": "Réorganiser le modèle" + "rerank": "Modèle de rerank" } }, "validation": { diff --git a/src/renderer/src/i18n/fr-FR/thread.json b/src/renderer/src/i18n/fr-FR/thread.json index 4f4d9f999..fba5a66e4 100644 --- a/src/renderer/src/i18n/fr-FR/thread.json +++ b/src/renderer/src/i18n/fr-FR/thread.json @@ -9,23 +9,23 @@ "exportText": "Texte brut" }, "toolbar": { - "save": "sauvegarder", + "save": "Enregistrer", "cancel": "Annuler", - "previousVariant": "Passez à la version précédente", - "nextVariant": "Passez à la version suivante", - "copy": "Copiez la réponse à Markdown", + "previousVariant": "Variante précédente", + "nextVariant": "Variante suivante", + "copy": "Copier en Markdown", "copyImage": "Copier en tant qu'image", "retry": "Régénérer", - "fork": "Fourche à une nouvelle session", + "fork": "Créer une branche", "edit": "Modifier le message", "delete": "Supprimer le message", - "capturing": "Capture d'écran ...", + "capturing": "Capture en cours...", "copyFromTopSuccess": "Image de session complète copiée", - "copyImageWithLongPress": "Copier en tant qu'image (appuyez sur longue pour commencer à intercepter du haut)" + "copyImageWithLongPress": "Copier en image (appui long pour capturer depuis le haut)" }, "message": { "toolbar": { - "save": "sauvegarder" + "save": "Enregistrer" } }, "export": { diff --git a/src/renderer/src/i18n/fr-FR/toolCall.json b/src/renderer/src/i18n/fr-FR/toolCall.json index 659b90601..00c7a4fd4 100644 --- a/src/renderer/src/i18n/fr-FR/toolCall.json +++ b/src/renderer/src/i18n/fr-FR/toolCall.json @@ -4,7 +4,7 @@ "end": "Terminé", "error": "Erreur", "title": "Appel d'outil", - "clickToView": "Cliquez pour voir les détails", + "clickToView": "Cliquer pour afficher les détails", "functionName": "Nom de la fonction", "permission": "Demande d'autorisation", "params": "Paramètres de la fonction", diff --git a/src/renderer/src/i18n/fr-FR/update.json b/src/renderer/src/i18n/fr-FR/update.json index 3f800fef7..4c448809f 100644 --- a/src/renderer/src/i18n/fr-FR/update.json +++ b/src/renderer/src/i18n/fr-FR/update.json @@ -4,7 +4,7 @@ "releaseDate": "Date de sortie", "releaseNotes": "Notes de version", "later": "Plus tard", - "githubDownload": "Téléchargement Github", + "githubDownload": "Téléchargement GitHub", "netdiskDownload": "Téléchargement Cloud", "checkUpdate": "Vérifier les mises à jour", "autoUpdate": "Mise à jour automatique", diff --git a/src/renderer/src/i18n/ja-JP/about.json b/src/renderer/src/i18n/ja-JP/about.json index 53dfb3b13..3838e1ef2 100644 --- a/src/renderer/src/i18n/ja-JP/about.json +++ b/src/renderer/src/i18n/ja-JP/about.json @@ -1,10 +1,10 @@ { "title": "DeepChat", "description": "DeepChatはクロスプラットフォームのAIクライアントで、より多くの人々にAIを便利に使用できるよう努めています。", - "website": "ウェブサイトを訪問", - "disclaimerButton": "免責条項", - "disclaimerTitle": "利用規約声明", - "checkUpdateButton": "アップデートを確認する", + "website": "Webサイトを開く", + "disclaimerButton": "免責事項", + "disclaimerTitle": "利用規約", + "checkUpdateButton": "アップデートを確認", "deviceInfo": { "title": "デバイス情報", "platform": "プラットフォーム", diff --git a/src/renderer/src/i18n/ja-JP/artifacts.json b/src/renderer/src/i18n/ja-JP/artifacts.json index cdd19a1fb..776f80995 100644 --- a/src/renderer/src/i18n/ja-JP/artifacts.json +++ b/src/renderer/src/i18n/ja-JP/artifacts.json @@ -30,11 +30,11 @@ "htmlPreviewTitle": "HTMLプレビュー", "svgPreviewTitle": "SVGプレビュー", "copy": "コピー", - "copySuccess": "コピー成功", + "copySuccess": "コピーしました", "copySuccessDesc": "クリップボードにコピーしました", - "copyFailed": "コピー失敗", + "copyFailed": "コピーできませんでした", "copyFailedDesc": "クリップボードへのコピーに失敗しました", "copyAsImage": "画像としてコピー", "copyImageSuccessDesc": "画像をクリップボードにコピーしました", - "copyImageFailedDesc": "画像のコピーに失敗しました" + "copyImageFailedDesc": "画像をコピーできませんでした" } diff --git a/src/renderer/src/i18n/ja-JP/chat.json b/src/renderer/src/i18n/ja-JP/chat.json index 88af57757..ac16dd5c1 100644 --- a/src/renderer/src/i18n/ja-JP/chat.json +++ b/src/renderer/src/i18n/ja-JP/chat.json @@ -4,14 +4,14 @@ "fileArea": "ファイルエリア", "inputArea": "入力エリア", "functionSwitch": "機能スイッチ", - "fileSelect": "ファイル選択", - "pasteFiles": "ファイルのコピー&ペーストをサポート", + "fileSelect": "ファイルを選択", + "pasteFiles": "クリップボードから貼り付けに対応", "dropFiles": "ファイルをここにドラッグ", "promptFilesAdded": "プロンプトファイルが追加されました", "promptFilesAddedDesc": "{count}個のファイルが正常に追加されました", "promptFilesError": "ファイル処理エラー", "promptFilesErrorDesc": "{count}個のファイルの処理に失敗しました", - "historyPlaceholder": "(タブを押して入力)", + "historyPlaceholder": "(Tabで補完)", "rateLimitQueue": "キュー {count}", "rateLimitWait": "待機 {seconds}秒", "rateLimitQueueTooltip": "キューに{count}個のリクエスト、間隔{interval}秒", diff --git a/src/renderer/src/i18n/ja-JP/common.json b/src/renderer/src/i18n/ja-JP/common.json index b281164d2..1f16d561c 100644 --- a/src/renderer/src/i18n/ja-JP/common.json +++ b/src/renderer/src/i18n/ja-JP/common.json @@ -2,11 +2,11 @@ "enabled": "有効", "disabled": "無効", "loading": "読み込み中...", - "copySuccess": "コピー成功", + "copySuccess": "コピーしました", "copySuccessDesc": "クリップボードにコピーしました", - "copyImageSuccess": "コピー成功", + "copyImageSuccess": "コピーしました", "copyImageSuccessDesc": "画像をクリップボードにコピーしました", - "copyFailed": "コピー失敗", + "copyFailed": "コピーできませんでした", "copyFailedDesc": "クリップボードへのコピーに失敗しました", "copyCode": "コードをコピー", "copy": "コピー", diff --git a/src/renderer/src/i18n/ja-JP/components.json b/src/renderer/src/i18n/ja-JP/components.json index 29c22c9d8..22b8a9dde 100644 --- a/src/renderer/src/i18n/ja-JP/components.json +++ b/src/renderer/src/i18n/ja-JP/components.json @@ -12,8 +12,8 @@ "flags": "旗" }, "messageBlockAction": { - "continue": "続く", - "continued": "続く" + "continue": "続行", + "continued": "続行済み" }, "messageBlockPermissionRequest": { "title": "権限が必要", @@ -23,14 +23,14 @@ "granted": "権限が許可されました", "denied": "権限が拒否されました", "type": { - "read": "読み取り権限", - "write": "書き込み権限", - "all": "全ての権限" + "read": "読み取りアクセス", + "write": "書き込みアクセス", + "all": "フルアクセス" }, "description": { - "all": "'{servername}'の '{toolname}'を読み取り操作を実行することを許可しますか?", - "read": "'{servername}'の '{toolname}'を読み取り操作を実行することを許可しますか?", - "write": "'{servername}'の '{toolname}'を許可しますか?" + "read": "'{serverName}' の '{toolName}' が読み取り操作を行うことを許可しますか?", + "write": "'{serverName}' の '{toolName}' が書き込み操作を行うことを許可しますか?", + "all": "'{serverName}' の '{toolName}' が読み取り・書き込みを行うことを許可しますか?" } } } diff --git a/src/renderer/src/i18n/ja-JP/contextMenu.json b/src/renderer/src/i18n/ja-JP/contextMenu.json index 1b83cbaf8..b66a19b4b 100644 --- a/src/renderer/src/i18n/ja-JP/contextMenu.json +++ b/src/renderer/src/i18n/ja-JP/contextMenu.json @@ -6,10 +6,10 @@ "error": "翻訳に失敗しました" }, "askAI": { - "title": "AI 質問", + "title": "AIに聞く", "question": "質問", "answer": "回答", - "error": "AI の回答に失敗しました" + "error": "AIの応答に失敗しました" }, "copy": "コピー", "paste": "貼り付け", diff --git a/src/renderer/src/i18n/ja-JP/dialog.json b/src/renderer/src/i18n/ja-JP/dialog.json index 6bfbd2581..346f8b14b 100644 --- a/src/renderer/src/i18n/ja-JP/dialog.json +++ b/src/renderer/src/i18n/ja-JP/dialog.json @@ -4,8 +4,8 @@ "close": "閉じる", "ok": "OK", "delete": { - "title": "このチャットを削除してもよろしいですか?", - "description": "この操作は元に戻せませんので、慎重に行ってください。", + "title": "この会話を削除しますか?", + "description": "この操作は元に戻せません。", "confirm": "削除" }, "rename": { @@ -17,13 +17,13 @@ }, "cleanMessages": { "confirm": "クリア", - "description": "メッセージをクリアすると、セッション内のすべてのメッセージとファイルが削除されます。続行しますか?", - "title": "セッションメッセージをクリアします" + "description": "この会話のすべてのメッセージとファイルを削除します。続行しますか?", + "title": "メッセージをクリア" }, "fork": { - "tag": "枝", - "confirm": "分岐作成", - "description": "最初のメッセージから現在選択されているメッセージにメッセージをコピーして、会話を続けることができます。", - "title": "会話を分岐" + "tag": "ブランチ", + "confirm": "ブランチ作成", + "description": "最初のメッセージから現在のメッセージまでを新しい会話にコピーします。", + "title": "ブランチを作成" } } diff --git a/src/renderer/src/i18n/ja-JP/mcp.json b/src/renderer/src/i18n/ja-JP/mcp.json index 5cbc9c52c..4fe0c8b63 100644 --- a/src/renderer/src/i18n/ja-JP/mcp.json +++ b/src/renderer/src/i18n/ja-JP/mcp.json @@ -34,50 +34,50 @@ "annotations": "注釈", "selectToolToDebug": "デバッグするツールを選択", "dialogDescription": "MCPサーバーが提供するツールのデバッグとテスト", - "toolsCount": "ツール", + "toolsCount": "{count} 個のツール", "availableTools": "利用可能なツール", "invalidJson": "無効なJSON形式", "inputHint": "JSON形式でパラメータを入力してください", "required": "必須", "noDescription": "説明なし" }, - "addServer": "サーバーを追加します", + "addServer": "サーバーを追加", "addServerDialog": { "description": "新しいMCPサーバーを構成します", - "title": "サーバーを追加します" + "title": "サーバーを追加" }, "confirmDelete": { "cancel": "キャンセル", - "confirm": "消去", + "confirm": "削除", "description": "サーバー{name}を削除したいですか?この操作はキャンセルできません。", - "title": "削除を確認します" + "title": "削除の確認" }, - "confirmRemoveServer": "サーバー{name}を削除したいですか?この操作はキャンセルできません。", + "confirmRemoveServer": "サーバー{name}を削除してもよろしいですか?この操作は取り消せません。", "default": "デフォルト", - "deleteServer": "サーバーを削除します", + "deleteServer": "サーバーを削除", "description": "MCP(Model Context Protocol)サーバーとツールの管理と設定", - "editServer": "サーバーを編集します", + "editServer": "サーバーを編集", "editServerDialog": { "description": "MCPサーバーの構成を編集します", - "title": "サーバーを編集します" + "title": "サーバーを編集" }, "enableToAccess": "設定オプションにアクセスするには、まずMCPを有効にしてください", "enabledDescription": "MCP機能とツールの有効化または無効化", "enabledTitle": "MCPを有効化", "isDefault": "デフォルトサーバー", "noServersFound": "サーバーが見つかりません", - "removeDefault": "デフォルトを削除します", - "removeServer": "サーバーを削除します", + "removeDefault": "デフォルトを削除", + "removeServer": "サーバーを削除", "removeServerDialog": { - "title": "サーバーを削除します" + "title": "サーバーを削除" }, - "resetConfirm": "回復する", + "resetConfirm": "復元", "resetConfirmDescription": "このアクションは、カスタマイズされたサーバーを保持しながら、すべてのデフォルトサーバーを復元します。デフォルトのサーバーの変更は失われます。", - "resetConfirmTitle": "デフォルトサービスを復元します", - "resetToDefault": "デフォルトサービスを復元します", + "resetConfirmTitle": "デフォルトサービスを復元", + "resetToDefault": "デフォルトサービスを復元", "running": "ランニング", "serverForm": { - "add": "に追加", + "add": "追加", "args": "パラメーター", "argsPlaceholder": "スペースで区切られたパラメーターを入力します", "argsRequired": "パラメーターを空にすることはできません", @@ -93,10 +93,10 @@ "commandPlaceholder": "コマンドを入力します", "commandRequired": "コマンドを空にすることはできません", "configImported": "構成インポートが成功しました", - "description": "説明する", - "descriptionPlaceholder": "サーバーの説明を入力します", - "descriptions": "説明する", - "descriptionsPlaceholder": "サーバーの説明を入力します", + "description": "説明", + "descriptionPlaceholder": "サーバーの説明を入力", + "descriptions": "説明", + "descriptionsPlaceholder": "サーバーの説明を入力", "env": "環境変数", "envInvalid": "環境変数は有効なJSON形式でなければなりません", "envPlaceholder": "JSON形式で環境変数を入力します", @@ -113,26 +113,26 @@ "nameRequired": "サーバー名を空にすることはできません", "parseAndContinue": "分析して続行します", "parseError": "解析エラー", - "parseSuccess": "構成解析に正常に", + "parseSuccess": "設定の解析に成功しました", "skipToManual": "手動構成までスキップします", - "submit": "提出する", + "submit": "送信", "type": "サーバータイプ", "typeInMemory": "メモリ", "typePlaceholder": "サーバータイプを選択します", - "typeSse": "サーバーはイベントを送信します", + "typeSse": "サーバー送信イベント(SSE)", "typeStdio": "標準入力と出力", "update": "更新します", "addFolder": "フォルダーを追加します", - "folders": "フォルダーリスト", + "folders": "フォルダー一覧", "noFoldersSelected": "フォルダーは選択されていません", "selectFolder": "フォルダーを選択します", "selectFolderError": "フォルダーの選択に失敗しました" }, - "serverList": "サーバーリスト", - "setAsDefault": "デフォルトのサーバーとして設定します", - "setDefault": "デフォルトとして設定します", - "startServer": "サーバーを起動します", - "stopServer": "サーバーを停止します", + "serverList": "サーバー一覧", + "setAsDefault": "デフォルトサーバーに設定", + "setDefault": "デフォルトに設定", + "startServer": "サーバーを起動", + "stopServer": "サーバーを停止", "stopped": "停止", "tabs": { "servers": "サーバ", diff --git a/src/renderer/src/i18n/ja-JP/routes.json b/src/renderer/src/i18n/ja-JP/routes.json index caa2ebc90..a69506efc 100644 --- a/src/renderer/src/i18n/ja-JP/routes.json +++ b/src/renderer/src/i18n/ja-JP/routes.json @@ -8,7 +8,7 @@ "settings-database": "データ設定", "settings-mcp": "MCP設定", "settings-shortcut": "ショートカット", - "settings-display": "設定を表示します", + "settings-display": "表示設定", "settings-knowledge-base": "ナレッジベース", "settings-prompt": "プロンプト管理" } diff --git a/src/renderer/src/i18n/ja-JP/settings.json b/src/renderer/src/i18n/ja-JP/settings.json index 9d26517a5..0bd4bc743 100644 --- a/src/renderer/src/i18n/ja-JP/settings.json +++ b/src/renderer/src/i18n/ja-JP/settings.json @@ -20,8 +20,8 @@ "invalidProxyUrl": "無効なプロキシURL、有効なhttp/https URLを入力してください", "contentProtection": "画面キャプチャ保護", "contentProtectionDialogTitle": "画面保護の切り替え確認", - "contentProtectionEnableDesc": "画面保護を有効にすると、画面共有ソフトウェアがDeepChatウィンドウをキャプチャするのを防ぎ、コンテンツのプライバシーを保護します。この機能はすべてのインターフェイスを完全に隠すわけではありません。責任を持って規制に準拠して使用してください。また、画面共有ソフトウェアがこの機能をサポートしない場合があります。また、一部の環境では黒いウィンドウが残る可能性があります。", - "contentProtectionDisableDesc": "画面保護を無効にすると、画面共有ソフトウェアがDeepChatウィンドウをキャプチャできるようになります。", + "contentProtectionEnableDesc": "DeepChatウィンドウの画面共有によるキャプチャを防止し、プライバシー保護に役立ちます。すべてのアプリがこの設定に対応するわけではなく、環境によっては黒いウィンドウが残る場合があります。", + "contentProtectionDisableDesc": "画面共有アプリによるDeepChatウィンドウのキャプチャを許可します。", "contentProtectionRestartNotice": "この設定を変更するとアプリケーションが再起動します。続行しますか?", "soundEnabled": "サウンドを有効にする", "copyWithCotEnabled": "COT情報をコピー", @@ -51,7 +51,7 @@ "title": "ショートカットキー設定" }, "notifications": "システム通知", - "notificationsDesc": "Deepchatが前景にない場合、セッションが生成された場合、システム通知が送信されます" + "notificationsDesc": "DeepChatがバックグラウンドのとき、応答が完了すると通知を送信します" }, "data": { "title": "データ設定", @@ -108,10 +108,10 @@ }, "artifacts": { "description": "Artifacts機能を有効にすると、AIはより豊かなコンテンツを生成できます", - "title": "アーティファクト効果" + "title": "Artifacts" }, - "addModel": "モデルを追加します", - "configureModel": "構成モデル", + "addModel": "モデルを追加", + "configureModel": "モデルを構成", "modelList": "モデルリスト", "provider": "サービスプロバイダー", "providerSetting": "サービスプロバイダーの設定", @@ -120,7 +120,7 @@ "cancel": "キャンセル", "contextLength": { "description": "モデルが処理できるコンテキスト長を設定します", - "label": "コンテキストの長さ" + "label": "コンテキスト長" }, "description": "この構成は現在のモデルに対してのみ有効であり、他のモデルには影響しないことに注意してください。注意して変更してください。パラメーターが誤っていると、モデルが適切に機能しない場合があります。", "functionCall": { @@ -172,7 +172,7 @@ "useModelDefault": "モデルのデフォルト設定を使用", "currentUsingModelDefault": "現在モデルのデフォルト設定を使用中", "temperature": { - "description": "出力のランダム性を制御します。ほとんどのモデルは0-1で、0〜2の間のサポートがあります。ランダム性が高くなるほど。", + "description": "出力のランダム性を制御します。値が高いほどランダム性が増します。", "label": "温度" }, "title": "カスタムモデルパラメーター", @@ -183,7 +183,7 @@ "chat": "言語モデル", "embedding": "埋め込みモデル", "imageGeneration": "画像生成モデル", - "rerank": "モデルを並べ替えます" + "rerank": "リランクモデル" } }, "validation": { diff --git a/src/renderer/src/i18n/ja-JP/thread.json b/src/renderer/src/i18n/ja-JP/thread.json index 33958b01a..3b426bc1a 100644 --- a/src/renderer/src/i18n/ja-JP/thread.json +++ b/src/renderer/src/i18n/ja-JP/thread.json @@ -2,26 +2,26 @@ "actions": { "rename": "名前を変更", "delete": "削除", - "cleanMessages": "メッセージをクリアします", + "cleanMessages": "メッセージをクリア", "pin": "ピン留めする", "unpin": "ピン留めを外す", - "export": "輸出", + "export": "エクスポート", "exportText": "プレーンテキスト" }, "toolbar": { "save": "保存", "cancel": "キャンセル", - "previousVariant": "前のバージョンに切り替えます", - "nextVariant": "次のバージョンに切り替えます", - "copy": "答えをマークダウンにコピーします", - "copyImage": "画像としてコピーします", + "previousVariant": "前のバリアント", + "nextVariant": "次のバリアント", + "copy": "Markdownとしてコピー", + "copyImage": "画像としてコピー", "retry": "再生成", "fork": "新会話に分岐", - "edit": "メッセージを編集します", - "delete": "メッセージを削除します", - "capturing": "スクリーンショット...", + "edit": "メッセージを編集", + "delete": "メッセージを削除", + "capturing": "キャプチャ中...", "copyFromTopSuccess": "完全なセッション画像がコピーされました", - "copyImageWithLongPress": "画像としてコピー(長押しで上部から取得)" + "copyImageWithLongPress": "画像としてコピー(長押しで上部からキャプチャ)" }, "message": { "toolbar": { diff --git a/src/renderer/src/i18n/ja-JP/toolCall.json b/src/renderer/src/i18n/ja-JP/toolCall.json index c6faa63a9..10285dbca 100644 --- a/src/renderer/src/i18n/ja-JP/toolCall.json +++ b/src/renderer/src/i18n/ja-JP/toolCall.json @@ -4,7 +4,7 @@ "end": "完了", "error": "エラー", "title": "ツール呼び出し", - "clickToView": "詳細を表示するにはクリック", + "clickToView": "クリックして詳細を表示", "functionName": "関数名", "permission": "権限リクエスト中", "params": "関数パラメータ", diff --git a/src/renderer/src/i18n/ja-JP/update.json b/src/renderer/src/i18n/ja-JP/update.json index 983317114..bad3121ce 100644 --- a/src/renderer/src/i18n/ja-JP/update.json +++ b/src/renderer/src/i18n/ja-JP/update.json @@ -4,12 +4,12 @@ "releaseDate": "リリース日", "releaseNotes": "リリースノート", "later": "後で", - "githubDownload": "Github ダウンロード", + "githubDownload": "GitHub ダウンロード", "netdiskDownload": "クラウドダウンロード", "checkUpdate": "アップデートを確認する", "autoUpdate": "自動更新", "downloading": "ダウンロード", - "installNow": "今すぐインストールしてください", + "installNow": "今すぐインストール", "restarting": "再起動", "alreadyUpToDate": "すでに最新です", "alreadyUpToDateDesc": "現在、DeepChatは最新バージョンに更新されており、更新は必要ありません。" diff --git a/src/renderer/src/i18n/ru-RU/about.json b/src/renderer/src/i18n/ru-RU/about.json index 5f8ba7f34..43c99ef6e 100644 --- a/src/renderer/src/i18n/ru-RU/about.json +++ b/src/renderer/src/i18n/ru-RU/about.json @@ -1,12 +1,12 @@ { "title": "DeepChat", "description": "DeepChat - это кросс-платформенный AI клиент. Мы стремимся сделать AI доступным для большего числа людей.", - "website": "Посетить наш сайт", + "website": "Открыть сайт", "disclaimerButton": "Отказ от ответственности", "disclaimerTitle": "Условия использования", "checkUpdateButton": "Проверить обновления", "deviceInfo": { - "title": "Информация о устройстве", + "title": "Сведения об устройстве", "platform": "Платформа", "arch": "Архитектура", "cpuModel": "Модель процессора", diff --git a/src/renderer/src/i18n/ru-RU/artifacts.json b/src/renderer/src/i18n/ru-RU/artifacts.json index 35fd3ade8..423f3faa1 100644 --- a/src/renderer/src/i18n/ru-RU/artifacts.json +++ b/src/renderer/src/i18n/ru-RU/artifacts.json @@ -30,11 +30,11 @@ "htmlPreviewTitle": "HTML Предварительный просмотр", "svgPreviewTitle": "Предварительный просмотр SVG", "copy": "Копировать", - "copySuccess": "Копирование успешно", + "copySuccess": "Скопировано", "copySuccessDesc": "Содержимое скопировано в буфер обмена", - "copyFailed": "Ошибка копирования", + "copyFailed": "Не удалось скопировать", "copyFailedDesc": "Не удалось скопировать содержимое в буфер обмена", "copyAsImage": "Копировать как изображение", "copyImageSuccessDesc": "Изображение скопировано в буфер обмена", - "copyImageFailedDesc": "Невозможно скопировать картинки в буфер обмена" + "copyImageFailedDesc": "Не удалось скопировать изображение в буфер обмена" } diff --git a/src/renderer/src/i18n/ru-RU/chat.json b/src/renderer/src/i18n/ru-RU/chat.json index 902437a30..8160f9f91 100644 --- a/src/renderer/src/i18n/ru-RU/chat.json +++ b/src/renderer/src/i18n/ru-RU/chat.json @@ -4,14 +4,14 @@ "fileArea": "Область файлов", "inputArea": "Область ввода", "functionSwitch": "Переключатель функций", - "fileSelect": "Выбрать файл", - "pasteFiles": "Поддержка копирования и вставки файлов", + "fileSelect": "Выберите файл", + "pasteFiles": "Вставить файлы из буфера обмена", "dropFiles": "Перетащите файлы сюда", "promptFilesAdded": "Файлы промпта добавлены", "promptFilesAddedDesc": "Успешно добавлено {count} файлов", "promptFilesError": "Ошибка обработки файлов", "promptFilesErrorDesc": "Не удалось обработать {count} файлов", - "historyPlaceholder": "(Нажмите вкладку, чтобы заполнить)", + "historyPlaceholder": "(Нажмите Tab для автозаполнения)", "rateLimitQueue": "Очередь {count}", "rateLimitWait": "Ожидание {seconds}с", "rateLimitQueueTooltip": "{count} запросов в очереди, интервал {interval} секунд", diff --git a/src/renderer/src/i18n/ru-RU/common.json b/src/renderer/src/i18n/ru-RU/common.json index 0a8b90ab4..bb5461a64 100644 --- a/src/renderer/src/i18n/ru-RU/common.json +++ b/src/renderer/src/i18n/ru-RU/common.json @@ -2,7 +2,7 @@ "enabled": "Включено", "disabled": "Выключено", "loading": "Загрузка...", - "copySuccess": "Копирование успешно", + "copySuccess": "Скопировано", "copySuccessDesc": "Содержимое скопировано в буфер обмена", "copyCode": "Скопировать код", "copy": "Копировать", @@ -62,9 +62,9 @@ "collapse": "Свернуть", "expand": "Развернуть", "image": "картина", - "copyImageSuccess": "Копирование успешно", + "copyImageSuccess": "Скопировано", "copyImageSuccessDesc": "Изображение скопировано в буфер обмена", - "copyFailed": "Ошибка копирования", + "copyFailed": "Не удалось скопировать", "copyFailedDesc": "Не удалось скопировать содержимое в буфер обмена", "add": "Добавить", "reset": "Сброс", diff --git a/src/renderer/src/i18n/ru-RU/components.json b/src/renderer/src/i18n/ru-RU/components.json index 30d260a7d..e9a0afeec 100644 --- a/src/renderer/src/i18n/ru-RU/components.json +++ b/src/renderer/src/i18n/ru-RU/components.json @@ -12,8 +12,8 @@ "flags": "Флаги" }, "messageBlockAction": { - "continue": "продолжать", - "continued": "Продолжать" + "continue": "Продолжить", + "continued": "Продолжено" }, "messageBlockPermissionRequest": { "title": "Требуется разрешение", @@ -23,14 +23,14 @@ "granted": "Разрешение предоставлено", "denied": "Разрешение отклонено", "type": { - "read": "Разрешения на чтение", - "write": "Разрешения на запись", - "all": "Полные разрешения" + "read": "Доступ на чтение", + "write": "Доступ на запись", + "all": "Полный доступ" }, "description": { - "all": "Разрешить '{ToolName}' of '{ServerName}' для выполнения операций чтения и записи?", - "read": "Разрешить '{ToolName}' of '{ServerName}' для выполнения операций чтения?", - "write": "Разрешить '{ToolName}' of '{ServerName}' для выполнения операций записи?" + "read": "Разрешить '{toolName}' от '{serverName}' выполнять операции чтения?", + "write": "Разрешить '{toolName}' от '{serverName}' выполнять операции записи?", + "all": "Разрешить '{toolName}' от '{serverName}' читать и записывать?" } } } diff --git a/src/renderer/src/i18n/ru-RU/contextMenu.json b/src/renderer/src/i18n/ru-RU/contextMenu.json index 645f8beef..14fe55c36 100644 --- a/src/renderer/src/i18n/ru-RU/contextMenu.json +++ b/src/renderer/src/i18n/ru-RU/contextMenu.json @@ -1,17 +1,17 @@ { "askAI": { - "answer": "отвечать", - "error": "AI Ответ не удался", - "question": "вопрос", - "title": "Ai wask" + "title": "Спросить ИИ", + "question": "Вопрос", + "answer": "Ответ", + "error": "Не удалось получить ответ ИИ" }, - "copy": "копия", - "cut": "Резать", - "paste": "Вставка", + "copy": "Копировать", + "paste": "Вставить", + "cut": "Вырезать", "translate": { - "error": "Перевод не удался", - "original": "оригинал", - "title": "переводить", - "translated": "Результаты перевода" + "title": "Перевести", + "original": "Оригинал", + "translated": "Перевод", + "error": "Не удалось перевести" } } diff --git a/src/renderer/src/i18n/ru-RU/dialog.json b/src/renderer/src/i18n/ru-RU/dialog.json index 51df335f0..39cac5070 100644 --- a/src/renderer/src/i18n/ru-RU/dialog.json +++ b/src/renderer/src/i18n/ru-RU/dialog.json @@ -4,8 +4,8 @@ "close": "Закрыть", "ok": "OK", "delete": { - "title": "Вы уверены, что хотите удалить этот чат?", - "description": "Это действие нельзя отменить, будьте осторожны.", + "title": "Удалить этот разговор?", + "description": "Это действие необратимо.", "confirm": "Удалить" }, "rename": { @@ -16,14 +16,14 @@ "title": "Ошибка" }, "cleanMessages": { - "confirm": "Прозрачный", - "description": "Очистка сообщения удалит все сообщения и файлы под сеансом. Вы уверены, что хотите продолжить?", - "title": "Очистить сообщения сеанса" + "title": "Очистить сообщения", + "description": "Будут удалены все сообщения и файлы в этом разговоре. Продолжить?", + "confirm": "Очистить" }, "fork": { - "tag": "Ветви", - "confirm": "Создать ветвь", - "description": "Скопируйте сообщение из первого сообщения в выбранное в настоящее время сообщение в новом сеансе, где вы можете продолжить разговор.", - "title": "Создать филиал сеанса" + "tag": "Ветка", + "confirm": "Создать ветку", + "description": "Скопировать сообщения с начала до текущего в новый разговор.", + "title": "Создать ветку" } } diff --git a/src/renderer/src/i18n/ru-RU/routes.json b/src/renderer/src/i18n/ru-RU/routes.json index ba7924334..9157b9943 100644 --- a/src/renderer/src/i18n/ru-RU/routes.json +++ b/src/renderer/src/i18n/ru-RU/routes.json @@ -10,5 +10,5 @@ "settings-mcp": "Настройки MCP", "settings-display": "Настройки отображения", "settings-knowledge-base": "База знаний", - "settings-prompt": "Управление Prompt" + "settings-prompt": "Управление промптами" } diff --git a/src/renderer/src/i18n/ru-RU/thread.json b/src/renderer/src/i18n/ru-RU/thread.json index bd73e6770..f5d3e5691 100644 --- a/src/renderer/src/i18n/ru-RU/thread.json +++ b/src/renderer/src/i18n/ru-RU/thread.json @@ -2,30 +2,30 @@ "actions": { "rename": "Переименовать", "delete": "Удалить", - "cleanMessages": "Очистить сообщение", + "cleanMessages": "Очистить сообщения", "pin": "Закрепить", "unpin": "Открепить", "export": "Экспорт", "exportText": "Простой текст" }, "toolbar": { - "save": "сохранять", + "save": "Сохранить", "cancel": "Отмена", - "previousVariant": "Переключиться на предыдущую версию", - "nextVariant": "Переключиться на следующую версию", - "copy": "Скопируйте ответ на Markdown", + "previousVariant": "Предыдущая вариация", + "nextVariant": "Следующая вариация", + "copy": "Копировать в Markdown", "copyImage": "Копировать как изображение", "retry": "Регенерация", - "fork": "Вилка на новую сессию", + "fork": "Создать ветку", "edit": "Редактировать сообщение", "delete": "Удалить сообщение", - "capturing": "Скриншот ...", + "capturing": "Захват...", "copyFromTopSuccess": "Полное сессионное изображение скопировано", - "copyImageWithLongPress": "Скопируйте как изображение (длинное нажмите, чтобы начать перехватывать сверху)" + "copyImageWithLongPress": "Копировать как изображение (долгое нажатие — для захвата сверху)" }, "message": { "toolbar": { - "save": "сохранять" + "save": "Сохранить" } }, "export": { diff --git a/src/renderer/src/i18n/ru-RU/toolCall.json b/src/renderer/src/i18n/ru-RU/toolCall.json index c4fad0fb0..e6e34ee55 100644 --- a/src/renderer/src/i18n/ru-RU/toolCall.json +++ b/src/renderer/src/i18n/ru-RU/toolCall.json @@ -4,7 +4,7 @@ "end": "Вызов завершен", "error": "Ошибка", "title": "Вызов инструмента", - "clickToView": "Нажмите, чтобы просмотреть детали", + "clickToView": "Нажмите, чтобы посмотреть детали", "functionName": "Имя функции", "permission": "Запрос разрешения", "params": "Параметры функции", diff --git a/src/renderer/src/i18n/ru-RU/update.json b/src/renderer/src/i18n/ru-RU/update.json index 05356100b..e0cf2ac08 100644 --- a/src/renderer/src/i18n/ru-RU/update.json +++ b/src/renderer/src/i18n/ru-RU/update.json @@ -4,12 +4,12 @@ "releaseDate": "Дата выпуска", "releaseNotes": "Примечания к выпуску", "later": "Позже", - "githubDownload": "Скачать с Github", + "githubDownload": "Скачать с GitHub", "netdiskDownload": "Скачать с облака", "checkUpdate": "Проверить обновления", "autoUpdate": "Автоматическое обновление", "downloading": "Загрузка", - "installNow": "Установить сейчас", + "installNow": "Установить", "restarting": "Перезапуск", "alreadyUpToDate": "Уже обновлено", "alreadyUpToDateDesc": "Ваш DeepChat уже обновлен до последней версии, обновление не требуется." From d78c05c15ed7760b2baa613fb4ae55252b7c5e29 Mon Sep 17 00:00:00 2001 From: yyhhyyyyyy Date: Sat, 9 Aug 2025 13:47:09 +0800 Subject: [PATCH 06/22] feat: add GPT-5 series model support (#717) --- .../presenter/configPresenter/modelConfig.ts | 10 ++- .../configPresenter/modelDefaultSettings.ts | 52 ++++++++++++ .../configPresenter/providerModelSettings.ts | 66 ++++++++++++++- .../providers/openAICompatibleProvider.ts | 16 +++- .../providers/openAIResponsesProvider.ts | 33 +++++++- src/main/presenter/threadPresenter/index.ts | 2 +- src/renderer/src/components/ChatConfig.vue | 9 +- src/renderer/src/components/NewThread.vue | 4 +- .../AnthropicProviderSettingsDetail.vue | 16 +++- .../components/settings/ModelConfigDialog.vue | 84 +++++++++++++++++-- .../settings/OllamaProviderSettingsDetail.vue | 19 ++++- .../settings/ProviderDialogContainer.vue | 31 +++++-- src/renderer/src/i18n/en-US/settings.json | 29 ++++++- src/renderer/src/i18n/fa-IR/settings.json | 29 ++++++- src/renderer/src/i18n/fr-FR/settings.json | 29 ++++++- src/renderer/src/i18n/ja-JP/settings.json | 29 ++++++- src/renderer/src/i18n/ko-KR/settings.json | 29 ++++++- src/renderer/src/i18n/ru-RU/settings.json | 29 ++++++- src/renderer/src/i18n/zh-CN/settings.json | 29 ++++++- src/renderer/src/i18n/zh-HK/settings.json | 29 ++++++- src/renderer/src/i18n/zh-TW/settings.json | 29 ++++++- src/shared/presenter.d.ts | 12 ++- 22 files changed, 558 insertions(+), 57 deletions(-) diff --git a/src/main/presenter/configPresenter/modelConfig.ts b/src/main/presenter/configPresenter/modelConfig.ts index e80915b67..6a70117f5 100644 --- a/src/main/presenter/configPresenter/modelConfig.ts +++ b/src/main/presenter/configPresenter/modelConfig.ts @@ -142,7 +142,10 @@ export class ModelConfigHelper { functionCall: config.functionCall || false, reasoning: config.reasoning || false, type: config.type || ModelType.Chat, - thinkingBudget: config.thinkingBudget + thinkingBudget: config.thinkingBudget, + reasoningEffort: config.reasoningEffort, + verbosity: config.verbosity, + maxCompletionTokens: config.maxCompletionTokens } break } @@ -160,7 +163,10 @@ export class ModelConfigHelper { functionCall: false, reasoning: false, type: ModelType.Chat, - thinkingBudget: undefined + thinkingBudget: undefined, + reasoningEffort: undefined, + verbosity: undefined, + maxCompletionTokens: undefined } } } diff --git a/src/main/presenter/configPresenter/modelDefaultSettings.ts b/src/main/presenter/configPresenter/modelDefaultSettings.ts index 055eafdfb..0a452e7aa 100644 --- a/src/main/presenter/configPresenter/modelDefaultSettings.ts +++ b/src/main/presenter/configPresenter/modelDefaultSettings.ts @@ -865,6 +865,58 @@ export const defaultModelsSettings: DefaultModelSetting[] = [ functionCall: false, reasoning: true }, + { + id: 'gpt-5-chat', + name: 'GPT-5 Chat', + maxTokens: 16384, + contextLength: 272000, + match: ['gpt-5-chat', 'gpt-5-chat-latest'], + vision: true, + functionCall: false, + reasoning: true, + reasoningEffort: 'medium', + verbosity: 'medium', + maxCompletionTokens: 16384 + }, + { + id: 'gpt-5-mini', + name: 'GPT-5 Mini', + maxTokens: 128000, + contextLength: 272000, + match: ['gpt-5-mini', 'gpt-5-mini-2025-08-07'], + vision: true, + functionCall: true, + reasoning: true, + reasoningEffort: 'medium', + verbosity: 'medium', + maxCompletionTokens: 128000 + }, + { + id: 'gpt-5-nano', + name: 'GPT-5 Nano', + maxTokens: 128000, + contextLength: 272000, + match: ['gpt-5-nano', 'gpt-5-nano-2025-08-07'], + vision: true, + functionCall: true, + reasoning: true, + reasoningEffort: 'medium', + verbosity: 'medium', + maxCompletionTokens: 128000 + }, + { + id: 'gpt-5', + name: 'GPT-5', + maxTokens: 128000, + contextLength: 272000, + match: ['gpt-5', 'gpt-5-2025-08-07'], + vision: true, + functionCall: true, + reasoning: true, + reasoningEffort: 'medium', + verbosity: 'medium', + maxCompletionTokens: 128000 + }, { id: 'gpt-4.5-preview', name: 'GPT-4.5 Preview', diff --git a/src/main/presenter/configPresenter/providerModelSettings.ts b/src/main/presenter/configPresenter/providerModelSettings.ts index 47d2ea171..b5af6cd05 100644 --- a/src/main/presenter/configPresenter/providerModelSettings.ts +++ b/src/main/presenter/configPresenter/providerModelSettings.ts @@ -13,13 +13,70 @@ export interface ProviderModelSetting { functionCall?: boolean // 是否支持函数调用 reasoning?: boolean // 是否支持推理能力 type?: ModelType // 模型类型,默认为Chat + // GPT-5 系列新参数 + reasoningEffort?: 'minimal' | 'low' | 'medium' | 'high' + verbosity?: 'low' | 'medium' | 'high' + maxCompletionTokens?: number // GPT-5 系列使用此参数替代 maxTokens } // 为每个提供商创建映射对象,使用models数组包装模型配置 export const providerModelSettings: Record = { // OpenAI提供商特定模型配置 openai: { - models: [] + models: [ + { + id: 'gpt-5-chat', + name: 'GPT-5 Chat', + maxTokens: 16384, + contextLength: 272000, + match: ['gpt-5-chat', 'gpt-5-chat-latest'], + vision: true, + functionCall: false, + reasoning: true, + reasoningEffort: 'medium', + verbosity: 'medium', + maxCompletionTokens: 16384 + }, + { + id: 'gpt-5-mini', + name: 'GPT-5 Mini', + maxTokens: 128000, + contextLength: 272000, + match: ['gpt-5-mini', 'gpt-5-mini-2025-08-07'], + vision: true, + functionCall: true, + reasoning: true, + reasoningEffort: 'medium', + verbosity: 'medium', + maxCompletionTokens: 128000 + }, + { + id: 'gpt-5-nano', + name: 'GPT-5 Nano', + maxTokens: 128000, + contextLength: 272000, + match: ['gpt-5-nano', 'gpt-5-nano-2025-08-07'], + vision: true, + functionCall: true, + reasoning: true, + reasoningEffort: 'medium', + verbosity: 'medium', + maxCompletionTokens: 128000 + }, + { + id: 'gpt-5', + name: 'GPT-5', + maxTokens: 128000, + contextLength: 272000, + match: ['gpt-5', 'gpt-5-2025-08-07'], + vision: true, + functionCall: true, + reasoning: true, + reasoningEffort: 'medium', + verbosity: 'medium', + maxCompletionTokens: 128000 + } + ] }, // 火山引擎(Doubao)提供商特定模型配置 @@ -2390,11 +2447,14 @@ export function getProviderSpecificModelConfig( return { maxTokens: config.maxTokens, contextLength: config.contextLength, - temperature: config.temperature || 0.7, + temperature: config.temperature, // 保持可选,某些模型不支持 vision: config.vision || false, functionCall: config.functionCall || false, reasoning: config.reasoning || false, - type: config.type || ModelType.Chat + type: config.type || ModelType.Chat, + reasoningEffort: config.reasoningEffort, + verbosity: config.verbosity, + maxCompletionTokens: config.maxCompletionTokens } } } diff --git a/src/main/presenter/llmProviderPresenter/providers/openAICompatibleProvider.ts b/src/main/presenter/llmProviderPresenter/providers/openAICompatibleProvider.ts index 7f7dfda8a..dadb89264 100644 --- a/src/main/presenter/llmProviderPresenter/providers/openAICompatibleProvider.ts +++ b/src/main/presenter/llmProviderPresenter/providers/openAICompatibleProvider.ts @@ -41,7 +41,11 @@ const OPENAI_REASONING_MODELS = [ 'o1-mini', 'o1-pro', 'o1-preview', - 'o1' + 'o1', + 'gpt-5', + 'gpt-5-mini', + 'gpt-5-nano', + 'gpt-5-chat' ] const OPENAI_IMAGE_GENERATION_MODELS = [ 'gpt-4o-all', @@ -210,7 +214,10 @@ export class OpenAICompatibleProvider extends BaseLLMProvider { model: modelId, stream: false, temperature: temperature, - ...(modelId.startsWith('o1') || modelId.startsWith('o3') || modelId.startsWith('o4') + ...(modelId.startsWith('o1') || + modelId.startsWith('o3') || + modelId.startsWith('o4') || + modelId.startsWith('gpt-5') ? { max_completion_tokens: maxTokens } : { max_tokens: maxTokens }) } @@ -528,7 +535,10 @@ export class OpenAICompatibleProvider extends BaseLLMProvider { model: modelId, stream: true, temperature, - ...(modelId.startsWith('o1') || modelId.startsWith('o3') || modelId.startsWith('o4') + ...(modelId.startsWith('o1') || + modelId.startsWith('o3') || + modelId.startsWith('o4') || + modelId.startsWith('gpt-5') ? { max_completion_tokens: maxTokens } : { max_tokens: maxTokens }) } diff --git a/src/main/presenter/llmProviderPresenter/providers/openAIResponsesProvider.ts b/src/main/presenter/llmProviderPresenter/providers/openAIResponsesProvider.ts index 78a4fa6c0..9734f8540 100644 --- a/src/main/presenter/llmProviderPresenter/providers/openAIResponsesProvider.ts +++ b/src/main/presenter/llmProviderPresenter/providers/openAIResponsesProvider.ts @@ -31,7 +31,11 @@ const OPENAI_REASONING_MODELS = [ 'o1-mini', 'o1-pro', 'o1-preview', - 'o1' + 'o1', + 'gpt-5', + 'gpt-5-mini', + 'gpt-5-nano', + 'gpt-5-chat' ] const OPENAI_IMAGE_GENERATION_MODELS = [ 'gpt-4o-all', @@ -224,6 +228,20 @@ export class OpenAIResponsesProvider extends BaseLLMProvider { stream: false } + if (modelId.startsWith('gpt-5')) { + const modelConfig = this.configPresenter.getModelConfig(modelId, this.provider.id) + if (modelConfig.reasoningEffort) { + ;(requestParams as any).reasoning = { + effort: modelConfig.reasoningEffort + } + } + if (modelConfig.verbosity) { + ;(requestParams as any).text = { + verbosity: modelConfig.verbosity + } + } + } + OPENAI_REASONING_MODELS.forEach((noTempId) => { if (modelId.startsWith(noTempId)) { delete requestParams.temperature @@ -554,6 +572,19 @@ export class OpenAIResponsesProvider extends BaseLLMProvider { requestParams.tools = apiTools } + if (modelId.startsWith('gpt-5')) { + if (modelConfig.reasoningEffort) { + ;(requestParams as any).reasoning = { + effort: modelConfig.reasoningEffort + } + } + if (modelConfig.verbosity) { + ;(requestParams as any).text = { + verbosity: modelConfig.verbosity + } + } + } + OPENAI_REASONING_MODELS.forEach((noTempId) => { if (modelId.startsWith(noTempId)) delete requestParams.temperature }) diff --git a/src/main/presenter/threadPresenter/index.ts b/src/main/presenter/threadPresenter/index.ts index f66638f90..d8e377891 100644 --- a/src/main/presenter/threadPresenter/index.ts +++ b/src/main/presenter/threadPresenter/index.ts @@ -745,7 +745,7 @@ export class ThreadPresenter implements IThreadPresenter { if (defaultModelsSettings) { mergedSettings.maxTokens = defaultModelsSettings.maxTokens mergedSettings.contextLength = defaultModelsSettings.contextLength - mergedSettings.temperature = defaultModelsSettings.temperature + mergedSettings.temperature = defaultModelsSettings.temperature ?? 0.7 // 重置 thinkingBudget 为模型默认配置,如果模型配置中没有则设为 undefined mergedSettings.thinkingBudget = defaultModelsSettings.thinkingBudget } diff --git a/src/renderer/src/components/ChatConfig.vue b/src/renderer/src/components/ChatConfig.vue index 254e00fa8..c433907c5 100644 --- a/src/renderer/src/components/ChatConfig.vue +++ b/src/renderer/src/components/ChatConfig.vue @@ -73,6 +73,11 @@ const showThinkingBudget = computed(() => { return isGemini && isGemini25 }) +const isGPT5Model = computed(() => { + const modelId = props.modelId?.toLowerCase() || '' + return modelId.startsWith('gpt-5') +}) + // 当前显示的思考预算值 const displayThinkingBudget = computed({ get: () => { @@ -126,8 +131,8 @@ const handleDynamicThinkingToggle = (enabled: boolean) => { /> - -
+ +
diff --git a/src/renderer/src/components/NewThread.vue b/src/renderer/src/components/NewThread.vue index e67447d1f..5121074a8 100644 --- a/src/renderer/src/components/NewThread.vue +++ b/src/renderer/src/components/NewThread.vue @@ -87,6 +87,8 @@ v-model:artifacts="artifacts" :context-length-limit="contextLengthLimit" :max-tokens-limit="maxTokensLimit" + :model-id="activeModel?.id" + :provider-id="activeModel?.providerId" /> @@ -163,7 +165,7 @@ watch( activeModel.value.id, activeModel.value.providerId ) - temperature.value = config.temperature + temperature.value = config.temperature ?? 0.7 contextLength.value = config.contextLength maxTokens.value = config.maxTokens contextLengthLimit.value = config.contextLength diff --git a/src/renderer/src/components/settings/AnthropicProviderSettingsDetail.vue b/src/renderer/src/components/settings/AnthropicProviderSettingsDetail.vue index 27f2ebb70..bdf60f38f 100644 --- a/src/renderer/src/components/settings/AnthropicProviderSettingsDetail.vue +++ b/src/renderer/src/components/settings/AnthropicProviderSettingsDetail.vue @@ -239,6 +239,15 @@ ) }} + + {{ + t( + checkResult + ? 'settings.provider.dialog.verify.successDesc' + : 'settings.provider.dialog.verify.failedDesc' + ) + }} +
@@ -104,6 +108,7 @@ import { useI18n } from 'vue-i18n' import ChatInput from './ChatInput.vue' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' +import ScrollablePopover from './ScrollablePopover.vue' import { Button } from '@/components/ui/button' import ModelIcon from './icons/ModelIcon.vue' import { Badge } from '@/components/ui/badge' diff --git a/src/renderer/src/components/ScrollablePopover.vue b/src/renderer/src/components/ScrollablePopover.vue new file mode 100644 index 000000000..7afa43c45 --- /dev/null +++ b/src/renderer/src/components/ScrollablePopover.vue @@ -0,0 +1,34 @@ + + + diff --git a/src/renderer/src/components/TitleView.vue b/src/renderer/src/components/TitleView.vue index ba27aba6f..7618af8b0 100644 --- a/src/renderer/src/components/TitleView.vue +++ b/src/renderer/src/components/TitleView.vue @@ -43,30 +43,28 @@
- - + + + +
@@ -77,6 +75,7 @@ import { Icon } from '@iconify/vue' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' +import ScrollablePopover from './ScrollablePopover.vue' import ChatConfig from './ChatConfig.vue' import ModelSelect from './ModelSelect.vue' import ModelIcon from './icons/ModelIcon.vue' diff --git a/src/renderer/src/components/ui/popover/PopoverContent.vue b/src/renderer/src/components/ui/popover/PopoverContent.vue index 10ae0adde..6744b583b 100644 --- a/src/renderer/src/components/ui/popover/PopoverContent.vue +++ b/src/renderer/src/components/ui/popover/PopoverContent.vue @@ -10,7 +10,9 @@ defineOptions({ }) const props = withDefaults( - defineProps(), + defineProps(), { align: 'center', sideOffset: 4 From 08658c312fb11bcabe34106ab90254baf6dc7151 Mon Sep 17 00:00:00 2001 From: yyhhyyyyyy Date: Mon, 11 Aug 2025 20:59:19 +0800 Subject: [PATCH 11/22] feat: implement floating chat window system with performance optimization (#724) --- src/main/events.ts | 1 + .../FloatingButtonWindow.ts | 4 + .../floatingButtonPresenter/index.ts | 122 +++++- src/main/presenter/tabPresenter.ts | 36 ++ .../windowPresenter/FloatingChatWindow.ts | 398 ++++++++++++++++++ src/main/presenter/windowPresenter/index.ts | 108 ++++- src/preload/floating-preload.ts | 11 +- src/renderer/floating/FloatingButton.vue | 23 + src/renderer/floating/env.d.ts | 1 + src/shared/presenter.d.ts | 3 + 10 files changed, 696 insertions(+), 11 deletions(-) create mode 100644 src/main/presenter/windowPresenter/FloatingChatWindow.ts diff --git a/src/main/events.ts b/src/main/events.ts index dae18ecaa..996df620e 100644 --- a/src/main/events.ts +++ b/src/main/events.ts @@ -177,6 +177,7 @@ export const MEETING_EVENTS = { // 悬浮按钮相关事件 export const FLOATING_BUTTON_EVENTS = { CLICKED: 'floating-button:clicked', // 悬浮按钮被点击 + RIGHT_CLICKED: 'floating-button:right-clicked', // 悬浮按钮被右键点击 VISIBILITY_CHANGED: 'floating-button:visibility-changed', // 悬浮按钮显示状态改变 POSITION_CHANGED: 'floating-button:position-changed', // 悬浮按钮位置改变 ENABLED_CHANGED: 'floating-button:enabled-changed' // 悬浮按钮启用状态改变 diff --git a/src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts b/src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts index 41b7d4d5b..f9cc1caac 100644 --- a/src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts +++ b/src/main/presenter/floatingButtonPresenter/FloatingButtonWindow.ts @@ -170,6 +170,10 @@ export class FloatingButtonWindow { return this.window !== null && !this.window.isDestroyed() } + public getWindow(): BrowserWindow | null { + return this.window + } + /** * 计算悬浮按钮位置 */ diff --git a/src/main/presenter/floatingButtonPresenter/index.ts b/src/main/presenter/floatingButtonPresenter/index.ts index a7b2833dd..5f67bb09f 100644 --- a/src/main/presenter/floatingButtonPresenter/index.ts +++ b/src/main/presenter/floatingButtonPresenter/index.ts @@ -1,9 +1,9 @@ import { FloatingButtonWindow } from './FloatingButtonWindow' import { FloatingButtonConfig, FloatingButtonState, DEFAULT_FLOATING_BUTTON_CONFIG } from './types' import { ConfigPresenter } from '../configPresenter' -import { ipcMain } from 'electron' +import { ipcMain, Menu, app } from 'electron' import { FLOATING_BUTTON_EVENTS } from '@/events' -import { handleShowHiddenWindow } from '@/utils' +import { presenter } from '../index' export class FloatingButtonPresenter { private floatingWindow: FloatingButtonWindow | null = null @@ -48,6 +48,7 @@ export class FloatingButtonPresenter { this.config.enabled = false ipcMain.removeAllListeners(FLOATING_BUTTON_EVENTS.CLICKED) + ipcMain.removeAllListeners(FLOATING_BUTTON_EVENTS.RIGHT_CLICKED) if (this.floatingWindow) { this.floatingWindow.destroy() this.floatingWindow = null @@ -107,12 +108,40 @@ export class FloatingButtonPresenter { */ private async createFloatingWindow(): Promise { ipcMain.removeAllListeners(FLOATING_BUTTON_EVENTS.CLICKED) + ipcMain.removeAllListeners(FLOATING_BUTTON_EVENTS.RIGHT_CLICKED) - ipcMain.on(FLOATING_BUTTON_EVENTS.CLICKED, () => { + ipcMain.on(FLOATING_BUTTON_EVENTS.CLICKED, async () => { try { - // 触发内置事件处理器 - handleShowHiddenWindow(true) - } catch {} + let floatingButtonPosition: { x: number; y: number; width: number; height: number } | null = + null + if (this.floatingWindow && this.floatingWindow.exists()) { + const buttonWindow = this.floatingWindow.getWindow() + if (buttonWindow && !buttonWindow.isDestroyed()) { + const bounds = buttonWindow.getBounds() + floatingButtonPosition = { + x: bounds.x, + y: bounds.y, + width: bounds.width, + height: bounds.height + } + } + } + if (floatingButtonPosition) { + await presenter.windowPresenter.toggleFloatingChatWindow(floatingButtonPosition) + } else { + await presenter.windowPresenter.toggleFloatingChatWindow() + } + } catch (error) { + console.error('Failed to handle floating button click:', error) + } + }) + + ipcMain.on(FLOATING_BUTTON_EVENTS.RIGHT_CLICKED, () => { + try { + this.showContextMenu() + } catch (error) { + console.error('Failed to handle floating button right click:', error) + } }) if (!this.floatingWindow) { @@ -122,5 +151,86 @@ export class FloatingButtonPresenter { // 悬浮按钮创建后立即显示 this.floatingWindow.show() + + this.preCreateFloatingChatWindow() + } + + private preCreateFloatingChatWindow(): void { + try { + presenter.windowPresenter.createFloatingChatWindow().catch((error) => { + console.error('Failed to pre-create floating chat window:', error) + }) + console.log('Started pre-creating floating chat window in background') + } catch (error) { + console.error('Error starting pre-creation of floating chat window:', error) + } + } + + private showContextMenu(): void { + const template = [ + { + label: '打开主窗口', + click: () => { + this.openMainWindow() + } + }, + { + type: 'separator' as const + }, + { + label: '退出应用', + click: () => { + this.exitApplication() + } + } + ] + + const contextMenu = Menu.buildFromTemplate(template) + + if (this.floatingWindow && this.floatingWindow.exists()) { + const buttonWindow = this.floatingWindow.getWindow() + if (buttonWindow && !buttonWindow.isDestroyed()) { + contextMenu.popup({ window: buttonWindow }) + return + } + } + + const mainWindow = presenter.windowPresenter.mainWindow + if (mainWindow) { + contextMenu.popup({ window: mainWindow }) + } else { + contextMenu.popup() + } + } + + private openMainWindow(): void { + try { + const windowPresenter = presenter.windowPresenter + if (windowPresenter) { + const mainWindow = windowPresenter.mainWindow + if (mainWindow && !mainWindow.isDestroyed()) { + if (mainWindow.isMinimized()) { + mainWindow.restore() + } + mainWindow.show() + mainWindow.focus() + console.log('Main window opened from floating button context menu') + } else { + windowPresenter.createShellWindow({ initialTab: { url: 'local://chat' } }) + console.log('Created new main window from floating button context menu') + } + } + } catch (error) { + console.error('Failed to open main window from floating button:', error) + } + } + + private exitApplication(): void { + try { + console.log('Exiting application from floating button context menu') + app.quit() + } catch (error) { + console.error('Failed to exit application from floating button:', error) + } } } diff --git a/src/main/presenter/tabPresenter.ts b/src/main/presenter/tabPresenter.ts index 27053a0e7..037b56379 100644 --- a/src/main/presenter/tabPresenter.ts +++ b/src/main/presenter/tabPresenter.ts @@ -942,4 +942,40 @@ export class TabPresenter implements ITabPresenter { } } } + + registerFloatingWindow(webContentsId: number, webContents: Electron.WebContents): void { + try { + console.log(`TabPresenter: Registering floating window as virtual tab, ID: ${webContentsId}`) + if (this.tabs.has(webContentsId)) { + console.warn(`TabPresenter: Tab ${webContentsId} already exists, skipping registration`) + return + } + const virtualView = { + webContents: webContents, + setVisible: () => {}, + setBounds: () => {}, + getBounds: () => ({ x: 0, y: 0, width: 400, height: 600 }) + } as any + this.webContentsToTabId.set(webContentsId, webContentsId) + this.tabs.set(webContentsId, virtualView) + console.log( + `TabPresenter: Virtual tab registered successfully for floating window ${webContentsId}` + ) + } catch (error) { + console.error('TabPresenter: Failed to register floating window:', error) + } + } + + unregisterFloatingWindow(webContentsId: number): void { + try { + console.log(`TabPresenter: Unregistering floating window virtual tab, ID: ${webContentsId}`) + this.webContentsToTabId.delete(webContentsId) + this.tabs.delete(webContentsId) + console.log( + `TabPresenter: Virtual tab unregistered successfully for floating window ${webContentsId}` + ) + } catch (error) { + console.error('TabPresenter: Failed to unregister floating window:', error) + } + } } diff --git a/src/main/presenter/windowPresenter/FloatingChatWindow.ts b/src/main/presenter/windowPresenter/FloatingChatWindow.ts new file mode 100644 index 000000000..b97233600 --- /dev/null +++ b/src/main/presenter/windowPresenter/FloatingChatWindow.ts @@ -0,0 +1,398 @@ +import { BrowserWindow, screen, nativeImage } from 'electron' +import path from 'path' +import logger from '../../../shared/logger' +import { platform, is } from '@electron-toolkit/utils' +import icon from '../../../../resources/icon.png?asset' +import iconWin from '../../../../resources/icon.ico?asset' +import { eventBus } from '../../eventbus' +import { TAB_EVENTS } from '../../events' +import { presenter } from '../' + +interface FloatingChatConfig { + size: { + width: number + height: number + } + minSize: { + width: number + height: number + } + opacity: number + alwaysOnTop: boolean +} + +interface FloatingButtonPosition { + x: number + y: number + width: number + height: number +} + +const DEFAULT_FLOATING_CHAT_CONFIG: FloatingChatConfig = { + size: { + width: 400, + height: 600 + }, + minSize: { + width: 350, + height: 450 + }, + opacity: 0.95, + alwaysOnTop: true +} + +export class FloatingChatWindow { + private window: BrowserWindow | null = null + private config: FloatingChatConfig + private isVisible: boolean = false + private shouldShowWhenReady: boolean = false + + constructor(config?: Partial) { + this.config = { + ...DEFAULT_FLOATING_CHAT_CONFIG, + ...config + } + } + + public async create(floatingButtonPosition?: FloatingButtonPosition): Promise { + if (this.window) { + return + } + + try { + const position = this.calculatePosition(floatingButtonPosition) + const iconFile = nativeImage.createFromPath(process.platform === 'win32' ? iconWin : icon) + const isDev = is.dev + + this.window = new BrowserWindow({ + width: this.config.size.width, + height: this.config.size.height, + minWidth: this.config.minSize.width, + minHeight: this.config.minSize.height, + x: position.x, + y: position.y, + frame: false, + transparent: true, + alwaysOnTop: this.config.alwaysOnTop, + skipTaskbar: true, + resizable: true, + minimizable: false, + maximizable: false, + closable: true, + show: false, + movable: true, + autoHideMenuBar: true, + icon: iconFile, + vibrancy: platform.isMacOS ? 'under-window' : undefined, + visualEffectState: platform.isMacOS ? 'followWindow' : undefined, + backgroundMaterial: platform.isWindows ? 'mica' : undefined, + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + preload: path.join(__dirname, '../preload/index.mjs'), + webSecurity: false, + devTools: isDev, + sandbox: false + } + }) + + this.window.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }) + this.window.setAlwaysOnTop(true, 'floating') + this.window.setOpacity(this.config.opacity) + this.setupWindowEvents() + this.registerVirtualTab() + + logger.info('FloatingChatWindow created successfully') + + this.loadPageContent() + .then(() => logger.info('FloatingChatWindow page content loaded')) + .catch((error) => logger.error('Failed to load FloatingChatWindow page content:', error)) + } catch (error) { + logger.error('Failed to create FloatingChatWindow:', error) + throw error + } + } + + public show(floatingButtonPosition?: FloatingButtonPosition): void { + if (!this.window) { + return + } + + if (floatingButtonPosition) { + const position = this.calculatePosition(floatingButtonPosition) + this.window.setPosition(position.x, position.y) + } + if (!this.window.isVisible()) { + if (this.window.webContents.isLoading() === false) { + this.window.show() + this.window.focus() + this.refreshWindowData() + } else { + this.window.show() + this.window.focus() + this.shouldShowWhenReady = true + this.window.webContents.once('did-finish-load', () => { + if (this.shouldShowWhenReady) { + this.refreshWindowData() + this.shouldShowWhenReady = false + } + }) + } + } else { + this.window.show() + this.window.focus() + this.refreshWindowData() + } + this.isVisible = true + logger.debug('FloatingChatWindow shown') + } + + public hide(): void { + if (!this.window) { + return + } + + this.window.hide() + this.isVisible = false + logger.debug('FloatingChatWindow hidden') + } + + public toggle(floatingButtonPosition?: FloatingButtonPosition): void { + if (this.isVisible) { + this.hide() + } else { + this.show(floatingButtonPosition) + } + } + + public destroy(): void { + if (this.window) { + this.unregisterVirtualTab() + try { + if (!this.window.isDestroyed()) { + this.window.destroy() + } + } catch (error) { + logger.error('Error destroying FloatingChatWindow:', error) + } + this.window = null + this.isVisible = false + logger.debug('FloatingChatWindow destroyed') + } + } + + public isShowing(): boolean { + return this.window !== null && !this.window.isDestroyed() && this.isVisible + } + + public getWindow(): BrowserWindow | null { + return this.window + } + + private refreshWindowData(): void { + if (this.window && !this.window.isDestroyed()) { + logger.debug('Refreshing floating window data') + setTimeout(() => { + if (this.window && !this.window.isDestroyed()) { + eventBus.sendToMain(TAB_EVENTS.RENDERER_TAB_READY, this.window.webContents.id) + } + }, 100) + } + } + + private registerVirtualTab(): void { + if (!this.window || this.window.isDestroyed()) { + return + } + + try { + const tabPresenter = presenter.tabPresenter + if (tabPresenter) { + const webContentsId = this.window.webContents.id + logger.info(`Registering virtual tab for floating window, WebContents ID: ${webContentsId}`) + tabPresenter.registerFloatingWindow(webContentsId, this.window.webContents) + } + } catch (error) { + logger.error('Failed to register virtual tab for floating window:', error) + } + } + + private unregisterVirtualTab(): void { + if (!this.window) { + return + } + + try { + const tabPresenter = presenter.tabPresenter + if (tabPresenter) { + const webContentsId = this.window.webContents.id + logger.info( + `Unregistering virtual tab for floating window, WebContents ID: ${webContentsId}` + ) + tabPresenter.unregisterFloatingWindow(webContentsId) + } + } catch (error) { + logger.error('Failed to unregister virtual tab for floating window:', error) + } + } + private calculatePosition(floatingButtonPosition?: FloatingButtonPosition): { + x: number + y: number + } { + const primaryDisplay = screen.getPrimaryDisplay() + const { workArea } = primaryDisplay + let x: number, y: number + + if (!floatingButtonPosition) { + x = workArea.x + workArea.width - this.config.size.width - 20 + y = workArea.y + workArea.height - this.config.size.height - 20 + return { x, y } + } + + const buttonX = floatingButtonPosition.x + const buttonY = floatingButtonPosition.y + const buttonWidth = floatingButtonPosition.width + const buttonHeight = floatingButtonPosition.height + const windowWidth = this.config.size.width + const windowHeight = this.config.size.height + const gap = 15 + const buttonCenterX = buttonX + buttonWidth / 2 + const buttonCenterY = buttonY + buttonHeight / 2 + const screenCenterX = workArea.x + workArea.width / 2 + const screenCenterY = workArea.y + workArea.height / 2 + + let positions: Array<{ x: number; y: number; priority: number }> = [] + if (buttonX + buttonWidth + gap + windowWidth <= workArea.x + workArea.width) { + positions.push({ + x: buttonX + buttonWidth + gap, + y: Math.max( + workArea.y, + Math.min( + buttonY + (buttonHeight - windowHeight) / 2, + workArea.y + workArea.height - windowHeight + ) + ), + priority: buttonCenterX < screenCenterX ? 1 : 3 + }) + } + + if (buttonX - gap - windowWidth >= workArea.x) { + positions.push({ + x: buttonX - gap - windowWidth, + y: Math.max( + workArea.y, + Math.min( + buttonY + (buttonHeight - windowHeight) / 2, + workArea.y + workArea.height - windowHeight + ) + ), + priority: buttonCenterX >= screenCenterX ? 1 : 3 + }) + } + + if (buttonY + buttonHeight + gap + windowHeight <= workArea.y + workArea.height) { + positions.push({ + x: Math.max( + workArea.x, + Math.min( + buttonX + (buttonWidth - windowWidth) / 2, + workArea.x + workArea.width - windowWidth + ) + ), + y: buttonY + buttonHeight + gap, + priority: buttonCenterY < screenCenterY ? 2 : 4 + }) + } + + if (buttonY - gap - windowHeight >= workArea.y) { + positions.push({ + x: Math.max( + workArea.x, + Math.min( + buttonX + (buttonWidth - windowWidth) / 2, + workArea.x + workArea.width - windowWidth + ) + ), + y: buttonY - gap - windowHeight, + priority: buttonCenterY >= screenCenterY ? 2 : 4 + }) + } + + if (positions.length === 0) { + x = workArea.x + workArea.width - windowWidth - 20 + y = workArea.y + workArea.height - windowHeight - 20 + } else { + positions.sort((a, b) => a.priority - b.priority) + x = positions[0].x + y = positions[0].y + } + x = Math.max(workArea.x + 10, Math.min(x, workArea.x + workArea.width - windowWidth - 10)) + y = Math.max(workArea.y + 10, Math.min(y, workArea.y + workArea.height - windowHeight - 10)) + return { x, y } + } + + private async loadPageContent(): Promise { + if (!this.window || this.window.isDestroyed()) { + throw new Error('Window is not available for page loading') + } + + const isDev = is.dev + if (isDev) { + await this.window.loadURL('http://localhost:5173/') + } else { + await this.window.loadFile(path.join(__dirname, '../renderer/index.html')) + } + + this.window.webContents.once('did-finish-load', () => { + logger.info('FloatingChatWindow did-finish-load, requesting fresh data') + setTimeout(async () => { + if (this.window && !this.window.isDestroyed()) { + logger.info(`Broadcasting thread list update for floating window`) + eventBus.sendToMain(TAB_EVENTS.RENDERER_TAB_READY, this.window.webContents.id) + } + }, 300) + }) + } + + private setupWindowEvents(): void { + if (!this.window) { + return + } + + this.window.on('ready-to-show', () => { + if (this.window && !this.window.isDestroyed()) { + if (this.shouldShowWhenReady) { + this.window.show() + this.window.focus() + this.shouldShowWhenReady = false + this.refreshWindowData() + } + } + }) + + this.window.on('close', (event) => { + const windowPresenter = presenter.windowPresenter + const isAppQuitting = windowPresenter?.isApplicationQuitting() || false + if (isAppQuitting) { + logger.info('App is quitting, allowing FloatingChatWindow to close normally') + return + } + event.preventDefault() + this.hide() + logger.debug('FloatingChatWindow close prevented, window hidden instead') + }) + + this.window.on('closed', () => { + this.window = null + this.isVisible = false + }) + + this.window.on('show', () => { + this.isVisible = true + }) + + this.window.on('hide', () => { + this.isVisible = false + }) + } +} diff --git a/src/main/presenter/windowPresenter/index.ts b/src/main/presenter/windowPresenter/index.ts index c35c25262..a05201bf7 100644 --- a/src/main/presenter/windowPresenter/index.ts +++ b/src/main/presenter/windowPresenter/index.ts @@ -13,6 +13,7 @@ import windowStateManager from 'electron-window-state' // 窗口状态管理器 import { SHORTCUT_EVENTS } from '@/events' // 快捷键事件常量 // TrayPresenter 在 main/index.ts 中全局管理,本 Presenter 不负责其生命周期 import { TabPresenter } from '../tabPresenter' // TabPresenter 类型 +import { FloatingChatWindow } from './FloatingChatWindow' // 悬浮对话窗口 /** * 窗口 Presenter,负责管理所有 BrowserWindow 实例及其生命周期。 @@ -38,6 +39,7 @@ export class WindowPresenter implements IWindowPresenter { hasInitialFocus: boolean } >() + private floatingChatWindow: FloatingChatWindow | null = null constructor(configPresenter: ConfigPresenter) { this.windows = new Map() @@ -57,6 +59,7 @@ export class WindowPresenter implements IWindowPresenter { app.on('before-quit', () => { console.log('App is quitting, setting isQuitting flag.') this.isQuitting = true + this.destroyFloatingChatWindow() }) // 监听快捷键事件:创建新窗口 @@ -171,16 +174,26 @@ export class WindowPresenter implements IWindowPresenter { * @param filePath 文件路径。 */ previewFile(filePath: string): void { - const window = this.mainWindow - if (window) { + let targetWindow = this.getFocusedWindow() + if (!targetWindow && this.floatingChatWindow && this.floatingChatWindow.isShowing()) { + const floatingWindow = this.floatingChatWindow.getWindow() + if (floatingWindow) { + targetWindow = floatingWindow + } + } + if (!targetWindow) { + targetWindow = this.mainWindow + } + + if (targetWindow && !targetWindow.isDestroyed()) { console.log(`Previewing file: ${filePath}`) if (process.platform === 'darwin') { - window.previewFile(filePath) + targetWindow.previewFile(filePath) } else { shell.openPath(filePath) // 使用系统默认应用打开 } } else { - console.warn('Cannot preview file, no valid main window found.') + console.warn('Cannot preview file, no valid window found.') } } @@ -487,6 +500,17 @@ export class WindowPresenter implements IWindowPresenter { console.warn(`Skipping sending message "${channel}" to destroyed window ${window.id}.`) } } + + if (this.floatingChatWindow && this.floatingChatWindow.isShowing()) { + const floatingWindow = this.floatingChatWindow.getWindow() + if (floatingWindow && !floatingWindow.isDestroyed()) { + try { + floatingWindow.webContents.send(channel, ...args) + } catch (error) { + console.error(`Error sending message "${channel}" to floating chat window:`, error) + } + } + } } /** @@ -1078,4 +1102,80 @@ export class WindowPresenter implements IWindowPresenter { return false // 过程中发生错误 } } + + public async createFloatingChatWindow(): Promise { + if (this.floatingChatWindow) { + console.log('FloatingChatWindow already exists') + return + } + + try { + this.floatingChatWindow = new FloatingChatWindow() + await this.floatingChatWindow.create() + console.log('FloatingChatWindow created successfully') + } catch (error) { + console.error('Failed to create FloatingChatWindow:', error) + this.floatingChatWindow = null + throw error + } + } + + public async showFloatingChatWindow(floatingButtonPosition?: { + x: number + y: number + width: number + height: number + }): Promise { + if (!this.floatingChatWindow) { + await this.createFloatingChatWindow() + } + + if (this.floatingChatWindow) { + this.floatingChatWindow.show(floatingButtonPosition) + console.log('FloatingChatWindow shown') + } + } + + public hideFloatingChatWindow(): void { + if (this.floatingChatWindow) { + this.floatingChatWindow.hide() + console.log('FloatingChatWindow hidden') + } + } + + public async toggleFloatingChatWindow(floatingButtonPosition?: { + x: number + y: number + width: number + height: number + }): Promise { + if (!this.floatingChatWindow) { + await this.createFloatingChatWindow() + } + + if (this.floatingChatWindow) { + this.floatingChatWindow.toggle(floatingButtonPosition) + console.log('FloatingChatWindow toggled') + } + } + + public destroyFloatingChatWindow(): void { + if (this.floatingChatWindow) { + this.floatingChatWindow.destroy() + this.floatingChatWindow = null + console.log('FloatingChatWindow destroyed') + } + } + + public isFloatingChatWindowVisible(): boolean { + return this.floatingChatWindow?.isShowing() || false + } + + public getFloatingChatWindow(): FloatingChatWindow | null { + return this.floatingChatWindow + } + + public isApplicationQuitting(): boolean { + return this.isQuitting + } } diff --git a/src/preload/floating-preload.ts b/src/preload/floating-preload.ts index 4639912b8..2839f9a9b 100644 --- a/src/preload/floating-preload.ts +++ b/src/preload/floating-preload.ts @@ -2,7 +2,8 @@ import { contextBridge, ipcRenderer } from 'electron' // 直接定义事件常量,避免路径解析问题 const FLOATING_BUTTON_EVENTS = { - CLICKED: 'floating-button:clicked' + CLICKED: 'floating-button:clicked', + RIGHT_CLICKED: 'floating-button:right-clicked' } as const // 定义悬浮按钮的 API @@ -16,6 +17,14 @@ const floatingButtonAPI = { } }, + onRightClick: () => { + try { + ipcRenderer.send(FLOATING_BUTTON_EVENTS.RIGHT_CLICKED) + } catch (error) { + console.error('FloatingPreload: Error sending right click IPC message:', error) + } + }, + // 监听来自主进程的事件 onConfigUpdate: (callback: (config: any) => void) => { ipcRenderer.on('floating-button-config-update', (_event, config) => { diff --git a/src/renderer/floating/FloatingButton.vue b/src/renderer/floating/FloatingButton.vue index 0806e973c..db4b0f9f7 100644 --- a/src/renderer/floating/FloatingButton.vue +++ b/src/renderer/floating/FloatingButton.vue @@ -7,6 +7,7 @@ class="w-15 h-15 rounded-full border-2 border-white/30 flex items-center justify-center cursor-pointer transition-all duration-300 relative overflow-hidden select-none floating-button no-drag" :class="{ 'floating-button-pulse': isPulsing }" @click="handleClick" + @contextmenu="handleRightClick" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave" > @@ -48,6 +49,28 @@ const handleClick = () => { } } +const handleRightClick = (event: MouseEvent) => { + event.preventDefault() + if (floatingButton.value) { + floatingButton.value.style.transform = 'scale(0.9)' + setTimeout(() => { + if (floatingButton.value) { + floatingButton.value.style.transform = '' + } + }, 150) + } + + if (window.floatingButtonAPI) { + try { + window.floatingButtonAPI.onRightClick() + } catch (error) { + console.error('=== FloatingButton: Error calling onRightClick API ===:', error) + } + } else { + console.error('=== FloatingButton: floatingButtonAPI not available ===') + } +} + // 鼠标事件处理 const handleMouseEnter = () => { isPulsing.value = false diff --git a/src/renderer/floating/env.d.ts b/src/renderer/floating/env.d.ts index 6f624ddac..58ffc5d98 100644 --- a/src/renderer/floating/env.d.ts +++ b/src/renderer/floating/env.d.ts @@ -11,6 +11,7 @@ declare global { interface Window { floatingButtonAPI: { onClick: () => void + onRightClick: () => void onConfigUpdate: (callback: (config: any) => void) => void removeAllListeners: () => void } diff --git a/src/shared/presenter.d.ts b/src/shared/presenter.d.ts index e74ee929e..777b6177a 100644 --- a/src/shared/presenter.d.ts +++ b/src/shared/presenter.d.ts @@ -178,6 +178,7 @@ export interface IWindowPresenter { sendToWindow(windowId: number, channel: string, ...args: unknown[]): boolean sendToDefaultTab(channel: string, switchToTarget?: boolean, ...args: unknown[]): Promise closeWindow(windowId: number, forceClose?: boolean): Promise + isApplicationQuitting(): boolean } export interface ITabPresenter { @@ -214,6 +215,8 @@ export interface ITabPresenter { onRendererTabReady(tabId: number): Promise onRendererTabActivated(threadId: string): Promise isLastTabInWindow(tabId: number): Promise + registerFloatingWindow(webContentsId: number, webContents: Electron.WebContents): void + unregisterFloatingWindow(webContentsId: number): void resetTabToBlank(tabId: number): Promise } From 3feafbc0408379e14e6f36112a584c2c3be90370 Mon Sep 17 00:00:00 2001 From: duskzhen Date: Mon, 11 Aug 2025 22:46:17 +0800 Subject: [PATCH 12/22] feat: add mcp sync and modelscope provider #615 (#723) * wip: add modelscope provider * feat: add mcp sync to modelscope * fix: add scrollable support to PopoverContent to prevent overflow (#720) * feat: implement floating chat window system with performance optimization (#724) * chore: i18n and format * feat: better style * fix: mcp tool display --------- Co-authored-by: yyhhyyyyyy --- src/main/presenter/configPresenter/index.ts | 28 ++ .../configPresenter/mcpConfHelper.ts | 169 +++++++++ .../presenter/configPresenter/providers.ts | 15 + .../presenter/llmProviderPresenter/index.ts | 143 +++++++- .../providers/modelscopeProvider.ts | 338 ++++++++++++++++++ .../src/components/icons/ModelIcon.vue | 2 + .../mcp-config/components/McpToolPanel.vue | 16 +- .../settings/ModelProviderSettingsDetail.vue | 4 + .../components/settings/ModelScopeMcpSync.vue | 184 ++++++++++ src/renderer/src/i18n/en-US/settings.json | 65 +++- src/renderer/src/i18n/fa-IR/settings.json | 65 +++- src/renderer/src/i18n/fr-FR/settings.json | 65 +++- src/renderer/src/i18n/ja-JP/settings.json | 65 +++- src/renderer/src/i18n/ko-KR/settings.json | 65 +++- src/renderer/src/i18n/ru-RU/settings.json | 65 +++- src/renderer/src/i18n/zh-CN/settings.json | 63 +++- src/renderer/src/i18n/zh-HK/settings.json | 65 +++- src/renderer/src/i18n/zh-TW/settings.json | 65 +++- src/shared/presenter.d.ts | 17 + 19 files changed, 1478 insertions(+), 21 deletions(-) create mode 100644 src/main/presenter/llmProviderPresenter/providers/modelscopeProvider.ts create mode 100644 src/renderer/src/components/settings/ModelScopeMcpSync.vue diff --git a/src/main/presenter/configPresenter/index.ts b/src/main/presenter/configPresenter/index.ts index 6dada2d34..4d248ce53 100644 --- a/src/main/presenter/configPresenter/index.ts +++ b/src/main/presenter/configPresenter/index.ts @@ -1166,6 +1166,34 @@ export class ConfigPresenter implements IConfigPresenter { newConfigs ) } + + // 批量导入MCP服务器 + async batchImportMcpServers( + servers: Array<{ + name: string + description: string + package: string + version?: string + type?: any + args?: string[] + env?: Record + enabled?: boolean + source?: string + [key: string]: unknown + }>, + options: { + skipExisting?: boolean + enableByDefault?: boolean + overwriteExisting?: boolean + } = {} + ): Promise<{ imported: number; skipped: number; errors: string[] }> { + return this.mcpConfHelper.batchImportMcpServers(servers, options) + } + + // 根据包名查找服务器 + async findMcpServerByPackage(packageName: string): Promise { + return this.mcpConfHelper.findServerByPackage(packageName) + } } export { defaultShortcutKey } from './shortcutKeySettings' diff --git a/src/main/presenter/configPresenter/mcpConfHelper.ts b/src/main/presenter/configPresenter/mcpConfHelper.ts index 593eb211a..06399b9ae 100644 --- a/src/main/presenter/configPresenter/mcpConfHelper.ts +++ b/src/main/presenter/configPresenter/mcpConfHelper.ts @@ -26,6 +26,23 @@ interface IMcpSettings { } export type MCPServerType = 'stdio' | 'sse' | 'inmemory' | 'http' +// Extended MCP server config with additional properties for ModelScope sync +export interface ExtendedMCPServerConfig { + name: string + description: string + args: string[] + env: Record + enabled: boolean + type: MCPServerType + package?: string + version?: string + source?: string + logo_url?: string + publisher?: string + tags?: string[] + view_count?: number +} + // 检查当前系统平台 function isMacOS(): boolean { return process.platform === 'darwin' @@ -624,6 +641,158 @@ export class McpConfHelper { }) } + /** + * Batch import MCP servers from external source (like ModelScope) + * @param servers - Array of MCP server configs to import + * @param options - Import options + * @returns Promise<{ imported: number; skipped: number; errors: string[] }> + */ + async batchImportMcpServers( + servers: Array<{ + name: string + description: string + package: string + version?: string + type?: MCPServerType + args?: string[] + env?: Record + enabled?: boolean + source?: string + [key: string]: unknown + }>, + options: { + skipExisting?: boolean + enableByDefault?: boolean + overwriteExisting?: boolean + } = {} + ): Promise<{ imported: number; skipped: number; errors: string[] }> { + const { skipExisting = true, enableByDefault = false, overwriteExisting = false } = options + const result = { + imported: 0, + skipped: 0, + errors: [] as string[] + } + + const existingServers = await this.getMcpServers() + + for (const serverConfig of servers) { + try { + // Generate unique server name based on package name + const serverName = this.generateUniqueServerName(serverConfig.package, existingServers) + const existingServer = existingServers[serverName] + + // Check if server already exists + if (existingServer && !overwriteExisting) { + if (skipExisting) { + console.log(`Skipping existing MCP server: ${serverName}`) + result.skipped++ + continue + } else { + result.errors.push(`Server ${serverName} already exists`) + continue + } + } + + // Create MCP server config + const mcpConfig: ExtendedMCPServerConfig = { + name: serverConfig.name, + description: serverConfig.description, + args: serverConfig.args || [], + env: serverConfig.env || {}, + enabled: serverConfig.enabled ?? enableByDefault, + type: (serverConfig.type as MCPServerType) || 'stdio', + package: serverConfig.package, + version: serverConfig.version || 'latest', + source: serverConfig.source as string | undefined, + logo_url: serverConfig.logo_url as string | undefined, + publisher: serverConfig.publisher as string | undefined, + tags: serverConfig.tags as string[] | undefined, + view_count: serverConfig.view_count as number | undefined + } + + // Add or update the server + const success = await this.addMcpServer(serverName, mcpConfig as unknown as MCPServerConfig) + if (success || overwriteExisting) { + if (existingServer && overwriteExisting) { + await this.updateMcpServer(serverName, mcpConfig as unknown as Partial) + console.log(`Updated MCP server: ${serverName}`) + } else { + console.log(`Imported MCP server: ${serverName}`) + } + result.imported++ + } else { + result.errors.push(`Failed to import server: ${serverName}`) + } + } catch (error) { + const errorMsg = `Error importing server ${serverConfig.name}: ${error instanceof Error ? error.message : String(error)}` + console.error(errorMsg) + result.errors.push(errorMsg) + } + } + + console.log( + `MCP batch import completed. Imported: ${result.imported}, Skipped: ${result.skipped}, Errors: ${result.errors.length}` + ) + + // Emit event to notify about the import + eventBus.sendToRenderer(MCP_EVENTS.CONFIG_CHANGED, SendTarget.ALL_WINDOWS, { + action: 'batch_import', + result + }) + + return result + } + + /** + * Generate a unique server name based on package name + * @param packageName - The package name to base the server name on + * @param existingServers - Existing servers to check against + * @returns Unique server name + */ + private generateUniqueServerName( + packageName: string, + existingServers: Record + ): string { + // Clean up package name to create a suitable server name + let baseName = packageName + .replace(/[@/]/g, '-') + .replace(/[^a-zA-Z0-9-_]/g, '') + .toLowerCase() + + // If the base name doesn't exist, use it directly + if (!existingServers[baseName]) { + return baseName + } + + // If it exists, append a number suffix + let counter = 1 + let uniqueName = `${baseName}-${counter}` + while (existingServers[uniqueName]) { + counter++ + uniqueName = `${baseName}-${counter}` + } + + return uniqueName + } + + /** + * Check if a server with given package already exists + * @param packageName - Package name to check + * @returns Promise - Returns server name if exists, null otherwise + */ + async findServerByPackage(packageName: string): Promise { + const servers = await this.getMcpServers() + + for (const [serverName, config] of Object.entries(servers)) { + const extendedConfig = config as unknown as ExtendedMCPServerConfig + if (extendedConfig.package === packageName) { + return serverName + } + } + + return null + } + public onUpgrade(oldVersion: string | undefined): void { console.log('onUpgrade', oldVersion) if (oldVersion && compare(oldVersion, '0.0.12', '<=')) { diff --git a/src/main/presenter/configPresenter/providers.ts b/src/main/presenter/configPresenter/providers.ts index 32c2df736..b3485ff15 100644 --- a/src/main/presenter/configPresenter/providers.ts +++ b/src/main/presenter/configPresenter/providers.ts @@ -562,5 +562,20 @@ export const DEFAULT_PROVIDERS: LLM_PROVIDER_BASE[] = [ defaultBaseUrl: 'https://your-resource-name.openai.azure.com/openai/deployments/your-deployment-name' } + }, + { + id: 'modelscope', + name: 'ModelScope', + apiType: 'openai', + apiKey: '', + baseUrl: 'https://api-inference.modelscope.cn/v1/', + enable: false, + websites: { + official: 'https://modelscope.cn/', + apiKey: 'https://modelscope.cn/my/myaccesstoken', + docs: 'https://modelscope.cn/docs/modelscope_agent/api_service', + models: 'https://modelscope.cn/models', + defaultBaseUrl: 'https://api-inference.modelscope.cn/v1/' + } } ] diff --git a/src/main/presenter/llmProviderPresenter/index.ts b/src/main/presenter/llmProviderPresenter/index.ts index 0cc2a5b46..33b76b575 100644 --- a/src/main/presenter/llmProviderPresenter/index.ts +++ b/src/main/presenter/llmProviderPresenter/index.ts @@ -8,7 +8,9 @@ import { ChatMessage, LLMAgentEvent, KeyStatus, - LLM_EMBEDDING_ATTRS + LLM_EMBEDDING_ATTRS, + ModelScopeMcpSyncOptions, + ModelScopeMcpSyncResult } from '@shared/presenter' import { BaseLLMProvider } from './baseProvider' import { OpenAIProvider } from './providers/openAIProvider' @@ -38,6 +40,7 @@ import { OpenRouterProvider } from './providers/openRouterProvider' import { MinimaxProvider } from './providers/minimaxProvider' import { AihubmixProvider } from './providers/aihubmixProvider' import { _302AIProvider } from './providers/_302AIProvider' +import { ModelscopeProvider } from './providers/modelscopeProvider' // 速率限制配置接口 interface RateLimitConfig { @@ -167,6 +170,9 @@ export class LLMProviderPresenter implements ILlmProviderPresenter { if (provider.id === 'aihubmix') { return new AihubmixProvider(provider, this.configPresenter) } + if (provider.id === 'modelscope') { + return new ModelscopeProvider(provider, this.configPresenter) + } switch (provider.apiType) { case 'minimax': return new OpenAIProvider(provider, this.configPresenter) @@ -1599,4 +1605,139 @@ export class LLMProviderPresenter implements ILlmProviderPresenter { } } } + + /** + * Sync MCP servers from ModelScope and import them to local configuration + * @param providerId - Provider ID (should be 'modelscope') + * @param syncOptions - Simplified sync options + * @returns Promise with sync result statistics + */ + async syncModelScopeMcpServers( + providerId: string, + syncOptions?: ModelScopeMcpSyncOptions + ): Promise { + console.log(`[ModelScope MCP Sync] Starting sync for provider: ${providerId}`) + console.log(`[ModelScope MCP Sync] Sync options:`, syncOptions) + + if (providerId !== 'modelscope') { + const error = 'MCP sync is only supported for ModelScope provider' + console.error(`[ModelScope MCP Sync] Error: ${error}`) + throw new Error(error) + } + + const provider = this.getProviderInstance(providerId) + + // Type check for ModelscopeProvider + if (provider.constructor.name !== 'ModelscopeProvider') { + const error = 'Provider is not a ModelScope provider instance' + console.error(`[ModelScope MCP Sync] Error: ${error}`) + throw new Error(error) + } + + const result: ModelScopeMcpSyncResult = { + imported: 0, + skipped: 0, + errors: [] + } + + try { + // Create async task to prevent blocking main thread + const syncTask = async () => { + console.log(`[ModelScope MCP Sync] Fetching MCP servers from ModelScope API...`) + + // Call ModelscopeProvider to fetch MCP servers + const modelscopeProvider = provider as any + const mcpResponse = await modelscopeProvider.syncMcpServers(syncOptions) + + if (!mcpResponse || !mcpResponse.success || !mcpResponse.data?.mcp_server_list) { + const errorMsg = 'Invalid response from ModelScope MCP API' + console.error(`[ModelScope MCP Sync] ${errorMsg}`, mcpResponse) + result.errors.push(errorMsg) + return result + } + + const mcpServers = mcpResponse.data.mcp_server_list + console.log(`[ModelScope MCP Sync] Fetched ${mcpServers.length} MCP servers from API`) + + // Convert ModelScope operational MCP servers to internal format + const convertedServers = mcpServers + .map((server: any) => { + try { + // Check if operational URLs are available + if (!server.operational_urls || server.operational_urls.length === 0) { + const errorMsg = `No operational URLs found for server ${server.id}` + console.warn(`[ModelScope MCP Sync] ${errorMsg}`) + result.errors.push(errorMsg) + return null + } + + // Use ModelScope provider's conversion method for consistency + const modelscopeProvider = provider as any + const converted = modelscopeProvider.convertMcpServerToConfig(server) + + console.log( + `[ModelScope MCP Sync] Converted operational server: ${converted.displayName} (${converted.name})` + ) + return converted + } catch (conversionError) { + const errorMsg = `Failed to convert server ${server.name || server.id}: ${conversionError instanceof Error ? conversionError.message : String(conversionError)}` + console.error(`[ModelScope MCP Sync] ${errorMsg}`) + result.errors.push(errorMsg) + return null + } + }) + .filter((server: any) => server !== null) + + console.log( + `[ModelScope MCP Sync] Successfully converted ${convertedServers.length} servers` + ) + + // Import servers to configuration using configPresenter + for (const serverConfig of convertedServers) { + try { + const existingServers = await this.configPresenter.getMcpServers() + + // Check if server already exists + if (existingServers[serverConfig.name]) { + console.log( + `[ModelScope MCP Sync] Server ${serverConfig.name} already exists, skipping` + ) + result.skipped++ + continue + } + + // Add server to configuration + const success = await this.configPresenter.addMcpServer(serverConfig.name, serverConfig) + if (success) { + console.log( + `[ModelScope MCP Sync] Successfully imported server: ${serverConfig.name}` + ) + result.imported++ + } else { + const errorMsg = `Failed to add server ${serverConfig.name} to configuration` + console.error(`[ModelScope MCP Sync] ${errorMsg}`) + result.errors.push(errorMsg) + } + } catch (importError) { + const errorMsg = `Failed to import server ${serverConfig.name}: ${importError instanceof Error ? importError.message : String(importError)}` + console.error(`[ModelScope MCP Sync] ${errorMsg}`) + result.errors.push(errorMsg) + } + } + + console.log( + `[ModelScope MCP Sync] Sync completed. Imported: ${result.imported}, Skipped: ${result.skipped}, Errors: ${result.errors.length}` + ) + return result + } + + // Execute async without blocking + return await syncTask() + } catch (error) { + const errorMsg = `ModelScope MCP sync failed: ${error instanceof Error ? error.message : String(error)}` + console.error(`[ModelScope MCP Sync] ${errorMsg}`) + result.errors.push(errorMsg) + return result + } + } } diff --git a/src/main/presenter/llmProviderPresenter/providers/modelscopeProvider.ts b/src/main/presenter/llmProviderPresenter/providers/modelscopeProvider.ts new file mode 100644 index 000000000..e6b4df50b --- /dev/null +++ b/src/main/presenter/llmProviderPresenter/providers/modelscopeProvider.ts @@ -0,0 +1,338 @@ +import { LLM_PROVIDER, LLMResponse, ChatMessage, KeyStatus } from '@shared/presenter' +import { OpenAICompatibleProvider } from './openAICompatibleProvider' +import { ConfigPresenter } from '../../configPresenter' + +// Define interface for ModelScope MCP API response +interface ModelScopeMcpServerResponse { + code: number + data: { + mcp_server_list: ModelScopeMcpServer[] + total_count: number + } + message: string + request_id: string + success: boolean +} + +// Define interface for ModelScope MCP server (updated for operational API) +interface ModelScopeMcpServer { + name: string + description: string + id: string + chinese_name?: string // Chinese name field + logo_url: string + operational_urls: Array<{ + id: string + url: string + }> + tags: string[] + locales: { + zh: { + name: string + description: string + } + en: { + name: string + description: string + } + } +} + +export class ModelscopeProvider extends OpenAICompatibleProvider { + constructor(provider: LLM_PROVIDER, configPresenter: ConfigPresenter) { + super(provider, configPresenter) + } + + async completions( + messages: ChatMessage[], + modelId: string, + temperature?: number, + maxTokens?: number + ): Promise { + return this.openAICompletion(messages, modelId, temperature, maxTokens) + } + + async summaries( + text: string, + modelId: string, + temperature?: number, + maxTokens?: number + ): Promise { + return this.openAICompletion( + [ + { + role: 'user', + content: `You need to summarize the user's conversation into a title of no more than 10 words, with the title language matching the user's primary language, without using punctuation or other special symbols:\n${text}` + } + ], + modelId, + temperature, + maxTokens + ) + } + + async generateText( + prompt: string, + modelId: string, + temperature?: number, + maxTokens?: number + ): Promise { + return this.openAICompletion( + [ + { + role: 'user', + content: prompt + } + ], + modelId, + temperature, + maxTokens + ) + } + + /** + * Get current API key status from ModelScope + * @returns Promise API key status information + */ + public async getKeyStatus(): Promise { + if (!this.provider.apiKey) { + throw new Error('API key is required') + } + + try { + // Use models endpoint to check API key validity + const response = await this.openai.models.list({ timeout: 10000 }) + + return { + limit_remaining: 'Available', + remainNum: response.data?.length || 0 + } + } catch (error) { + console.error('ModelScope API key check failed:', error) + throw new Error( + `ModelScope API key check failed: ${error instanceof Error ? error.message : String(error)}` + ) + } + } + + /** + * Override check method to use ModelScope's API validation + * @returns Promise<{ isOk: boolean; errorMsg: string | null }> + */ + public async check(): Promise<{ isOk: boolean; errorMsg: string | null }> { + try { + await this.getKeyStatus() + return { isOk: true, errorMsg: null } + } catch (error: unknown) { + let errorMessage = 'An unknown error occurred during ModelScope API key check.' + if (error instanceof Error) { + errorMessage = error.message + } else if (typeof error === 'string') { + errorMessage = error + } + + console.error('ModelScope API key check failed:', error) + return { isOk: false, errorMsg: errorMessage } + } + } + + /** + * Sync operational MCP servers from ModelScope API + * @param _options - Sync options including filters (currently not used by operational API) + * @returns Promise MCP servers response + */ + public async syncMcpServers(_options?: { + page_number?: number + page_size?: number + }): Promise { + if (!this.provider.apiKey) { + throw new Error('API key is required for MCP sync') + } + + try { + // Use the operational API endpoint - GET request, no body needed + const response = await fetch('https://www.modelscope.cn/openapi/v1/mcp/servers/operational', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.provider.apiKey}` + }, + signal: AbortSignal.timeout(30000) // 30 second timeout + }) + + // Handle authentication errors + if (response.status === 401 || response.status === 403) { + throw new Error('ModelScope MCP sync unauthorized: Invalid or expired API key') + } + + // Handle server errors + if (response.status === 500 || !response.ok) { + const errorText = await response.text() + throw new Error( + `ModelScope MCP sync failed: ${response.status} ${response.statusText} - ${errorText}` + ) + } + + const data: ModelScopeMcpServerResponse = await response.json() + + if (!data.success) { + throw new Error(`ModelScope MCP sync failed: ${data.message}`) + } + + console.log( + `Successfully fetched ${data.data.mcp_server_list.length} operational MCP servers from ModelScope` + ) + return data + } catch (error) { + console.error('ModelScope MCP sync error:', error) + throw error + } + } + + /** + * Convert ModelScope operational MCP server to internal MCP server config format + * @param mcpServer - ModelScope MCP server data + * @returns Internal MCP server config + */ + public convertMcpServerToConfig(mcpServer: ModelScopeMcpServer) { + // Check if operational URLs are available + if (!mcpServer.operational_urls || mcpServer.operational_urls.length === 0) { + throw new Error(`No operational URLs found for server ${mcpServer.id}`) + } + + // Use the first operational URL + const baseUrl = mcpServer.operational_urls[0].url + + // Generate random emoji for icon + const emojis = [ + '🔧', + '⚡', + '🚀', + '🔨', + '⚙️', + '🛠️', + '🔥', + '💡', + '⭐', + '🎯', + '🎨', + '🔮', + '💎', + '🎪', + '🎭', + '🎨', + '🔬', + '📱', + '💻', + '🖥️', + '⌨️', + '🖱️', + '📡', + '🔊', + '📢', + '📣', + '📯', + '🔔', + '🔕', + '📻', + '📺', + '📷', + '📹', + '🎥', + '📽️', + '🔍', + '🔎', + '💰', + '💳', + '💸', + '💵', + '🎲', + '🃏', + '🎮', + '🕹️', + '🎯', + '🎳', + '🎨', + '🖌️', + '🖍️', + '📝', + '✏️', + '📏', + '📐', + '📌', + '📍', + '🗂️', + '📂', + '📁', + '📰', + '📄', + '📃', + '📜', + '📋', + '📊', + '📈', + '📉', + '📦', + '📫', + '📪', + '📬', + '📭', + '📮', + '🗳️', + '✉️', + '📧', + '📨', + '📩', + '📤', + '📥', + '📬', + '📭', + '📮', + '🗂️', + '📂', + '📁', + '🗄️', + '🗃️', + '📋', + '📑', + '📄', + '📃', + '📰', + '🗞️', + '📜', + '🔖' + ] + const randomEmoji = emojis[Math.floor(Math.random() * emojis.length)] + + // Get display name: chinese_name first, then id + const displayName = mcpServer.chinese_name || mcpServer.id + + return { + name: `@modelscope/${mcpServer.id}`, // Use ModelScope ID format + description: + mcpServer.locales?.zh?.description || + mcpServer.description || + `ModelScope MCP Server: ${displayName}`, + command: '', // Not needed for SSE type + args: [], // Not needed for SSE type + env: {}, + type: 'sse' as const, // SSE type for operational servers + baseUrl: baseUrl, // Use operational URL + enabled: false, // Default to disabled for safety + source: 'modelscope' as const, + // Additional metadata + descriptions: + mcpServer.locales?.zh?.description || + mcpServer.description || + `ModelScope MCP Server: ${displayName}`, + icons: randomEmoji, // Random emoji instead of URL + provider: 'ModelScope', + providerUrl: `https://www.modelscope.cn/mcp/servers/@${mcpServer.id}`, + logoUrl: '', // No longer using logo URL + tags: mcpServer.tags || [], + // Store original data for reference + originalId: mcpServer.id, + displayName: displayName + } + } +} diff --git a/src/renderer/src/components/icons/ModelIcon.vue b/src/renderer/src/components/icons/ModelIcon.vue index 2d00f3c58..2ef7c4db1 100644 --- a/src/renderer/src/components/icons/ModelIcon.vue +++ b/src/renderer/src/components/icons/ModelIcon.vue @@ -57,8 +57,10 @@ import defaultIcon from '@/assets/logo.png?url' import metaColorIcon from '@/assets/llm-icons/meta.svg?url' import lmstudioColorIcon from '@/assets/llm-icons/lmstudio.svg?url' import _302aiIcon from '@/assets/llm-icons/302ai.svg?url' +import modelscopeColorIcon from '@/assets/llm-icons/modelscope-color.svg?url' // 导入所有图标 const icons = { + modelscope: modelscopeColorIcon, '302ai': _302aiIcon, aihubmix: aihubmixColorIcon, dashscope: dashscopeColorIcon, diff --git a/src/renderer/src/components/mcp-config/components/McpToolPanel.vue b/src/renderer/src/components/mcp-config/components/McpToolPanel.vue index b4861b3cf..1d6d71ba1 100644 --- a/src/renderer/src/components/mcp-config/components/McpToolPanel.vue +++ b/src/renderer/src/components/mcp-config/components/McpToolPanel.vue @@ -19,6 +19,7 @@ import { SelectValue } from '@/components/ui/select' import { useMcpStore } from '@/stores/mcp' +import { useMediaQuery } from '@vueuse/core' import { useI18n } from 'vue-i18n' import McpJsonViewer from './McpJsonViewer.vue' import type { MCPToolDefinition } from '@shared/presenter' @@ -52,6 +53,14 @@ const serverTools = computed(() => { return mcpStore.tools.filter((tool) => tool.server.name === props.serverName) }) +// 屏幕断点:lg 及以上 +const isLgScreen = useMediaQuery('(min-width: 1024px)') + +// 顶部下拉是否显示:小屏时显示;或大屏但左侧列表不可用(无工具)时显示 +const showTopSelector = computed(() => { + return !isLgScreen.value || serverTools.value.length === 0 +}) + watch(open, (newOpen) => { if (newOpen) { selectedToolName.value = '' @@ -161,8 +170,8 @@ const selectTool = (tool: MCPToolDefinition) => {
- -
+ +
+ + + + + +
+ + +
+ + +
+ + + + + +
+ +
+
+ + +
+ + {{ t('settings.provider.modelscope.mcpSync.imported', { count: syncResult.imported }) }} + + + {{ t('settings.provider.modelscope.mcpSync.skipped', { count: syncResult.skipped }) }} + + + {{ + t('settings.provider.modelscope.mcpSync.errors', { count: syncResult.errors.length }) + }} + +
+ + +
+

{{ errorMessage }}

+
+ + +
+

+ {{ t('settings.provider.modelscope.mcpSync.errorDetails') }} +

+
+
+ {{ error }} +
+
+
+
+
+ + + diff --git a/src/renderer/src/i18n/en-US/settings.json b/src/renderer/src/i18n/en-US/settings.json index f3884ff1f..1984cb34d 100644 --- a/src/renderer/src/i18n/en-US/settings.json +++ b/src/renderer/src/i18n/en-US/settings.json @@ -266,7 +266,10 @@ "failed": "Verification failed", "success": "Verification successful", "failedDesc": "API key or configuration verification failed, please check your configuration", - "successDesc": "API key and configuration verified successfully, ready to use" + "successDesc": "API key and configuration verified successfully, ready to use", + "connectionError": "Connection error, please check the network connection and API address", + "serverError": "Server error, please try again later", + "unauthorized": "Authentication failed, API Key is invalid or expired" }, "addCustomProvider": { "title": "Add Custom Provider", @@ -351,6 +354,58 @@ "modelRunning": "The model is running", "modelRunningDesc": "Please stop the model {model} first and then delete it." }, + "modelscope": { + "mcpSync": { + "title": "Sync MCP Services", + "description": "Sync MCP servers from ModelScope to local configuration, allowing quick addition of common MCP tools.", + "sync": "Start Sync", + "syncing": "Syncing...", + "pageSize": "Page Size", + "imported": "Imported {count} services", + "skipped": "Skipped {count} services", + "errors": "{count} errors", + "errorDetails": "Error Details", + "noApiKey": "Please configure ModelScope API Key first", + "noServersFound": "No available MCP services found", + "authenticationFailed": "Authentication failed, please check API Key", + "convertingServers": "Converting server configuration...", + "fetchingServers": "Getting the list of MCP servers...", + "importingServers": "Importing server configuration...", + "noOperationalUrls": "No available operating address found", + "pageNumber": "page number", + "pageNumberPlaceholder": "Please enter the page number", + "serverAlreadyExists": "The server already exists, skip import", + "syncComplete": "Synchronous completion", + "invalidServerData": "Invalid server data" + }, + "apiKey": "API Key", + "apiKeyHelper": "Get your API Key in the ModelScope console", + "apiKeyPlaceholder": "Please enter ModelScope API Key", + "baseUrl": "API Address", + "baseUrlHelper": "ModelScope API service address", + "connected": "Connected", + "connecting": "Connecting...", + "description": "ModelScope is a model-as-a-service sharing platform launched by Alibaba Damo Academy", + "details": { + "apiConfig": "API Configuration", + "mcpSync": "MCP Synchronization", + "modelManagement": "Model Management", + "operationalDescription": "Synchronize MCP servers that can be used directly on the ModelScope platform", + "operationalServers": "Operating a server", + "rateLimitConfig": "Rate limit configuration", + "safetySettings": "Security settings", + "specialConfig": "Special configuration", + "syncFromModelScope": "Sync from ModelScope", + "title": "Provider settings details" + }, + "invalidKey": "Invalid API Key", + "keyRequired": "Please enter API Key", + "name": "ModelScope", + "networkError": "Network connection error", + "notConnected": "Not connected", + "verifyFailed": "Verification failed", + "verifySuccess": "Verification is successful" + }, "anthropicApiKeyTip": "Please go to Anthropic Console to get your API Key", "anthropicConnected": "Anthropic connected", "anthropicNotConnected": "Anthropic not connected", @@ -374,7 +429,13 @@ "manageModels": "Manage Models", "anthropicOAuthActiveTip": "OAuth authentication is enabled, you can use Anthropic services directly", "oauthVerifySuccess": "OAuth connection verified successfully", - "oauthVerifyFailed": "OAuth connection verification failed" + "oauthVerifyFailed": "OAuth connection verification failed", + "configurationSaved": "Configuration saved", + "configurationUpdated": "Configuration updated", + "dataRefreshed": "Data has been refreshed", + "operationFailed": "Operation failed", + "operationSuccess": "Operation is successful", + "settingsApplied": "Settings applied" }, "knowledgeBase": { "title": "Knowledge Base Settings", diff --git a/src/renderer/src/i18n/fa-IR/settings.json b/src/renderer/src/i18n/fa-IR/settings.json index db8c2cf2c..87dd3fe13 100644 --- a/src/renderer/src/i18n/fa-IR/settings.json +++ b/src/renderer/src/i18n/fa-IR/settings.json @@ -266,7 +266,10 @@ "failed": "راستی‌آزمایی ناموفق", "success": "راستی‌آزمایی موفق", "failedDesc": "راستی‌آزمایی کلید API یا پیکربندی ناموفق بود، لطفاً تنظیمات را بررسی کنید", - "successDesc": "کلید API و پیکربندی با موفقیت راستی‌آزمایی شد، آماده استفاده است" + "successDesc": "کلید API و پیکربندی با موفقیت راستی‌آزمایی شد، آماده استفاده است", + "connectionError": "خطای اتصال ، لطفا اتصال شبکه و آدرس API را بررسی کنید", + "serverError": "خطای سرور ، لطفاً بعداً دوباره امتحان کنید", + "unauthorized": "احراز هویت انجام نشد ، کلید API نامعتبر یا منقضی شده است" }, "addCustomProvider": { "title": "افزودن فراهم‌کننده دلخواه", @@ -374,7 +377,65 @@ "manageModels": "مدیریت مدل‌ها", "anthropicOAuthActiveTip": "احراز هویت OAuth فعال است، می‌توانید مستقیماً از سرویس‌های Anthropic استفاده کنید", "oauthVerifySuccess": "اتصال OAuth با موفقیت تأیید شد", - "oauthVerifyFailed": "تأیید اتصال OAuth ناموفق بود" + "oauthVerifyFailed": "تأیید اتصال OAuth ناموفق بود", + "configurationSaved": "پیکربندی ذخیره شده", + "configurationUpdated": "پیکربندی به روز شد", + "dataRefreshed": "داده ها تازه شده است", + "modelscope": { + "apiKey": "کلید API", + "apiKeyHelper": "کلید API خود را در کنسول ModelCope دریافت کنید", + "apiKeyPlaceholder": "لطفاً کلید API Modelcope را وارد کنید", + "baseUrl": "آدرس API", + "baseUrlHelper": "آدرس خدمات API Modelcope", + "connected": "متصل", + "connecting": "اتصال ...", + "description": "Modelcope یک بستر اشتراک گذاری مدل به عنوان یک سرویس است که توسط آکادمی Alibaba Damo راه اندازی شده است", + "details": { + "apiConfig": "پیکربندی API", + "mcpSync": "همگام سازی MCP", + "modelManagement": "مدیریت مدل", + "operationalDescription": "سرورهای MCP را همگام سازی کنید که می توانند مستقیماً در سیستم عامل ModelCope استفاده شوند", + "operationalServers": "کار کردن یک سرور", + "rateLimitConfig": "پیکربندی حد نرخ", + "safetySettings": "تنظیمات امنیتی", + "specialConfig": "پیکربندی خاص", + "syncFromModelScope": "همگام سازی از Modelcope", + "title": "جزئیات تنظیمات ارائه دهنده" + }, + "invalidKey": "کلید API نامعتبر", + "keyRequired": "لطفا کلید API را وارد کنید", + "mcpSync": { + "authenticationFailed": "احراز هویت انجام نشد ، لطفاً کلید API را بررسی کنید", + "convertingServers": "تبدیل پیکربندی سرور ...", + "description": "همگام سازی یک سرور MCP از ModelScope به تنظیمات محلی به شما امکان می دهد تا به سرعت ابزارهای MCP متداول را اضافه کنید. کلیه خدمات به طور پیش فرض غیرفعال می شوند و پس از وارد کردن می توانند به صورت دستی فعال شوند.", + "errorDetails": "جزئیات خطا", + "errors": "خطا {تعداد}", + "fetchingServers": "دریافت لیست سرورهای MCP ...", + "imported": "{تعداد خدمات وارد شده است", + "importingServers": "وارد کردن پیکربندی سرور ...", + "invalidServerData": "داده های سرور نامعتبر", + "noApiKey": "لطفاً ابتدا کلید API Modelcope را پیکربندی کنید", + "noOperationalUrls": "هیچ آدرس عملیاتی موجود یافت نمی شود", + "noServersFound": "هیچ سرویس MCP در دسترس یافت نشد", + "pageNumber": "شماره صفحه", + "pageNumberPlaceholder": "لطفا شماره صفحه را وارد کنید", + "pageSize": "مقدار در هر صفحه", + "serverAlreadyExists": "سرور در حال حاضر وجود دارد ، واردات را پرش کنید", + "skipped": "خدمات SKIP {COUNT", + "sync": "همگام سازی را شروع کنید", + "syncComplete": "اتمام همزمان", + "syncing": "هماهنگ سازی ...", + "title": "همگام سازی خدمات MCP" + }, + "name": "مدلهای مدل", + "networkError": "خطای اتصال شبکه", + "notConnected": "متصل نیست", + "verifyFailed": "تأیید انجام نشد", + "verifySuccess": "تأیید موفقیت آمیز است" + }, + "operationFailed": "عملیات شکست خورد", + "operationSuccess": "عملیات موفقیت آمیز است", + "settingsApplied": "تنظیمات اعمال شده" }, "knowledgeBase": { "title": "تنظیمات پایگاه دانش", diff --git a/src/renderer/src/i18n/fr-FR/settings.json b/src/renderer/src/i18n/fr-FR/settings.json index ddd01ab00..375b7ebcb 100644 --- a/src/renderer/src/i18n/fr-FR/settings.json +++ b/src/renderer/src/i18n/fr-FR/settings.json @@ -266,7 +266,10 @@ "failed": "Échec de la vérification", "success": "Vérification réussie", "failedDesc": "La vérification de la clé API ou de la configuration a échoué, veuillez vérifier vos paramètres", - "successDesc": "La clé API et la configuration ont été vérifiées avec succès, prêt à utiliser" + "successDesc": "La clé API et la configuration ont été vérifiées avec succès, prêt à utiliser", + "connectionError": "Erreur de connexion, veuillez vérifier la connexion réseau et l'adresse de l'API", + "serverError": "Erreur du serveur, veuillez réessayer plus tard", + "unauthorized": "Échec de l'authentification, la clé API n'est pas valide ou expirée" }, "addCustomProvider": { "title": "Ajouter un fournisseur personnalisé", @@ -374,7 +377,65 @@ "manageModels": "Gérer les modèles", "anthropicOAuthActiveTip": "L'authentification OAuth est activée, vous pouvez utiliser les services Anthropic directement", "oauthVerifySuccess": "Connexion OAuth vérifiée avec succès", - "oauthVerifyFailed": "Échec de la vérification de la connexion OAuth" + "oauthVerifyFailed": "Échec de la vérification de la connexion OAuth", + "configurationSaved": "Configuration enregistrée", + "configurationUpdated": "Configuration mise à jour", + "dataRefreshed": "Les données ont été rafraîchies", + "modelscope": { + "apiKey": "Clé API", + "apiKeyHelper": "Obtenez votre clé API dans la console Modelcope", + "apiKeyPlaceholder": "Veuillez saisir la clé de l'API Modelscope", + "baseUrl": "Adresse API", + "baseUrlHelper": "Adresse du service API Modelcope", + "connected": "Connecté", + "connecting": "De liaison...", + "description": "Modelcope est une plate-forme de partage modèle en tant que service lancée par Alibaba Damo Academy", + "details": { + "apiConfig": "Configuration de l'API", + "mcpSync": "Synchronisation MCP", + "modelManagement": "Gestion des modèles", + "operationalDescription": "Synchroniser les serveurs MCP qui peuvent être utilisés directement sur la plate-forme Modelcope", + "operationalServers": "Exploitation d'un serveur", + "rateLimitConfig": "Configuration de la limite de taux", + "safetySettings": "Paramètres de sécurité", + "specialConfig": "Configuration spéciale", + "syncFromModelScope": "Synchronisation de Modelscope", + "title": "Détails des paramètres du fournisseur" + }, + "invalidKey": "Clé API non valide", + "keyRequired": "Veuillez saisir la clé de l'API", + "mcpSync": { + "authenticationFailed": "Échec de l'authentification, veuillez vérifier la clé de l'API", + "convertingServers": "Conversion de la configuration du serveur ...", + "description": "La synchronisation d'un serveur MCP de Modelcope aux configurations locales vous permet d'ajouter rapidement des outils MCP couramment utilisés. Tous les services sont désactivés par défaut et peuvent être activés manuellement après l'importation.", + "errorDetails": "Détails d'erreur", + "errors": "Erreur {count}", + "fetchingServers": "Obtenir la liste des serveurs MCP ...", + "imported": "{comte} Les services ont été importés", + "importingServers": "Configuration d'importation de serveur ...", + "invalidServerData": "Données de serveur non valides", + "noApiKey": "Veuillez d'abord configurer la clé de l'API Modelcope", + "noOperationalUrls": "Aucune adresse de fonctionnement disponible trouvée", + "noServersFound": "Aucun service MCP disponible trouvé", + "pageNumber": "numéro de page", + "pageNumberPlaceholder": "Veuillez saisir le numéro de page", + "pageSize": "Quantité par page", + "serverAlreadyExists": "Le serveur existe déjà, sautez l'importation", + "skipped": "Skip {count} Services", + "sync": "Commencer à se synchroniser", + "syncComplete": "Achèvement synchrone", + "syncing": "Synchronisation...", + "title": "Synchroniser les services MCP" + }, + "name": "Modelcope", + "networkError": "Erreur de connexion réseau", + "notConnected": "Non connecté", + "verifyFailed": "Échec de la vérification", + "verifySuccess": "La vérification est réussie" + }, + "operationFailed": "L'opération a échoué", + "operationSuccess": "L'opération est réussie", + "settingsApplied": "Paramètres appliqués" }, "knowledgeBase": { "title": "Paramètres de la base de connaissances", diff --git a/src/renderer/src/i18n/ja-JP/settings.json b/src/renderer/src/i18n/ja-JP/settings.json index 948c3f2eb..1ae73a93b 100644 --- a/src/renderer/src/i18n/ja-JP/settings.json +++ b/src/renderer/src/i18n/ja-JP/settings.json @@ -266,7 +266,10 @@ "failed": "検証に失敗しました", "success": "検証に成功しました", "failedDesc": "APIキーまたは設定の検証に失敗しました。設定を確認してください", - "successDesc": "APIキーと設定の検証に成功しました。使用可能です" + "successDesc": "APIキーと設定の検証に成功しました。使用可能です", + "connectionError": "接続エラー、ネットワーク接続とAPIアドレスを確認してください", + "serverError": "サーバーエラー、後でもう一度やり直してください", + "unauthorized": "認証に失敗し、APIキーが無効または期限切れです" }, "addCustomProvider": { "title": "カスタムプロバイダーを追加", @@ -374,7 +377,65 @@ "manageModels": "モデル管理", "anthropicOAuthActiveTip": "OAuth認証が有効になっており、Anthropicサービスを直接使用できます", "oauthVerifySuccess": "OAuth接続の確認が成功しました", - "oauthVerifyFailed": "OAuth接続の確認に失敗しました" + "oauthVerifyFailed": "OAuth接続の確認に失敗しました", + "configurationSaved": "構成が保存されました", + "configurationUpdated": "設定が更新されました", + "dataRefreshed": "データは更新されています", + "modelscope": { + "apiKey": "APIキー", + "apiKeyHelper": "ModelScopeコンソールでAPIキーを取得します", + "apiKeyPlaceholder": "ModelScope APIキーを入力してください", + "baseUrl": "APIアドレス", + "baseUrlHelper": "ModelScope APIサービスアドレス", + "connected": "接続", + "connecting": "接続...", + "description": "ModelScopeは、Alibaba Damo Academyによって開始されたサービスとしてのモデル共有プラットフォームです", + "details": { + "apiConfig": "API構成", + "mcpSync": "MCP同期", + "modelManagement": "モデル管理", + "operationalDescription": "ModelScopeプラットフォームで直接使用できるMCPサーバーを同期する", + "operationalServers": "サーバーの操作", + "rateLimitConfig": "レート制限構成", + "safetySettings": "セキュリティ設定", + "specialConfig": "特別な構成", + "syncFromModelScope": "ModelScopeから同期します", + "title": "プロバイダーの設定の詳細" + }, + "invalidKey": "無効なAPIキー", + "keyRequired": "APIキーを入力してください", + "mcpSync": { + "authenticationFailed": "認証に失敗しました。APIキーを確認してください", + "convertingServers": "サーバー構成の変換...", + "description": "MODESCOPEからローカル構成までMCPサーバーを同期すると、一般的に使用されるMCPツールをすばやく追加できます。すべてのサービスはデフォルトで無効になり、インポート後に手動で有効にすることができます。", + "errorDetails": "エラーの詳細", + "errors": "エラー{count}", + "fetchingServers": "MCPサーバーのリストを取得...", + "imported": "{count}サービスがインポートされています", + "importingServers": "サーバー構成のインポート...", + "invalidServerData": "無効なサーバーデータ", + "noApiKey": "ModelScope APIキーを最初に構成してください", + "noOperationalUrls": "利用可能な操作アドレスは見つかりません", + "noServersFound": "利用可能なMCPサービスは見つかりません", + "pageNumber": "ページ番号", + "pageNumberPlaceholder": "ページ番号を入力してください", + "pageSize": "ページごとの数量", + "serverAlreadyExists": "サーバーはすでに存在し、インポートをスキップします", + "skipped": "Skip {count}サービス", + "sync": "同期を開始します", + "syncComplete": "同期完了", + "syncing": "同期...", + "title": "MCPサービスを同期します" + }, + "name": "ModelScope", + "networkError": "ネットワーク接続エラー", + "notConnected": "接続されていません", + "verifyFailed": "検証に失敗しました", + "verifySuccess": "検証は成功しました" + }, + "operationFailed": "操作に失敗しました", + "operationSuccess": "操作は成功しました", + "settingsApplied": "適用された設定" }, "knowledgeBase": { "title": "ナレッジベース設定", diff --git a/src/renderer/src/i18n/ko-KR/settings.json b/src/renderer/src/i18n/ko-KR/settings.json index e0f8b8028..22e0f732f 100644 --- a/src/renderer/src/i18n/ko-KR/settings.json +++ b/src/renderer/src/i18n/ko-KR/settings.json @@ -265,7 +265,10 @@ "failed": "확인 실패", "success": "확인 성공", "failedDesc": "API 키 또는 설정 확인에 실패했습니다. 설정을 확인해주세요", - "successDesc": "API 키와 설정이 성공적으로 확인되었습니다. 사용 가능합니다" + "successDesc": "API 키와 설정이 성공적으로 확인되었습니다. 사용 가능합니다", + "connectionError": "연결 오류, 네트워크 연결 및 API 주소를 확인하십시오.", + "serverError": "서버 오류, 나중에 다시 시도하십시오", + "unauthorized": "인증 실패, API 키가 유효하지 않거나 만료되었습니다" }, "addCustomProvider": { "title": "커스텀 제공자 추가", @@ -374,7 +377,65 @@ "manageModels": "모델 관리", "anthropicOAuthActiveTip": "OAuth 인증이 활성화되어 있어 Anthropic 서비스를 직접 사용할 수 있습니다", "oauthVerifySuccess": "OAuth 연결 확인 성공", - "oauthVerifyFailed": "OAuth 연결 확인 실패" + "oauthVerifyFailed": "OAuth 연결 확인 실패", + "configurationSaved": "구성 저장", + "configurationUpdated": "구성 업데이트", + "dataRefreshed": "데이터가 새로 고쳐졌습니다", + "modelscope": { + "apiKey": "API 키", + "apiKeyHelper": "ModelScope 콘솔에서 API 키를 얻으십시오", + "apiKeyPlaceholder": "ModelScope API 키를 입력하십시오", + "baseUrl": "API 주소", + "baseUrlHelper": "ModelScope API 서비스 주소", + "connected": "연결", + "connecting": "연결 ...", + "description": "ModelScope는 Alibaba Damo Academy가 시작한 Model-as-A-Service 공유 플랫폼입니다.", + "details": { + "apiConfig": "API 구성", + "mcpSync": "MCP 동기화", + "modelManagement": "모델 관리", + "operationalDescription": "ModelScope 플랫폼에서 직접 사용할 수있는 MCP 서버 동기화", + "operationalServers": "서버 작동", + "rateLimitConfig": "요율 제한 구성", + "safetySettings": "보안 설정", + "specialConfig": "특별 구성", + "syncFromModelScope": "ModelScope에서 동기화됩니다", + "title": "제공자 설정 세부 정보" + }, + "invalidKey": "잘못된 API 키", + "keyRequired": "API 키를 입력하십시오", + "mcpSync": { + "authenticationFailed": "인증 실패, API 키를 확인하십시오", + "convertingServers": "서버 구성 변환 ...", + "description": "Modescope에서 로컬 구성으로 MCP 서버를 동기화하면 일반적으로 사용되는 MCP 도구를 빠르게 추가 할 수 있습니다. 모든 서비스는 기본적으로 비활성화되며 가져온 후 수동으로 활성화 할 수 있습니다.", + "errorDetails": "오류 세부 사항", + "errors": "오류 {count}", + "fetchingServers": "MCP 서버 목록 얻기 ...", + "imported": "{count} 서비스가 가져 왔습니다", + "importingServers": "서버 구성 가져 오기 ...", + "invalidServerData": "잘못된 서버 데이터", + "noApiKey": "먼저 ModelScope API 키를 구성하십시오", + "noOperationalUrls": "사용 가능한 운영 주소가 없습니다", + "noServersFound": "사용 가능한 MCP 서비스가 없습니다", + "pageNumber": "페이지 번호", + "pageNumberPlaceholder": "페이지 번호를 입력하십시오", + "pageSize": "페이지 당 수량", + "serverAlreadyExists": "서버가 이미 존재하고 가져 오기를 건너 뛰십시오", + "skipped": "{count} 서비스를 건너 뛰십시오", + "sync": "동기화를 시작하십시오", + "syncComplete": "동기 완성", + "syncing": "동기화...", + "title": "MCP 서비스 동기화" + }, + "name": "ModelsCope", + "networkError": "네트워크 연결 오류", + "notConnected": "연결되지 않았습니다", + "verifyFailed": "확인이 실패했습니다", + "verifySuccess": "확인이 성공적입니다" + }, + "operationFailed": "작동 실패", + "operationSuccess": "운영이 성공적입니다", + "settingsApplied": "설정이 적용됩니다" }, "knowledgeBase": { "title": "지식 베이스 설정", diff --git a/src/renderer/src/i18n/ru-RU/settings.json b/src/renderer/src/i18n/ru-RU/settings.json index ee8b305da..5ae09d61e 100644 --- a/src/renderer/src/i18n/ru-RU/settings.json +++ b/src/renderer/src/i18n/ru-RU/settings.json @@ -265,7 +265,10 @@ "failed": "Проверка не удалась", "success": "Проверка успешна", "failedDesc": "Проверка API ключа или конфигурации не удалась, проверьте настройки", - "successDesc": "API ключ и конфигурация успешно проверены, готовы к использованию" + "successDesc": "API ключ и конфигурация успешно проверены, готовы к использованию", + "connectionError": "Ошибка соединения, пожалуйста, проверьте сетевое соединение и адрес API", + "serverError": "Ошибка сервера, попробуйте еще раз позже", + "unauthorized": "Аутентификация не удалась, ключ API является недействительным или истекшим" }, "addCustomProvider": { "title": "Добавить пользовательский провайдер", @@ -374,7 +377,65 @@ "manageModels": "Управление моделями", "anthropicOAuthActiveTip": "Аутентификация OAuth включена, вы можете использовать сервисы Anthropic напрямую", "oauthVerifySuccess": "Соединение OAuth успешно проверено", - "oauthVerifyFailed": "Ошибка проверки соединения OAuth" + "oauthVerifyFailed": "Ошибка проверки соединения OAuth", + "configurationSaved": "Конфигурация сохранена", + "configurationUpdated": "Конфигурация обновлена", + "dataRefreshed": "Данные были обновлены", + "modelscope": { + "apiKey": "API -ключ", + "apiKeyHelper": "Получите ключ API в консоли моделей", + "apiKeyPlaceholder": "Пожалуйста, введите ключ API моделей моделей", + "baseUrl": "Адрес API", + "baseUrlHelper": "ModelsCope API -адрес службы службы", + "connected": "Подключенный", + "connecting": "Соединение ...", + "description": "ModelCope-это платформа для обмена моделями, запущенная Alibaba Damo Academy", + "details": { + "apiConfig": "Конфигурация API", + "mcpSync": "Синхронизация MCP", + "modelManagement": "Управление моделями", + "operationalDescription": "Синхронизировать серверы MCP, которые можно использовать непосредственно на платформе моделей", + "operationalServers": "Эксплуатация сервера", + "rateLimitConfig": "Конфигурация ограничения скорости", + "safetySettings": "Настройки безопасности", + "specialConfig": "Специальная конфигурация", + "syncFromModelScope": "Синхронизация от моделей", + "title": "Детали настроек поставщика" + }, + "invalidKey": "Неверный ключ API", + "keyRequired": "Пожалуйста, введите ключ API", + "mcpSync": { + "authenticationFailed": "Аутентификация не удалась, пожалуйста, проверьте ключ API", + "convertingServers": "Преобразование конфигурации сервера ...", + "description": "Синхронизация сервера MCP от моделей к локальным конфигурациям позволяет быстро добавлять обычно используемые инструменты MCP. Все службы отключены по умолчанию и могут быть включены вручную после импорта.", + "errorDetails": "Детали ошибки", + "errors": "Ошибка {count}", + "fetchingServers": "Получение списка серверов MCP ...", + "imported": "{count} Услуги были импортированы", + "importingServers": "Конфигурация импорта сервера ...", + "invalidServerData": "Неверные данные сервера", + "noApiKey": "Сначала настройте ключ API моделей ModelsCope", + "noOperationalUrls": "Не найдено доступного операционного адреса", + "noServersFound": "Не найдена доступной услуги MCP", + "pageNumber": "номер страницы", + "pageNumberPlaceholder": "Пожалуйста, введите номер страницы", + "pageSize": "Количество на страницу", + "serverAlreadyExists": "Сервер уже существует, пропустите импорт", + "skipped": "Skip {count} Сервисы", + "sync": "Начните синхронизировать", + "syncComplete": "Синхронное завершение", + "syncing": "Синхронизация ...", + "title": "Синхронизировать услуги MCP" + }, + "name": "Моделикоп", + "networkError": "Ошибка сетевого соединения", + "notConnected": "Не подключен", + "verifyFailed": "Проверка не удалась", + "verifySuccess": "Проверка успешна" + }, + "operationFailed": "Операция не удалась", + "operationSuccess": "Операция успешна", + "settingsApplied": "Применяются настройки" }, "knowledgeBase": { "title": "Настройки базы знаний", diff --git a/src/renderer/src/i18n/zh-CN/settings.json b/src/renderer/src/i18n/zh-CN/settings.json index 30e0b5783..95e9004fe 100644 --- a/src/renderer/src/i18n/zh-CN/settings.json +++ b/src/renderer/src/i18n/zh-CN/settings.json @@ -247,10 +247,68 @@ "stopModel": "停止模型", "pulling": "拉取中...", "runModel": "运行模型", + "configurationUpdated": "配置已更新", + "configurationSaved": "配置已保存", + "operationSuccess": "操作成功", + "operationFailed": "操作失败", + "dataRefreshed": "数据已刷新", + "settingsApplied": "设置已应用", "toast": { "modelRunning": "模型正在运行", "modelRunningDesc": "请先停止模型 {model},然后再删除。" }, + "modelscope": { + "name": "ModelScope", + "description": "ModelScope 是阿里巴巴达摩院推出的模型即服务共享平台", + "apiKey": "API 密钥", + "apiKeyPlaceholder": "请输入 ModelScope API Key", + "apiKeyHelper": "在 ModelScope 控制台获取您的 API Key", + "baseUrl": "API 地址", + "baseUrlHelper": "ModelScope API 服务地址", + "connected": "已连接", + "notConnected": "未连接", + "connecting": "连接中...", + "verifySuccess": "验证成功", + "verifyFailed": "验证失败", + "keyRequired": "请输入 API Key", + "invalidKey": "无效的 API Key", + "networkError": "网络连接错误", + "mcpSync": { + "title": "同步 MCP 服务", + "description": "从 ModelScope 同步 MCP 服务器到本地配置,可以快速添加常用的 MCP 工具。所有服务默认禁用,导入后可手动启用。", + "sync": "开始同步", + "syncing": "同步中...", + "pageSize": "每页数量", + "pageNumber": "页码", + "pageNumberPlaceholder": "请输入页码", + "imported": "已导入 {count} 个服务", + "skipped": "跳过 {count} 个服务", + "errors": "错误 {count} 个", + "errorDetails": "错误详情", + "noApiKey": "请先配置 ModelScope API Key", + "noServersFound": "未找到可用的 MCP 服务", + "fetchingServers": "正在获取 MCP 服务器列表...", + "convertingServers": "正在转换服务器配置...", + "importingServers": "正在导入服务器配置...", + "syncComplete": "同步完成", + "serverAlreadyExists": "服务器已存在,跳过导入", + "noOperationalUrls": "未找到可用的运营地址", + "invalidServerData": "无效的服务器数据", + "authenticationFailed": "认证失败,请检查 API Key" + }, + "details": { + "title": "提供商设置详情", + "apiConfig": "API 配置", + "rateLimitConfig": "速率限制配置", + "modelManagement": "模型管理", + "safetySettings": "安全设置", + "specialConfig": "特殊配置", + "mcpSync": "MCP 同步", + "operationalServers": "运营服务器", + "syncFromModelScope": "从 ModelScope 同步", + "operationalDescription": "同步 ModelScope 平台上可直接使用的 MCP 服务器" + } + }, "dialog": { "disableModel": { "title": "确认禁用模型", @@ -271,7 +329,10 @@ "failed": "验证失败", "success": "验证成功", "failedDesc": "API 密钥或配置验证失败,请检查配置信息", - "successDesc": "API 密钥和配置验证成功,可以正常使用" + "successDesc": "API 密钥和配置验证成功,可以正常使用", + "unauthorized": "认证失败,API Key 无效或已过期", + "serverError": "服务器错误,请稍后重试", + "connectionError": "连接错误,请检查网络连接和 API 地址" }, "addCustomProvider": { "title": "添加自定义服务商", diff --git a/src/renderer/src/i18n/zh-HK/settings.json b/src/renderer/src/i18n/zh-HK/settings.json index e73cc8fbb..5b21434b2 100644 --- a/src/renderer/src/i18n/zh-HK/settings.json +++ b/src/renderer/src/i18n/zh-HK/settings.json @@ -265,7 +265,10 @@ "failed": "驗證失敗", "success": "驗證成功", "failedDesc": "API 密鑰或配置驗證失敗,請檢查配置信息", - "successDesc": "API 密鑰和配置驗證成功,可以正常使用" + "successDesc": "API 密鑰和配置驗證成功,可以正常使用", + "connectionError": "連接錯誤,請檢查網絡連接和 API 地址", + "serverError": "服務器錯誤,請稍後重試", + "unauthorized": "認證失敗,API Key 無效或已過期" }, "addCustomProvider": { "title": "添加自定義服務商", @@ -374,7 +377,65 @@ "manageModels": "管理模型", "anthropicOAuthActiveTip": "OAuth 認證已啟用,您可以直接使用 Anthropic 服務", "oauthVerifySuccess": "OAuth 連接驗證成功", - "oauthVerifyFailed": "OAuth 連接驗證失敗" + "oauthVerifyFailed": "OAuth 連接驗證失敗", + "configurationSaved": "配置已保存", + "configurationUpdated": "配置已更新", + "dataRefreshed": "數據已刷新", + "modelscope": { + "apiKey": "API 密鑰", + "apiKeyHelper": "在 ModelScope 控制台獲取您的 API Key", + "apiKeyPlaceholder": "請輸入 ModelScope API Key", + "baseUrl": "API 地址", + "baseUrlHelper": "ModelScope API 服務地址", + "connected": "已連接", + "connecting": "連接中...", + "description": "ModelScope 是阿里巴巴達摩院推出的模型即服務共享平台", + "details": { + "apiConfig": "API 配置", + "mcpSync": "MCP 同步", + "modelManagement": "模型管理", + "operationalDescription": "同步 ModelScope 平台上可直接使用的 MCP 服務器", + "operationalServers": "運營服務器", + "rateLimitConfig": "速率限製配置", + "safetySettings": "安全設置", + "specialConfig": "特殊配置", + "syncFromModelScope": "從 ModelScope 同步", + "title": "提供商設置詳情" + }, + "invalidKey": "無效的 API Key", + "keyRequired": "請輸入 API Key", + "mcpSync": { + "authenticationFailed": "認證失敗,請檢查 API Key", + "convertingServers": "正在轉換服務器配置...", + "description": "從 ModelScope 同步 MCP 服務器到本地配置,可以快速添加常用的 MCP 工具。\n所有服務默認禁用,導入後可手動啟用。", + "errorDetails": "錯誤詳情", + "errors": "錯誤 {count} 個", + "fetchingServers": "正在獲取 MCP 服務器列表...", + "imported": "已導入 {count} 個服務", + "importingServers": "正在導入服務器配置...", + "invalidServerData": "無效的服務器數據", + "noApiKey": "請先配置 ModelScope API Key", + "noOperationalUrls": "未找到可用的運營地址", + "noServersFound": "未找到可用的 MCP 服務", + "pageNumber": "頁碼", + "pageNumberPlaceholder": "請輸入頁碼", + "pageSize": "每頁數量", + "serverAlreadyExists": "服務器已存在,跳過導入", + "skipped": "跳過 {count} 個服務", + "sync": "開始同步", + "syncComplete": "同步完成", + "syncing": "同步中...", + "title": "同步 MCP 服務" + }, + "name": "ModelScope", + "networkError": "網絡連接錯誤", + "notConnected": "未連接", + "verifyFailed": "驗證失敗", + "verifySuccess": "驗證成功" + }, + "operationFailed": "操作失敗", + "operationSuccess": "操作成功", + "settingsApplied": "設置已應用" }, "knowledgeBase": { "fastgptTitle": "FastGPT知識庫", diff --git a/src/renderer/src/i18n/zh-TW/settings.json b/src/renderer/src/i18n/zh-TW/settings.json index 728529296..421b7de4a 100644 --- a/src/renderer/src/i18n/zh-TW/settings.json +++ b/src/renderer/src/i18n/zh-TW/settings.json @@ -266,7 +266,10 @@ "failed": "驗證失敗", "success": "驗證成功", "failedDesc": "API 金鑰或設定驗證失敗,請檢查設定資訊", - "successDesc": "API 金鑰和設定驗證成功,可以正常使用" + "successDesc": "API 金鑰和設定驗證成功,可以正常使用", + "connectionError": "連接錯誤,請檢查網絡連接和 API 地址", + "serverError": "服務器錯誤,請稍後重試", + "unauthorized": "認證失敗,API Key 無效或已過期" }, "addCustomProvider": { "title": "新增自訂服務提供者", @@ -374,7 +377,65 @@ "manageModels": "管理模型", "anthropicOAuthActiveTip": "OAuth 認證已啟用,您可以直接使用 Anthropic 服務", "oauthVerifySuccess": "OAuth 連接驗證成功", - "oauthVerifyFailed": "OAuth 連接驗證失敗" + "oauthVerifyFailed": "OAuth 連接驗證失敗", + "modelscope": { + "mcpSync": { + "invalidServerData": "無效的服務器數據", + "authenticationFailed": "認證失敗,請檢查 API Key", + "convertingServers": "正在轉換服務器配置...", + "description": "從 ModelScope 同步 MCP 服務器到本地配置,可以快速添加常用的 MCP 工具。\n所有服務默認禁用,導入後可手動啟用。", + "errorDetails": "錯誤詳情", + "errors": "錯誤 {count} 個", + "fetchingServers": "正在獲取 MCP 服務器列表...", + "imported": "已導入 {count} 個服務", + "importingServers": "正在導入服務器配置...", + "noApiKey": "請先配置 ModelScope API Key", + "noOperationalUrls": "未找到可用的運營地址", + "noServersFound": "未找到可用的 MCP 服務", + "pageNumber": "頁碼", + "pageNumberPlaceholder": "請輸入頁碼", + "pageSize": "每頁數量", + "serverAlreadyExists": "服務器已存在,跳過導入", + "skipped": "跳過 {count} 個服務", + "sync": "開始同步", + "syncComplete": "同步完成", + "syncing": "同步中...", + "title": "同步 MCP 服務" + }, + "apiKey": "API 密鑰", + "apiKeyHelper": "在 ModelScope 控制台獲取您的 API Key", + "apiKeyPlaceholder": "請輸入 ModelScope API Key", + "baseUrl": "API 地址", + "baseUrlHelper": "ModelScope API 服務地址", + "connected": "已連接", + "connecting": "連接中...", + "description": "ModelScope 是阿里巴巴達摩院推出的模型即服務共享平台", + "details": { + "apiConfig": "API 配置", + "mcpSync": "MCP 同步", + "modelManagement": "模型管理", + "operationalDescription": "同步 ModelScope 平台上可直接使用的 MCP 服務器", + "operationalServers": "運營服務器", + "rateLimitConfig": "速率限製配置", + "safetySettings": "安全設置", + "specialConfig": "特殊配置", + "syncFromModelScope": "從 ModelScope 同步", + "title": "提供商設置詳情" + }, + "invalidKey": "無效的 API Key", + "keyRequired": "請輸入 API Key", + "name": "ModelScope", + "networkError": "網絡連接錯誤", + "notConnected": "未連接", + "verifyFailed": "驗證失敗", + "verifySuccess": "驗證成功" + }, + "configurationSaved": "配置已保存", + "configurationUpdated": "配置已更新", + "dataRefreshed": "數據已刷新", + "operationFailed": "操作失敗", + "operationSuccess": "操作成功", + "settingsApplied": "設置已應用" }, "knowledgeBase": { "title": "知識庫設置", diff --git a/src/shared/presenter.d.ts b/src/shared/presenter.d.ts index 777b6177a..b73a94f8d 100644 --- a/src/shared/presenter.d.ts +++ b/src/shared/presenter.d.ts @@ -528,6 +528,19 @@ export type LLM_EMBEDDING_ATTRS = { normalized: boolean } +// Simplified ModelScope MCP sync options +export interface ModelScopeMcpSyncOptions { + page_number?: number + page_size?: number +} + +// ModelScope MCP sync result interface +export interface ModelScopeMcpSyncResult { + imported: number + skipped: number + errors: string[] +} + export interface ILlmProviderPresenter { setProviders(provider: LLM_PROVIDER[]): void getProviders(): LLM_PROVIDER[] @@ -597,6 +610,10 @@ export interface ILlmProviderPresenter { lastRequestTime: number } > + syncModelScopeMcpServers( + providerId: string, + syncOptions?: ModelScopeMcpSyncOptions + ): Promise } export type CONVERSATION_SETTINGS = { systemPrompt: string From 720ee4ffd4fbbfec970d7524413cb2a4e1bb6a85 Mon Sep 17 00:00:00 2001 From: wanna Date: Tue, 12 Aug 2025 12:29:39 +0800 Subject: [PATCH 13/22] fix: move_files newPath parse issue (#725) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: move_files newPath 参数计算规则 * fix: move_files 移动前需要判断dest是目录还是文件 --- .../mcpPresenter/inMemoryServers/filesystem.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/presenter/mcpPresenter/inMemoryServers/filesystem.ts b/src/main/presenter/mcpPresenter/inMemoryServers/filesystem.ts index 44fc37e95..e500b1cd9 100644 --- a/src/main/presenter/mcpPresenter/inMemoryServers/filesystem.ts +++ b/src/main/presenter/mcpPresenter/inMemoryServers/filesystem.ts @@ -1027,12 +1027,18 @@ export class FileSystemServer { if (!parsed.success) { throw new Error(`Invalid arguments for move_files: ${parsed.error}`) } + const destInfo = await this.getFileStats(parsed.data.destination) const results = await Promise.all( parsed.data.sources.map(async (source) => { const validSourcePath = await this.validatePath(source) - const validDestPath = await this.validatePath( - path.join(parsed.data.destination, path.basename(source)) - ) + let validDestPath = '' + if (destInfo.isFile) { + validDestPath = await this.validatePath(parsed.data.destination) + } else { + validDestPath = await this.validatePath( + path.join(parsed.data.destination, path.basename(source)) + ) + } try { await fs.rename(validSourcePath, validDestPath) return `Successfully moved ${source} to ${parsed.data.destination}` From f5f93c9220e484883ea981dc72801119e53b682a Mon Sep 17 00:00:00 2001 From: yyhhyyyyyy Date: Tue, 12 Aug 2025 16:32:58 +0800 Subject: [PATCH 14/22] feat: add Claude Opus 4.1 to anthropic default model list (#726) --- .../providers/anthropicProvider.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/presenter/llmProviderPresenter/providers/anthropicProvider.ts b/src/main/presenter/llmProviderPresenter/providers/anthropicProvider.ts index da77f773f..8b78c2274 100644 --- a/src/main/presenter/llmProviderPresenter/providers/anthropicProvider.ts +++ b/src/main/presenter/llmProviderPresenter/providers/anthropicProvider.ts @@ -168,6 +168,18 @@ export class AnthropicProvider extends BaseLLMProvider { // 默认的模型列表(如API调用失败或数据格式不正确) return [ + { + id: 'claude-opus-4-1-20250805', + name: 'Claude Opus 4.1', + providerId: this.provider.id, + maxTokens: 32_000, + group: 'Claude 4.1', + isCustom: false, + contextLength: 200000, + vision: true, + functionCall: true, + reasoning: true + }, { id: 'claude-opus-4-20250514', name: 'Claude Opus 4', From f02aaaf30590147f7e8cab4ea7d26845fdb5cf36 Mon Sep 17 00:00:00 2001 From: duskzhen Date: Tue, 12 Aug 2025 19:22:35 +0800 Subject: [PATCH 15/22] feat: Add mcprouter's MCP marketplace api support (#727) * wip: add mcp market * feat: mcp market install * wip: mcp install status sync * feat: mcp server config mask * chore: remove working doc * chore: add translate --- .../providers/modelscopeProvider.ts | 27 +- src/main/presenter/mcpPresenter/index.ts | 81 +++++ .../mcpPresenter/mcprouterManager.ts | 124 ++++++++ .../components/mcp-config/mcpServerForm.vue | 105 +++++- .../components/settings/McpBuiltinMarket.vue | 300 ++++++++++++++++++ .../src/components/settings/McpSettings.vue | 17 + src/renderer/src/i18n/en-US/common.json | 3 +- src/renderer/src/i18n/en-US/mcp.json | 19 ++ src/renderer/src/i18n/en-US/routes.json | 3 +- src/renderer/src/i18n/en-US/settings.json | 1 + src/renderer/src/i18n/fa-IR/common.json | 3 +- src/renderer/src/i18n/fa-IR/mcp.json | 19 ++ src/renderer/src/i18n/fa-IR/routes.json | 3 +- src/renderer/src/i18n/fa-IR/settings.json | 3 +- src/renderer/src/i18n/fr-FR/common.json | 3 +- src/renderer/src/i18n/fr-FR/mcp.json | 19 ++ src/renderer/src/i18n/fr-FR/routes.json | 3 +- src/renderer/src/i18n/fr-FR/settings.json | 3 +- src/renderer/src/i18n/ja-JP/common.json | 3 +- src/renderer/src/i18n/ja-JP/mcp.json | 19 ++ src/renderer/src/i18n/ja-JP/routes.json | 3 +- src/renderer/src/i18n/ja-JP/settings.json | 1 + src/renderer/src/i18n/ko-KR/common.json | 3 +- src/renderer/src/i18n/ko-KR/mcp.json | 19 ++ src/renderer/src/i18n/ko-KR/routes.json | 3 +- src/renderer/src/i18n/ko-KR/settings.json | 3 +- src/renderer/src/i18n/ru-RU/common.json | 3 +- src/renderer/src/i18n/ru-RU/mcp.json | 19 ++ src/renderer/src/i18n/ru-RU/routes.json | 3 +- src/renderer/src/i18n/ru-RU/settings.json | 3 +- src/renderer/src/i18n/zh-CN/common.json | 3 +- src/renderer/src/i18n/zh-CN/mcp.json | 19 ++ src/renderer/src/i18n/zh-CN/routes.json | 3 +- src/renderer/src/i18n/zh-CN/settings.json | 1 + src/renderer/src/i18n/zh-HK/common.json | 3 +- src/renderer/src/i18n/zh-HK/mcp.json | 19 ++ src/renderer/src/i18n/zh-HK/routes.json | 3 +- src/renderer/src/i18n/zh-HK/settings.json | 3 +- src/renderer/src/i18n/zh-TW/common.json | 3 +- src/renderer/src/i18n/zh-TW/mcp.json | 19 ++ src/renderer/src/i18n/zh-TW/routes.json | 3 +- src/renderer/src/i18n/zh-TW/settings.json | 3 +- src/renderer/src/router/index.ts | 9 + src/shared/presenter.d.ts | 27 ++ 44 files changed, 883 insertions(+), 53 deletions(-) create mode 100644 src/main/presenter/mcpPresenter/mcprouterManager.ts create mode 100644 src/renderer/src/components/settings/McpBuiltinMarket.vue diff --git a/src/main/presenter/llmProviderPresenter/providers/modelscopeProvider.ts b/src/main/presenter/llmProviderPresenter/providers/modelscopeProvider.ts index e6b4df50b..eca4e601f 100644 --- a/src/main/presenter/llmProviderPresenter/providers/modelscopeProvider.ts +++ b/src/main/presenter/llmProviderPresenter/providers/modelscopeProvider.ts @@ -304,35 +304,24 @@ export class ModelscopeProvider extends OpenAICompatibleProvider { ] const randomEmoji = emojis[Math.floor(Math.random() * emojis.length)] - // Get display name: chinese_name first, then id - const displayName = mcpServer.chinese_name || mcpServer.id + // Get display name: chinese_name first, then name, then id + const displayName = mcpServer.chinese_name || mcpServer.name || mcpServer.id return { - name: `@modelscope/${mcpServer.id}`, // Use ModelScope ID format - description: - mcpServer.locales?.zh?.description || - mcpServer.description || - `ModelScope MCP Server: ${displayName}`, command: '', // Not needed for SSE type args: [], // Not needed for SSE type env: {}, - type: 'sse' as const, // SSE type for operational servers - baseUrl: baseUrl, // Use operational URL - enabled: false, // Default to disabled for safety - source: 'modelscope' as const, - // Additional metadata descriptions: mcpServer.locales?.zh?.description || mcpServer.description || `ModelScope MCP Server: ${displayName}`, icons: randomEmoji, // Random emoji instead of URL - provider: 'ModelScope', - providerUrl: `https://www.modelscope.cn/mcp/servers/@${mcpServer.id}`, - logoUrl: '', // No longer using logo URL - tags: mcpServer.tags || [], - // Store original data for reference - originalId: mcpServer.id, - displayName: displayName + autoApprove: ['all'], + disable: false, // Default to disabled for safety + type: 'sse' as const, // SSE type for operational servers + baseUrl: baseUrl, // Use operational URL + source: 'modelscope', + sourceId: mcpServer.id } } } diff --git a/src/main/presenter/mcpPresenter/index.ts b/src/main/presenter/mcpPresenter/index.ts index 2e7981eb8..3d1b7715a 100644 --- a/src/main/presenter/mcpPresenter/index.ts +++ b/src/main/presenter/mcpPresenter/index.ts @@ -12,6 +12,7 @@ import { } from '@shared/presenter' import { ServerManager } from './serverManager' import { ToolManager } from './toolManager' +import { McpRouterManager } from './mcprouterManager' import { eventBus, SendTarget } from '@/eventbus' import { MCP_EVENTS, NOTIFICATION_EVENTS } from '@/events' import { IConfigPresenter } from '@shared/presenter' @@ -83,6 +84,8 @@ export class McpPresenter implements IMCPPresenter { private toolManager: ToolManager private configPresenter: IConfigPresenter private isInitialized: boolean = false + // McpRouter + private mcprouter?: McpRouterManager constructor(configPresenter?: IConfigPresenter) { console.log('Initializing MCP Presenter') @@ -90,6 +93,12 @@ export class McpPresenter implements IMCPPresenter { this.configPresenter = configPresenter || presenter.configPresenter this.serverManager = new ServerManager(this.configPresenter) this.toolManager = new ToolManager(this.configPresenter, this.serverManager) + // init mcprouter manager + try { + this.mcprouter = new McpRouterManager(this.configPresenter) + } catch (e) { + console.warn('[MCP] McpRouterManager init failed:', e) + } // 监听自定义提示词服务器检查事件 eventBus.on(CONFIG_EVENTS.CUSTOM_PROMPTS_SERVER_CHECK_REQUIRED, async () => { @@ -181,6 +190,78 @@ export class McpPresenter implements IMCPPresenter { } } + // =============== McpRouter marketplace APIs =============== + async listMcpRouterServers( + page: number, + limit: number + ): Promise<{ + servers: Array<{ + uuid: string + created_at: string + updated_at: string + name: string + author_name: string + title: string + description: string + content?: string + server_key: string + config_name?: string + server_url?: string + }> + }> { + if (!this.mcprouter) throw new Error('McpRouterManager not available') + const data = await this.mcprouter.listServers(page, limit) + return { servers: data && data.servers ? data.servers : [] } + } + + async installMcpRouterServer(serverKey: string): Promise { + if (!this.mcprouter) throw new Error('McpRouterManager not available') + return this.mcprouter.installServer(serverKey) + } + + async getMcpRouterApiKey(): Promise { + return this.configPresenter.getSetting('mcprouterApiKey') || '' + } + + async setMcpRouterApiKey(key: string): Promise { + this.configPresenter.setSetting('mcprouterApiKey', key) + } + + async isServerInstalled(source: string, sourceId: string): Promise { + const servers = await this.configPresenter.getMcpServers() + for (const config of Object.values(servers)) { + if (config.source === source && config.sourceId === sourceId) { + return true + } + } + return false + } + + async updateMcpRouterServersAuth(apiKey: string): Promise { + const servers = await this.configPresenter.getMcpServers() + const updates: Array<{ name: string; config: Partial }> = [] + + for (const [serverName, config] of Object.entries(servers)) { + if (config.source === 'mcprouter' && config.customHeaders) { + const updatedHeaders = { + ...config.customHeaders, + Authorization: `Bearer ${apiKey}` + } + updates.push({ + name: serverName, + config: { customHeaders: updatedHeaders } + }) + } + } + + // 批量更新所有服务器的 Authorization + for (const update of updates) { + await this.configPresenter.updateMcpServer(update.name, update.config) + } + + console.log(`Updated Authorization for ${updates.length} mcprouter servers`) + } + private scheduleBackgroundRegistryUpdate(): void { setTimeout(async () => { try { diff --git a/src/main/presenter/mcpPresenter/mcprouterManager.ts b/src/main/presenter/mcpPresenter/mcprouterManager.ts new file mode 100644 index 000000000..186682847 --- /dev/null +++ b/src/main/presenter/mcpPresenter/mcprouterManager.ts @@ -0,0 +1,124 @@ +import { IConfigPresenter, MCPServerConfig } from '@shared/presenter' + +type McpRouterListResponse = { + code: number + message: string + data?: { + servers: Array<{ + uuid: string + created_at: string + updated_at: string + name: string + author_name: string + title: string + description: string + content?: string + server_key: string + config_name?: string + server_url?: string + }> + } +} + +type McpRouterGetResponse = { + code: number + message: string + data?: { + created_at: string + updated_at: string + name: string + author_name: string + title: string + description: string + content?: string + server_key: string + config_name: string + server_url: string + } +} + +const LIST_ENDPOINT = 'https://api.mcprouter.to/v1/list-servers' +const GET_ENDPOINT = 'https://api.mcprouter.to/v1/get-server' + +export class McpRouterManager { + constructor(private readonly configPresenter: IConfigPresenter) {} + + private getCommonHeaders(): Record { + return { + 'Content-Type': 'application/json', + 'HTTP-Referer': 'deepchatai.cn', + 'X-Title': 'DeepChat' + } + } + + async listServers(page: number, limit: number): Promise { + const res = await fetch(LIST_ENDPOINT, { + method: 'POST', + headers: this.getCommonHeaders(), + body: JSON.stringify({ page, limit }) + }) + if (!res.ok) throw new Error(`McpRouter list failed: HTTP ${res.status}`) + const json = (await res.json()) as McpRouterListResponse + if (json.code !== 0) throw new Error(json.message || 'List servers error') + return json.data || { servers: [] } + } + + async getServer(serverKey: string): Promise { + const apiKey = this.configPresenter.getSetting('mcprouterApiKey') || '' + if (!apiKey) throw new Error('McpRouter API key missing') + const headers = { + ...this.getCommonHeaders(), + Authorization: `Bearer ${apiKey}` + } + const res = await fetch(GET_ENDPOINT, { + method: 'POST', + headers, + body: JSON.stringify({ server: serverKey }) + }) + if (!res.ok) throw new Error(`McpRouter get failed: HTTP ${res.status}`) + const json = (await res.json()) as McpRouterGetResponse + if (json.code !== 0 || !json.data) throw new Error(json.message || 'Get server error') + return json.data + } + + private pickRandomEmoji(): string { + const emojis = ['🧩', '🛠️', '⚙️', '🚀', '🔧', '🧪', '📦', '🛰️', '🧠', '🔌', '📡', '🗂️'] + const idx = Math.floor(Math.random() * emojis.length) + return emojis[idx] + } + + /** + * Install a server from McpRouter to local MCP config as HTTP (Streamable) server + */ + async installServer(serverKey: string): Promise { + const detail = await this.getServer(serverKey) + if (!detail) throw new Error('Server detail not found') + + const apiKey = this.configPresenter.getSetting('mcprouterApiKey') || '' + if (!apiKey) throw new Error('McpRouter API key missing') + + // Build MCPServerConfig + const config: MCPServerConfig = { + command: '', + args: [], + env: {}, + descriptions: detail.description || detail.title || detail.name, + icons: this.pickRandomEmoji(), + autoApprove: ['all'], + disable: false, + type: 'http', + baseUrl: detail.server_url, + customHeaders: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${apiKey}`, + 'HTTP-Referer': 'deepchatai.cn', + 'X-Title': 'DeepChat' + }, + source: 'mcprouter', + sourceId: serverKey + } + + const serverName = detail.config_name || detail.server_key || detail.name + return await this.configPresenter.addMcpServer(serverName, config) + } +} diff --git a/src/renderer/src/components/mcp-config/mcpServerForm.vue b/src/renderer/src/components/mcp-config/mcpServerForm.vue index c2c32f679..4fdda6211 100644 --- a/src/renderer/src/components/mcp-config/mcpServerForm.vue +++ b/src/renderer/src/components/mcp-config/mcpServerForm.vue @@ -56,6 +56,8 @@ const icons = ref(props.initialConfig?.icons || '📁') const type = ref<'sse' | 'stdio' | 'inmemory' | 'http'>(props.initialConfig?.type || 'stdio') const baseUrl = ref(props.initialConfig?.baseUrl || '') const customHeaders = ref('') +const customHeadersFocused = ref(false) +const customHeadersDisplayValue = ref('') const npmRegistry = ref(props.initialConfig?.customNpmRegistry || '') // 模型选择相关 @@ -551,6 +553,65 @@ watch( { immediate: true } ) +// 遮蔽敏感内容的函数 +const maskSensitiveValue = (value: string): string => { + // 只遮蔽等号后面的值,保留键名 + return value.replace(/=(.+)/g, (_, val) => { + const trimmedVal = val.trim() + if (trimmedVal.length <= 4) { + // 很短的值完全遮蔽 + return '=' + '*'.repeat(trimmedVal.length) + } else if (trimmedVal.length <= 12) { + // 中等长度:显示前1个字符,其余用固定数量星号 + return '=' + trimmedVal.substring(0, 1) + '*'.repeat(6) + } else { + // 长值:显示前2个和后2个字符,中间用固定8个星号 + const start = trimmedVal.substring(0, 2) + const end = trimmedVal.substring(trimmedVal.length - 2) + return '=' + start + '*'.repeat(8) + end + } + }) +} + +// 生成用于显示的 customHeaders 值 +const updateCustomHeadersDisplay = (): void => { + if (customHeadersFocused.value || !customHeaders.value.trim()) { + customHeadersDisplayValue.value = customHeaders.value + } else { + // 遮蔽敏感内容 + const lines = customHeaders.value.split('\n') + const maskedLines = lines.map((line) => { + const trimmedLine = line.trim() + if (!trimmedLine || !trimmedLine.includes('=')) { + return line + } + return maskSensitiveValue(line) + }) + customHeadersDisplayValue.value = maskedLines.join('\n') + } +} + +// 处理 customHeaders 获得焦点 +const handleCustomHeadersFocus = (): void => { + customHeadersFocused.value = true + updateCustomHeadersDisplay() +} + +// 处理 customHeaders 失去焦点 +const handleCustomHeadersBlur = (): void => { + customHeadersFocused.value = false + updateCustomHeadersDisplay() +} + +// 监听 customHeaders 变化以更新显示值 +watch( + customHeaders, + () => { + updateCustomHeadersDisplay() + }, + { immediate: true } +) + // 初始化时解析args中的provider和modelId(针对imageServer) watch( [() => name.value, () => args.value, () => type.value], @@ -655,8 +716,6 @@ const parseKeyValueHeaders = (text: string): Record => { return headers } -// --- 结束新增辅助函数 --- - // 定义 customHeaders 的 placeholder const customHeadersPlaceholder = `Authorization=Bearer your_token HTTP-Referer=deepchatai.cn` @@ -1083,17 +1142,43 @@ HTTP-Referer=deepchatai.cn` -