From 83e4d4363fc069e7477ee06feafa0099dfa87630 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Thu, 6 Nov 2025 10:43:33 +0800 Subject: [PATCH 1/4] fix: add Perplexity provider support and update API host formatting (#11162) * feat: add Perplexity provider support and update API host formatting - Introduced `isPerplexityProvider` function to identify Perplexity providers. - Updated `formatProviderApiHost` to handle Perplexity provider API host formatting. - Added unit tests for Perplexity provider configuration to ensure correct API host formatting behavior. * fix: add 'perplexity' to unsupported API version providers list --- .../provider/__tests__/providerConfig.test.ts | 80 ++++++++++++++++++- .../src/aiCore/provider/providerConfig.ts | 5 +- src/renderer/src/config/providers.ts | 6 +- 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/aiCore/provider/__tests__/providerConfig.test.ts b/src/renderer/src/aiCore/provider/__tests__/providerConfig.test.ts index cc5f20c63e..39786231e6 100644 --- a/src/renderer/src/aiCore/provider/__tests__/providerConfig.test.ts +++ b/src/renderer/src/aiCore/provider/__tests__/providerConfig.test.ts @@ -39,6 +39,7 @@ vi.mock('@renderer/config/providers', async (importOriginal) => { return { ...actual, isCherryAIProvider: vi.fn(), + isPerplexityProvider: vi.fn(), isAnthropicProvider: vi.fn(() => false), isAzureOpenAIProvider: vi.fn(() => false), isGeminiProvider: vi.fn(() => false), @@ -52,7 +53,7 @@ vi.mock('@renderer/hooks/useVertexAI', () => ({ createVertexProvider: vi.fn() })) -import { isCherryAIProvider } from '@renderer/config/providers' +import { isCherryAIProvider, isPerplexityProvider } from '@renderer/config/providers' import { getProviderByModel } from '@renderer/services/AssistantService' import type { Model, Provider } from '@renderer/types' import { formatApiHost } from '@renderer/utils/api' @@ -97,6 +98,16 @@ const createCherryAIProvider = (): Provider => ({ isSystem: false }) +const createPerplexityProvider = (): Provider => ({ + id: 'perplexity', + type: 'openai', + name: 'Perplexity', + apiKey: 'test-key', + apiHost: 'https://api.perplexity.ai', + models: [], + isSystem: false +}) + describe('Copilot responses routing', () => { beforeEach(() => { ;(globalThis as any).window = { @@ -195,3 +206,70 @@ describe('CherryAI provider configuration', () => { expect(actualProvider.apiHost).toBe('') }) }) + +describe('Perplexity provider configuration', () => { + beforeEach(() => { + ;(globalThis as any).window = { + ...(globalThis as any).window, + keyv: createWindowKeyv() + } + vi.clearAllMocks() + }) + + it('formats Perplexity provider apiHost with false parameter', () => { + const provider = createPerplexityProvider() + const model = createModel('sonar', 'Sonar', 'perplexity') + + // Mock the functions to simulate Perplexity provider detection + vi.mocked(isCherryAIProvider).mockReturnValue(false) + vi.mocked(isPerplexityProvider).mockReturnValue(true) + vi.mocked(getProviderByModel).mockReturnValue(provider) + + // Call getActualProvider which should trigger formatProviderApiHost + const actualProvider = getActualProvider(model) + + // Verify that formatApiHost was called with false as the second parameter + expect(formatApiHost).toHaveBeenCalledWith('https://api.perplexity.ai', false) + expect(actualProvider.apiHost).toBe('https://api.perplexity.ai') + }) + + it('does not format non-Perplexity provider with false parameter', () => { + const provider = { + id: 'openai', + type: 'openai', + name: 'OpenAI', + apiKey: 'test-key', + apiHost: 'https://api.openai.com', + models: [], + isSystem: false + } as Provider + const model = createModel('gpt-4', 'GPT-4', 'openai') + + // Mock the functions to simulate non-Perplexity provider + vi.mocked(isCherryAIProvider).mockReturnValue(false) + vi.mocked(isPerplexityProvider).mockReturnValue(false) + vi.mocked(getProviderByModel).mockReturnValue(provider) + + // Call getActualProvider + const actualProvider = getActualProvider(model) + + // Verify that formatApiHost was called with default parameters (true) + expect(formatApiHost).toHaveBeenCalledWith('https://api.openai.com') + expect(actualProvider.apiHost).toBe('https://api.openai.com/v1') + }) + + it('handles Perplexity provider with empty apiHost', () => { + const provider = createPerplexityProvider() + provider.apiHost = '' + const model = createModel('sonar', 'Sonar', 'perplexity') + + vi.mocked(isCherryAIProvider).mockReturnValue(false) + vi.mocked(isPerplexityProvider).mockReturnValue(true) + vi.mocked(getProviderByModel).mockReturnValue(provider) + + const actualProvider = getActualProvider(model) + + expect(formatApiHost).toHaveBeenCalledWith('', false) + expect(actualProvider.apiHost).toBe('') + }) +}) diff --git a/src/renderer/src/aiCore/provider/providerConfig.ts b/src/renderer/src/aiCore/provider/providerConfig.ts index 4669d4c851..7f279a3898 100644 --- a/src/renderer/src/aiCore/provider/providerConfig.ts +++ b/src/renderer/src/aiCore/provider/providerConfig.ts @@ -11,7 +11,8 @@ import { isAzureOpenAIProvider, isCherryAIProvider, isGeminiProvider, - isNewApiProvider + isNewApiProvider, + isPerplexityProvider } from '@renderer/config/providers' import { getAwsBedrockAccessKeyId, @@ -103,6 +104,8 @@ function formatProviderApiHost(provider: Provider): Provider { formatted.apiHost = formatVertexApiHost(formatted) } else if (isCherryAIProvider(formatted)) { formatted.apiHost = formatApiHost(formatted.apiHost, false) + } else if (isPerplexityProvider(formatted)) { + formatted.apiHost = formatApiHost(formatted.apiHost, false) } else { formatted.apiHost = formatApiHost(formatted.apiHost) } diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index 5fbae73dbf..50b0dbaece 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -1490,6 +1490,10 @@ export function isCherryAIProvider(provider: Provider): boolean { return provider.id === 'cherryai' } +export function isPerplexityProvider(provider: Provider): boolean { + return provider.id === 'perplexity' +} + /** * 判断是否为 OpenAI 兼容的提供商 * @param {Provider} provider 提供商对象 @@ -1515,7 +1519,7 @@ export function isGeminiProvider(provider: Provider): boolean { return provider.type === 'gemini' } -const NOT_SUPPORT_API_VERSION_PROVIDERS = ['github', 'copilot'] as const satisfies SystemProviderId[] +const NOT_SUPPORT_API_VERSION_PROVIDERS = ['github', 'copilot', 'perplexity'] as const satisfies SystemProviderId[] export const isSupportAPIVersionProvider = (provider: Provider) => { if (isSystemProvider(provider)) { From 816a92c6094d1ec221c3c2d10fc90e81bd70c84a Mon Sep 17 00:00:00 2001 From: Jake Jia <87770044+Phoen1xCode@users.noreply.github.com> Date: Thu, 6 Nov 2025 14:46:42 +0800 Subject: [PATCH 2/4] feat(app-menu): add full i18n support and sync lanuage with app language settings (#11131) Previously, the macOS menu bar was always displayed in English regardless of system language or in-app language settings. This change enables the menu bar to dynamically follow the application's language preference. Key changes: - Add language change listener to automatically update menu when user switches language - Refactor AppMenuService with proper subscription management and cleanup - Add appMenu translations for en-us, zh-cn, and zh-tw locales - Implement destroy method to prevent memory leaks from config subscriptions - Convert all menu items (File, Edit, View, Window, Help) to use localized labels The menu bar now respects the in-app language setting and updates in real-time when users change their preferences, providing a consistent multilingual experience. --- src/main/services/AppMenuService.ts | 77 +++++++++++++++++++----- src/renderer/src/i18n/locales/en-us.json | 35 +++++++++++ src/renderer/src/i18n/locales/zh-cn.json | 35 +++++++++++ src/renderer/src/i18n/locales/zh-tw.json | 35 +++++++++++ 4 files changed, 166 insertions(+), 16 deletions(-) diff --git a/src/main/services/AppMenuService.ts b/src/main/services/AppMenuService.ts index abb494f707..b3a9de01d0 100644 --- a/src/main/services/AppMenuService.ts +++ b/src/main/services/AppMenuService.ts @@ -7,16 +7,33 @@ import { app, Menu, shell } from 'electron' import { configManager } from './ConfigManager' export class AppMenuService { + private languageChangeCallback?: (newLanguage: string) => void + + constructor() { + // Subscribe to language change events + this.languageChangeCallback = () => { + this.setupApplicationMenu() + } + configManager.subscribe('language', this.languageChangeCallback) + } + + public destroy(): void { + // Clean up subscription to prevent memory leaks + if (this.languageChangeCallback) { + configManager.unsubscribe('language', this.languageChangeCallback) + } + } + public setupApplicationMenu(): void { const locale = locales[configManager.getLanguage()] - const { common } = locale.translation + const { appMenu } = locale.translation const template: MenuItemConstructorOptions[] = [ { label: app.name, submenu: [ { - label: common.about + ' ' + app.name, + label: appMenu.about + ' ' + app.name, click: () => { // Emit event to navigate to About page const mainWindow = windowService.getMainWindow() @@ -27,50 +44,78 @@ export class AppMenuService { } }, { type: 'separator' }, - { role: 'services' }, + { role: 'services', label: appMenu.services }, { type: 'separator' }, - { role: 'hide' }, - { role: 'hideOthers' }, - { role: 'unhide' }, + { role: 'hide', label: `${appMenu.hide} ${app.name}` }, + { role: 'hideOthers', label: appMenu.hideOthers }, + { role: 'unhide', label: appMenu.unhide }, { type: 'separator' }, - { role: 'quit' } + { role: 'quit', label: `${appMenu.quit} ${app.name}` } ] }, { - role: 'fileMenu' + label: appMenu.file, + submenu: [{ role: 'close', label: appMenu.close }] }, { - role: 'editMenu' + label: appMenu.edit, + submenu: [ + { role: 'undo', label: appMenu.undo }, + { role: 'redo', label: appMenu.redo }, + { type: 'separator' }, + { role: 'cut', label: appMenu.cut }, + { role: 'copy', label: appMenu.copy }, + { role: 'paste', label: appMenu.paste }, + { role: 'delete', label: appMenu.delete }, + { role: 'selectAll', label: appMenu.selectAll } + ] }, { - role: 'viewMenu' + label: appMenu.view, + submenu: [ + { role: 'reload', label: appMenu.reload }, + { role: 'forceReload', label: appMenu.forceReload }, + { role: 'toggleDevTools', label: appMenu.toggleDevTools }, + { type: 'separator' }, + { role: 'resetZoom', label: appMenu.resetZoom }, + { role: 'zoomIn', label: appMenu.zoomIn }, + { role: 'zoomOut', label: appMenu.zoomOut }, + { type: 'separator' }, + { role: 'togglefullscreen', label: appMenu.toggleFullscreen } + ] }, { - role: 'windowMenu' + label: appMenu.window, + submenu: [ + { role: 'minimize', label: appMenu.minimize }, + { role: 'zoom', label: appMenu.zoom }, + { type: 'separator' }, + { role: 'front', label: appMenu.front } + ] }, { - role: 'help', + label: appMenu.help, submenu: [ { - label: 'Website', + label: appMenu.website, click: () => { shell.openExternal('https://cherry-ai.com') } }, { - label: 'Documentation', + label: appMenu.documentation, click: () => { shell.openExternal('https://cherry-ai.com/docs') } }, { - label: 'Feedback', + label: appMenu.feedback, click: () => { shell.openExternal('https://github.com/CherryHQ/cherry-studio/issues/new/choose') } }, { - label: 'Releases', + label: appMenu.releases, click: () => { shell.openExternal('https://github.com/CherryHQ/cherry-studio/releases') } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 36ce60edb6..e851242559 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -339,6 +339,41 @@ }, "title": "API Server" }, + "appMenu": { + "about": "About", + "close": "Close Window", + "copy": "Copy", + "cut": "Cut", + "delete": "Delete", + "documentation": "Documentation", + "edit": "Edit", + "feedback": "Feedback", + "file": "File", + "forceReload": "Force Reload", + "front": "Bring All to Front", + "help": "Help", + "hide": "Hide", + "hideOthers": "Hide Others", + "minimize": "Minimize", + "paste": "Paste", + "quit": "Quit", + "redo": "Redo", + "releases": "Releases", + "reload": "Reload", + "resetZoom": "Actual Size", + "selectAll": "Select All", + "services": "Services", + "toggleDevTools": "Toggle Developer Tools", + "toggleFullscreen": "Toggle Fullscreen", + "undo": "Undo", + "unhide": "Show All", + "view": "View", + "website": "Website", + "window": "Window", + "zoom": "Zoom", + "zoomIn": "Zoom In", + "zoomOut": "Zoom Out" + }, "assistants": { "abbr": "Assistants", "clear": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 0acb816a1c..9174938a8e 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -339,6 +339,41 @@ }, "title": "API 服务器" }, + "appMenu": { + "about": "关于", + "close": "关闭窗口", + "copy": "复制", + "cut": "剪切", + "delete": "删除", + "documentation": "文档", + "edit": "编辑", + "feedback": "反馈", + "file": "文件", + "forceReload": "强制重新加载", + "front": "全部置于顶层", + "help": "帮助", + "hide": "隐藏", + "hideOthers": "隐藏其他", + "minimize": "最小化", + "paste": "粘贴", + "quit": "退出", + "redo": "重做", + "releases": "版本发布", + "reload": "重新加载", + "resetZoom": "实际大小", + "selectAll": "全选", + "services": "服务", + "toggleDevTools": "切换开发者工具", + "toggleFullscreen": "切换全屏", + "undo": "撤销", + "unhide": "全部显示", + "view": "视图", + "website": "网站", + "window": "窗口", + "zoom": "缩放", + "zoomIn": "放大", + "zoomOut": "缩小" + }, "assistants": { "abbr": "助手", "clear": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 913a4de5ad..01fc28df0d 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -339,6 +339,41 @@ }, "title": "API 伺服器" }, + "appMenu": { + "about": "關於", + "close": "關閉視窗", + "copy": "複製", + "cut": "剪下", + "delete": "刪除", + "documentation": "文件", + "edit": "編輯", + "feedback": "回饋", + "file": "檔案", + "forceReload": "強制重新載入", + "front": "全部置於頂層", + "help": "幫助", + "hide": "隱藏", + "hideOthers": "隱藏其他", + "minimize": "最小化", + "paste": "貼上", + "quit": "結束", + "redo": "重做", + "releases": "版本發布", + "reload": "重新載入", + "resetZoom": "實際大小", + "selectAll": "全選", + "services": "服務", + "toggleDevTools": "切換開發者工具", + "toggleFullscreen": "切換全螢幕", + "undo": "復原", + "unhide": "全部顯示", + "view": "檢視", + "website": "網站", + "window": "視窗", + "zoom": "縮放", + "zoomIn": "放大", + "zoomOut": "縮小" + }, "assistants": { "abbr": "助手", "clear": { From 76483d828e4aebcc592b2d3ccf2d58726768378c Mon Sep 17 00:00:00 2001 From: Phantom Date: Thu, 6 Nov 2025 18:25:04 +0800 Subject: [PATCH 3/4] ci(i18n): change auto i18n workflow to run weekly (#11152) * ci(i18n): change auto i18n workflow to run weekly Update the workflow to run on a weekly schedule instead of on pull request events. Also simplify the workflow by using yarn for dependency management and create a PR for changes instead of committing directly to the branch. * ci(github-actions): improve workflow step names with emojis Use emojis in step names to enhance readability and visual scanning of workflow logs * ci(workflow): prevent committing package.json and yarn.lock changes in i18n workflow --- .github/workflows/auto-i18n.yml | 85 +++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/.github/workflows/auto-i18n.yml b/.github/workflows/auto-i18n.yml index 1a85b16757..1584ab48db 100644 --- a/.github/workflows/auto-i18n.yml +++ b/.github/workflows/auto-i18n.yml @@ -1,4 +1,4 @@ -name: Auto I18N +name: Auto I18N Weekly env: TRANSLATION_API_KEY: ${{ secrets.TRANSLATE_API_KEY }} @@ -7,14 +7,15 @@ env: TRANSLATION_BASE_LOCALE: ${{ vars.AUTO_I18N_BASE_LOCALE || 'en-us'}} on: - pull_request: - types: [opened, synchronize, reopened] + schedule: + # Runs at 00:00 UTC every Sunday. + # This corresponds to 08:00 AM UTC+8 (Beijing time) every Sunday. + - cron: "0 0 * * 0" workflow_dispatch: jobs: auto-i18n: runs-on: ubuntu-latest - if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == 'CherryHQ/cherry-studio' name: Auto I18N permissions: contents: write @@ -24,45 +25,69 @@ jobs: - name: 🐈‍⬛ Checkout uses: actions/checkout@v5 with: - ref: ${{ github.event.pull_request.head.ref }} + fetch-depth: 0 - name: 📦 Setting Node.js uses: actions/setup-node@v6 with: node-version: 22 - package-manager-cache: false - - name: 📦 Install dependencies in isolated directory + - name: 📦 Install corepack + run: corepack enable && corepack prepare yarn@4.9.1 --activate + + - name: 📂 Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT + + - name: 💾 Cache yarn dependencies + uses: actions/cache@v4 + with: + path: | + ${{ steps.yarn-cache-dir-path.outputs.dir }} + node_modules + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: 📦 Install dependencies run: | - # 在临时目录安装依赖 - mkdir -p /tmp/translation-deps - cd /tmp/translation-deps - echo '{"dependencies": {"@cherrystudio/openai": "^6.5.0", "cli-progress": "^3.12.0", "tsx": "^4.20.3", "@biomejs/biome": "2.2.4"}}' > package.json - npm install --no-package-lock - - # 设置 NODE_PATH 让项目能找到这些依赖 - echo "NODE_PATH=/tmp/translation-deps/node_modules" >> $GITHUB_ENV + yarn install - name: 🏃‍♀️ Translate - run: npx tsx scripts/sync-i18n.ts && npx tsx scripts/auto-translate-i18n.ts + run: yarn sync:i18n && yarn auto:i18n - name: 🔍 Format - run: cd /tmp/translation-deps && npx biome format --config-path /home/runner/work/cherry-studio/cherry-studio/biome.jsonc --write /home/runner/work/cherry-studio/cherry-studio/src/renderer/src/i18n/ + run: yarn format - - name: 🔄 Commit changes + - name: 🔍 Check for changes + id: git_status run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add . + # Check if there are any uncommitted changes git reset -- package.json yarn.lock # 不提交 package.json 和 yarn.lock 的更改 - if git diff --cached --quiet; then - echo "No changes to commit" - else - git commit -m "fix(i18n): Auto update translations for PR #${{ github.event.pull_request.number }}" - fi + git diff --exit-code --quiet || echo "::set-output name=has_changes::true" + git status --porcelain - - name: 🚀 Push changes - uses: ad-m/github-push-action@master + - name: 📅 Set current date for PR title + id: set_date + run: echo "CURRENT_DATE=$(date +'%b %d, %Y')" >> $GITHUB_ENV # e.g., "Jun 06, 2024" + + - name: 🚀 Create Pull Request if changes exist + if: steps.git_status.outputs.has_changes == 'true' + uses: peter-evans/create-pull-request@v6 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: ${{ github.event.pull_request.head.ref }} + token: ${{ secrets.GITHUB_TOKEN }} # Use the built-in GITHUB_TOKEN for bot actions + commit-message: "feat(bot): Weekly automated script run" + title: "🤖 Weekly Automated Update: ${{ env.CURRENT_DATE }}" + body: | + This PR includes changes generated by the weekly auto i18n. + Review the changes before merging. + + --- + _Generated by the automated weekly workflow_ + branch: "auto-i18n-weekly-${{ github.run_id }}" # Unique branch name + base: "main" # Or 'develop', set your base branch + delete-branch: true # Delete the branch after merging or closing the PR + + - name: 📢 Notify if no changes + if: steps.git_status.outputs.has_changes != 'true' + run: echo "Bot script ran, but no changes were detected. No PR created." From 78278ce96d72eaa2bdb62ec115cc872ee25227e4 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Thu, 6 Nov 2025 18:04:26 +0800 Subject: [PATCH 4/4] refactor: remove heroui commit 7c8bf8b591aaff90655b1453e3fac6e51b8b3426 Author: defi-failure <159208748+defi-failure@users.noreply.github.com> Date: Thu Nov 6 17:59:38 2025 +0800 fix: add token usage to agent session message commit ff8e5ddd27c94eea87fc8cb50b31033f94e81b68 Author: defi-failure <159208748+defi-failure@users.noreply.github.com> Date: Thu Nov 6 17:25:54 2025 +0800 fix: close prompt stream when finish or error chunk received commit 530e6516fd029494b04e2ebfac041b4074e02a74 Author: defi-failure <159208748+defi-failure@users.noreply.github.com> Date: Thu Nov 6 17:19:53 2025 +0800 chore: code cleanup commit ab21c0d56c4d3bc78037d2f2cb419c173679650c Author: kangfenmao Date: Thu Nov 6 16:13:36 2025 +0800 feat(SessionItem): implement auto-rename feature for sessions and improve context menu handling - Added a new context menu option to automatically rename sessions based on topics. - Introduced useDeferredValue for managing target session state. - Updated imports to include necessary thunk actions and components. - Enhanced API service to handle optional assistant model in message summary fetching. - Exported renameAgentSessionIfNeeded function for better accessibility in the store. commit 21ea8ccf372f3c08de6402298d54e3b996259653 Merge: ab7b207d2 816a92c60 Author: kangfenmao Date: Thu Nov 6 15:29:09 2025 +0800 Merge branch 'main' of github.com:CherryHQ/cherry-studio into refactor/heroui-antd # Conflicts: # src/renderer/src/pages/home/Tabs/components/AddButton.tsx # src/renderer/src/pages/home/Tabs/components/SessionItem.tsx # src/renderer/src/pages/home/Tabs/components/Sessions.tsx # src/renderer/src/pages/home/Tabs/components/Topics.tsx # src/renderer/src/pages/paintings/NewApiPage.tsx commit ab7b207d295dbfed5971b3976bad54578ceeeae1 Author: kangfenmao Date: Thu Nov 6 14:50:05 2025 +0800 refactor: streamline event listener management in useAppInit and update ToolPermissionRequestCard styling commit 3834c5d4021afad2c5b755e180f260863255dc7f Author: kangfenmao Date: Thu Nov 6 14:21:25 2025 +0800 refactor: enhance API server state management and remove unused initialization in useAppInit commit a64b94a41f15bcfe93e7641fc28dcf703ca08ffd Author: kangfenmao Date: Thu Nov 6 13:21:58 2025 +0800 refactor: update OpenAPI documentation paths to include subdirectories for better route coverage commit 2e0ff28505c4372e14aff8ba33eae4f5310939f3 Author: kangfenmao Date: Thu Nov 6 12:26:09 2025 +0800 refactor: center align columns in InstalledPluginsList and set AntTable size to small commit 84bf94e2ff3c3783ac08ff5e70d2b7b243073c6c Author: defi-failure <159208748+defi-failure@users.noreply.github.com> Date: Thu Nov 6 12:06:09 2025 +0800 refactor: align create agent model selection with edit agent commit 84f22815063dddc76bd6efa6b9fba05322fffe5c Author: kangfenmao Date: Thu Nov 6 11:29:32 2025 +0800 refactor: integrate API server functionality into various components and enhance user notifications commit 4e01210df4b6bff43b05da9636c7e396664e9c72 Author: kangfenmao Date: Thu Nov 6 10:56:38 2025 +0800 refactor: replace ContextMenu with Dropdown in AgentItem and SessionItem components for improved context menu handling commit 9df38c7e8376f83087aebcf0a0dc842729afe11f Author: kangfenmao Date: Thu Nov 6 10:27:30 2025 +0800 refactor: update AddButton styling to use CSS variable for border radius and remove unused settings hook commit 251c269ab3cb053682d7219385e90093138cd643 Author: kangfenmao Date: Thu Nov 6 10:11:21 2025 +0800 refactor: remove unused error handling alerts from AssistantsTab component commit 9b9640d8d150b559ec57e211b0f9140d63a89c76 Author: kangfenmao Date: Thu Nov 6 10:07:26 2025 +0800 refactor: adjust margin styling for UnifiedAddButton component commit edd6b11aa7955459f78a11f8b539528cbdff8b19 Author: kangfenmao Date: Thu Nov 6 10:04:01 2025 +0800 refactor: update AddButton styling based on topic position and clean up CSS for root element commit 1c0de625d8018c7073f91483592795a89674422f Author: kangfenmao Date: Thu Nov 6 09:56:42 2025 +0800 fix: update assistant addition messages for multiple languages commit 0ea4dd4e3a51adae81b6d7fd46b3fea29e5bd58a Author: dev Date: Wed Nov 5 21:01:24 2025 +0800 fix: init message api err commit f3bbd4ed449ad91dca618b118dd44532904579ac Author: dev Date: Wed Nov 5 20:42:49 2025 +0800 refactor: remove heroui commit d01609fc367cc58c5c95fd9eda598112ff87c747 Author: dev Date: Wed Nov 5 19:08:41 2025 +0800 refactor: migrate heroui/toast to antd message commit f4b14dfc105a1e86b38eb4c6eb141b98d33b8453 Author: kangfenmao Date: Wed Nov 5 18:51:29 2025 +0800 refactor: enhance Sessions component layout with styled Scrollbar and adjust UnifiedAddButton margins commit 6ae5f6916389a3b0d8c79da9e0eb70233c97df54 Author: kangfenmao Date: Wed Nov 5 18:44:13 2025 +0800 refactor: update PluginSettings and ToolingSettings for improved layout and functionality commit fcb002078734961a43e11ebe3d1d72e7d1844840 Author: kangfenmao Date: Wed Nov 5 18:29:52 2025 +0800 wip commit 02265f369e1b7b22307a4cd8422564a783bf9f12 Author: dev Date: Wed Nov 5 17:26:39 2025 +0800 fix: error block related commit 5e22d9d36fa6eaae0b5700353a25c5b30059570e Author: dev Date: Wed Nov 5 17:14:25 2025 +0800 fix: note head nav related commit 3f52b7766a3157d010dc8d06d742bf7de86e4e76 Author: dev Date: Wed Nov 5 16:45:49 2025 +0800 chore: remove dead code commit 484622f12bd0687c7d5a482c2c934ed1a06a7cf0 Author: dev Date: Wed Nov 5 16:43:12 2025 +0800 chore: remove dead code commit 2bceb302e08378e03720b8915649787f22494590 Author: dev Date: Wed Nov 5 15:33:25 2025 +0800 fix: tool setting related commit 5c455f25eb1eaceb72951d0b74137b3e9b77b4a1 Author: dev Date: Wed Nov 5 13:59:33 2025 +0800 chore: remove dead code commit d1d1dbc046fd0d1e2e4a5a576be282d935ef37e8 Author: dev Date: Wed Nov 5 13:51:41 2025 +0800 fix: tool permission card related commit bf4ec23ef79aec2e4a3648eb745ad3def92a00e9 Author: dev Date: Wed Nov 5 12:22:53 2025 +0800 fix: remove button and modal renaming commit 47db5baeb1a232946a419620d0e1374f848029c6 Author: dev Date: Wed Nov 5 12:20:36 2025 +0800 fix: plugin setting related commit 81fecce552d79e1f5650e3851136588ed169c53c Author: kangfenmao Date: Wed Nov 5 12:16:42 2025 +0800 refactor: enhance ChatNavbarContent structure by replacing Breadcrumbs with custom layout and adding separators commit fc64b6c6115d1ed51ceefea301d3ded573ae43cb Author: kangfenmao Date: Wed Nov 5 12:10:48 2025 +0800 refactor: simplify MessageAgentTools component structure by removing unnecessary wrapper div commit e0f383a050e2157a399a27f103d42f63f707efd2 Author: kangfenmao Date: Wed Nov 5 12:08:32 2025 +0800 fix: update button classes in AddAssistantOrAgentPopup for improved cursor behavior commit 720284262ffb35fe187284306e5d0d5f68095e6e Author: kangfenmao Date: Wed Nov 5 12:06:58 2025 +0800 refactor: update AgentModal to use TopView for improved modal management and enhance form structure commit b334a2c5be4f7caa61d6885a7782e2f317b64f56 Author: kangfenmao Date: Wed Nov 5 11:40:47 2025 +0800 refactor: replace UpdateDialog with UpdateDialogPopup for better modal handling commit 468aebd632739a37e5c91c0957fb0ec44264a915 Author: dev Date: Wed Nov 5 10:56:40 2025 +0800 fix: plugins related wip commit bd4a979f6208f980b180fed0b8acab53af28b6e7 Author: dev Date: Tue Nov 4 17:46:14 2025 +0800 fix: add button related commit b3316a4dc831be038699777d433cd76c24f91377 Author: dev Date: Tue Nov 4 17:18:31 2025 +0800 fix: agent tool result related components commit 6ca7597a988d3c61a137da77e8d3625b47a06ac0 Author: dev Date: Tue Nov 4 11:12:01 2025 +0800 fix: lint commit 7d0f0b38a66ac92f7a9c8e1c7431efd665b55df0 Author: kangfenmao Date: Tue Nov 4 09:56:32 2025 +0800 wip commit 96a607a41042bf8edfb89d9bdcc0305e2f648b0c Author: kangfenmao Date: Mon Nov 3 20:23:25 2025 +0800 wip commit 235ad16252b4a3e25c6e708001a8d55555855be0 Author: kangfenmao Date: Mon Nov 3 20:08:45 2025 +0800 wip commit f23fe1b9e91cc8c347e15d9e32dafde34690d15c Author: kangfenmao Date: Mon Nov 3 19:15:01 2025 +0800 wip commit 28fac543fca703e065cd75e6db7f85454c61a1dd Author: kangfenmao Date: Mon Nov 3 18:39:39 2025 +0800 wip commit 3cc7ee01e296d86f00f66feba7eb34057779ad9a Author: kangfenmao Date: Mon Nov 3 17:33:13 2025 +0800 wip commit 37bdf9e508e72b5ce194e693af55164507ac9e34 Author: kangfenmao Date: Sat Nov 1 19:16:58 2025 +0800 wip commit 1bf5104f97b1e59006a06cd7cca255b3463d6962 Author: kangfenmao Date: Sat Nov 1 12:12:01 2025 +0800 wip --- CLAUDE.md | 2 - package.json | 2 +- src/main/apiServer/middleware/openapi.ts | 2 +- .../agents/services/claudecode/index.ts | 10 + src/renderer/src/App.tsx | 35 +- src/renderer/src/assets/styles/index.css | 4 +- src/renderer/src/assets/styles/tailwind.css | 9 - src/renderer/src/components/ApiModelLabel.tsx | 31 - .../Avatar/EmojiAvatarWithPicker.tsx | 13 +- .../components/Buttons/ActionIconButton.tsx | 2 +- src/renderer/src/components/ConfirmDialog.tsx | 26 +- src/renderer/src/components/ErrorBoundary.tsx | 6 +- .../HorizontalScrollContainer/index.tsx | 2 +- .../Popups/AddAssistantOrAgentPopup.tsx | 6 +- .../Popups/ExportToPhoneLanPopup.tsx | 179 +- .../components/Popups/UpdateDialogPopup.tsx | 205 ++ .../components/Popups/agent/AgentModal.tsx | 596 ++-- .../components/Popups/agent/SessionModal.tsx | 320 -- .../src/components/Popups/agent/shared.tsx | 45 - src/renderer/src/components/ToastPortal.tsx | 32 - src/renderer/src/components/TopView/index.tsx | 9 +- src/renderer/src/components/TopView/toast.ts | 72 - src/renderer/src/components/TopView/toast.tsx | 231 ++ src/renderer/src/components/UpdateDialog.tsx | 101 - .../components/agent/AllowedToolsSelect.tsx | 55 - src/renderer/src/components/agent/index.tsx | 1 - src/renderer/src/context/HeroUIProvider.tsx | 13 - src/renderer/src/env.d.ts | 14 +- src/renderer/src/hero.ts | 2 - src/renderer/src/hooks/agents/useAgents.ts | 1 + src/renderer/src/hooks/useApiServer.ts | 17 +- src/renderer/src/hooks/useAppInit.ts | 11 +- src/renderer/src/hooks/useUserTheme.ts | 12 - src/renderer/src/i18n/locales/zh-cn.json | 2 +- src/renderer/src/i18n/locales/zh-tw.json | 2 +- src/renderer/src/i18n/translate/de-de.json | 2 +- src/renderer/src/i18n/translate/el-gr.json | 2 +- src/renderer/src/i18n/translate/ja-jp.json | 2 +- src/renderer/src/pages/home/Chat.tsx | 21 +- .../home/Inputbar/AgentSessionInputbar.tsx | 13 +- .../pages/home/Messages/Blocks/ErrorBlock.tsx | 14 +- .../home/Messages/Blocks/PlaceholderBlock.tsx | 4 +- .../src/pages/home/Messages/Message.tsx | 3 +- .../MessageAgentTools/BashOutputTool.tsx | 158 +- .../Tools/MessageAgentTools/BashTool.tsx | 36 +- .../Tools/MessageAgentTools/EditTool.tsx | 39 +- .../MessageAgentTools/ExitPlanModeTool.tsx | 35 +- .../Tools/MessageAgentTools/GlobTool.tsx | 37 +- .../Tools/MessageAgentTools/GrepTool.tsx | 47 +- .../Tools/MessageAgentTools/MultiEditTool.tsx | 36 +- .../MessageAgentTools/NotebookEditTool.tsx | 32 +- .../Tools/MessageAgentTools/ReadTool.tsx | 37 +- .../Tools/MessageAgentTools/SearchTool.tsx | 37 +- .../Tools/MessageAgentTools/SkillTool.tsx | 23 +- .../Tools/MessageAgentTools/TaskTool.tsx | 35 +- .../Tools/MessageAgentTools/TodoWriteTool.tsx | 77 +- .../MessageAgentTools/UnknownToolRenderer.tsx | 33 +- .../Tools/MessageAgentTools/WebFetchTool.tsx | 24 +- .../Tools/MessageAgentTools/WebSearchTool.tsx | 37 +- .../Tools/MessageAgentTools/WriteTool.tsx | 22 +- .../Tools/MessageAgentTools/index.tsx | 43 +- .../Tools/ToolPermissionRequestCard.tsx | 50 +- .../src/pages/home/Tabs/AssistantsTab.tsx | 38 +- .../pages/home/Tabs/SessionSettingsTab.tsx | 8 +- .../src/pages/home/Tabs/SessionsTab.tsx | 15 +- .../pages/home/Tabs/components/AddButton.tsx | 38 +- .../pages/home/Tabs/components/AgentItem.tsx | 92 +- .../home/Tabs/components/AssistantItem.tsx | 3 +- .../home/Tabs/components/SessionItem.tsx | 311 +- .../pages/home/Tabs/components/Sessions.tsx | 24 +- .../home/Tabs/components/SessionsSection.tsx | 0 .../pages/home/Tabs/components/TagGroup.tsx | 2 +- .../src/pages/home/Tabs/components/Topics.tsx | 356 +- .../home/Tabs/components/UnifiedAddButton.tsx | 40 +- .../src/pages/home/Tabs/components/shared.tsx | 131 - src/renderer/src/pages/home/Tabs/index.tsx | 9 +- .../home/components/ChatNavbarContent.tsx | 64 +- .../components/SelectAgentBaseModelButton.tsx | 72 +- .../pages/home/components/UpdateAppButton.tsx | 12 +- .../minapps/components/WebviewSearch.tsx | 69 +- src/renderer/src/pages/notes/HeaderNavbar.tsx | 79 +- .../paintings/components/ProviderSelect.tsx | 60 +- .../src/pages/settings/AboutSettings.tsx | 12 +- .../AgentSettings/AccessibleDirsSetting.tsx | 18 +- .../AgentSettings/AdvancedSettings.tsx | 37 +- .../AgentSettings/AgentSettingsPopup.tsx | 22 +- .../AgentSettings/DescriptionSetting.tsx | 9 +- .../AgentSettings/EssentialSettings.tsx | 4 +- .../settings/AgentSettings/NameSetting.tsx | 7 +- .../settings/AgentSettings/PluginSettings.tsx | 142 +- .../AgentSettings/SessionSettingsPopup.tsx | 21 +- .../AgentSettings/ToolingSettings.tsx | 285 +- .../components/InstalledPluginsList.tsx | 116 +- .../components/PluginBrowser.tsx | 153 +- .../AgentSettings/components/PluginCard.tsx | 72 +- .../components/PluginDetailModal.tsx | 367 +- .../pages/settings/AgentSettings/shared.tsx | 2 +- .../settings/DataSettings/DataSettings.tsx | 47 +- .../DocProcessSettings/OcrImageSettings.tsx | 49 +- .../ApiServerSettings/ApiServerSettings.tsx | 7 +- .../src/pages/translate/TranslateSettings.tsx | 31 +- src/renderer/src/services/ApiService.ts | 2 +- src/renderer/src/store/runtime.ts | 8 +- src/renderer/src/store/thunk/messageThunk.ts | 2 +- src/renderer/src/ui/context-menu.tsx | 2 +- src/renderer/src/utils/dataLimit.ts | 6 +- src/renderer/src/utils/style.ts | 6 + .../src/windows/mini/MiniWindowApp.tsx | 27 +- .../windows/selection/action/entryPoint.tsx | 23 +- yarn.lock | 2976 +---------------- 110 files changed, 3053 insertions(+), 5792 deletions(-) delete mode 100644 src/renderer/src/components/ApiModelLabel.tsx create mode 100644 src/renderer/src/components/Popups/UpdateDialogPopup.tsx delete mode 100644 src/renderer/src/components/Popups/agent/SessionModal.tsx delete mode 100644 src/renderer/src/components/ToastPortal.tsx delete mode 100644 src/renderer/src/components/TopView/toast.ts create mode 100644 src/renderer/src/components/TopView/toast.tsx delete mode 100644 src/renderer/src/components/UpdateDialog.tsx delete mode 100644 src/renderer/src/components/agent/AllowedToolsSelect.tsx delete mode 100644 src/renderer/src/components/agent/index.tsx delete mode 100644 src/renderer/src/context/HeroUIProvider.tsx delete mode 100644 src/renderer/src/hero.ts delete mode 100644 src/renderer/src/pages/home/Tabs/components/SessionsSection.tsx delete mode 100644 src/renderer/src/pages/home/Tabs/components/shared.tsx diff --git a/CLAUDE.md b/CLAUDE.md index 2716815ba2..0728605824 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,7 +7,6 @@ This file provides guidance to AI coding assistants when working with code in th - **Keep it clear**: Write code that is easy to read, maintain, and explain. - **Match the house style**: Reuse existing patterns, naming, and conventions. - **Search smart**: Prefer `ast-grep` for semantic queries; fall back to `rg`/`grep` when needed. -- **Build with HeroUI**: Use HeroUI for every new UI component; never add `antd` or `styled-components`. - **Log centrally**: Route all logging through `loggerService` with the right context—no `console.log`. - **Research via subagent**: Lean on `subagent` for external docs, APIs, news, and references. - **Always propose before executing**: Before making any changes, clearly explain your planned approach and wait for explicit user approval to ensure alignment and prevent unwanted modifications. @@ -41,7 +40,6 @@ This file provides guidance to AI coding assistants when working with code in th - **Services** (`src/main/services/`): MCPService, KnowledgeService, WindowService, etc. - **Build System**: Electron-Vite with experimental rolldown-vite, yarn workspaces. - **State Management**: Redux Toolkit (`src/renderer/src/store/`) for predictable state. -- **UI Components**: HeroUI (`@heroui/*`) for all new UI elements. ### Logging ```typescript diff --git a/package.json b/package.json index c4259a85a1..5c41f0df65 100644 --- a/package.json +++ b/package.json @@ -147,7 +147,6 @@ "@eslint/js": "^9.22.0", "@google/genai": "patch:@google/genai@npm%3A1.0.1#~/.yarn/patches/@google-genai-npm-1.0.1-e26f0f9af7.patch", "@hello-pangea/dnd": "^18.0.1", - "@heroui/react": "^2.8.3", "@kangfenmao/keyv-storage": "^0.1.0", "@langchain/community": "^1.0.0", "@langchain/core": "patch:@langchain/core@npm%3A1.0.2#~/.yarn/patches/@langchain-core-npm-1.0.2-183ef83fe4.patch", @@ -349,6 +348,7 @@ "striptags": "^3.2.0", "styled-components": "^6.1.11", "swr": "^2.3.6", + "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.13", "tar": "^7.4.3", "tiny-pinyin": "^1.3.2", diff --git a/src/main/apiServer/middleware/openapi.ts b/src/main/apiServer/middleware/openapi.ts index c136fecdde..ff01005bd9 100644 --- a/src/main/apiServer/middleware/openapi.ts +++ b/src/main/apiServer/middleware/openapi.ts @@ -171,7 +171,7 @@ const swaggerOptions: swaggerJSDoc.Options = { } ] }, - apis: ['./src/main/apiServer/routes/*.ts', './src/main/apiServer/app.ts'] + apis: ['./src/main/apiServer/routes/**/*.ts', './src/main/apiServer/app.ts'] } export function setupOpenAPIDocumentation(app: Express) { diff --git a/src/main/services/agents/services/claudecode/index.ts b/src/main/services/agents/services/claudecode/index.ts index c9a8c677cd..4e20520017 100644 --- a/src/main/services/agents/services/claudecode/index.ts +++ b/src/main/services/agents/services/claudecode/index.ts @@ -365,6 +365,16 @@ class ClaudeCodeService implements AgentServiceInterface { type: 'chunk', chunk }) + + // Close prompt stream when SDK signals completion or error + if (chunk.type === 'finish' || chunk.type === 'error') { + logger.info('Closing prompt stream as SDK signaled completion', { + chunkType: chunk.type, + reason: chunk.type === 'finish' ? 'finished' : 'error_occurred' + }) + closePromptStream() + logger.info('Prompt stream closed successfully') + } } } diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 78396c49e7..703015e30e 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -6,11 +6,9 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { Provider } from 'react-redux' import { PersistGate } from 'redux-persist/integration/react' -import { ToastPortal } from './components/ToastPortal' import TopViewContainer from './components/TopView' import AntdProvider from './context/AntdProvider' import { CodeStyleProvider } from './context/CodeStyleProvider' -import { HeroUIProvider } from './context/HeroUIProvider' import { NotificationProvider } from './context/NotificationProvider' import StyleSheetManager from './context/StyleSheetManager' import { ThemeProvider } from './context/ThemeProvider' @@ -34,24 +32,21 @@ function App(): React.ReactElement { return ( - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + ) diff --git a/src/renderer/src/assets/styles/index.css b/src/renderer/src/assets/styles/index.css index eaa984270f..ed1eb555a3 100644 --- a/src/renderer/src/assets/styles/index.css +++ b/src/renderer/src/assets/styles/index.css @@ -41,11 +41,11 @@ body, margin: 0; } -/* #root { +#root { display: flex; flex-direction: row; flex: 1; -} */ +} body { display: flex; diff --git a/src/renderer/src/assets/styles/tailwind.css b/src/renderer/src/assets/styles/tailwind.css index f05b01b65c..5cd4046689 100644 --- a/src/renderer/src/assets/styles/tailwind.css +++ b/src/renderer/src/assets/styles/tailwind.css @@ -1,10 +1,6 @@ @import 'tailwindcss' source('../../../../renderer'); @import 'tw-animate-css'; -/* heroui */ -@plugin '../../hero.ts'; -@source '../../../../../node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}'; - @custom-variant dark (&:is(.dark *)); /* 如需自定义: @@ -156,11 +152,6 @@ body { @apply bg-background text-foreground; } - - /* To disable drag title bar on toast. tailwind css doesn't provide such class name. */ - .hero-toast { - -webkit-app-region: no-drag; - } } :root { diff --git a/src/renderer/src/components/ApiModelLabel.tsx b/src/renderer/src/components/ApiModelLabel.tsx deleted file mode 100644 index 3e36083a69..0000000000 --- a/src/renderer/src/components/ApiModelLabel.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Avatar, cn } from '@heroui/react' -import { getModelLogoById } from '@renderer/config/models' -import type { ApiModel } from '@renderer/types' -import React from 'react' - -import Ellipsis from './Ellipsis' - -export interface ModelLabelProps extends Omit, 'children'> { - model?: ApiModel - classNames?: { - container?: string - avatar?: string - modelName?: string - divider?: string - providerName?: string - } -} - -export const ApiModelLabel: React.FC = ({ model, className, classNames, ...props }) => { - return ( -
- - {model?.name} - | - {model?.provider_name} -
- ) -} diff --git a/src/renderer/src/components/Avatar/EmojiAvatarWithPicker.tsx b/src/renderer/src/components/Avatar/EmojiAvatarWithPicker.tsx index 6735d86a4e..649f619cba 100644 --- a/src/renderer/src/components/Avatar/EmojiAvatarWithPicker.tsx +++ b/src/renderer/src/components/Avatar/EmojiAvatarWithPicker.tsx @@ -1,4 +1,4 @@ -import { Button, Popover, PopoverContent, PopoverTrigger } from '@heroui/react' +import { Button, Popover } from 'antd' import React from 'react' import EmojiPicker from '../EmojiPicker' @@ -10,13 +10,10 @@ type Props = { export const EmojiAvatarWithPicker: React.FC = ({ emoji, onPick }) => { return ( - - - ) } diff --git a/src/renderer/src/components/Buttons/ActionIconButton.tsx b/src/renderer/src/components/Buttons/ActionIconButton.tsx index 9f5c98abf0..bf3a6d288d 100644 --- a/src/renderer/src/components/Buttons/ActionIconButton.tsx +++ b/src/renderer/src/components/Buttons/ActionIconButton.tsx @@ -1,4 +1,4 @@ -import { cn } from '@heroui/react' +import { cn } from '@renderer/utils' import type { ButtonProps } from 'antd' import { Button } from 'antd' import React, { memo } from 'react' diff --git a/src/renderer/src/components/ConfirmDialog.tsx b/src/renderer/src/components/ConfirmDialog.tsx index 5ac0ae1273..a3ffa5e270 100644 --- a/src/renderer/src/components/ConfirmDialog.tsx +++ b/src/renderer/src/components/ConfirmDialog.tsx @@ -1,5 +1,5 @@ -import { Button } from '@heroui/react' -import { CheckIcon, XIcon } from 'lucide-react' +import { CheckOutlined, CloseOutlined } from '@ant-design/icons' +import { Button } from 'antd' import type { FC } from 'react' import { createPortal } from 'react-dom' @@ -28,12 +28,22 @@ const ConfirmDialog: FC = ({ x, y, message, onConfirm, onCancel }) => {
{message}
- - +
diff --git a/src/renderer/src/components/ErrorBoundary.tsx b/src/renderer/src/components/ErrorBoundary.tsx index 12e9ab5935..838c6f136b 100644 --- a/src/renderer/src/components/ErrorBoundary.tsx +++ b/src/renderer/src/components/ErrorBoundary.tsx @@ -1,5 +1,5 @@ -import { Button } from '@heroui/button' import { formatErrorMessage } from '@renderer/utils/error' +import { Button } from 'antd' import { Alert, Space } from 'antd' import type { ComponentType, ReactNode } from 'react' import type { FallbackProps } from 'react-error-boundary' @@ -24,10 +24,10 @@ const DefaultFallback: ComponentType = (props: FallbackProps): Re type="error" action={ - - diff --git a/src/renderer/src/components/HorizontalScrollContainer/index.tsx b/src/renderer/src/components/HorizontalScrollContainer/index.tsx index fdc890d2e2..ec9f7a1043 100644 --- a/src/renderer/src/components/HorizontalScrollContainer/index.tsx +++ b/src/renderer/src/components/HorizontalScrollContainer/index.tsx @@ -1,5 +1,5 @@ -import { cn } from '@heroui/react' import Scrollbar from '@renderer/components/Scrollbar' +import { cn } from '@renderer/utils' import { ChevronRight } from 'lucide-react' import { useEffect, useRef, useState } from 'react' import styled from 'styled-components' diff --git a/src/renderer/src/components/Popups/AddAssistantOrAgentPopup.tsx b/src/renderer/src/components/Popups/AddAssistantOrAgentPopup.tsx index 4094b3f3d1..4b3f969a26 100644 --- a/src/renderer/src/components/Popups/AddAssistantOrAgentPopup.tsx +++ b/src/renderer/src/components/Popups/AddAssistantOrAgentPopup.tsx @@ -1,5 +1,5 @@ -import { cn } from '@heroui/react' import { TopView } from '@renderer/components/TopView' +import { cn } from '@renderer/utils' import { Modal } from 'antd' import { Bot, MessageSquare } from 'lucide-react' import { useState } from 'react' @@ -51,7 +51,7 @@ const PopupContainer: React.FC = ({ onSelect, resolve }) => { - - - + +
+ + +
+
- - {selectedFolderPath || t('settings.data.export_to_phone.lan.noZipSelected')} - + + {selectedFolderPath || t('settings.data.export_to_phone.lan.noZipSelected')} + - - - - - - {showCloseConfirm && ( - -
-
- ⚠️ - - {t('settings.data.export_to_phone.lan.confirm_close_title')} - -
- - {t('settings.data.export_to_phone.lan.confirm_close_message')} - -
- - -
-
-
- )} - - )} - + + + ) } diff --git a/src/renderer/src/components/Popups/UpdateDialogPopup.tsx b/src/renderer/src/components/Popups/UpdateDialogPopup.tsx new file mode 100644 index 0000000000..29afcc0d24 --- /dev/null +++ b/src/renderer/src/components/Popups/UpdateDialogPopup.tsx @@ -0,0 +1,205 @@ +import { loggerService } from '@logger' +import { TopView } from '@renderer/components/TopView' +import { handleSaveData } from '@renderer/store' +import { Button, Modal } from 'antd' +import type { ReleaseNoteInfo, UpdateInfo } from 'builder-util-runtime' +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Markdown from 'react-markdown' +import styled from 'styled-components' + +const logger = loggerService.withContext('UpdateDialog') + +interface ShowParams { + releaseInfo: UpdateInfo | null +} + +interface Props extends ShowParams { + resolve: (data: any) => void +} + +const PopupContainer: React.FC = ({ releaseInfo, resolve }) => { + const { t } = useTranslation() + const [open, setOpen] = useState(true) + const [isInstalling, setIsInstalling] = useState(false) + + useEffect(() => { + if (releaseInfo) { + logger.info('Update dialog opened', { version: releaseInfo.version }) + } + }, [releaseInfo]) + + const handleInstall = async () => { + setIsInstalling(true) + try { + await handleSaveData() + await window.api.quitAndInstall() + setOpen(false) + } catch (error) { + logger.error('Failed to save data before update', error as Error) + setIsInstalling(false) + window.toast.error(t('update.saveDataError')) + } + } + + const onCancel = () => { + setOpen(false) + } + + const onClose = () => { + resolve({}) + } + + UpdateDialogPopup.hide = onCancel + + const releaseNotes = releaseInfo?.releaseNotes + + return ( + +

{t('update.title')}

+

{t('update.message').replace('{{version}}', releaseInfo?.version || '')}

+ + } + open={open} + onCancel={onCancel} + afterClose={onClose} + transitionName="animation-move-down" + centered + width={720} + footer={[ + , + + ]}> + + + + {typeof releaseNotes === 'string' + ? releaseNotes + : Array.isArray(releaseNotes) + ? releaseNotes + .map((note: ReleaseNoteInfo) => note.note) + .filter(Boolean) + .join('\n\n') + : t('update.noReleaseNotes')} + + + +
+ ) +} + +const TopViewKey = 'UpdateDialogPopup' + +export default class UpdateDialogPopup { + static topviewId = 0 + static hide() { + TopView.hide(TopViewKey) + } + static show(props: ShowParams) { + return new Promise((resolve) => { + TopView.show( + { + resolve(v) + TopView.hide(TopViewKey) + }} + />, + TopViewKey + ) + }) + } +} + +const ModalHeaderWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + + h3 { + margin: 0; + font-size: 16px; + font-weight: 600; + color: var(--color-text-1); + } + + p { + margin: 0; + font-size: 14px; + color: var(--color-text-2); + } +` + +const ModalBodyWrapper = styled.div` + max-height: 450px; + overflow-y: auto; + padding: 12px 0; +` + +const ReleaseNotesWrapper = styled.div` + background-color: var(--color-bg-2); + border-radius: 8px; + + p { + margin: 0 0 12px 0; + color: var(--color-text-2); + font-size: 14px; + line-height: 1.6; + + &:last-child { + margin-bottom: 0; + } + } + + h1, + h2, + h3, + h4, + h5, + h6 { + margin: 16px 0 8px 0; + color: var(--color-text-1); + font-weight: 600; + + &:first-child { + margin-top: 0; + } + } + + ul, + ol { + margin: 8px 0; + padding-left: 24px; + color: var(--color-text-2); + } + + li { + margin: 4px 0; + } + + code { + padding: 2px 6px; + background-color: var(--color-bg-3); + border-radius: 4px; + font-family: 'Consolas', 'Monaco', monospace; + font-size: 13px; + } + + pre { + padding: 12px; + background-color: var(--color-bg-3); + border-radius: 6px; + overflow-x: auto; + + code { + padding: 0; + background-color: transparent; + } + } +` diff --git a/src/renderer/src/components/Popups/agent/AgentModal.tsx b/src/renderer/src/components/Popups/agent/AgentModal.tsx index cb53879fcc..d504699399 100644 --- a/src/renderer/src/components/Popups/agent/AgentModal.tsx +++ b/src/renderer/src/components/Popups/agent/AgentModal.tsx @@ -1,44 +1,32 @@ -import type { SelectedItemProps } from '@heroui/react' -import { - Button, - Form, - Input, - Modal, - ModalBody, - ModalContent, - ModalFooter, - ModalHeader, - Select, - SelectItem, - Textarea, - useDisclosure -} from '@heroui/react' import { loggerService } from '@logger' -import type { Selection } from '@react-types/shared' import ClaudeIcon from '@renderer/assets/images/models/claude.png' +import { ErrorBoundary } from '@renderer/components/ErrorBoundary' +import { TopView } from '@renderer/components/TopView' import { permissionModeCards } from '@renderer/config/agent' -import { agentModelFilter, getModelLogoById } from '@renderer/config/models' import { useAgents } from '@renderer/hooks/agents/useAgents' -import { useApiModels } from '@renderer/hooks/agents/useModels' import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent' +import SelectAgentBaseModelButton from '@renderer/pages/home/components/SelectAgentBaseModelButton' import type { AddAgentForm, AgentEntity, AgentType, + ApiModel, BaseAgentForm, PermissionMode, Tool, UpdateAgentForm } from '@renderer/types' import { AgentConfigurationSchema, isAgentType } from '@renderer/types' +import { Avatar, Button, Input, Modal, Select } from 'antd' import { AlertTriangleIcon } from 'lucide-react' import type { ChangeEvent, FormEvent } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import styled from 'styled-components' -import { ErrorBoundary } from '../../ErrorBoundary' -import type { BaseOption, ModelOption } from './shared' -import { Option, renderOption } from './shared' +import type { BaseOption } from './shared' + +const { TextArea } = Input const logger = loggerService.withContext('AddAgentPopup') @@ -48,8 +36,6 @@ interface AgentTypeOption extends BaseOption { name: AgentEntity['name'] } -type Option = AgentTypeOption | ModelOption - type AgentWithTools = AgentEntity & { tools?: Tool[] } const buildAgentForm = (existing?: AgentWithTools): BaseAgentForm => ({ @@ -64,58 +50,37 @@ const buildAgentForm = (existing?: AgentWithTools): BaseAgentForm => ({ configuration: AgentConfigurationSchema.parse(existing?.configuration ?? {}) }) -type Props = { +interface ShowParams { agent?: AgentWithTools - isOpen: boolean - onClose: () => void afterSubmit?: (a: AgentEntity) => void } -/** - * Modal component for creating or editing an agent. - * - * Either trigger or isOpen and onClose is given. - * @param agent - Optional agent entity for editing mode. - * @param isOpen - Optional controlled modal open state. From useDisclosure. - * @param onClose - Optional callback when modal closes. From useDisclosure. - * @returns Modal component for agent creation/editing - */ -export const AgentModal: React.FC = ({ agent, isOpen: _isOpen, onClose: _onClose, afterSubmit }) => { - const { isOpen, onClose } = useDisclosure({ isOpen: _isOpen, onClose: _onClose }) +interface Props extends ShowParams { + resolve: (data: any) => void +} + +const PopupContainer: React.FC = ({ agent, afterSubmit, resolve }) => { const { t } = useTranslation() + const [open, setOpen] = useState(true) const loadingRef = useRef(false) - // const { setTimeoutTimer } = useTimer() const { addAgent } = useAgents() const { updateAgent } = useUpdateAgent() - // hard-coded. We only support anthropic for now. - const { models } = useApiModels({ providerType: 'anthropic' }) const isEditing = (agent?: AgentWithTools) => agent !== undefined const [form, setForm] = useState(() => buildAgentForm(agent)) useEffect(() => { - if (isOpen) { + if (open) { setForm(buildAgentForm(agent)) } - }, [agent, isOpen]) + }, [agent, open]) const selectedPermissionMode = form.configuration?.permission_mode ?? 'default' - const onPermissionModeChange = useCallback((keys: Selection) => { - if (keys === 'all') { - return - } - - const [first] = Array.from(keys) - if (!first) { - return - } - + const onPermissionModeChange = useCallback((value: PermissionMode) => { setForm((prev) => { const parsedConfiguration = AgentConfigurationSchema.parse(prev.configuration ?? {}) - const nextMode = first as PermissionMode - - if (parsedConfiguration.permission_mode === nextMode) { + if (parsedConfiguration.permission_mode === value) { if (!prev.configuration) { return { ...prev, @@ -129,7 +94,7 @@ export const AgentModal: React.FC = ({ agent, isOpen: _isOpen, onClose: _ ...prev, configuration: { ...parsedConfiguration, - permission_mode: nextMode + permission_mode: value } } }) @@ -150,55 +115,57 @@ export const AgentModal: React.FC = ({ agent, isOpen: _isOpen, onClose: _ [] ) - const agentOptions: AgentTypeOption[] = useMemo( + const agentOptions = useMemo( () => - agentConfig.map( - (option) => - ({ - ...option, - rendered: