Compare commits

...

401 Commits

Author SHA1 Message Date
suyao
92ba7089bb feat: implement default parameter configuration for MCP tools
- Added functionality to apply default parameters to tool arguments in MCPService.
- Introduced UI components for managing tool parameter configurations in MCPSettings.
- Updated translations for new settings and tool configurations across multiple languages.
- Enhanced MCPToolsSection to allow editing and saving of tool parameter defaults.
2025-05-25 03:19:21 +08:00
kangfenmao
bca4fbe7de fix: update MainTextBlock to use a class for markdown styling
- Changed the paragraph element in MainTextBlock to include a "markdown" class for improved styling consistency.
2025-05-24 18:35:57 +08:00
Caelan
2b99d6066f feat: 文字生成图新增提供商DMXAPI (#6352)
dmxapi文字生成图

Co-authored-by: 亢奋猫 <kangfenmao@qq.com>
2025-05-24 16:14:32 +08:00
自由的世界人
9357bd6e0f feat: add disable MCP server functionality and update translations (#6398)
* feat: add disable MCP server functionality and update translations

* feat: update MCPToolsButton and WebSearchButton to use CircleX icon and change labels to 'close'

---------

Co-authored-by: kangfenmao <kangfenmao@qq.com>
2025-05-24 16:00:55 +08:00
Pleasurecruise
c4e0744806 fix: improve multi-select functionality in Messages and SelectionBox components 2025-05-24 15:44:50 +08:00
one
1b6cba454d chore: add dependabot (#6369) 2025-05-24 08:58:24 +08:00
SuYao
77cd958d08 fix: floating-sidebar header sticky (#6371) 2025-05-24 08:55:20 +08:00
kangfenmao
d8aac9ecb8 feat: add support for Windows ARM64 architecture in bun installation script
- Included package mappings for 'win32-arm64' and 'win32-arm64-baseline' to the BUN_PACKAGES object in install-bun.js, enhancing compatibility with ARM64 devices on Windows.
2025-05-23 18:53:55 +08:00
kangfenmao
2758321821 chore: disable code signature verification for Windows updates in electron-builder configuration 2025-05-23 17:50:50 +08:00
kangfenmao
9dfa81f11f chore(version): 1.3.11 2025-05-23 17:15:54 +08:00
kangfenmao
3693e115a6 chore: update release notes and improve README assets
- Updated release notes to include new features such as TokenFlux service support, Claude 4 model integration, and fixes for various issues including Windows user startup problems and search crashes.
- Replaced outdated screenshots in README files with new images across English, Japanese, and Chinese documentation.
- Enhanced the 'Related Projects' section for better visibility.
2025-05-23 17:11:33 +08:00
kangfenmao
2f3a3c8c48 refactor: remove manual chunking logic from Vite configuration
- Eliminated custom output chunking strategy for worker files and node_modules from the electron Vite configuration.
- Streamlined the configuration for improved maintainability and clarity.
2025-05-23 17:10:23 +08:00
LiuVaayne
f6d71868cb feat: support tokenflux provider (#6358)
* feat: add Cherry Cloud provider with associated assets and localization support

* feat: add Cherry Cloud provider support with OAuth integration and IPC handling

* fix: add success message for Cherry Cloud API key update

* feat: enhance provider navigation with dynamic ID in URL and update selected provider state

* feat: implement Cherry Cloud server synchronization with token management and error handling

* feat: add CherryCloud provider configuration and token management

* feat: integrate TokenFlux provider support and update related configurations

fix: update redux-persist version to 104

refactor: remove redundant tokenflux provider model assignment in migration

fix: update migration to add TokenFlux provider instead of CherryCloud

* feat: enhance TokenFlux server synchronization with API key authentication support

* feat: update TokenFlux provider assets and add new models

* feat: update migration logic for version 106 to add TokenFlux provider

* feat: disable TokenFlux provider by default in INITIAL_PROVIDERS

* feat: add TokenFlux billing URLs to providerCharge and providerBills functions
2025-05-23 17:10:07 +08:00
beyondkmp
e5bf6916a6 chore: update electron version to 35.4.0 in package.json and yarn.lock 2025-05-23 16:28:27 +08:00
beyondkmp
7c119e2747 feat: add navigation buttons for webview history in MinApp popup (#6342)
* feat: add navigation buttons for webview history in MinApp popup

- Implemented 'Go Back' and 'Go Forward' functionality in the MinApp popup.
- Added corresponding translations for English, Japanese, Russian, and Chinese locales.
- Included icons for navigation buttons to enhance user experience.

* fix: update Russian and Traditional Chinese translations for UI elements

- Revised translations for "rightclick_copyurl", "close", and "minimize" to improve clarity and consistency in the Russian and Traditional Chinese locales.
- Ensured that the translations align better with user expectations and common usage.

* fix: update Russian translations for MinApp popup UI elements

- Revised translations for "close" and "minimize" to specify their context within the MinApp, enhancing clarity for users.
- Ensured consistency with existing translations and improved user understanding of the interface.

---------

Co-authored-by: beyondkmp <beyondkmkp@gmail.com>
2025-05-23 15:29:13 +08:00
kangfenmao
03ef52c6a7 fix: ensure correct PATH assignment in shell environment
- Updated the environment variable initialization to use a consistent type.
- Added logic to set the PATH variable correctly, ensuring it falls back to existing values if necessary.
2025-05-23 15:25:01 +08:00
kangfenmao
e21e0d238e fix: minapp search error 2025-05-23 15:18:58 +08:00
jtsang4
631fa3a42a feat: support pin topic to the top
Signed-off-by: jtsang4 <info@jtsang.me>
2025-05-23 13:58:48 +08:00
karl
ee042e11f1 fix: editing user messages is not re-sent; it can only be saved#6327 2025-05-23 12:57:54 +08:00
kangfenmao
178d164ff9 chore(version): 1.3.10 2025-05-23 11:17:12 +08:00
suyao
e5ded81d9b feat: gemini thinking summary support
- Removed unnecessary content accumulation and streamlined chunk processing logic.
- Introduced distinct handling for 'thinking' and 'text' parts, ensuring accurate onChunk calls for both types.
- Enhanced timing tracking for reasoning content, improving overall responsiveness in streaming scenarios.
2025-05-23 11:10:46 +08:00
hutchisr
c8e6872fb8 feat: Support Claude 4 (#6328) 2025-05-23 08:45:44 +08:00
one
2820f85be4 feat: toggle MCP servers on the card list (#6232) 2025-05-23 00:39:02 +08:00
suyao
1989f246fd feat: add support for 'grok' provider in web search functionality
- Enhanced `isWebSearchModel` to recognize 'grok' as a valid web search model.
- Updated `getOpenAIWebSearchParams` to return specific search parameters for 'grok'.
- Modified `OpenAIProvider` to handle citations from 'grok' in web search results.
- Added 'grok' to the `WebSearchSource` enum for citation formatting.
2025-05-22 23:28:32 +08:00
kangfenmao
3ac414e97b fix: handle proxy environment variables for bun command in MCPService
- Added logic to remove proxy environment variables when the command ends with 'bun'.
- Introduced a new private method `removeProxyEnv` to clean up the environment variables before starting the server.
2025-05-22 22:57:49 +08:00
kangfenmao
bcb93fc2d0 fix: adjusted max-width in McpDescription.tsx 2025-05-22 22:57:40 +08:00
karl
9f579334f8 fix: shiki does not load language as a fallback & themes error (#6281)
* fix: shiki does not load language as a fallback & themes error

* feat: shiki automatic loading language
2025-05-22 22:00:30 +08:00
自由的世界人
6f75b1738d fix: #6301 (#6317)
* fix: #6301

* fix: update dependencies in useUpdateHandler and add eslint comment in ContentSearch
2025-05-22 21:24:40 +08:00
SuYao
72d939721d fix: non-streaming reasoning_content (#6308)
* fix: non-streaming reasoning_content

* fix: streamline reasoning handling in OpenAIProvider
2025-05-22 20:35:47 +08:00
Pleasurecruise
7d6ef1d69a fix: handle empty block content in MessageTranslate component 2025-05-22 19:39:34 +08:00
SuYao
e162da55bd fix: enhance backup and restore functionality with skip option (#6294)
* fix: enhance backup and restore functionality with skip option

- Updated `restore` and `restoreFromWebdav` methods in `BackupManager` to include a `skipBackupFile` parameter, allowing users to skip restoring the Data directory if desired.
- Modified corresponding IPC calls in `preload` and `BackupService` to support the new parameter.
- Added success message translations in multiple languages for improved user feedback.

* fix: integrate skipBackupFile option in RestorePopup for enhanced restore functionality

* refactor: remove skipBackupFile parameter from restore methods for simplified usage

- Updated `restore` and `restoreFromWebdav` methods in `BackupManager`, `BackupService`, and `NutstoreService` to remove the `skipBackupFile` parameter, streamlining the restore process.
- Adjusted corresponding IPC calls in `preload` and `RestorePopup` to reflect the changes, ensuring consistent functionality across the application.
2025-05-22 19:38:34 +08:00
kangfenmao
fc5209723f build: add win-sign script 2025-05-22 19:33:40 +08:00
kangfenmao
75153ce83c chore: upgrade yarn version to v4.9.1 2025-05-22 15:48:20 +08:00
kangfenmao
fd1cf1331f feat: implement message location functionality and refactor multi-select handling
- Added event listeners for LOCATE_MESSAGE events to scroll to specific messages in the MessageGroup component.
- Introduced a new SelectionBox component to handle multi-select functionality, allowing users to select multiple messages with drag actions.
- Refactored Messages component to remove unused multi-select logic and improve overall structure.
- Cleaned up code by removing commented-out sections and unnecessary state management related to dragging.
2025-05-22 15:14:13 +08:00
MyPrototypeWhat
a5738fdae5 feat: add functionality to insert messages at a specific index in the… (#6299)
* feat: add functionality to insert messages at a specific index in the Redux store

- Introduced a new interface for inserting messages at a specified index.
- Implemented the insertMessageAtIndex reducer to handle message insertion.
- Updated saveMessageAndBlocksToDB to support message insertion logic.
- Modified appendAssistantResponseThunk to utilize the new insertion functionality.

* feat: integrate multi-select mode handling in MessageGroup component

- Added useChatContext hook to access multi-select mode state.
- Updated isGrouped logic to account for multi-select mode, ensuring proper message grouping behavior.
- Enhanced MessageWrapper styles for better layout management in different modes.

---------

Co-authored-by: kangfenmao <kangfenmao@qq.com>
2025-05-22 14:57:54 +08:00
chenxue
86b95ee17a fix: aihubmix provider model proxy rule (#6293)
Update AihubmixProvider.ts

Co-authored-by: zhaochenxue <zhaochenxue@bixin.cn>
2025-05-22 14:46:40 +08:00
one
587e1d9971 fix: Use effective theme for code style in SettingsTab (#6305)
* Fix: Use effective theme for code style in SettingsTab

The SettingsTab component was previously using the theme setting directly from useSettings to determine whether to apply the light or dark code style. This caused an issue when the theme was set to 'auto', as it wouldn't correctly reflect the actual system theme (light or dark).

This commit modifies SettingsTab.tsx to use the `theme` from the `useTheme` hook (which provides the effective theme) for the logic that determines the code editor and preview styles. This ensures that the code style accurately reflects your current effective theme, including when 'auto' theme is selected and the OS theme changes.

* Refactor: Remove unnecessary comments in SettingsTab

This commit removes non-essential comments that were added during a previous refactoring of `SettingsTab.tsx`. The core logic for using the effective theme for code style selection remains unchanged. This change is purely for code cleanliness.

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-05-22 14:45:36 +08:00
SuYao
b8e978f2a1 fix: token 取整 (#6300) 2025-05-22 14:13:28 +08:00
kangfenmao
bd8452032e fix: enhance message loading and search functionality in HistoryPage and SearchResults
- Integrated Redux dispatch to load topic messages when a message is clicked in HistoryPage.
- Updated SearchResults to utilize message blocks for improved search results, including content extraction.
- Refactored state management to accommodate new content structure in search results.
2025-05-22 11:48:38 +08:00
kangfenmao
0079f4f437 refactor: update Gemini file upload method to accept baseURL parameter
- Modified the uploadFile method in GeminiService to include baseURL in the parameters.
- Updated the corresponding calls in the preload and renderer layers to pass the baseURL along with the apiKey.
2025-05-22 10:11:26 +08:00
one
0436ea671e refactor: CodePreview fade in on the first highlighting (#6228)
* refactor(CodePreview): fade in on the first highlighting

* refactor: improve code placeholder style
2025-05-22 00:05:30 +08:00
kangfenmao
05c29b2bc1 revert: mcp run python (#6151)
This reverts commit c468c3cfd5.
2025-05-22 00:04:15 +08:00
one
3f97aef93f fix: show x-scrollbar in codeblock if unwrapped, simplify style definitions (#6266)
* fix: show x-scrollbar in codeblock if unwrapped, simplify style definitions

* chore: clean up useless code
2025-05-21 23:39:23 +08:00
SuYao
2e3adb0b1b fix: update service tier check to exclude GitHub and Copilot models
* refactor: improve shiki highlighter utils and reuse it in ShikiStreamService

* refactor: reuse shiki highlighter and markdown-it renderer

* hotfix: exclude GitHub and Copilot models

* fix: update service tier check to exclude GitHub and Copilot models

---------

Co-authored-by: one <wangan.cs@gmail.com>
2025-05-21 23:29:55 +08:00
kangfenmao
507da84b80 chore: update release notes in electron-builder.yml
- Added new features including message notification functionality and support for Google Mini Programs.
- Improved MCP capabilities to run Python code and fixed several issues related to message editing and display.
- Updated release notes to reflect these changes and enhancements.
2025-05-21 23:23:43 +08:00
suyao
b30a30efa8 refactor: enhance notification handling based on page context
- Updated NotificationProvider to truncate long messages for better display.
- Modified message sending logic in messageThunk to prevent notifications on the home page, improving user experience.
2025-05-21 23:18:55 +08:00
kangfenmao
c29ff577ed refactor: update IPC channel names for notifications
- Renamed IPC channels for notifications to improve clarity and consistency.
- Updated related handlers in the main process and preload scripts to reflect the new naming convention.
- Enhanced notification service to respect user settings before sending notifications.
2025-05-21 23:11:34 +08:00
kangfenmao
f9512d6ef2 fix: handle user cancellation and improve error reporting in file saving process
- Added a check for user cancellation in the file save dialog, rejecting the promise if canceled.
- Enhanced error handling to reject the promise with a detailed error message instead of returning null.
2025-05-21 22:40:20 +08:00
kangfenmao
326163d798 style: update border color in SearchBarContainer for improved UI consistency 2025-05-21 22:31:55 +08:00
kangfenmao
7099bee833 refactor: enhance multi-selection handling in Inputbar and MessageEditor
- Integrated `useAppSelector` in Inputbar to manage multi-selection state.
- Updated Inputbar to conditionally render based on multi-selection mode.
- Modified MessageEditor to display the resend button only for assistant messages, improving UI clarity.
2025-05-21 22:22:01 +08:00
kangfenmao
b1839b722f refactor: migrate chat context to a custom hook and enhance multi-selection functionality
- Replaced the ChatContext with a custom hook `useChatContext` for better modularity and reusability.
- Updated components to utilize the new hook, passing the active topic as an argument.
- Enhanced multi-selection logic and state management for messages, improving user experience in the chat interface.
- Removed the old ChatContext file to streamline the codebase.
2025-05-21 21:40:27 +08:00
kangfenmao
46ae4f9b55 refactor: improve ContextMenu and Message components layout
- Replaced the div in ContextMenu with a styled component for better styling control.
- Enhanced Message component to handle editing state more cleanly, separating the editor from the message display.
- Adjusted styling for the MessageEditor and FileBlocksContainer for improved layout and responsiveness.
2025-05-21 21:15:39 +08:00
one
a9a0ae87d3 refactor: reuse shiki highlighter utils (#6235)
* refactor: improve shiki highlighter utils and reuse it in ShikiStreamService

* refactor: reuse shiki highlighter and markdown-it renderer

* refactor: import shiki/markdown-it/core dynamically

* chore: update shiki
2025-05-21 19:06:56 +08:00
自由的世界人
d9661602b2 fix: increase the upper limit of issue-management operations-per-run (#6257) 2025-05-21 15:31:28 +08:00
kangfenmao
877a1f8306 chore(version): 1.3.9 2025-05-21 15:14:37 +08:00
kangfenmao
5dd508b4f4 fix: english serif font rendering issue #6224
close #6224
2025-05-21 15:09:10 +08:00
kangfenmao
406bf6a509 fix: topic prompt not working #6226
close #6226
2025-05-21 14:26:24 +08:00
kangfenmao
f0ae2aa6dc chore: remove electron-notification-state dependency and update notification settings
- Removed the electron-notification-state package from dependencies as it is no longer used.
- Updated NotificationService to eliminate Do Not Disturb handling.
- Changed default notification settings in the Redux store to false for assistant messages, backups, and knowledge embedding.
2025-05-21 14:03:24 +08:00
kangfenmao
0884caea97 fix: settings -> openAI -> summaryText undefined 2025-05-21 13:57:59 +08:00
kangfenmao
8e870710b5 fix: message content width will exceed the bubble limit in narrow layout mode 2025-05-21 13:57:39 +08:00
kangfenmao
27a92463bf fix: topic deletion button covering the text 2025-05-21 13:56:47 +08:00
kangfenmao
3298b3f403 refactor: simplify MCP service integration and cleanup logic
- Replaced singleton pattern with direct instantiation of McpService for cleaner code.
- Updated IPC handlers to use the new mcpService instance directly.
- Removed unnecessary logging in Inputbar and MCPToolsButton components to streamline functionality.
- Cleaned up error handling in McpSettings to improve user experience.
2025-05-21 13:10:27 +08:00
kangfenmao
894c20dd05 fix: enhance OpenAI service tier handling and initialize settings during migration
- Updated getServiceTier method to ensure proper handling of undefined OpenAI models.
- Added initialization for OpenAI settings in the migration process to set default values if not present.
2025-05-21 13:02:25 +08:00
kangfenmao
a0945af285 feat: add Google miniapp 2025-05-21 12:46:20 +08:00
kangfenmao
6e12d2fa2e refactor: update MultiSelectionPopup icons and improve styling
- Replaced Ant Design icons with Lucide icons for a more modern look.
- Adjusted ActionButton styling to have a circular border radius.
- Updated translation keys in Chinese locales for better formatting.
- Enhanced event handling in ChatContext to manage multi-select mode more effectively.
- Cleaned up unused imports and props in MessageGroup and MessageSelect components.
2025-05-21 12:18:31 +08:00
SuYao
2798bd9d9d feat/notification (#6144)
* WIP

* feat: integrate notification system using Electron's Notification API

- Replaced the previous notification implementation with Electron's Notification API for better integration.
- Updated notification types and structures to support new features.
- Added translations for notification messages in multiple languages.
- Cleaned up unused dependencies related to notifications.

* refactor: remove unused node-notifier dependency from Electron config

* clean: remove node-notifier from asarUnpack in Electron config

* fix: update notification type in preload script to align with new structure

* feat: Integrate NotificationService for user notifications across various components

- Implemented NotificationService in useUpdateHandler to notify users of available updates.
- Enhanced KnowledgeQueue to send success and error notifications during item processing.
- Updated BackupService to notify users upon successful restoration and backup completion.
- Added error notifications in message handling to improve user feedback on assistant responses.

This update improves user experience by providing timely notifications for important actions and events.

* feat: Refactor notification handling and integrate new notification settings

- Moved SYSTEM_MODELS to a new file for better organization.
- Enhanced notification handling across various components, including BackupService and KnowledgeQueue, to include a source attribute for better context.
- Introduced notification settings in the GeneralSettings component, allowing users to toggle notifications for assistant messages, backups, and knowledge embedding.
- Updated the Redux store to manage notification settings and added migration logic for the new settings structure.
- Improved user feedback by ensuring notifications are sent based on user preferences.

This update enhances the user experience by providing customizable notification options and clearer context for notifications.

* feat: Add 'update' source to NotificationSource type for enhanced notification context

* feat: Integrate electron-notification-state for improved notification handling

- Added electron-notification-state package to manage Do Not Disturb settings.
- Updated NotificationService to respect user DND preferences when sending notifications.
- Adjusted notification settings in various components (BackupService, KnowledgeQueue, useUpdateHandler) to ensure notifications are sent based on user preferences.
- Enhanced user feedback by allowing notifications to be silenced based on DND status.

* feat: Add notification icon to Electron notifications

* fix: import SYSTEM_MODELS in EditModelsPopup for improved model handling

* feat(i18n): add knowledge base notifications in multiple languages

- Added success and error messages for knowledge base operations in English, Japanese, Russian, Chinese (Simplified and Traditional).
- Updated the KnowledgeQueue to utilize the new notification messages for better user feedback.

* feat(notification): introduce NotificationProvider and integrate into App component

- Added NotificationProvider to manage notifications within the application.
- Updated App component to include NotificationProvider, enhancing user feedback through notifications.
- Refactored NotificationQueue to support multiple listeners for notification handling.
2025-05-21 12:11:52 +08:00
自由的世界人
d07c6ecc6b feat: add message multiple select (#6085)
* feat: add message multiple select

* fix: build error

* feat: add drag-and-drop multi-selection

* fix: code review

* Revert "fix: code review"

This reverts commit 7e29d5147c.

* fix: hide input bar display

* fix: extract the ChatContext

* fix: eventemitter

* feat: enhance multi-select functionality with message registration

* fix: history page message search

* fix: build error

* fix: remove Event Emitter

* fix: build error

* feat: add hideMenuBar prop to MessageItem and integrate MessageEditingProvider

* fix: improve message selection logic and handle drag events

* fix: update translation keys for multiple select functionality

* fix: refactor message deletion logic and enhance message selection handling

* fix: replace useSelector with useStore for message selection in ChatContext

* fix: refactor MessageGroup to utilize context for multi-select handling and message registration

* Revert "fix: refactor MessageGroup to utilize context for multi-select handling and message registration"

This reverts commit f4d1454525.

* fix: simplify MessageGroup props and utilize context for message selection handling

* fix: streamline multi-select handling by consolidating context usage and simplifying component props
2025-05-21 11:22:36 +08:00
kangfenmao
57f10bf56f fix: sse mcp 404 not found 2025-05-21 10:50:28 +08:00
one
d4fc5d6503 fix: improve CodeEditor controlled mode, prevent unnecessary trimming (#6221)
* fix: improve CodeEditor controlled mode, prevent unnecessary trimming

* fix: useEffect dependency
2025-05-21 03:51:30 +08:00
one
55c57d72ba fix: handle undefined html title (#6229) 2025-05-20 20:57:14 +08:00
LiuVaayne
c468c3cfd5 Feat/mcp run python (#6151)
* feat: add MCP Run Python server and integrate Pyodide for executing Python code

* fix: comment out unused polyfill imports in MCP Run Python server
2025-05-20 20:47:30 +08:00
beyondkmp
bb3bfafe7e refactor(PasteService): optimize handler registration logic (#6223)
- Updated registerHandler to only log and update the handler if it changes, reducing unnecessary operations.
- Removed logging from unregisterHandler for cleaner code.
2025-05-20 19:12:45 +08:00
kangfenmao
0641857f26 chore(version): 1.3.8 2025-05-20 18:41:41 +08:00
kangfenmao
0d5b9d8d62 feat: implement getFilePath method in FileManager and update KnowledgeContent to use it
- Added getFilePath method in FileManager to construct file paths based on file ID and extension.
- Updated KnowledgeContent to utilize the new getFilePath method for opening file paths, improving code clarity and maintainability.
2025-05-20 18:04:01 +08:00
kangfenmao
09952a4d3b Revert "feat: add resolveFilePath functionality to resolve restoring from different computer (#5980)"
This reverts commit 3abe0e803c.
2025-05-20 17:44:04 +08:00
kangfenmao
e8753e0cf3 refactor: improve FloatingSidebar and HomeTabs components
- Simplified the onOpenChange handler in FloatingSidebar for better readability.
- Added style prop to HomeTabs for enhanced customization.
- Adjusted Container style in HomeTabs to merge with existing border styles.
2025-05-20 17:17:57 +08:00
George Zhao
05dc5da7b0 fix: add context menu trigger to FloatingSidebar component 2025-05-20 17:10:47 +08:00
kangfenmao
12f5d9acfd feat: enhance ContentSearch and MessageTools components
- Added placeholder and adjusted styles for the Input in ContentSearch.
- Updated SearchBarContainer to use fixed positioning and improved padding.
- Refactored MessageTools to render raw content correctly and added a new MarkdownContainer for better styling.
- Minor adjustments to other components for improved layout and user experience.
2025-05-20 15:33:10 +08:00
Zhaker
30885655dd fix: knowledge icon consistency across components (#6209)
feat: knowledge icon consistency across components
2025-05-20 15:07:57 +08:00
karl
49d4ae6e9b fix(mcp): mcp result preview is missing parameters (#6165) 2025-05-20 14:46:21 +08:00
Wei
2d7caf9fc5 feat: add new model provider BurnCloud 2025-05-20 13:55:57 +08:00
fullex
5cf0a43b2c chore: update Hailuo logo (#6208)
* feat: update Hailuo model configuration with new logo property and border option

* chore: update hailuo_dark model image
2025-05-20 13:11:54 +08:00
beyondkmp
98426e084a refactor: centralize paste handling logic with PasteService (#6199)
- Integrated PasteService for handling paste events in Inputbar and MessageEditor components.
- Removed redundant paste handling code and improved maintainability.
- Registered paste handlers and set last focused component for better user experience.
- Ensured consistent behavior for text and file pasting across components.

Co-authored-by: beyondkmp <beyondkmkp@gmail.com>
2025-05-20 13:09:37 +08:00
kangfenmao
52559534c6 chore(version): 1.3.7 2025-05-20 11:33:52 +08:00
kangfenmao
ab5ffe4e2e fix: set default initial values for rendering speed in aihubmixConfig
- Added initialValue 'DEFAULT' for renderingSpeed in multiple mode configurations to ensure consistent default behavior across the application.
2025-05-20 11:31:35 +08:00
jwcrystal
3d8f514f30 feat: add MCP servers via JSON quickly (#6099)
* feat: add MCP servers via JSON quickly

* refactor(MCPSettings): Extract JSON parsing logic into a helper function

* feat: json linter for EditMcpJsonPopup

* feat(mcp): confirm the MCP server status as connection

* refactor(AddMcpServerModal): 移除冗余注释并修复加载状态

* feat(MCPSettings): Add support for SSE and streamableHttp formats and optimize server configuration parsing

- Add server type validation to ensure the type is stdio, SSE, or streamableHttp
- Optimize JSON parsing logic to ensure server configuration completeness and validity
- Update example text to provide more detailed configuration examples

* fix(MCPSettings): fix AddMcpServerModal default baseUrl login

移除serverToAdd.url作为baseUrl的备选值,确保baseUrl仅使用serverToAdd.baseUrl的值

* feat(MCPSettings): support CodeEditor in AddMcpServerModal

* fix: Remove unnecessary type checks for JSON parsing login

* fix(MCPSettings): fix compatibility issues with the URL field when parsing server data

* refactor: remove unnessary cdoe

* chore: Add a server dropdown button to integrate new features in UI

- Integrate the two buttons for adding a server into a single dropdown menu to enhance user experience and simplify the interface

* chroe: modify the Dropdown items' name of addServer

* refactor(i18n): unify the translation for the MCP server import function

---------

Co-authored-by: one <wangan.cs@gmail.com>
2025-05-20 10:41:25 +08:00
kangfenmao
47faa6edf2 fix: update default values and improve component structure
- Changed default value for `getTrayOnClose` to true in `ConfigManager`.
- Removed fullscreen toggle logic from `WindowService`.
- Adjusted styles in `OpenAIAlert` for better spacing.
- Reorganized imports in `Navbar` and updated component paths in `AssistantsTab` and `SettingsTab`.
- Added new components `AssistantItem` and `OpenAISettingsGroup` for better modularity.
- Enhanced `SettingGroup` styles for improved UI consistency.
- Updated `QuickPhraseSettings` to utilize theme context.
- Minor fixes and refactoring across various services and components.
2025-05-20 00:20:02 +08:00
suyao
a95ace3dc5 fix: update file path resolution in new Electron 2025-05-19 23:02:14 +08:00
kangfenmao
582427663f docs: update branching strategy documentation and add English and Chinese versions
- Updated the README to link to the new English branching strategy document.
- Added a new English version of the branching strategy document.
- Removed the outdated branching strategy document.
- Added a Chinese version of the branching strategy document.
2025-05-19 22:25:46 +08:00
beyondkmp
3abe0e803c feat: add resolveFilePath functionality to resolve restoring from different computer (#5980)
* feat: add resolveFilePath functionality to file management

* Added new IPC channel for resolving file paths.
* Implemented resolveFilePath method in FileStorage service.
* Updated FileManager to utilize the new resolveFilePath method.
* Enhanced preload API to expose resolveFilePath to the renderer.
* Updated KnowledgeService to ensure file paths are correctly resolved in knowledge bases.

* refactor: remove unused path import from preload index

* Removed the unused 'resolve' import to clean up the codebase.
* Improved code readability by eliminating unnecessary dependencies.

---------

Co-authored-by: beyondkmp <beyondkmkp@gmail.com>
2025-05-19 21:58:44 +08:00
kangfenmao
94b602b250 refactor: remove constructor from KnowledgeQueue and invoke checkAllBases in useAppInit hook 2025-05-19 21:28:25 +08:00
kangfenmao
d42bf89045 Merge branch 'develop' 2025-05-19 21:23:17 +08:00
jwcrystal
75f25d8a44 feat: support default Quick Assistant model (#6150)
* feat: support default Quick Assistant model

- support the configuration of the quick assistant model.
- manage the models independently in Quick Assistant and Default Assistant.

* docs(i18n): Add translation for quick assistant model

* fix(llm): fix the default value of quickAssistantModel to silicon[1]

* refactor(i18n): uniformly the terms, changing "快速助手" to "快捷助手"
2025-05-19 20:49:13 +08:00
SuYao
ae163ff0ed hotfix: add OpenAI settings tab and related functionality (#6040)
* feat: add OpenAI settings tab and related functionality

* fix: update related logic to support flexible service layer.

* fix(OpenAIResponseProvider): remove unused isOpenAILLMModel import
2025-05-19 20:18:16 +08:00
SuYao
cc4008bf2b feat(settings): add OpenAI alert (#6164) 2025-05-19 20:06:54 +08:00
one
e9dc0d12d6 hotfix: respect user-defined model group name (#6174) 2025-05-19 18:40:01 +08:00
SuYao
00f1fb1e11 Hotfix/gemini-para-bug (#6173)
* fix(OpenAIProvider): enhance model support and formatting logic

* fix(OpenAIProvider): Gemini OpenAI Compatible
2025-05-19 17:34:35 +08:00
beyondkmp
db01b4981d feat: add zoom factor handling on window restore in WindowService (#6169)
Co-authored-by: beyondkmp <beyondkmkp@gmail.com>
2025-05-19 17:14:55 +08:00
SuYao
6036a94690 hotfix: enhance reasoning summary handling in OpenAI response processing (#6037) 2025-05-19 17:07:35 +08:00
jwcrystal
0c32ac1262 refactor(Scrollbar): Optimize scroll handling logic to support external scroll events (#6047)
* refactor(Scrollbar): Optimize scroll handling logic to support external scroll events

- Refactor `onScroll` logic to support external scroll events
- Integrate with `useScrollPosition` hook for better scroll state management
- Memorize the scoll state for better user experience
- Fix type definition for `ref` attribute
- Remove unnecessary `ref` type overrides
- Improve component compatibility and maintainability

* perf(useScrollPosition): Optimize scroll position updates using requestAnimationFrame

- Wrap the `window.keyv.set` call in `requestAnimationFrame` to reduce unnecessary performance overhead and improve responsiveness during scrolling.

* fix(Messages): Remove unused FC imports and add onComponentUpdate and onFirstUpdate properties
2025-05-19 16:33:06 +08:00
Morax
e3f5999362 feat: Add-aihubmix-ideogram-v3 (#5958)
* chore: update Yarn version to 4.9.1 and add rendering speed option for V3 model

* Discard changes to .yarnrc.yml

* Discard changes to .yarn/releases/yarn-4.6.0.cjs

* Update package.json

* Delete .yarn/releases/yarn-4.9.1.cjs

* Discard changes to .yarn/releases/yarn-4.6.0.cjs

* docs: Update README.zh.md add GitCode✖️Cherry Studio【新源力】贡献挑战赛

GitCode✖️Cherry Studio【新源力】贡献挑战赛

* fix: Update the file permissions for yarn-4.6.0.cjs, modify image links and formatting in README.zh.md

* clean

* refactor: improve switch case blocks and update config parameters in AihubmixPage

* feat: add error handling for empty image URLs and update localization messages

* refactor: replace modal error handling with warning messages for empty image URLs

* feat: update localization for rendering speed and translating messages in Japanese, Russian, and Traditional Chinese

* feat: add style types and rendering speed options to localization files

---------

Co-authored-by: 亢奋猫 <kangfenmao@qq.com>
Co-authored-by: suyao <sy20010504@gmail.com>
2025-05-19 16:30:07 +08:00
SuYao
f20b8c58ee refactor: implement message editing and resend functionality (#5901)
* feat: implement message editing and resend functionality with block management

* fix: move start_time_millsec initialization to onChunk for accurate timing

* feat: refactor message update thunks to separate block addition and updates

* feat: enhance MessageBlockEditor with toolbar buttons and attachment functionality

* refactor: implement message editing context and integrate with message operations

* style: adjust padding in MessageBlockEditor and related components

* refactor: remove MessageEditingContext and integrate editing logic directly in Message component

* refactor: streamline message rendering logic by using conditional rendering for MessageEditor and MessageContent

* refactor: remove redundant ipcRenderer variable in useMCPServers hook

* fix: Add mock for electron's ipcRenderer to support testing

* test: Update mocks for ipcRenderer and remove redundant window.electron mock

* fix: enhance file handling in MessageEditor with new Electron API

- Added support for file dragging with visual feedback.
- Improved file type validation during drag-and-drop and clipboard pasting.
- Displayed user notifications for unsupported file types.
- Refactored file handling logic for better clarity and maintainability.
2025-05-19 15:38:00 +08:00
MyPrototypeWhat
553e3d7989 refactor(messageThunk): optimize message update logic with atomic modifications
* Wrapped message and block updates in a database transaction for improved consistency.
* Replaced direct updates with atomic modifications using where().modify() for better performance and clarity.
* Enhanced error handling for message updates to ensure robustness.
2025-05-19 13:48:47 +08:00
suyao
b1e0b56783 fix: update token limits for Claude models 2025-05-19 13:47:51 +08:00
LiuVaayne
6356e1c0c2 refactor: improve sanitization and formatting in buildFunctionCallToo… (#6152)
refactor: improve sanitization and formatting in buildFunctionCallToolName function
2025-05-19 11:20:38 +08:00
George Zhao
362fdc069e feat: add FloatingSidebar component and integrate assistant switching… (#5852)
* feat: add FloatingSidebar component and integrate assistant switching functionality

* refactor: simplify FloatingSidebar by removing unused hooks and components

* refactor: remove unused AddAssistantPopup and related code from FloatingSidebar

* feat: implement sidebar hide cooldown and adjust tooltip delays in Navbar.

* feat: integrate HomeTabs into FloatingSidebar and update Navbar props

* refactor: remove commented-out code and unused components from FloatingSidebar

* fix: update Popover placement from rightTop to bottomRight in FloatingSidebar.

* feat: add forceToSeeAllTab prop to HomeTabs for improved tab visibility control

* fix: update HomeTabs logic to respect forceToSeeAllTab prop for tab selection

* feat: pass position prop to FloatingSidebar and HomeTabs for consistent layout control

* feat: integrate FloatingSidebar into Navbar for improved topic visibility and update HomeTabs logic for consistent tab rendering

* fix: remove unused showTopics from Navbar component

* feat: enhance topic visibility control in Navbar with cooldown logic for sidebar toggle

* fix: add onMouseOut handler to NavbarIcon for sidebar cooldown reset

---------

Co-authored-by: George Zhao <georgezhao@SKJLAB>
2025-05-19 11:05:15 +08:00
kangfenmao
ee67f7bf9b fix: update min token limit for qwen3 models in THINKING_TOKEN_MAP 2025-05-19 10:49:33 +08:00
kangfenmao
5cee35b167 Merge branch 'main' into develop 2025-05-19 10:12:15 +08:00
one
cd02ee3125 refactor: transparent border for code blocks 2025-05-19 08:36:09 +08:00
fullex
7dac8cee64 refactor: simplify font family handling and improve layout styles in MessageTools and ThinkingBlock components 2025-05-19 08:32:37 +08:00
SuYao
5d3b751aa0 fix: 多发消息时除fold布局以外的其他布局不能被渲染 (#6143) 2025-05-19 01:43:21 +08:00
SuYao
0707be1e1f Revert "feat: add MCP Run Python server and integrate Pyodide for exe… (#6141)
Revert "feat: add MCP Run Python server and integrate Pyodide for executing P… (#5793)"

This reverts commit 086b09d99a.
2025-05-19 00:54:32 +08:00
kangfenmao
5e1c45370b chore(version): 1.3.6 2025-05-18 23:57:53 +08:00
kangfenmao
d4abea4101 refactor(SyntaxHighlighter): modularize highlighter logic and improve theme/language loading
* Moved highlighter initialization and loading functions to a new utility file for better organization.
* Simplified theme and language loading in the SyntaxHighlighterProvider using the new utility functions.
* Removed redundant code and improved readability in MessageTools by introducing a new CollapsedContent component for rendering tool responses.
* Updated MCPDescription to use an async function for Shiki instance initialization.
2025-05-18 23:33:43 +08:00
kangfenmao
08e7b6a7ba feat(electron.vite.config): add manual chunking for vendor modules in output configuration 2025-05-18 23:33:43 +08:00
LiuVaayne
086b09d99a feat: add MCP Run Python server and integrate Pyodide for executing P… (#5793)
feat: add MCP Run Python server and integrate Pyodide for executing Python code
2025-05-18 23:31:05 +08:00
LiuVaayne
1475f75a35 feat: add buildFunctionCallToolName utility for generating tool names (#6136) 2025-05-18 23:05:44 +08:00
jwcrystal
b1babc8cb3 hotfix(i18n): Add multi-language support for network search settings (#6138) 2025-05-18 22:44:57 +08:00
Rudbeckia.hirta.L
537ada3256 fix: Search results are on the right side (#6133) 2025-05-18 22:21:13 +08:00
kangfenmao
bdbb937403 refactor: update spinner handling and improve initialization timing
* Modified the Content Security Policy to include 'unsafe-inline' for script-src.
* Changed the spinner display style from 'none' to 'flex' for better visibility.
* Removed the initSpinner function and directly initialized the spinner in useAppInit.
* Added console timing for initialization to track performance.
2025-05-18 22:05:35 +08:00
kangfenmao
8bfbbd497c refactor: streamline MCP service handling and improve IPC registration
* Refactored MCPService to implement a singleton pattern for better instance management.
* Updated IPC registration to utilize the new getMcpInstance method for handling MCP-related requests.
* Removed redundant IPC handlers from the main index file and centralized them in the ipc module.
* Added background throttling option in WindowService configuration to enhance performance.
* Introduced delays in MCPToolsButton to optimize resource and prompt fetching after initial load.
2025-05-18 22:04:56 +08:00
自由的世界人
4b2417ce37 hotfix: github models check error (#6128) 2025-05-18 20:23:29 +08:00
自由的世界人
68689692b0 hotfix: github models check error (#6128) 2025-05-18 20:16:33 +08:00
kangfenmao
03fa6b5a74 fix(WindowService): handle fullscreen toggle before hiding window 2025-05-18 19:43:47 +08:00
George Zhao
2202b82f33 fix: remove infinite token display for max count and simplify context count rendering. (#6103)
Co-authored-by: George Zhao <georgezhao@SKJLAB>
2025-05-18 18:37:08 +08:00
MyPrototypeWhat
fed855a4ae refactor(MessageGroup): optimize selected message handling with useMe… (#6124)
* refactor(MessageGroup): optimize selected message handling with useMemo and clean up unused code

* refactor(MainTextBlock): optimize citation handling with useMemo and improve code clarity

* fix:del console
2025-05-18 18:08:13 +08:00
SuYao
22ee6f042f Hotfix/rich text paste (#6122)
fix(Inputbar): 优化粘贴处理逻辑,优先处理文本粘贴
2025-05-18 17:16:05 +08:00
Konjac-XZ
114d4850b1 fix: Summary for single message export doesn't work. 2025-05-18 15:11:30 +08:00
purefkh
711888a897 fix: Prevent sending message during input method composition in mini window (#6104) 2025-05-18 15:09:56 +08:00
kangfenmao
f43c16b85f fix(WindowService): add backgroundThrottling option to Electron configuration 2025-05-18 14:41:10 +08:00
purefkh
2f47250748 fix(i18n): websearch has no translation (#6118) 2025-05-18 12:44:06 +08:00
kangfenmao
653f1d54d2 chore: update dependencies in yarn.lock
- Removed outdated dependencies including "@babel/plugin-syntax-jsx" and "@bcoe/v8-coverage".
- Added new dependencies such as "@sec-ant/readable-stream", "browserslist", and "cacheable-request".
- Updated versions for existing packages including "got", "ow", and "type-fest".
- Ensured compatibility with the latest versions of peer dependencies.
2025-05-18 11:58:02 +08:00
kangfenmao
f0e43e9bcd refactor: streamline SettingsTab layout and remove unnecessary Divider components 2025-05-18 11:50:26 +08:00
Konjac-XZ
7706a4cc36 fix: Summary for single message export doesn't work. 2025-05-18 10:21:45 +08:00
自由的世界人
bd65e7bac4 feat: make sidebar setting group collapsible (#6019)
* feat: code tools, editor, executor

CodeEditor & Preview
- CodeEditor: CodeMirror 6
  - Switch to CodeEditor in the settings
  - Support edit&save with a accurate diff&lookup strategy
  - Use CodeEditor for editing MCP json configuration
- CodePreview: Original Shiki syntax highlighting
  - Implemented using a custom Shiki stream tokenizer
  - Remov code caching as it is incompatible with the current streaming code highlighting
  - Add a webworker for shiki
- Other preview components
  - Merge MermaidPopup and Mermaid to MermaidPreview, use local mermaidjs
  - Show mermaid syntax error message on demand
  - Rename PlantUML to PlantUmlPreview
- Rename SyntaxHighlighterProvider to CodeStyleProvider for clarity
- Both light and dark themes are preserved for convenience

CodeToolbar
- Top sticky toolbar provides quick tools (left) and core tools (right)
- Quick tools are hidden under the `More` button to avoid clutter, while core tools are always visible
- View&edit mode
  - Allow switching between preview and edit modes
  - Add a split view

Code execution
- Pyodide for executing Python scripts
- Add a webworker for Pyodide

* fix: migrate version and lint error

* refactor: use constants for defining tool specs

* feat: make setting group collapsible

* fix: yarn.lock

* fix: conflict error

---------

Co-authored-by: one <wangan.cs@gmail.com>
2025-05-18 10:11:14 +08:00
one
f6a496d1b9 refactor: clean up code for MessageGroupModelList (#6084)
* refactor: clean up comments and useless z-index

* refactor: remove useless styles, restore segment animation, extract renderLabel

* refactor: add left margin to the toggle button

* revert: font size
2025-05-18 10:05:19 +08:00
eeee0717
ba88a24455 fix(knowledge): remove topN 2025-05-18 10:02:05 +08:00
MyPrototypeWhat
42f5485899 fix: group message resend (#6106) 2025-05-18 09:52:27 +08:00
MyPrototypeWhat
e51a37cc74 fix: group message resend (#6106) 2025-05-17 23:53:57 +08:00
kangfenmao
5a7423463f Merge branch 'main' into develop 2025-05-17 21:41:38 +08:00
fullex
928a597d38 chore: remove unused packages (#5948)
* chore: update @cherrystudio/embedjs packages to version 0.1.29 and update sass to version 1.88.0; remove deprecated dependencies from package.json and yarn.lock

* chore: add node-stream-zip and update devDependencies in package.json and yarn.lock

* chore: reorganize and update @cherrystudio/embedjs dependencies in package.json

* chore: add zipread dependency to package.json and update yarn.lock

* chore: add @strongtz/win32-arm64-msvc dependency to package.json and update yarn.lock
2025-05-17 21:36:06 +08:00
purefkh
86a58ea050 fix: Prevent sending message during input method composition in mini window (#6104) 2025-05-17 21:34:17 +08:00
fullex
71ae6f2713 fix: font-family changed in Windows with new Electron (#6079) 2025-05-17 21:27:29 +08:00
icinggslits
dbc6014506 feat: Highlighted search in chat page (#3302)
* feat: Highlighted search in chat page

* Bug fixes and added a temporary F3 shortcut

* Bug fixes

* Bug fixes

* feat: Implement content search functionality with keyboard shortcuts

- Added a new `ContentSearch` component for searching text within a specified target.
- Integrated search functionality with keyboard shortcuts, allowing users to enable search via a new shortcut key.
- Updated internationalization files to include new search-related messages in multiple languages.
- Enhanced shortcut management to accommodate the new search feature, including a migration for shortcut updates.
- Refactored the `Chat` component to utilize the new content search capabilities.

* fix(ContentSearch): Update search index check and enhance container styling

* feat(ContentSearch): Enhance search functionality with case sensitivity and whole word options

- Added options for case sensitivity and whole word matching in the search feature.
- Updated the highlightText function to accommodate new search parameters.
- Improved styling and layout of the search input and buttons for better user experience.
- Refactored related components to support the new search options.

* refactor(useShortcuts): Remove console log for shortcut invocation

* feat: Add user filter and initial text to search

- Improve ContentSearch UI:
  - Add tooltips for search options
  - Enhance result display (e.g., "No results", "0/0")
  - Disable navigation buttons when no matches
  - Relocate search bar to the top of the chat view
- Apply case-sensitivity logic only to Latin characters
- Add translations for new options

* i18n: Translate "No results" message

* Add in-chat search shortcut, update global search to Cmd/Ctrl+Shift+F.

* feat: Allow users to scroll during input and optimize the search result index update logic

* feat: Update search message shortcut to include Shift for improved accessibility

* feat: Refactor search component layout and update highlight color variables

* fix: Adjust margin-bottom for SearchBarContainer

---------

Co-authored-by: suyao <sy20010504@gmail.com>
2025-05-17 21:14:03 +08:00
kangfenmao
6bbfa287e4 chore(version): 1.3.5 2025-05-17 20:05:16 +08:00
kangfenmao
b2ceea2483 fix: electron-store config.json missing
* Added 'electron-store' to the Vite configuration for Electron.
* Imported configuration from '@main/config' in the main index file.
* Changed default return value for getTrayOnClose method in ConfigManager to false
2025-05-17 19:58:38 +08:00
SuiYunsy
f7b5bd8d22 拓宽”请选择要检测的模型“模态框 (#6062)
许多模型名称很长,目前模态框太短,无法完全显示
2025-05-17 19:57:46 +08:00
kangfenmao
727a84e5ca fix: adjust visibility timer and trigger area dimensions in ChatNavigation
* Reduced the button visibility timer from 1500ms to 500ms for quicker feedback.
* Decreased the trigger width from 80 to 60 for improved interaction.
* Updated the height calculation from 40% to 30% of the window height for better layout consistency.
2025-05-17 19:57:09 +08:00
kangfenmao
d0a368d9ef refactor: remove deprecated max token settings from OpenAIProvider and OpenAIResponseProvider 2025-05-17 19:57:09 +08:00
George Zhao
d7139cca02 feat: implement useFullscreen hook and integrate with NavbarRight for dynamic padding (#6000)
* feat: implement useFullscreen hook and integrate with NavbarRight for dynamic padding

* feat: integrate useFullscreen hook to adjust sidebar layout based on fullscreen state

* fix: adjust sidebar height based on fullscreen state for better layout

---------

Co-authored-by: George Zhao <georgezhao@SKJLAB>
2025-05-17 19:48:40 +08:00
Rudbeckia.hirta.L
e9ab193a24 feat: support skipping files during backup(slim backup) (#6075)
* feat: support skipping files during backup

* 修复 lint

* 修复 lint

---------

Co-authored-by: daisai.1 <daisai.1@bytedance.com>
2025-05-17 19:20:40 +08:00
George Zhao
548db37066 fix: Ensure last app is displayed when no filtered apps are found (#6090)
* fix: Ensure last app is displayed when no filtered apps are found

* fix: Remove Empty component from AppsPage when no apps are found

---------

Co-authored-by: George Zhao <georgezhao@SKJLAB>
2025-05-17 17:27:45 +08:00
one
ece9e2ef13 chore: reduce package size (#6092) 2025-05-17 17:07:30 +08:00
Steven Ding
2894eec438 fix: change minimax's URL (#6091)
Signed-off-by: stevending1st <stevending1st@163.com>
2025-05-17 15:55:36 +08:00
beyondkmp
c2465c33b7 feat: Drop file improvements (#6089)
* feat: enhance file drag-and-drop functionality and global paste handling in Inputbar

- Added support for file dragging with visual feedback.
- Implemented global paste event handling to allow pasting outside the text area.
- Improved file drop handling to notify users of unsupported file types.

* refactor(Inputbar): simplify global paste handling logic

- Removed unnecessary checks for active element in global paste event handling.
- Streamlined the paste event processing for improved clarity and maintainability.

* style(Inputbar): clean up comments and maintain visual feedback for file dragging

- Removed unnecessary comments related to the styling of the file dragging state.
- Ensured the visual feedback for file dragging remains intact with updated background color.
2025-05-17 09:35:25 +08:00
kangfenmao
3c2e8e2f1d fix: electron-store config.json missing
* Added 'electron-store' to the Vite configuration for Electron.
* Imported configuration from '@main/config' in the main index file.
* Changed default return value for getTrayOnClose method in ConfigManager to false
2025-05-17 08:46:26 +08:00
1600822305
f84358adfd fix: Update file API usage for Electron 35.2.2 and add translations f… (#6087)
* Fix: Update file API usage for Electron 35.2.2 and add translations for file error messages

* 修复Electron 35.2.2中的文件API问题
2025-05-17 08:09:30 +08:00
one
51071d65fb refactor(CodeEditor): add more options to props for better customization, fix auto theme (#6071)
* refactor(CodeEditor): add more options to props for better customization

- A complete BasicSetupOptions could be passed in to override system-wise options
- EditMcpJsonPopup now use customized options

* fix: accommodate ThemeMode.auto

* fix: typos
2025-05-17 01:32:54 +08:00
方程
ba30bffa49 Gitee AI:update name, update models (#6006)
* Gitee AI:update name, update models

* Gitee AI:update name, update models

---------

Co-authored-by: 方程 <fangcheng@oschina.cn>
2025-05-17 00:47:42 +08:00
SuYao
5701b09c23 Hotfix/gemini openrouter (#6080)
feat: 添加对openai兼容情况下Gemini模型的思考配置支持
2025-05-17 00:45:26 +08:00
自由的世界人
c66563d335 feat: add citation list right-click copy (#6066)
* feat: add citation list right-click copy

* fix: extract the right-click menu as a component
2025-05-17 00:22:09 +08:00
SuiYunsy
c7b0b5ce53 拓宽”请选择要检测的模型“模态框 (#6062)
许多模型名称很长,目前模态框太短,无法完全显示
2025-05-16 23:40:22 +08:00
SuYao
883bffd2db fix: Update the state of the last message block as a fallback (#6076) 2025-05-16 22:21:05 +08:00
MyPrototypeWhat
2fca383e1e Hotfix/thinking time render (#6073)
refactor: simplify ThinkingBlock component and extract thinking time logic

* Removed unnecessary state and interval management from ThinkingBlock.
* Introduced ThinkingTimeSeconds component to handle thinking time display and logic.
* Cleaned up code for better readability and maintainability.
2025-05-16 21:56:40 +08:00
SuYao
523d6de257 hotfix: openai websearch render and gemini think (#6055) 2025-05-16 20:42:59 +08:00
SuiYunsy
89f31ddd94 fix: 禁止“自定义CSS”输入框的拼写检查 (#6064) 2025-05-16 19:23:43 +08:00
亢奋猫
273fabafcb docs: Update README.zh.md add GitCode✖️Cherry Studio【新源力】贡献挑战赛
GitCode✖️Cherry Studio【新源力】贡献挑战赛
2025-05-16 17:25:21 +08:00
karl
c088378d7e fix: mcp params error(issues/6050) (#6058) 2025-05-16 16:50:10 +08:00
kangfenmao
6f5f917c98 fix: adjust visibility timer and trigger area dimensions in ChatNavigation
* Reduced the button visibility timer from 1500ms to 500ms for quicker feedback.
* Decreased the trigger width from 80 to 60 for improved interaction.
* Updated the height calculation from 40% to 30% of the window height for better layout consistency.
2025-05-16 14:46:22 +08:00
kangfenmao
4ad3e44769 refactor: remove deprecated max token settings from OpenAIProvider and OpenAIResponseProvider 2025-05-16 14:36:40 +08:00
one
2dedd95fcc feat: code tools, editor, executor (#4632)
* feat: code tools, editor, executor

CodeEditor & Preview
- CodeEditor: CodeMirror 6
  - Switch to CodeEditor in the settings
  - Support edit&save with a accurate diff&lookup strategy
  - Use CodeEditor for editing MCP json configuration
- CodePreview: Original Shiki syntax highlighting
  - Implemented using a custom Shiki stream tokenizer
  - Remov code caching as it is incompatible with the current streaming code highlighting
  - Add a webworker for shiki
- Other preview components
  - Merge MermaidPopup and Mermaid to MermaidPreview, use local mermaidjs
  - Show mermaid syntax error message on demand
  - Rename PlantUML to PlantUmlPreview
- Rename SyntaxHighlighterProvider to CodeStyleProvider for clarity
- Both light and dark themes are preserved for convenience

CodeToolbar
- Top sticky toolbar provides quick tools (left) and core tools (right)
- Quick tools are hidden under the `More` button to avoid clutter, while core tools are always visible
- View&edit mode
  - Allow switching between preview and edit modes
  - Add a split view

Code execution
- Pyodide for executing Python scripts
- Add a webworker for Pyodide

* fix: migrate version and lint error

* refactor: use constants for defining tool specs

* refactor: add user-select, fix tool specs

* refactor: simplify some state changing

* fix: make sure editor tools registered after the editor is ready

---------

Co-authored-by: 自由的世界人 <3196812536@qq.com>
2025-05-16 13:53:44 +08:00
beyondkmp
c6b87b307b feat: upgrade electron to 35.2.2 (#5151)
* upgrade electron to 35.2.0

* update electron to 32.3.3

* udpate electron-vite to 3.0.0

* upgrade to 35.2.2

* patch https://github.com/electron-userland/electron-builder/pull/9046

* add minimumSystemVersion config

* update electron vite

---------

Co-authored-by: beyondkmp <beyondkmkp@gmail.com>
2025-05-16 12:56:52 +08:00
tiankuo.zhou
e6e0000328 fix: update mcp sdk version to solve the bug-preserve custom paths in SSE endpoint URLs (#6021)
* fix: update mcp sdk to slove the bug - preserve custom paths in SSE endpoint URLs

* add signoff

Signed-off-by: tiankuo.zhou <tiankuo.zhou@lofty.com>

* add signoff

Signed-off-by: tiankuo.zhou <tiankuo.zhou@lofty.com>

---------

Signed-off-by: tiankuo.zhou <tiankuo.zhou@lofty.com>
Co-authored-by: tiankuo.zhou <tiankuo.zhou@lofty.com>
2025-05-16 10:13:53 +08:00
kangfenmao
29d0e60136 chore(version): 1.3.4 2025-05-16 10:12:26 +08:00
kangfenmao
2211b23e0e Merge branch 'develop' 2025-05-16 10:11:57 +08:00
kangfenmao
51d2e03740 style: adjust padding and layout in various components
* Updated padding in CustomCollapse for improved spacing.
* Modified Messages component to conditionally apply padding based on prompt visibility.
* Enhanced ModelListSearchBar by adding a flex display style for better alignment.
* Increased gap in ProviderSetting's HStack for improved layout consistency.
2025-05-15 23:00:43 +08:00
Song
e9d6b9f441 fix: fix portable dir setup time (#6022) 2025-05-15 23:00:43 +08:00
kangfenmao
f18c81b7b8 refactor: add tool use mode translations and refactor settings
* Introduced new translations for "Tool Use Mode" and its options ("Function" and "Prompt") in English, Japanese, Russian, Simplified Chinese, and Traditional Chinese.
* Refactored settings components to replace the deprecated `enableToolUse` with `toolUseMode`, updating related logic and UI elements accordingly.
* Adjusted migration logic to ensure backward compatibility with previous settings.
2025-05-15 23:00:43 +08:00
kangfenmao
18a24b2b4f i18n: update max tokens settings translations for multiple languages
* Changed the wording for max tokens settings in English, Japanese, Russian, Simplified Chinese, and Traditional Chinese to improve clarity.
* Adjusted confirmation messages to better reflect the functionality of setting maximum token limits.
2025-05-15 23:00:43 +08:00
kangfenmao
fd76989c7e refactor: remove extended context settings and related logic
* Removed EXTENDED_CONTEXT_LIMIT and EXTENDED_CONTEXT_STEP constants from the configuration.
* Eliminated enableMaxContexts state and associated logic from settings components.
* Updated contextCount handling to accommodate a maximum of 100, with a special case for 100 to represent an unlimited context.
* Cleaned up related translations for max contexts in multiple languages.
2025-05-15 23:00:43 +08:00
fullex
1577dbefae feat: implement global error handling for uncaught exceptions and unhandled rejections in production mode 2025-05-15 22:54:04 +08:00
MyPrototypeWhat
1e40bd013d fix: remove undici dependency and clean up ProxyManager code (#6020) 2025-05-15 22:09:53 +08:00
Song
b48c6525ca fix: fix portable dir setup time (#6022) 2025-05-15 22:07:10 +08:00
kangfenmao
0d7f965e29 Merge branch 'main' into develop
# Conflicts:
#	yarn.lock
2025-05-15 20:24:00 +08:00
kangfenmao
385a6793f5 chore: remove artifacts and database packages
* Deleted the @cherry-studio/artifacts package including its README, package.json, and associated CSS files.
* Removed the @cherry-studio/database package along with its README, package.json, and source files for data handling.
* Cleaned up related yarn.lock files and installation states for both packages.
2025-05-15 20:21:43 +08:00
kangfenmao
e430731d02 chore(version): 1.3.3 2025-05-15 19:50:21 +08:00
Konv Suu
cc092e7222 fix: strange corner style in miniapp pop up (#5976)
* fix: strange corner style in miniapp pop up
2025-05-15 19:46:19 +08:00
MyPrototypeWhat
84251c0324 refactor: update ImageBlockGroup layout to use CSS grid for better re… (#5998)
refactor: update ImageBlockGroup layout to use CSS grid for better responsiveness
2025-05-15 19:46:15 +08:00
SuYao
9b3b71b6d8 fix: enhance image block handling in message processing (#5971) 2025-05-15 19:45:09 +08:00
SuYao
f67a0c330d fix: timing measurement before sending request (#5970) 2025-05-15 19:44:18 +08:00
Chen Tao
59438eac9d fix: remove dimensions(except voyage) (#6015)
* fix: remove dimensions(except voyage)

* fix: #6016
2025-05-15 18:05:29 +08:00
SuYao
873d3b22bd fix: update geminiapi check (#6002) 2025-05-15 17:51:08 +08:00
Lao
c3cf66fd52 fix: Update Server McpSettings (#6018)
fix:初始化一个新的MCP Server时,没有防止用户多次点击启用开关
2025-05-15 17:50:52 +08:00
one
7332763d9f fix: update current topic id and support EmojiAvatar for ChatFlowHistory (#5861)
* fix: update current topic id for ChatFlowHistory to work

* refactor: set current topic id early in loadTopicMessagesThunk

* refactor: extract EmojiAvatar

* fix: style
2025-05-15 01:42:18 +08:00
MyPrototypeWhat
928530c6f6 refactor: update ImageBlockGroup layout to use CSS grid for better re… (#5998)
refactor: update ImageBlockGroup layout to use CSS grid for better responsiveness
2025-05-14 23:44:48 +08:00
Konv Suu
33b28d783a fix: strange corner style in miniapp pop up (#5976)
* fix: strange corner style in miniapp pop up
2025-05-14 23:38:19 +08:00
George Zhao
15e3bdf20d feat: add support for allowing Escape key to exit fullscreen mode (#5930)
* feat: add support for allowing Escape key to exit fullscreen mode

* feat(i18n): add translation for allowing ESC key to exit fullscreen mode in multiple locales

* feat: enable Escape key to exit fullscreen mode regardless of platform

* feat: 添加允许使用Escape键退出全屏模式的功能,并更新相关国际化支持

* fix: 修复全屏模式下Escape键退出功能的状态管理,移除相关设置项

* feat: 添加全屏状态管理功能至导航栏,更新右侧导航栏组件以支持全屏模式

* feat: 更新导航栏以支持全屏模式,调整右侧导航栏的内边距

* fix: 更新全屏模式下Escape键退出功能的默认设置为启用

* refactor: 移除全屏模式下Escape键退出功能的状态管理逻辑

* fix: 移除全屏模式下Escape键退出功能的调试日志

* feat: 添加全屏模式下Escape键的快捷键配置,默认启用

* refactor: 移除与全屏模式下Escape键退出功能相关的IPC通道和配置

* refactor: 移除全屏模式下Escape键退出功能的配置项

* refactor: 移除Navbar和McpSettingsNavbar中与全屏模式相关的代码

* refactor: 移动exit_fullscreen快捷键配置到shortcuts数组末尾

* refactor: 添加全屏模式下快捷键未设置时直接退出全屏的逻辑

* refactor: 添加全屏模式下快捷键未设置时直接退出全屏的逻辑

* refactor: remove unused useFullscreen hook

* refactor: remove 'allow ESC key to exit fullscreen mode' translations from multiple locale files

---------

Co-authored-by: George Zhao <georgezhao@SKJLAB>
2025-05-14 23:35:02 +08:00
SuYao
85f2f454c4 fix: inaccurate temperature param (#5973)
* fix: inaccurate temperature param

* fix: enhance model support check for reasoning and web search models
2025-05-14 22:08:04 +08:00
kangfenmao
fb548ca20a fix: ensure topic is created if not found in loadTopicMessagesThunk
* Added logic to create a new topic with an empty messages array if the specified topic does not exist in the database.
2025-05-14 21:53:28 +08:00
George Zhao
1bc6571d7d docs: 贡献者数据源 (#5992)
更新贡献者数据源。
2025-05-14 21:40:47 +08:00
SuYao
3792ee3073 fix: enhance image block handling in message processing (#5971) 2025-05-14 20:55:03 +08:00
SuYao
88e472975a fix: timing measurement before sending request (#5970) 2025-05-14 20:53:08 +08:00
George Zhao
c004e06823 fix: use EXTENDED_CONTEXT_STEP for slider step value in settings (#5989)
Co-authored-by: George Zhao <georgezhao@SKJLAB>
2025-05-14 20:08:47 +08:00
George Zhao
3766fe4833 feat: 更长上下文 (#5963)
* feat: Extended the fillable context length to 100. #5514

Update src/renderer/src/pages/home/Tabs/SettingsTab.tsx

fix: update context length settings and localization for multiple languages

* fix: adjust context count slider limits and behavior based on max contexts setting

* feat: implement event handling for max contexts changes in settings

* feat: update context count handling to use EXTENDED_CONTEXT_LIMIT in settings

* feat: update context count logic to use EXTENDED_CONTEXT_LIMIT when max contexts are enabled

* refactor: remove unused EXTENDED_CONTEXT_LIMIT import from AssistantService

* feat: simplify context count logic with validAndChangeContextCount function

* feat: 移除TokenCount组件中对最大计数为20的特殊处理,简化上下文计数显示逻辑

* refactor: simplify getContextCount logic by removing max context handling

* feat: adjust step value for context count input based on max contexts setting

---------

Co-authored-by: George Zhao <georgezhao@SKJLAB>
2025-05-14 19:37:37 +08:00
Konv Suu
484e63b47e fix: define line-clamp for compatibility (#5983) 2025-05-14 18:57:02 +08:00
Lao
cb611d2671 fix lint errors (#5987)
* Fix code snippets that don't comply with code standards by applying lint rules

* update package.json:add test:lint script
2025-05-14 17:01:33 +08:00
SuYao
b6c29e6598 fix: append topic prompt if exists (#5969) 2025-05-14 13:52:31 +08:00
SuYao
03a4f4199a fix: improve citation deduplication logic for non-knowledge citations (#5981) 2025-05-14 13:51:25 +08:00
one
8bc8a9fc99 fix: quickpanel auto-scroll behaviour (#5950)
* fix: quickpanel scrollto changed to smart

* fix: add scrollTrigger as the replacement for scrollBlock

* fix: add a 'none' trigger to prevent accidental scrolling
2025-05-14 00:52:25 +08:00
自由的世界人
e7c0bbb348 feat: add citation content copy button (#5966)
* feat: add citation content copy button

* fix: build error
2025-05-14 00:13:00 +08:00
上房揭瓦
71cd2def2e 添加智能体订阅功能 (#5954)
* 添加智能体订阅功能

* 修改图标

* 修改hook点

修改图标

* 优雅的引用图标

* feat(i18n): add settings title for agents in multiple languages

* fix(i18n): update translations for improved clarity

* Merge branch 'main' into Subscribe

---------

Co-authored-by: VM 96 <eov@88.com>
Co-authored-by: suyao <sy20010504@gmail.com>
2025-05-13 23:09:38 +08:00
kangfenmao
e00f03db2b chore(version): 1.3.2 2025-05-13 21:04:14 +08:00
kangfenmao
8bd38ccd86 Merge branch 'main' into develop
# Conflicts:
#	src/renderer/src/providers/AiProvider/OpenAIProvider.ts
#	src/renderer/src/providers/AiProvider/OpenAIResponseProvider.ts
2025-05-13 20:53:23 +08:00
kangfenmao
02ae0349bf fix: Grouped message should not reset model and modelId
* Updated the reset logic to conditionally handle model and modelId for grouped messages.
* Ensured that the original model is retained when regenerating responses for grouped messages.
2025-05-13 20:50:52 +08:00
kangfenmao
ff8224a2dc fix: OpenAIResponseProvider summaryForSearch impl model wrong 2025-05-13 20:50:29 +08:00
kangfenmao
8214a70c38 revert: openai compatible type 2025-05-13 20:50:02 +08:00
kangfenmao
f642cfb3ba fix: regenerate message use assistant model 2025-05-13 20:45:50 +08:00
kangfenmao
d8b47d36ad fix: Grouped message should not reset model and modelId
* Updated the reset logic to conditionally handle model and modelId for grouped messages.
* Ensured that the original model is retained when regenerating responses for grouped messages.
2025-05-13 20:41:15 +08:00
kangfenmao
69ccd2aa32 fix: history topic message block is empty
* Added useEffect to dispatch loadTopicMessagesThunk when the topic is available
* Integrated useAppDispatch for state management
2025-05-13 20:24:41 +08:00
kangfenmao
c80e195ec2 fix: history topic message block is empty
* Added useEffect to dispatch loadTopicMessagesThunk when the topic is available
* Integrated useAppDispatch for state management
2025-05-13 20:24:19 +08:00
Yohann
011b9dca33 feat: add DevTools functionality and localization support (#5796)
* feat: add DevTools functionality and localization support

* Added new IPC channels for opening and toggling DevTools.
* Implemented corresponding handlers in the main process.
* Updated preload API to include DevTools methods.
* Enhanced the AboutSettings component with a debug section to control DevTools.
* Added localization strings for debug actions in English, Simplified Chinese, and Traditional Chinese.

* refactor: remove DevTools open state handling and related localization strings

* Removed the IPC channel and associated handlers for checking if DevTools is open.
* Updated the AboutSettings component to eliminate the DevTools open state management.
* Removed localization strings for the DevTools close action in English, Simplified Chinese, and Traditional Chinese.

* ToggleDevTools event uses the source window to trigger switching, compatible with multiple windows

* Remove empty comments

---------

Co-authored-by: yangheng <492238647@qq.com>
2025-05-13 16:53:55 +08:00
fullex
6381a28b27 fix(WebviewService): simplify user agent string modification by removing Chrome version replacement 2025-05-13 16:47:18 +08:00
Konv Suu
392a821c6c feat: add cache size retrieval functionality and integrate with UI (#5689)
* feat: add cache size retrieval functionality and integrate with UI

* chore: clean

* update

* update
2025-05-13 16:17:09 +08:00
one
dc93e168df refactor: improve model management UI, add animations to some buttons (#5932)
* feat: add motion to ModelListSearchBar

* feat: add motion to health checking button

* refactor(EditModelsPopup): show spin while fetching models

* refactor: remove redundant filtering, use transient props

* chore: remove useless component ModelTags

* refactor: extract and reuse ModelIdWithTags

* refactor(EditModelsPopup): use ExpandableText instead of expandable Typography.Paragraph

* refactor(EditModelsPopup): implement optimistic updates for filter type and loading state

* refactor: startTransition for search

* refactor(EditModelsPopup): enhance search and filter handling with optimistic updates

* refactor(EditModelsPopup): implement debounced search filter updates

---------

Co-authored-by: suyao <sy20010504@gmail.com>
2025-05-13 15:43:42 +08:00
one
b9224ed311 fix: animation on resolving SelectModelPopup (#5947) 2025-05-13 15:26:43 +08:00
dlzmoe
b1d87321aa feat: Optimize the display method for the three modes (#5938)
chore: Optimize the display method for the three modes
2025-05-13 15:26:04 +08:00
SuYao
044420dd7e fix: timer stop (#5914) 2025-05-13 15:25:27 +08:00
one
0da17e2808 refactor: improve model management UI, add animations to some buttons (#5932)
* feat: add motion to ModelListSearchBar

* feat: add motion to health checking button

* refactor(EditModelsPopup): show spin while fetching models

* refactor: remove redundant filtering, use transient props

* chore: remove useless component ModelTags

* refactor: extract and reuse ModelIdWithTags

* refactor(EditModelsPopup): use ExpandableText instead of expandable Typography.Paragraph

* refactor(EditModelsPopup): implement optimistic updates for filter type and loading state

* refactor: startTransition for search

* refactor(EditModelsPopup): enhance search and filter handling with optimistic updates

* refactor(EditModelsPopup): implement debounced search filter updates

---------

Co-authored-by: suyao <sy20010504@gmail.com>
2025-05-13 14:48:59 +08:00
kangfenmao
6a48b76c5e chore(version): 1.3.1 2025-05-13 13:48:19 +08:00
beyondkmp
a183cf6d61 chore: use node-stream-zip to improve perfermanc and remove unused dependencies (#5946)
* chore: remove unused dependencies from package.json and yarn.lock

* fix: update backup extraction progress logging in BackupManager

---------

Co-authored-by: beyondkmp <beyondkmkp@gmail.com>
2025-05-13 13:41:06 +08:00
one
cb98e49d75 fix: animation on resolving SelectModelPopup (#5947) 2025-05-13 13:22:24 +08:00
dlzmoe
487c864860 feat: Optimize the display method for the three modes (#5938)
chore: Optimize the display method for the three modes
2025-05-13 12:21:12 +08:00
jwcrystal
be97826ba7 docs: Add Photo instructions to the branch strategy document (#5944) 2025-05-13 12:20:01 +08:00
Teo
dac200f330 feature: Hide disabled options for web search (#5943)
* refactor(WebSearchButton): streamline provider item creation and filter disabled items

* refactor(WebSearchButton): optimize provider items mapping and add pageSize to quick panel

* refactor(WebSearchButton): filter out disabled providers in items mapping
2025-05-13 12:07:08 +08:00
kangfenmao
e4d1dd0c87 fix: OpenAIResponseProvider summaryForSearch impl model wrong 2025-05-13 10:23:12 +08:00
SuYao
872339ca75 fix: timer stop (#5914) 2025-05-13 08:47:14 +08:00
kangfenmao
352b449948 revert: openai compatible type 2025-05-12 22:40:32 +08:00
beyondkmp
341f744e07 fix(ipc): enhance theme handling with title bar overlay updates and broadcast notifications (#5915)
feat(ipc): enhance theme handling with title bar overlay updates and broadcast notifications
2025-05-12 22:27:27 +08:00
George Zhao
d456b3d4ed fix: ensure correct handling of custom mini app updates and removals (#5922)
* fix: ensure correct handling of custom mini app updates and removals

* fix: update title for custom mini app to be more concise in localization files

---------

Co-authored-by: George Zhao <georgezhao@SKJLAB>
2025-05-12 22:26:44 +08:00
Chen Tao
6314b391db fix: add i18n (#5921)
* fix(i18n)

* Update en-us.json
2025-05-12 22:26:38 +08:00
beyondkmp
440359cf75 fix(ipc): enhance theme handling with title bar overlay updates and broadcast notifications (#5915)
feat(ipc): enhance theme handling with title bar overlay updates and broadcast notifications
2025-05-12 21:57:50 +08:00
saica.go
5200dcfef6 feat: add undo functionality to agent prompt generation (#5821)
* feat: add undo functionality to agent prompt generation

* feat: add a standalone undo button
2025-05-12 21:48:53 +08:00
George Zhao
f416500ef1 fix: ensure correct handling of custom mini app updates and removals (#5922)
* fix: ensure correct handling of custom mini app updates and removals

* fix: update title for custom mini app to be more concise in localization files

---------

Co-authored-by: George Zhao <georgezhao@SKJLAB>
2025-05-12 20:58:35 +08:00
Chen Tao
0b5f05c489 fix: add i18n (#5921)
* fix(i18n)

* Update en-us.json
2025-05-12 20:49:31 +08:00
one
ff570a58d5 refactor: SelectModelPopup pinning (#5855)
* refactor: focus the hovered item when toggling a pinned model

* refactor: focus the selected item after loading pinned models

* refactor: update sticky group after loading pinned models

* fix: rapidly update sticky group

* refactor: defer lastscrolloffset

* refactor: rename updateOnListChange to focusOnListChange for clarity

* refactor: increaset overscan count

* refactor: use startTransition instead of deferred value

* refactor: add guard, clean up code

* refactor: simplify cleanup logic

* refactor: remove unnecessary dep on  pinnedModels

* fix: flicker on searching

* refactor: simplify tag tooltips, prevent tooltips in SelectModelPopup
2025-05-12 20:43:45 +08:00
Chen Tao
63b8ca4888 feat(knowledge): adjust default top-n to 10 (#5919) 2025-05-12 20:32:28 +08:00
kangfenmao
657c3148cb fix: regenerate message use assistant model 2025-05-12 20:28:51 +08:00
suyao
1ab9ea295d fix: move start_time_millsec initialization to onChunk for accurate timing 2025-05-12 20:28:12 +08:00
suyao
0bb25bfe9e fix: move start_time_millsec initialization to onChunk for accurate timing 2025-05-12 18:23:32 +08:00
Wang Jiyuan
55c78382e4 feat: minimize token usage when testing model (#5905) 2025-05-12 18:19:03 +08:00
jwcrystal
84b205cac2 fix: fix the formating error on qwen3 (#5899)
fix(ModelMessageService): fix the formating error on qwen3
2025-05-12 14:55:23 +08:00
fullex
698096e523 chore: remove bufferutil dependency from package.json and yarn.lock 2025-05-12 13:11:52 +08:00
kangfenmao
aaee8aa59c fix: update miniWindow URL path in dev mode 2025-05-12 09:50:13 +08:00
chenxue
4c15ca507c fix:painting support reload (#5886)
* add painting aihubmix provider

* fix: Cannot read properties of undefined (reading 'unshift')

* fix: painting redux data

* feat: customize aihubmix provider request logic

* fix: download error retry

* Update AihubmixProvider.ts

* back

* back

---------

Co-authored-by: zhaochenxue <zhaochenxue@bixin.cn>
Co-authored-by: 亢奋猫 <kangfenmao@qq.com>
2025-05-12 09:23:52 +08:00
beyondkmp
98d14fa02d feat(i18n): add page zoom settings in English, Japanese, and Russian locales 2025-05-12 09:03:09 +08:00
fullex
f9afa6f4f4 fix: miniWindow not found 2025-05-11 23:54:47 +08:00
GeorgeDong32
1c35ede129 chore(workflows): update workflows for new repo structure 2025-05-11 23:52:37 +08:00
kangfenmao
22295359e8 fix: update vitest configuration and add setup file for renderer tests
- Updated vitest.config.ts to handle optional chaining for plugins and alias.
- Added setup.ts for mocking electron-log in renderer tests.
- Adjusted test include and exclude patterns to refine test coverage.
2025-05-11 23:41:26 +08:00
Chen Tao
d091e3e9ee fix: copilot annotation bug (#5882)
fix: copilot annotation
2025-05-11 23:04:29 +08:00
jwcrystal
d2f7152d5a docs: Improve the README for better readability (#5880)
docs: Improve README readability
2025-05-11 22:02:31 +08:00
kangfenmao
29f0c72941 docs: add Branching Strategy document and update README with contribution guidelines 2025-05-11 21:07:57 +08:00
SuYao
9048cbb895 fix: ensure modelId is case-insensitive in findTokenLimit function (#5844)
* fix: ensure modelId is case-insensitive in findTokenLimit function

* fix: make modelId case-insensitive in findTokenLimit function
2025-05-11 20:59:42 +08:00
kangfenmao
8eb8505739 chore(version): 1.3.0 2025-05-11 20:28:51 +08:00
MyPrototypeWhat
431d04078f fix: next release bugs (#5801)
* style: update ChatNavigation and CitationsList components for improved UI consistency

- Added header style to remove borders in ChatNavigation.
- Enhanced CitationsList with new Skeleton loading state and improved layout for citation cards.
- Refactored CitationLink to a div for better styling control and adjusted padding in OpenButton for a more polished appearance.

* fix: update citation rendering logic in MainTextBlock component

- Added a check to determine if the citation URL is a valid link.
- Updated citation tag formatting to conditionally include the link based on the URL validity.

* Remove Zhipu mode and text-only link handling

* refactor: update message handling and state management

- Simplified message editing logic by removing unnecessary success/error logging.
- Added `updatedAt` timestamp to message updates for better tracking.
- Refactored `editMessageBlocks` to accept message ID and updates directly.
- Removed unused `getTopicLimit` function from `TopicManager`.
- Updated message rendering to use `updatedAt` when available.
- Enhanced type definitions to include `updatedAt` in message structure.

* refactor: optimize block update logic and remove unused code

- Updated `throttledBlockUpdate` to handle asynchronous updates directly.
- Removed the unused `throttledBlockDbUpdate` function and its related logic.
- Added cancellation for throttled updates on error and completion to improve performance and reliability.
- Cleaned up commented-out code for better readability.

* fix: enhance logging and update async handling in StreamProcessingService and messageThunk

- Enabled logging in `createStreamProcessor` for better debugging.
- Added logging for updated messages in `updateExistingMessageAndBlocksInDB` and `saveUpdatesToDB`.
- Updated `onTextComplete` and `onLLMWebSearchComplete` to handle asynchronous operations correctly.
- Commented out unused `saveUpdatedBlockToDB` calls to prevent unnecessary database updates.

* feat: enhance citation handling and add metadata support in citation blocks

* fix: prevent default action in handleLinkClick for better link handling

* fix: update styled component props to use dollar sign prefix for consistency

* feat(i18n): add download success and failure messages in multiple languages

* refactor(messageThunk): remove console.log statements for cleaner code

* refactor(StreamProcessingService): comment out console.log for cleaner code

* feat(MessageMenubar): add edit option to dropdown for single message editing

* fix:  set default zoomFactor in settings

* fix: add support for inline base64 image data in image block

* fix: remove 'auto' option from qwen model supported options

* fix: adjust thinking millisecond handling in message thunk

* fix: update regex for function calling models and improve time tracking logic

* fix: enhance error handling and metrics tracking across AI providers and message processing

* fix: add new image generation models to the configuration

* refactor(GeminiProvider): implement image generation handling in chat responses

* Revert "fix:  set default zoomFactor in settings"

This reverts commit 428b1fbe40.

---------

Co-authored-by: kangfenmao <kangfenmao@qq.com>
Co-authored-by: suyao <sy20010504@gmail.com>
2025-05-11 20:20:43 +08:00
kangfenmao
77286eb9dd feat: replace SvgSpinners180Ring with BeatLoader for improved loading animation in MinappPopupContainer 2025-05-11 19:51:02 +08:00
kangfenmao
a6379bd05e chore: remove opendal dependency and related code from RemoteStorage service 2025-05-11 19:50:55 +08:00
kangfenmao
b4945e2fea fix: update persisted reducer version and remove obsolete migration logic 2025-05-11 19:40:12 +08:00
kangfenmao
4f1414d5c6 feat: add transition animations to various popups and dialogs for improved user experience 2025-05-11 19:38:16 +08:00
kangfenmao
cb950ecde1 refactor: replace console.log with Logger for improved logging consistency across the application 2025-05-11 19:25:50 +08:00
kangfenmao
2b447a7911 fix: message and rerank errors
commit ee9c7ca69e
Merge: 476855c9 d4fe5dfa
Author: kangfenmao <kangfenmao@qq.com>
Date:   Sun May 11 18:43:56 2025 +0800

    Merge branch 'main' into fix/next-release-bugs

commit 476855c9c7
Author: suyao <sy20010504@gmail.com>
Date:   Sun May 11 18:09:58 2025 +0800

    refactor(GeminiProvider): implement image generation handling in chat responses

commit 66939a5302
Merge: 2f278b3f 499fb306
Author: suyao <sy20010504@gmail.com>
Date:   Sun May 11 16:45:29 2025 +0800

    Merge branch 'main' into fix/next-release-bugs

commit 2f278b3fac
Author: suyao <sy20010504@gmail.com>
Date:   Sun May 11 16:41:38 2025 +0800

    fix: add new image generation models to the configuration

commit a55e9759f3
Merge: 4c50dfbd 8e1c10ab
Author: suyao <sy20010504@gmail.com>
Date:   Sun May 11 13:14:17 2025 +0800

    Merge branch 'main' into fix/next-release-bugs

commit 4c50dfbd19
Author: suyao <sy20010504@gmail.com>
Date:   Sun May 11 13:13:20 2025 +0800

    fix: enhance error handling and metrics tracking across AI providers and message processing

commit 9b9a395451
Author: suyao <sy20010504@gmail.com>
Date:   Sun May 11 05:36:52 2025 +0800

    fix: update regex for function calling models and improve time tracking logic

commit c2f23d6916
Author: suyao <sy20010504@gmail.com>
Date:   Sun May 11 05:05:05 2025 +0800

    fix: adjust thinking millisecond handling in message thunk

commit c565d91591
Author: suyao <sy20010504@gmail.com>
Date:   Sun May 11 03:52:26 2025 +0800

    fix: remove 'auto' option from qwen model supported options

commit dfeb54fd94
Author: suyao <sy20010504@gmail.com>
Date:   Sun May 11 03:50:05 2025 +0800

    fix: add support for inline base64 image data in image block

commit 9b2992fce4
Merge: 428b1fbe db6408f3
Author: suyao <sy20010504@gmail.com>
Date:   Sun May 11 03:00:57 2025 +0800

    Merge branch 'main' into fix/next-release-bugs

commit 428b1fbe40
Author: suyao <sy20010504@gmail.com>
Date:   Sun May 11 03:00:11 2025 +0800

    fix:  set default zoomFactor in settings

commit 8a99eea28a
Merge: f4963888 2193a665
Author: suyao <sy20010504@gmail.com>
Date:   Sun May 11 01:31:05 2025 +0800

    Merge branch 'main' into fix/next-release-bugs

commit f4963888e8
Merge: c6cf7908 3f1fadb9
Author: kangfenmao <kangfenmao@qq.com>
Date:   Sat May 10 20:18:44 2025 +0800

    Merge branch 'main' into fix/next-release-bugs

commit c6cf790851
Author: lizhixuan <daoquqiexing@gmail.com>
Date:   Sat May 10 11:38:01 2025 +0800

    feat(MessageMenubar): add edit option to dropdown for single message editing

commit 87b106fad6
Author: lizhixuan <daoquqiexing@gmail.com>
Date:   Sat May 10 10:53:10 2025 +0800

    refactor(StreamProcessingService): comment out console.log for cleaner code

commit 7d0b8b33af
Author: lizhixuan <daoquqiexing@gmail.com>
Date:   Sat May 10 10:52:08 2025 +0800

    refactor(messageThunk): remove console.log statements for cleaner code

commit 7310eebebc
Author: kangfenmao <kangfenmao@qq.com>
Date:   Sat May 10 10:17:44 2025 +0800

    feat(i18n): add download success and failure messages in multiple languages

commit 42733d0fc8
Merge: 6364f4c0 ac0651a9
Author: kangfenmao <kangfenmao@qq.com>
Date:   Sat May 10 09:54:38 2025 +0800

    Merge branch 'main' into fix/next-release-bugs

commit 6364f4c006
Author: suyao <sy20010504@gmail.com>
Date:   Fri May 9 22:19:35 2025 +0800

    fix: update styled component props to use dollar sign prefix for consistency

commit 34c49b84f6
Author: suyao <sy20010504@gmail.com>
Date:   Fri May 9 22:02:06 2025 +0800

    fix: prevent default action in handleLinkClick for better link handling

commit 84bf76cc43
Merge: 572ffcc8 3697b31c
Author: suyao <sy20010504@gmail.com>
Date:   Fri May 9 21:55:54 2025 +0800

    Merge branch 'main' into fix/next-release-bugs

commit 572ffcc8be
Merge: 9ba630b5 a6a8324c
Author: suyao <sy20010504@gmail.com>
Date:   Fri May 9 21:48:13 2025 +0800

    Merge branch 'main' into fix/next-release-bugs

commit 9ba630b5e8
Merge: bf819a71 6d910755
Author: suyao <sy20010504@gmail.com>
Date:   Fri May 9 21:27:08 2025 +0800

    Merge branch 'fix/next-release-bugs' of github.com:CherryHQ/cherry-studio into fix/next-release-bugs

commit bf819a7142
Author: suyao <sy20010504@gmail.com>
Date:   Fri May 9 21:24:48 2025 +0800

    feat: enhance citation handling and add metadata support in citation blocks

commit 6d9107558e
Author: MyPrototypeWhat <daoquqiexing@gmail.com>
Date:   Fri May 9 19:47:24 2025 +0800

    fix: enhance logging and update async handling in StreamProcessingService and messageThunk

    - Enabled logging in `createStreamProcessor` for better debugging.
    - Added logging for updated messages in `updateExistingMessageAndBlocksInDB` and `saveUpdatesToDB`.
    - Updated `onTextComplete` and `onLLMWebSearchComplete` to handle asynchronous operations correctly.
    - Commented out unused `saveUpdatedBlockToDB` calls to prevent unnecessary database updates.

commit c402a1d21f
Author: MyPrototypeWhat <daoquqiexing@gmail.com>
Date:   Fri May 9 18:47:55 2025 +0800

    refactor: optimize block update logic and remove unused code

    - Updated `throttledBlockUpdate` to handle asynchronous updates directly.
    - Removed the unused `throttledBlockDbUpdate` function and its related logic.
    - Added cancellation for throttled updates on error and completion to improve performance and reliability.
    - Cleaned up commented-out code for better readability.

commit 6da1d08c9a
Author: MyPrototypeWhat <daoquqiexing@gmail.com>
Date:   Fri May 9 18:42:00 2025 +0800

    refactor: update message handling and state management

    - Simplified message editing logic by removing unnecessary success/error logging.
    - Added `updatedAt` timestamp to message updates for better tracking.
    - Refactored `editMessageBlocks` to accept message ID and updates directly.
    - Removed unused `getTopicLimit` function from `TopicManager`.
    - Updated message rendering to use `updatedAt` when available.
    - Enhanced type definitions to include `updatedAt` in message structure.

commit 30696e1ef1
Author: suyao <sy20010504@gmail.com>
Date:   Fri May 9 16:19:55 2025 +0800

    Remove Zhipu mode and text-only link handling

commit 5b95d20294
Author: kangfenmao <kangfenmao@qq.com>
Date:   Fri May 9 15:49:02 2025 +0800

    fix: update citation rendering logic in MainTextBlock component

    - Added a check to determine if the citation URL is a valid link.
    - Updated citation tag formatting to conditionally include the link based on the URL validity.

commit 28f1e486e6
Author: kangfenmao <kangfenmao@qq.com>
Date:   Thu May 8 18:31:14 2025 +0800

    style: update ChatNavigation and CitationsList components for improved UI consistency

    - Added header style to remove borders in ChatNavigation.
    - Enhanced CitationsList with new Skeleton loading state and improved layout for citation cards.
    - Refactored CitationLink to a div for better styling control and adjusted padding in OpenButton for a more polished appearance.
2025-05-11 18:44:28 +08:00
beyondkmp
d4fe5dfa32 refactor: update zoom handling in IPC and settings (#5868)
* refactor: update zoom handling in IPC and settings

* renamed zoom-related IPC channels for clarity
* refactored zoom handling logic in ipc.ts and ShortcutService
* removed unused zoom factor state management from settings
* updated DisplaySettings component to manage zoom via buttons
* localized zoom settings in multiple languages

* refactor: remove unused zoom factor state from settings

* eliminated the zoomFactor state from initialSettings as part of the zoom handling refactor

* refactor: improve zoom handling in DisplaySettings component

* initialized current zoom value on component mount
* refactored resize event listener to prevent memory leaks
* added cleanup for resize event listener to enhance performance

* refactor: enhance resize event handling in DisplaySettings component

* added logic to track previous window width to prevent unnecessary updates during resize events
* improved comments for clarity on zoom handling and resize event listener functionality

* refactor: streamline zoom handling across IPC and ShortcutService

* updated handleZoomFactor function to accept an array of BrowserWindows for batch processing
* simplified zoom factor adjustments in IPC and shortcut handlers
* removed unnecessary window destruction checks for improved performance
2025-05-11 18:42:25 +08:00
MyPrototypeWhat
499fb306f6 feat: add motion library for animations and enhance Spinner and Messa… (#5869)
feat: add motion library for animations and enhance Spinner and MessageBlock components

- Added 'motion' library to package.json and yarn.lock for animation support.
- Refactored Spinner component to utilize motion for animated effects.
- Introduced AnimatedBlockWrapper in MessageBlockRenderer for animated transitions.
- Updated ThinkingBlock to include animated lightbulb effect during thinking state.
2025-05-11 16:20:28 +08:00
one
d66e8cb4ec fix(Inputbar): do not reset selection on focus (#5866) 2025-05-11 13:47:25 +08:00
自由的世界人
1375d1a1c2 feat: add message translate copy & close (#5684)
* feat: add message translate copy & close

* fix: remove blockEntity
2025-05-11 13:40:09 +08:00
one
4f641c294b refactor(checkAPI): check api or model with stream enabled first (#5857)
* refactor(checkAPI): check api or model with stream enabled first

* refactor: improve healthcheck summary

* refactor: remove cursor style from status indicator

* fix: update apikey input box after deleting invalid keys
2025-05-11 13:32:47 +08:00
one
baa9fbbeeb fix: conditionally aquire focus for inputbar (#5860) 2025-05-11 13:27:10 +08:00
SuYao
8e1c10abd6 Revert "fix: update selectedTypes logic in ModelEditContent for better handling" (#5858) 2025-05-11 12:18:51 +08:00
one
db6408f3b9 revert: customtag tooltip delay (#5856) 2025-05-11 02:46:34 +08:00
自由的世界人
6ee181ed38 fix: clean up code (#5851) 2025-05-11 00:37:01 +08:00
one
2193a665c6 fix: check isComposing on keydown (#5848) 2025-05-10 23:55:18 +08:00
George Zhao
138ab1c5b7 feat(i18n): 更新机器翻译多语言,使用Qwen3 236b进行机器翻译。 (#5840)
feat(i18n): update Portuguese translations for import/export features, assistant settings, and mini app configurations

Co-authored-by: George Zhao <georgezhao@SKJLAB>
2025-05-10 23:40:57 +08:00
SuYao
8da51585ed fix: jina embedding error (#5839)
* fix: jina embedding error

* fix: handle jina model encoding format in Embeddings class
2025-05-10 23:03:48 +08:00
George Zhao
2f5349cb64 feat: regularPhrases可以随Agent一起导出。 (#5836)
* feat: add regularPhrases to AgentCard props

* fix: agentcard style btw

---------

Co-authored-by: George Zhao <georgezhao@SKJLAB>
Co-authored-by: Pleasurecruise <3196812536@qq.com>
2025-05-10 22:49:29 +08:00
kangfenmao
47d0a3cc0d style: add centered prop to modals and components for improved layout consistency 2025-05-10 22:21:37 +08:00
George Zhao
b80b63e207 feat: Agent Regular Phrases (#5775)
* feat: add regular prompts settings component and integrate into assistant settings.

* feat: add regular prompts to assistant creation and update QuickPhrasesButton to utilize them.

* feat: add regular prompts settings and update localization for multiple languages.

* fix: update assistantObj type to Assistant and clean up unused console logs.

* feat: enhance QuickPhrasesButton with modal for adding phrases and localization updates.

* feat: update localization for regular prompts to regular phrases in English, Simplified Chinese, and Traditional Chinese.

* fix: update localization for assistant prompts to assistant phrases in English and Simplified Chinese.

* feat: add regular prompts to new agents during import.

* refactor: rename regular_prompts to regular_phrases across localization and components

* feat: enhance QuickPhrasesButton with icons for location selection options

* perf: optimize loadQuickListPhrases with useCallback and update icon sizes in QuickPhrasesButton

---------

Co-authored-by: George Zhao <georgezhao@SKJLAB>
2025-05-10 21:57:57 +08:00
Camol
3fe6aee5b8 style: remove small size prop from Switch component in AssistantModel… (#5833)
style: remove small size prop from Switch component in AssistantModelSettings

Co-authored-by: kanweiwei <kanweiwei@nutstore.net>
2025-05-10 21:34:48 +08:00
Jee
3f1fadb9b6 feat: add prompt display control (#5439)
Co-authored-by: 亢奋猫 <kangfenmao@qq.com>
2025-05-10 20:16:59 +08:00
kangfenmao
b962126d98 fix: message render bugs
commit c6cf790851
Author: lizhixuan <daoquqiexing@gmail.com>
Date:   Sat May 10 11:38:01 2025 +0800

    feat(MessageMenubar): add edit option to dropdown for single message editing

commit 87b106fad6
Author: lizhixuan <daoquqiexing@gmail.com>
Date:   Sat May 10 10:53:10 2025 +0800

    refactor(StreamProcessingService): comment out console.log for cleaner code

commit 7d0b8b33af
Author: lizhixuan <daoquqiexing@gmail.com>
Date:   Sat May 10 10:52:08 2025 +0800

    refactor(messageThunk): remove console.log statements for cleaner code

commit 7310eebebc
Author: kangfenmao <kangfenmao@qq.com>
Date:   Sat May 10 10:17:44 2025 +0800

    feat(i18n): add download success and failure messages in multiple languages

commit 42733d0fc8
Merge: 6364f4c0 ac0651a9
Author: kangfenmao <kangfenmao@qq.com>
Date:   Sat May 10 09:54:38 2025 +0800

    Merge branch 'main' into fix/next-release-bugs

commit 6364f4c006
Author: suyao <sy20010504@gmail.com>
Date:   Fri May 9 22:19:35 2025 +0800

    fix: update styled component props to use dollar sign prefix for consistency

commit 34c49b84f6
Author: suyao <sy20010504@gmail.com>
Date:   Fri May 9 22:02:06 2025 +0800

    fix: prevent default action in handleLinkClick for better link handling

commit 84bf76cc43
Merge: 572ffcc8 3697b31c
Author: suyao <sy20010504@gmail.com>
Date:   Fri May 9 21:55:54 2025 +0800

    Merge branch 'main' into fix/next-release-bugs

commit 572ffcc8be
Merge: 9ba630b5 a6a8324c
Author: suyao <sy20010504@gmail.com>
Date:   Fri May 9 21:48:13 2025 +0800

    Merge branch 'main' into fix/next-release-bugs

commit 9ba630b5e8
Merge: bf819a71 6d910755
Author: suyao <sy20010504@gmail.com>
Date:   Fri May 9 21:27:08 2025 +0800

    Merge branch 'fix/next-release-bugs' of github.com:CherryHQ/cherry-studio into fix/next-release-bugs

commit bf819a7142
Author: suyao <sy20010504@gmail.com>
Date:   Fri May 9 21:24:48 2025 +0800

    feat: enhance citation handling and add metadata support in citation blocks

commit 6d9107558e
Author: MyPrototypeWhat <daoquqiexing@gmail.com>
Date:   Fri May 9 19:47:24 2025 +0800

    fix: enhance logging and update async handling in StreamProcessingService and messageThunk

    - Enabled logging in `createStreamProcessor` for better debugging.
    - Added logging for updated messages in `updateExistingMessageAndBlocksInDB` and `saveUpdatesToDB`.
    - Updated `onTextComplete` and `onLLMWebSearchComplete` to handle asynchronous operations correctly.
    - Commented out unused `saveUpdatedBlockToDB` calls to prevent unnecessary database updates.

commit c402a1d21f
Author: MyPrototypeWhat <daoquqiexing@gmail.com>
Date:   Fri May 9 18:47:55 2025 +0800

    refactor: optimize block update logic and remove unused code

    - Updated `throttledBlockUpdate` to handle asynchronous updates directly.
    - Removed the unused `throttledBlockDbUpdate` function and its related logic.
    - Added cancellation for throttled updates on error and completion to improve performance and reliability.
    - Cleaned up commented-out code for better readability.

commit 6da1d08c9a
Author: MyPrototypeWhat <daoquqiexing@gmail.com>
Date:   Fri May 9 18:42:00 2025 +0800

    refactor: update message handling and state management

    - Simplified message editing logic by removing unnecessary success/error logging.
    - Added `updatedAt` timestamp to message updates for better tracking.
    - Refactored `editMessageBlocks` to accept message ID and updates directly.
    - Removed unused `getTopicLimit` function from `TopicManager`.
    - Updated message rendering to use `updatedAt` when available.
    - Enhanced type definitions to include `updatedAt` in message structure.

commit 30696e1ef1
Author: suyao <sy20010504@gmail.com>
Date:   Fri May 9 16:19:55 2025 +0800

    Remove Zhipu mode and text-only link handling

commit 5b95d20294
Author: kangfenmao <kangfenmao@qq.com>
Date:   Fri May 9 15:49:02 2025 +0800

    fix: update citation rendering logic in MainTextBlock component

    - Added a check to determine if the citation URL is a valid link.
    - Updated citation tag formatting to conditionally include the link based on the URL validity.

commit 28f1e486e6
Author: kangfenmao <kangfenmao@qq.com>
Date:   Thu May 8 18:31:14 2025 +0800

    style: update ChatNavigation and CitationsList components for improved UI consistency

    - Added header style to remove borders in ChatNavigation.
    - Enhanced CitationsList with new Skeleton loading state and improved layout for citation cards.
    - Refactored CitationLink to a div for better styling control and adjusted padding in OpenButton for a more polished appearance.
2025-05-10 20:12:11 +08:00
Camol
14d013243c fix: update selectedTypes logic in ModelEditContent for better handling (#5819)
refactor: improve model type checking logic in models.ts and ModelEditContent.tsx

* Refactor isFunctionCallingModel, isEmbeddingModel, isVisionModel, isReasoningModel, and isWebSearchModel functions for better readability and maintainability.
* Simplify type selection logic in ModelEditContent by directly using model.type or defaultTypes.
* Remove unnecessary conditional checks to streamline the code.

Co-authored-by: kanweiwei <kanweiwei@nutstore.net>
2025-05-10 19:55:56 +08:00
jwcrystal
a448fa1804 docs(README): add Architecture overview document (#5824)
- Add the Architecture Overview document to help the contributor how to develop quickly.
2025-05-10 18:03:19 +08:00
SuYao
b07e5839c2 feat(webSearch): add Bocha web search provider integration (#5608)
* feat(webSearch): add Bocha web search provider integration

- Introduced BochaProvider for handling web search queries.
- Added Bocha logo and updated web search provider configuration.
- Implemented API host and key validation for Bocha.
- Enhanced web search settings to support Bocha provider.
- Updated Redux store to include Bocha in the web search provider list.
- Added validation schemas for Bocha search parameters and responses.

* fix(WebSearch): improve error handling in BochaProvider and validate web search questions

- Added error handling for failed Bocha search responses.
- Enhanced validation for web search questions to ensure they are an array and not empty.

* fix(WebSearch): add API host validation for Tavily provider

* chore: remove api host check button

* fix: add check for unnecessary web search in fetchExternalTool

---------

Co-authored-by: eeee0717 <chentao020717Work@outlook.com>
2025-05-10 16:42:09 +08:00
SuYao
3c7f72acc0 fix: qwen3 check (#5811) 2025-05-10 13:52:39 +08:00
one
77c6ae5a8c fix: SelectModelPopup scrolling behaviour (#5812)
* fix: focus the selected or the first item on searching

* refactor: remove unnecessary deferred values

* refactor: add a hook usePinnedModels for pinned models

* refactor: make the definition more consistent with other popups

* refactor: improve state management, improve scrolling behaviour

* fix: avoid potential modulo-by-zero

* fix: type error

* fix: async loading pinned models
2025-05-10 13:48:25 +08:00
Chen Tao
03c562bf5b feat: combine to general reranker (#5818)
* feat: combine to general reranker

* chore

* chore: set not support provider

* chore: add i18n
2025-05-10 13:42:54 +08:00
jwcrystal
ac0651a9f3 fix(Qwen3): Add Qwen3 Model Thinking Mode Switch in Thinking Button(#5781)
* feat(Qwen3): Add Qwen3 Model Thinking Mode Switch

- Add a thinking mode switch for the Qwen3 model on the settings page,
- Enabled: Generates thinking content. Disabled: Does not generate thinking content.

This feature is implemented by adding new settings items and corresponding logic.

* docs(i18n): Add multilingual translations for Qwen3 model's thinking mode

* refactor(Qwen3): Remove Qwen thinking mode related code from SettingTab

- Remove Qwen thinking mode related state and logic from the SettingsTab component
- Integarte Qwen 3 thinking mode switch login the ThinkingButton component to simplify the code and improve maintainability

* refactor(OpenAICompatibleProvider): Extract qwen3 handling logic to ModelMessageService

Move the postsuffix handling logic in OpenAICompatibleProvider to ModelMessageService to improve code maintainability and reusability

* docs(i18n): Remove Qwen3 model-related translations

Remove the translation content of the unused Qwen3 model's thinking mode and its prompts
2025-05-09 22:21:04 +08:00
beyondkmp
3a36da1bf9 feat: add inspect option to context menu with localization support (#5807)
* implemented a new inspect menu item in the context menu that toggles developer tools
* added localization for the inspect option in English, Japanese, Russian, and Chinese (both simplified and traditional)
2025-05-09 22:12:16 +08:00
Camol
3697b31c7b fix: update input schema reference in MessageTools for accurate tool … (#5804)
* fix: update input schema reference in MessageTools for accurate tool response handling

* refactor: remove unused CustomEditorContainer styles from MiniAppSettings

---------

Co-authored-by: kanweiwei <kanweiwei@nutstore.net>
2025-05-09 21:54:13 +08:00
kangfenmao
a6a8324cdb refactor: update icon usage and improve painting state management
* replaced InfoCircleFilled icon with Info from lucide-react for consistency
* simplified painting state initialization and handling in AihubmixPage and PaintingsPage
* removed unnecessary conditional logic for setting default painting
2025-05-09 20:50:18 +08:00
Camol
ce8b85020b feat: support both function call and system prompt for MCP tools (#5499)
* feat: support both function call and system prompt for MCP tools
- Add support for using both function call and system prompt to implement MCP tool calls
- Refactor tool handling logic to be more flexible and maintainable
- Improve code readability with better variable naming and comments
- Fix potential issues with tool call implementation

* fix: Add tool_calls in OpenAI streaming logic

* refactor: enhance OpenAICompatibleProvider and BaseOpenAiProvider structure

* feat: add tool call setting to SettingsTab component

* fix: enhance tool call handling in OpenAICompatibleProvider

* fix: enhance content handling in GeminiProvider for nonstreaming response

* refactor: improve tool property filtering logic in OpenAIProvider and mcp-tools utility

* fix: resolve eslint errors

* fix: add history for function call message in GeminiProvider

* refactor: unify MCP tool response handling across providers for consistency

* refactor: update mcp tools conversion logic in OpenAICompatibleProvider and OpenAIProvider

* refactor: enhance AihubmixProvider and BaseProvider with MCP tool handling methods

* refactor: introduce SYSTEM_PROMPT_THRESHOLD constant in BaseProvider for improved readability

* refactor: rename tool_call to enable_tool_use for clarity and consistency across the application

* refactor: remove unnecessary onChunk call in processStream for cleaner code

* fix: add toolCallId to response structure and enhance content handling in AnthropicProvider

* fix: respond image data to llm while using function call

* fix: add reasoning handling in OpenAICompatibleProvider for improved response processing

---------

Co-authored-by: kanweiwei <kanweiwei@nutstore.net>
Co-authored-by: jay <sevenjay@users.noreply.github.com>
2025-05-09 20:20:16 +08:00
kangfenmao
c0cb1693da feat: replace n8n icon with SVG version and update references
* removed the old n8n.ico file
* added new n8n.svg file
* updated references in minapps configuration and app components to use the new SVG logo
* changed file handling from 'customMiniAPP' to 'custom-minapps.json' for consistency
2025-05-09 20:19:34 +08:00
kangfenmao
6708efdbe1 Revert "feat: ParateraAI 添加支持 (#5792)"
This reverts commit e134cacbec.
2025-05-09 20:04:44 +08:00
one
8786ba6410 fix: SelectModelPopup sticky header (#5795)
* fix: remove console logs

* refactor: use onScroll instead of onItemsRendered
2025-05-09 16:43:22 +08:00
kangfenmao
c66dfa50cf fix: message citations styles and bugs 2025-05-09 15:56:54 +08:00
George Zhao
e134cacbec feat: ParateraAI 添加支持 (#5792)
* feat: add Paratera mini app with logo and URL.

* feat: add Paratera AI provider and associated model.

* feat: add Paratera provider to migration configuration

* fix: update Paratera entry in default mini apps configuration

* feat: add additional Paratera models to system models configuration

* feat: reintroduce Paratera provider in migration configuration

* fix: update redux-persist version to 99 in store configuration

---------

Co-authored-by: George Zhao <georgezhao@SKJLAB>
2025-05-09 14:45:29 +08:00
one
85e3233a72 fix: user message usage (#5657)
* fix: user message usage estimation

* fix: estimate usage on resending edited user message

* refactor: renaming

* refactor: renaming
2025-05-09 14:17:28 +08:00
Roland
be64faf9e2 feat: add the "Export Agent" feature (#5789) 2025-05-09 13:30:21 +08:00
one
45827890ac perf: virtual list for quick panel and SelectModelPopup (#5594)
* perf: quick panel with virtual list

* fix: adaptive virtual list height

* fix: keep panel content if it is re-opened in a very short interval

* refactor: use modified rc-virtual-list to support overscan

* fix: virtual list item key

* refactor: remove useless styles

* refactor: support smooth scrolling for virtual list

* fix: lint error

* perf: use virtual list for SelectModelPopup

* refactor: change model name style

* fix: better auto scroll behaviour

* perf: improve memorization for SelectModelPopup

* fix: group name background

* refactor: change model item margin

* chore: update rc-virtual-list to 3.19.2

* fix: adaptive list height

* refactor: improve styles for focused or selected items

* refactor: do not show model if the assistant has not default model

* chore: migrate to custom rc-virtual-list

* refactor: improve selected item style

* refactor: improve selected item style

* fix: left margin

* refactor: simplify the indicator for selected item

* fix: prevent mouse hover for keyboard events

* chore: bump rc-virtual-list

* refactor: simulate sticky group header in SelectModelPopup

* fix: cleanup timer, add comments

* perf: improve smooth scrolling

* chore: bump to rc-virtual-list:3.19.6

* refactor: update memorization

* refactor: extract item rendering logic

* refactor: delay CustomTag tooltip for performance

* fix: disable spellcheck in model search bar

* refactor: expand/collapse model label on resizing window

* refactor: simplify filtering

* chore: update rc-virtual-list

* refactor: always render virtual list to avoid inconsistent state

* chore: update dependencies

* chore: update dependencies

* chore: update dependencies

* refactor: remove useless states

* refactor: simplify selected state

* refactor: improve keyboard events for SelectModelPopup

* revert: do not expand mode tags

* refactor: reduce animation time

* chore: update dependencies

* refactor: better names and comments

* refactor: better error handling

* refactor: simplify auto-scrolling logic

* refactor: use react-window rather than rc-virtual-list

* fix: disable auto-scroll

* fix: scroll bar style and item margin

* fix: initialize sticky banner

* refactor: distinguish auto-scrolling behaviour for different causes

* fix: keyboard navigation error
2025-05-09 11:54:54 +08:00
Konv Suu
4fce9f8986 fix: remove useless border and border radius for code block 2025-05-09 09:55:47 +08:00
karl
db7a4a737f feat: tooluse result display style optimization (#5758) 2025-05-09 09:52:15 +08:00
Camol
6dd06c8e29 fix: display image from mcp response (#5780) 2025-05-09 09:43:22 +08:00
George Zhao
ba5de53c71 feat(minapps): add n8n mini app with logo and URL. (#5776)
Co-authored-by: George Zhao <georgezhao@SKJLAB>
2025-05-09 00:52:15 +08:00
one
e23127a515 refactor(ModelList): improve model list style and grouping (#5674)
* refactor(ModelList): make the group removing button consistent with model removing button

* refactor(ModelList): improve auto-grouping, reduce the number of groups

* refactor(EditModelsPopup): extract model group tools

* refactor(naming): add special grouping rules for some providers

* feat(ModelList): add a button to add/remove all the listed models

* refactor(naming): update auto grouping

* refactor: update auto grouping for dmxapi

* revert: remove ungrouped
2025-05-08 23:20:29 +08:00
George Zhao
2b91fac5fa feat: Custom mini app (#5731)
* feat: 新增文件写入功能,支持通过 ID 写入文件并加载自定义小应用配置。

* feat(i18n): 添加自定义小程序配置的多语言支持,包括英文、简体中文和繁体中文。

* fix(minapps): 使用 await 加载自定义小应用并合并到默认应用中,同时添加日志输出以便调试

* fix(minapps): 在开发环境中添加条件日志输出,以便调试加载的默认小应用。

* refactor(miniappSettings): 移动自定义小应用编辑区域的位置,优化界面布局。

* refactor(miniappSettings): 修改自定义小应用保存逻辑,优化应用列表重新加载方式。

* feat(i18n): 修复在merge过程中丢失的语言设置。

* fix(bug_risk): Consider adding stricter validation for the JSON config on load.

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

* feat(miniapp): 添加自定义小应用功能,优化应用列表展示,支持通过模态框添加新应用。

* feat(App): enhance custom app modal to support logo upload via URL.

* feat(miniapp): add application logo support and update mini app list reloading logic.

* feat(i18n): update mini app custom settings translations for multiple languages.

* feat(miniapp): add updateDefaultMinApps function and refactor mini app list reloading logic.

* feat(miniapp): add removeCustom functionality to handle custom mini app deletion

* feat(miniapp): add duplicate ID check when adding custom mini apps.

* feat(i18n): 重构侧边栏相关翻译为结构化格式,增加删除自定义应用的翻译支持。

* feat(miniapp): 优化删除自定义应用的逻辑,使用条件渲染简化代码结构。

* feat(miniapp): 添加自定义小应用内容的空值处理,确保 JSON 格式有效。

* feat(i18n): 更新默认语言为英语,并移除多个语言文件中的默认代理字段。

* feat(i18n): 为多个语言文件添加自定义小应用配置编辑描述翻译。

* feat(i18n): add success and error messages for deleting custom mini apps in multiple language files.

* feat(i18n): update success and error messages for custom mini app operations and add placeholder text in multiple language files.

* feat(i18n): 为多个语言文件添加重复ID和冲突ID的错误信息翻译,并在自定义小应用设置中实现相关检查逻辑。

* feat(miniapp): 在添加自定义小应用时,增加对默认最小应用ID的重复检查逻辑。

* feat(i18n): update edit description for custom mini app configuration in Traditional Chinese locale

* fix(miniapp): enhance error messages for duplicate and conflicting IDs in custom mini app configuration

---------

Co-authored-by: George Zhao <georgezhao@SKJLAB>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: suyao <sy20010504@gmail.com>
2025-05-08 21:59:55 +08:00
SuYao
8550e5be98 Feat/claude websearch support (#5771)
* feat: enhance web search capabilities in AnthropicProvider

- Added support for Claude models in web search functionality with a new regex.
- Implemented logic to retrieve web search parameters and handle web search results.
- Updated message handling to include web search progress and completion states.
- Enhanced citation formatting for web search results from Anthropic source.

* feat: import WebSearchResultBlock for enhanced message handling

- Added import for WebSearchResultBlock from the Anthropic SDK to improve message processing capabilities in messageBlock.ts.
- Removed duplicate import to streamline the code.

* chore: update @anthropic-ai/sdk to version 0.41.0 in package.json and yarn.lock

* Update src/renderer/src/store/messageBlock.ts

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: Chen Tao <70054568+eeee0717@users.noreply.github.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
2025-05-08 21:30:43 +08:00
Roland
0e202509d3 fix(MCP): Revise the server type judgment logic to ensure the correct… (#5769) 2025-05-08 20:57:48 +08:00
ous50 | ousfifty | 欧式fifty
cdbba49dd5 Feat: change Gemini Safety Settings to OFF; upgrade @google/genai package (#5763)
* Feat: change Gemini Safety Settings to OFF; upgrade @google/genai package

* fix: remove model.id param in getSafetySettings in Gemini Provider
2025-05-08 18:21:39 +08:00
MyPrototypeWhat
191aafe1fc fix: enhance search functionality with optional HTTP options (#5765)
* feat: enhance search functionality with optional HTTP options

- Updated the search method signatures in BaseWebSearchProvider, WebSearchEngineProvider, LocalSearchProvider, and WebSearchService to accept optional HTTP options.
- Modified fetchWebContent and fetchWebContents to utilize the new HTTP options parameter for improved request handling.
- Enhanced error handling in messageThunk to manage abort errors more effectively.

* feat: implement abortable promises for web search and fetch operations

- Added createAbortPromise utility to handle abort signals for promises.
- Updated LocalSearchProvider and fetchWebContent to utilize abortable promises, allowing for better control over ongoing requests.
- Enhanced error handling in ApiService to log errors consistently.
2025-05-08 18:03:19 +08:00
自由的世界人
2a787fe87b fix(theme): citation list dark theme display & url repeatedly requesting (#5752)
* fix(theme): citation list dark theme  display

* fix: 减少属性变量及反复请求

* fix: update react-query
2025-05-08 17:24:04 +08:00
MyPrototypeWhat
4b2882aab7 feat(MCP): update auto-install server name and add server registration logic
- Changed the auto-install server name to '@cherry/mcp-auto-install' in migration logic.
- Implemented logic to register the MCP server upon tool invocation if it matches the auto-install server name.
2025-05-08 13:26:40 +08:00
kangfenmao
8d3e243f3c feat: refactor AihubmixProvider and OpenAICompatibleProvider for improved model handling (#5732) 2025-05-08 13:20:12 +08:00
LiuVaayne
35bc5afdad feat(MCP): implement login shell environment retrieval (#5739)
feat: implement login shell environment retrieval
2025-05-08 10:14:01 +08:00
kangfenmao
1185ce2b73 feat: customize aihubmix provider request logic (#5728) 2025-05-08 09:52:53 +08:00
suyao
c19a6cb764 chore: remove unuseful doc 2025-05-08 09:40:08 +08:00
beyondkmp
930074a717 feat: add zoom factor setting and localization support (#5665)
* feat: add zoom factor setting and localization support

- Introduced App_SetZoomFactor IPC channel for managing zoom levels.
- Implemented zoom factor functionality in the main IPC handler.
- Added setZoomFactor method in the settings store and corresponding UI in DisplaySettings.
- Included localization for zoom settings in English and Chinese.

* add i18n

* recover file

* delete code

* fix: update zoom factor handling to apply to all windows

- Modified the IPC handler for App_SetZoomFactor to set the zoom factor for all non-destroyed windows instead of just the main window.

* add getzoomfactor api

* feat: synchronize zoom factor with Redux state on app initialization

- Added functionality to fetch the zoom factor from the main process and dispatch it to the Redux store during app initialization.
- Removed redundant zoom factor fetching logic from DisplaySettings component.

* feat: enhance zoom factor management with IPC updates

- Added a new IPC channel for zoom factor updates to notify all renderer processes.
- Introduced a constant for predefined zoom levels to streamline zoom adjustments.
- Updated the zoom handling logic to utilize the new zoom levels and ensure smooth transitions.
- Implemented a listener in the preload script to handle zoom factor updates from the main process.
- Refactored the app initialization to include real-time updates for the zoom factor in the Redux state.

* feat: integrate zoom options into DisplaySettings component

- Added ZOOM_OPTIONS constant to generate structured options for Ant Design Select from predefined zoom levels.
- Refactored DisplaySettings to utilize ZOOM_OPTIONS, removing redundant zoom option definitions.
- Simplified the zoom factor fetching logic in useAppInit for better readability and efficiency.

* refactor: streamline zoom factor handling and remove unused IPC channel

- Removed the App_GetZoomFactor IPC channel as it was no longer needed.
- Updated zoom factor handling to directly set and notify the main window of changes.
- Simplified the logic for setting the zoom factor in the WindowService and ShortcutService.
- Adjusted the useAppInit hook to utilize the new zoom factor management approach.

* refactor: improve zoom factor handling in WindowService and useAppInit hook

- Simplified the zoom factor setting in WindowService by directly using the config manager.
- Updated useAppInit to ensure the zoom factor is set correctly on initialization, enhancing responsiveness to changes.
2025-05-08 07:18:43 +08:00
Xuanwo
8b7bc0ff4e feat: Add OpenDAL based Remote Storage class (#2700)
* feat: Add OpenDAL based Remote Storage class

Signed-off-by: Xuanwo <github@xuanwo.io>

* Fix error logging

Signed-off-by: Xuanwo <github@xuanwo.io>

* fix(dependencies): update opendal version to ^0.47.11 and add platform-specific binaries

---------

Signed-off-by: Xuanwo <github@xuanwo.io>
Co-authored-by: suyao <sy20010504@gmail.com>
2025-05-08 03:09:40 +08:00
自由的世界人
80177458b1 fix: citation list loading (#5742)
* fix: citation loading

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

* fix: remove unused import & cleanup some code

---------

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
2025-05-08 03:04:31 +08:00
chenxi
a38f4a5ad3 fix: failed to connect OAuth MCP server (#5709)
* fix: failed to connect OAuth MCP server

* polish: handle token fetching errors gracefully
2025-05-08 00:32:51 +08:00
SuYao
f8b4b41b91 refactor(middleware): add extractReasoningMiddleware for enhanced text processing (#5637) 2025-05-07 22:09:12 +08:00
kangfenmao
06a590328c Revert "perf: <tool_use> display (#5489)"
This reverts commit 0782b24790.
2025-05-07 21:08:59 +08:00
kangfenmao
5fe1d077e3 fix(upgrades): add check for empty content in error block creation during upgrade to v7 2025-05-07 21:08:59 +08:00
自由的世界人
636c788e2b fix: knowledge base url error (#5735) 2025-05-07 19:42:48 +08:00
Chen Tao
97130cfb1e feat: optimize knowledge recognize (#5707)
Co-authored-by: 自由的世界人 <3196812536@qq.com>
2025-05-07 14:34:38 +08:00
自由的世界人
b6520cdc9a feat: change citation list style (#5516)
* feat: change citation list style

* feat: add link preview

* fix: model inside searching

* fix: change button word
2025-05-07 14:21:58 +08:00
自由的世界人
599370be4e fix: chat message translate (#5682)
* fix: add updateTranslationBlockThunk
2025-05-07 14:19:47 +08:00
Chen Tao
0f34bde749 feat: support dashscope reranker (#5725) 2025-05-07 14:17:03 +08:00
MyPrototypeWhat
afcb0eeae2 优化消息块渲染性能,使用 useMemo 缓存图像块组;移除调试日志以清理代码 (#5722) 2025-05-07 13:38:09 +08:00
fullex
22315b7201 fix(StoreSyncService): set flag bug when register IPC handler (#5715)
fix: register IPC handler in StoreSyncService
2025-05-07 10:52:51 +08:00
Teo
2e804eeebc feat(WebSearchButton): add 'Disable Web Search' option (#5717) 2025-05-07 10:37:08 +08:00
SuYao
1035dd2572 fix: update thinkingbutton model checks (#5708)
* fix: update model checks to include supported reasoning and thinking token models

* fix: simplify thinking button visibility logic in Inputbar component
2025-05-06 23:39:36 +08:00
fullex
6d3a994392 feat: emit event on message complete (#5696)
feat: add MESSAGE_COMPLETE event to EventService and emit on message processing completion
2025-05-06 23:14:41 +08:00
ipcjs
c920ca3da6 feat(miniapps): add Grok / X (#5706) 2025-05-06 20:17:57 +08:00
one
c0c6435fc5 fix: memorize assistant with model (#5701) 2025-05-06 18:05:32 +08:00
SuYao
89e403ae61 fix: include thinking_millsec in message block creation (#5685) 2025-05-06 08:09:25 +08:00
自由的世界人
4a925870d1 fix: openaiprovider timeout is not an integer (#5681)
Update OpenAIProvider.ts
2025-05-05 22:03:28 +08:00
SuYao
0c369b5a3e feat(OpenAI): new Responses API support (#5621)
* feat(OpenAI): new Responses API support

- Added OpenAICompatibleProvider to handle requests for OpenAI-compatible models.
- Updated ProviderSettings to reflect changes in provider types from 'openai' to 'openai-compatible'.
- Enhanced model validation and response handling in OpenAIProvider to support new input formats.
- Refactored existing components to accommodate the new provider structure and ensure compatibility with OpenAI's response API.
- Incremented store version to 98 to reflect changes in provider types and settings.
- Updated migration logic to convert existing 'openai' provider types to 'openai-compatible' where applicable.

* refactor(OpenAI): update model validation and response handling

- Renamed `isOpenAIModel` to `isOpenAILLMModel` for clarity in model type checking.
- Updated references to the new model validation function across `OpenAICompatibleProvider` and `OpenAIProvider`.
- Enhanced web search model validation logic to accommodate new model checks.
- Refactored `getOpenAIWebSearchParams` to return structured parameters based on model type.
- Improved citation formatting in message blocks for better web search results handling.

* fix(OpenAICompatibleProvider): reset timestamps for first token handling

- Updated logic to reset timestamps for the first token and content when no prior thinking content is present.
- Added comments for clarity on the purpose of the changes and marked a temporary fix for timestamp handling.

* refactor(OpenAICompatibleProvider): improve code readability with consistent formatting

* fix(OpenAIProvider): refine service tier logic for model identification

* fix: eslint error

* fix(OpenAIProvider): enhance response metrics tracking in streaming process

* feat(OpenAIProvider): add timeout handling for model requests

---------

Co-authored-by: 自由的世界人 <3196812536@qq.com>
2025-05-05 21:08:05 +08:00
chenxi
bb5c6a8bb9 Fix: 'Web Search' and 'Clear Context' don't work (#5677) 2025-05-05 21:04:18 +08:00
beyondkmp
197016f0db refactor: add Linux support for margin adjustments in MinappPopupConainer and McpSettingsNavbar (#5673)
* refactor: add Linux support for margin adjustments in MinappPopupContainer and McpSettingsNavbar

* refactor: adjust margin and padding for Linux support in MinappPopupContainer and McpSettingsNavbar

* refactor: enhance Linux support in MinappPopupContainer by updating button group class condition
2025-05-05 16:58:14 +08:00
one
7ce0b3072b fix: infinite scroller layout (#5671) 2025-05-05 15:17:10 +08:00
chenxi
e506460ef9 feat: popup question editor support translation assistant (#5660)
* feat: TextEditPopup support translation assistant

* polish: ensure safe state updates in TextEditPopup during unmount

* test: make delay assertion more lenient in unclassified utils tests

* feat: add loading indicator to translation button in TextEditPopup
2025-05-05 14:58:57 +08:00
one
82884e3945 fix: conditionally show loading more spinner (#5670) 2025-05-05 14:57:01 +08:00
SuYao
9acd2121d4 Fix/image height (#5658) 2025-05-04 22:31:00 +08:00
George Zhao
7ec5953892 feat: 支持自定义助手地址 (#5540)
* feat: 支持自定义助手地址

feat: 支持自定义助手地址

* feat: 更新多语言支持,添加“defaultaides”字段至隐私设置,并修改默认智能体的值为设置中的引用。

* feat: 更新默认助手设置,修复函数命名错误并优化状态管理

* refactor: update agent loading logic to use settings and improve error handling

* refactor: update DefaultaidesSettings to use custom hook for state management and replace icon in DataSettings

* fix: improve error message formatting in callMCPTool function

* feat: 添加多语言支持的默认助手设置,包括英文、日文、俄文和中文的翻译。

* refactor: 优化智能体加载逻辑,合并本地和远程智能体,并改进错误处理。

* feat: add import functionality for agents and update translations in multiple languages.

* feat: implement agent import functionality with validation and error handling.

* feat(i18n): add import success and error messages for agents in multiple languages.

* fix(i18n): standardize import success message key across multiple languages.

* refactor(i18n): remove default aides section from multiple language files

* refactor(i18n): update Traditional Chinese translations for agent management and privacy settings.

* feat(i18n): add import functionality for agents with URL and file options, including error handling and translations in multiple languages.

* refactor(i18n): rename 'defaultaides' to 'defaultAgent' across multiple language files and update related settings.

* refactor(AgentsPage): remove unused addAgent function from useAgents hook.

---------

Co-authored-by: 上房揭瓦 <hoaobo@foxmail.com>
Co-authored-by: George Zhao <georgezhao@SKJLAB>
Co-authored-by: suyao <sy20010504@gmail.com>
2025-05-04 21:04:04 +08:00
自由的世界人
f258f5b2cb fix: remove redundant local variables (#5654) 2025-05-04 19:56:56 +08:00
beyondkmp
5ceb2af056 chore(dependencies): update electron-builder to 26.0.15 (#5651)
* chore(dependencies): update electron-builder and related dependencies to version 26.0.15

* chore(electron-builder): update portable build configuration and clean up artifact build script

- Set buildUniversalInstaller to false for portable builds in electron-builder configuration.
- Removed obsolete condition for deleting portable files in artifact build completion script.
2025-05-04 18:34:43 +08:00
beyondkmp
0d0aae8863 chore(dependencies): update electron-updater to version 6.6.4 and remove patches (#5650)
chore(dependencies): update electron-updater to version 6.6.4 and remove obsolete patch
2025-05-04 18:33:27 +08:00
SuYao
97a3f89e73 fix(models): normalize model IDs to lowercase for consistency (#5642)
fix(models): normalize model IDs to lowercase for consistent matching in isSupportedThinkingTokenQwenModel function
2025-05-04 16:07:35 +08:00
beyondkmp
9e86cc9bfb refactor(WindowService, ThemeProvider): streamline title bar style and theme toggling logic (#5633)
* fix(config): update title bar overlay colors for better visibility

* fix(Navbar): add Linux support for padding adjustment in Navbar component
2025-05-04 13:19:56 +08:00
Starsky Wong
5985946c2a fix the issue of webdav local backup file storage (#5643)
fix the issue of unlimited webdav local backup file storage
2025-05-04 13:16:06 +08:00
beyondkmp
01ef941e52 refactor(WindowService): improve context menu setup (#5589)
* refactor(WindowService): streamline context menu setup and enhance webview spellcheck functionality

* update format

* delete setSpellCheckerLanguages
2025-05-04 11:04:56 +08:00
SuYao
e43d316f10 fix(OpenAIProvider): enhance token budget calculation in OpenRouter (#5625)
* fix(OpenAIProvider): enhance token budget calculation in OpenRouter

* fix(OpenAIProvider): update budget token calculation and adjust effort ratio for improved reasoning support
2025-05-04 01:04:38 +08:00
MyPrototypeWhat
62bf38b1f3 feat(messageOperations): add editMessageBlocks functionality and update message handling logic (#5641)
feat(messageOperations): add editMessageBlocks functionality and update message handling logic

- Introduced editMessageBlocks to update properties of message blocks.
- Enhanced editMessage to include error handling and logging.
- Updated useTopicMessages to accept topic ID directly.
- Refactored MessageMenubar to utilize editMessageBlocks for editing messages.
- Improved saveMessageAndBlocksToDB for better state management.
2025-05-04 00:57:10 +08:00
MyPrototypeWhat
81a9e6c1fe fix(messageThunk): enhance reset message logic to include model for s… (#5632)
fix(messageThunk): enhance reset message logic to include model for single message resets
2025-05-03 20:39:40 +08:00
SuYao
15b51940c0 fix(i18n): add missing translation for Ru (#5631)
fix(i18n): add missing translation for reasoning effort settings in Russian locale
2025-05-03 17:22:17 +08:00
yushihang
227ea3e39b Remove redundant condition check in MCP service args handling (#5606)
Remove duplicate !args.includes('-y') check
2025-05-02 22:51:40 +08:00
KumaKorin
f7f35f095b fix: resolve scrolling issue on files page (#5618) (#5619) 2025-05-02 22:37:05 +08:00
SuYao
eedbaa965c fix(Anthropic): add base64 file handling to IPC and file management (#5595) 2025-05-02 22:30:34 +08:00
fullex
66abb416df refactor: simplify custom CSS functionality by store sync (#5596)
* feat: implement store synchronization across windows

- Added new IPC channels for store synchronization: StoreSync_Subscribe, StoreSync_Unsubscribe, StoreSync_OnUpdate, and StoreSync_BroadcastSync.
- Integrated store sync service in various components, including the main IPC handler and renderer store.
- Removed the MiniWindowReload IPC channel as it was no longer needed.
- Updated the store configuration to support synchronization of specific state slices.

* refactor: remove custom CSS functionality from IPC and related components

- Removed the App_SetCustomCss channel and associated handlers from the IPC and ConfigManager.
- Updated the renderer components to eliminate references to custom CSS settings.
- Refactored the MiniWindow component to manage custom CSS directly without IPC communication.
2025-05-02 15:19:47 +08:00
one
9d35f1d6ab perf(inputbar): improve long text paste (#5580) 2025-05-02 11:27:16 +08:00
fullex
36e1340e6e feat: implement store synchronization across windows (#5592)
- Added new IPC channels for store synchronization: StoreSync_Subscribe, StoreSync_Unsubscribe, StoreSync_OnUpdate, and StoreSync_BroadcastSync.
- Integrated store sync service in various components, including the main IPC handler and renderer store.
- Removed the MiniWindowReload IPC channel as it was no longer needed.
- Updated the store configuration to support synchronization of specific state slices.
2025-05-02 03:08:26 +08:00
SuYao
ef7abbcb0e Feat/qwen3 support (#5533)
* feat: implement ThinkingPanel for managing reasoning effort and token limits

- Added ThinkingPanel component to handle user settings for reasoning effort and thinking budget.
- Introduced ThinkingSelect and ThinkingSlider components for selecting reasoning effort and adjusting token limits.
- Updated models and hooks to support new reasoning effort and thinking budget features.
- Enhanced Inputbar to integrate ThinkingPanel and provide a toggle for enabling thinking features.
- Updated translations and styles for new components.

* refactor: enhance ThinkingPanel and related components for improved reasoning effort management

- Updated ThinkingPanel to streamline token mapping and error messaging.
- Refactored ThinkingSelect to utilize a list for better UI interaction.
- Enhanced ThinkingSlider with styled components for a more intuitive user experience.
- Adjusted model checks in the configuration to support new reasoning models.
- Improved translations for clarity and consistency across languages.

* feat: close ThinkingPanel on outside click

* feat(icons): add new lightbulb icons and update reasoning effort settings in translations

- Introduced new SVG icons for lightbulb states (off, on at 10%, 50%, and 90%).
- Added "Off" reasoning effort option in English, Japanese, Russian, Simplified Chinese, and Traditional Chinese translations.
- Refactored Inputbar and ThinkingButton components to integrate new reasoning effort logic and icon display.

* feat(thinking): refactor reasoning effort management and introduce new lightbulb icon

- Removed ThinkingPanel, ThinkingSelect, and ThinkingSlider components to streamline reasoning effort management.
- Added new MdiLightbulbAutoOutline icon for the auto reasoning effort option.
- Updated reasoning effort logic in ThinkingButton to support 'auto' and fallback options.
- Enhanced translations for reasoning effort options across multiple languages.
- Adjusted model configurations to integrate new reasoning effort settings.

* refactor(messageThunk): update multi-model dispatch logic for improved assistant handling

- Changed the parameter from baseAssistantId to assistant object for better clarity.
- Updated tasksToQueue structure to include assistant configuration instead of just ID and model.
- Enhanced the creation of assistant messages to utilize the full assistant object.

* chore(messageThunk): remove unused comment for clarity in multi-model response handling

* fix(ThinkingButton): update quick panel title for reasoning effort settings

* fix(GeminiProvider, OpenAIProvider): apply Math.floor to budget calculations for improved accuracy

---------

Co-authored-by: ousugo <dkzyxh@gmail.com>
Co-authored-by: Teo <cheesen.xu@gmail.com>
2025-05-01 22:57:04 +08:00
fullex
7c0086a484 refactor: Seperate MiniWindow loading from MainWindow (#5581) 2025-05-01 21:29:41 +08:00
Chen Tao
49c9e14790 fix: 调整图片高度,修复gemini画图 (#5585) 2025-05-01 21:03:10 +08:00
fullex
3b81f60c93 refactor: remove unused selection window (#5586) 2025-05-01 20:57:31 +08:00
SuYao
68e98f9a56 refactor: enhance image block rendering and styling (#5567)
* refactor: enhance image block rendering and styling

* 更新 index.tsx

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

* refactor: update key usage in MessageBlockRenderer for improved readability

---------

Co-authored-by: Chen Tao <70054568+eeee0717@users.noreply.github.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
2025-05-01 18:22:27 +08:00
MyPrototypeWhat
f4debacbfc fix(mcp-tools): improve error message formatting and response handlin… (#5565)
fix(mcp-tools): improve error message formatting and response handling in upsertMCPToolResponse function
2025-05-01 18:06:53 +08:00
one
3a496a7d1f perf: improve inputbar button memorization 2025-05-01 17:49:51 +08:00
SuYao
a068d36f87 feat(OpenAIProvider): support image edit (#5469)
* feat(OpenAIProvider): support image edit

* fix: can edit image

* feat: add upload situation

* chore: optimize  abort

* fix: image cannot read

* chore(SvgSpinners180Ring): remove unused React import

* refactor(FileManager): simplify file reading logic by storing intermediate data

---------

Co-authored-by: eeee0717 <chentao020717Work@outlook.com>
2025-05-01 12:38:33 +08:00
one
cb9c42b262 fix: reset hasReasoningContent (#5563) 2025-05-01 11:47:53 +08:00
SuYao
18092480f2 fix(messageThunk): ensure usage is only estimated when not already present in response (#5553)
* fix(messageThunk): ensure usage is only estimated when not already present in response

* refactor: clean logic
2025-05-01 11:04:43 +08:00
one
a2c5b8e191 fix: refresh thinking state on streaming (#5557) 2025-05-01 01:08:22 +08:00
nmnmtttt
b6cb567de2 Fix error #5456 (#5543)
* Fix default assistant model settings where temperature value 0 fails to trigger updates #5456

Signed-off-by: LeeSH <shuhao_lin@fzzixun.com>

* fix: remove console.log

Signed-off-by: LeeSH <shuhao_lin@fzzixun.com>

* fix: use simple condition to tell value  (#5456)
*use  value not equal null to  tell false condition
Signed-off-by: LeeSH <shuhao_lin@fzzixun.com>

---------

Signed-off-by: LeeSH <shuhao_lin@fzzixun.com>
Co-authored-by: Lee SH <shuhao_lin@fzzixun.com>
2025-04-30 22:52:18 +08:00
one
32d03eae30 feat: support streaming for model health check (#5546) 2025-04-30 22:25:32 +08:00
one
432c65e5ef fix: prevent overriding block status on aborting (#5547) 2025-04-30 20:15:13 +08:00
beyondkmp
54355db0d3 feat(WindowService): add main window monitor for renderer process events (#5532)
* feat(WindowService): add main window monitor for renderer process events

* fix(WindowService): enhance renderer process crash handling to improve application stability
2025-04-30 18:02:25 +08:00
MyPrototypeWhat
23d086a1d2 Fix/message block structure (#5536)
* refactor:  重构快捷助手

- 移除不必要的 `model` 属性,简化 `MessageContent` 和相关组件的参数传递。
- 更新 `MessageItem` 和 `MessageBlockRenderer` 以提高可读性和性能,确保消息内容的正确渲染。
- 修复 `fetchCitations` 中的潜在错误,确保引用数据的正确处理。
- 清理未使用的代码和注释,提升代码整洁性。

* refactor: 优化消息块处理和错误显示逻辑

- 在 `upgradeToV7` 函数中调整了消息块的创建顺序,以保持与旧版本的一致性。
- 更新了 `ErrorBlock` 组件,增强了错误信息的显示逻辑,支持更详细的 HTTP 错误状态处理。
- 在多个语言文件中添加了暂停占位符文本,提升了用户体验。
- 移除了未使用的代码和注释,提升了代码整洁性。

* refactor(useAssistant): optimize topic handling with useMemo

* feat(locales): add Russian translations for MinApp and MiniWindow components

- Introduced new translations for various UI elements in the MinApp and MiniWindow sections, enhancing the user experience for Russian-speaking users.
- Updated the HomeWindow component to streamline topic handling by directly accessing the default assistant's topics, improving code clarity and performance.

* refactor(message): remove loading state management from newMessage slice and streamline message loading logic

- Removed the loading state update for topics in the `newMessage` slice to simplify state management.
- Updated `loadTopicMessagesThunk` to eliminate unnecessary loading checks, enhancing clarity and performance during message loading.

---------

Co-authored-by: suyao <sy20010504@gmail.com>
2025-04-30 17:58:08 +08:00
Akey Zhang
ecd7518505 style: optimize mcp arg name display layout when tool or prompt descr… (#5467)
* style: optimize mcp arg name display layout when tool or prompt description is short

* style: optimize mcp arg name display layout, make it simple

* fix: remove unused import
2025-04-30 11:22:39 +08:00
MyPrototypeWhat
08ee877676 fix: update webSearch type and results structure in upgrade logic (#5512) 2025-04-30 00:16:58 +08:00
one
6ee8a72823 fix: determining thinking process using block status (#5509)
* fix: determining thinking process using block status

* refactor: merge MessageThought to ThinkingBlock

* style: fix typos

* fix: error handling

* refactor: set collapsed status as default

* refactor: remove pending status

* refactor: better collapsing behaviour

* refactor: remove processing status
2025-04-30 00:04:13 +08:00
SuYao
b0d6f209d7 feat(messageThunk): integrate autoRenameTopic functionality to update topic names based on assistant responses (#5504) 2025-04-29 23:07:09 +08:00
Chen Tao
1c5526c020 feat: optimize extract logic (#5470)
* feat: optimize extract logic

* chore
2025-04-29 20:43:50 +08:00
karl
f29b83faab fix: The Error display of the failed mcp call shows that the Error type cannot be displayed (#5492)
* fix: The Error display of the failed mcp call shows that the Error type cannot be displayed

* Update src/renderer/src/utils/mcp-tools.ts

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
2025-04-29 17:40:50 +08:00
SuYao
57e8b9a592 fix(GeminiProvider): relocate grounding metadata handling (#5490)
* fix(GeminiProvider): relocate grounding metadata handling to improve code clarity and maintainability

* refactor(GeminiProvider): enhance conditional checks for chunk processing
2025-04-29 17:36:35 +08:00
karl
0782b24790 perf: <tool_use> display (#5489) 2025-04-29 16:39:19 +08:00
Teo
0a7bf99f9c refactor: network search module, support quick menu (#5291)
* refactor: Reconstruct the network search module to support quick menu switching between different suppliers.

* refactor(GeminiProvider): simplify web search enablement logic

* refactor(settings): remove unnecessary SettingDivider for cleaner layout

* refactor(SelectModelButton): remove unused setModel function for improved performance

* refactor(ApiService): simplify web search condition by removing redundant check
2025-04-29 16:31:41 +08:00
Chen Tao
7be6ddfb59 fix: message do not use knowledge (#5485) 2025-04-29 16:19:19 +08:00
SuYao
0413884021 refactor(MainTextBlock): enhance content processing by ignoring tooluse (#5483) 2025-04-29 16:16:34 +08:00
MyPrototypeWhat
4225d20760 feat: 添加 messageBlock、messageThunk 和 useMessageOperations 使用指南文档
- 新增 `how-to-use-messageBlock.md`,详细介绍 `messageBlock.ts` 的 Redux Slice 及其状态管理、actions 和 selectors。
- 新增 `how-to-use-messageThunk.md`,概述 `messageThunk.ts` 的核心功能和主要 Thunks 的使用。
- 新增 `how-to-use-useMessageOperations.md`,提供 `useMessageOperations` Hook 的使用示例和功能说明,简化组件与消息数据的交互。
2025-04-29 15:14:55 +08:00
chenxue
efad8f9ad0 feat: add painting aihubmix provider (#4503)
* add painting aihubmix provider

* fix: Cannot read properties of undefined (reading 'unshift')

* fix: painting redux data

---------

Co-authored-by: zhaochenxue <zhaochenxue@bixin.cn>
Co-authored-by: 亢奋猫 <kangfenmao@qq.com>
2025-04-29 14:38:59 +08:00
MyPrototypeWhat
a6822d4037 refactor: message block structure (#4660)
* feat(message-blocks): introduce new message block types and middleware for handling message updates

- Added new types for message blocks including MainText, Thinking, Translation, Code, Image, ToolCall, ToolResult, KnowledgeCitation, WebSearch, File, and Error blocks.
- Implemented middleware to manage message block mapping and updates in Redux, enhancing the handling of message states and blocks.
- Introduced LRU caching for efficient message block retrieval and management.

* feat(messages): implement message management slice and utility functions

- Introduced a new Redux slice for managing messages, including actions for setting the current topic, loading state, error handling, and message updates.
- Added utility functions for creating various message block types, enhancing the structure and management of message content.
- Updated message type definitions to include timestamped block references, improving the tracking of message block states.

* feat(store): add messageBlocks reducer and integrate into rootReducer

- Introduced a new messageBlocks reducer to manage message block state.
- Updated rootReducer to include messageBlocks alongside existing reducers.
- Refactored newMessage slice to accommodate changes in message structure and block handling.
- Removed obsolete messageBlockMap utility file to streamline codebase.

* feat(database): implement version 7 migration and enhance message structure

- Added new message_blocks table to the database schema for improved message handling.
- Introduced upgradeToV7 function to migrate existing topics and messages to the new structure.
- Refactored message creation utilities to support new message block types and improved error handling.
- Implemented various message filtering utilities to streamline message processing.
- Updated Redux store to accommodate new message structure and loading states.

* feat(message): refactor message handling and introduce messageStreamProcessor

- Updated database schema to correct types for topics and message blocks.
- Introduced messageStreamProcessor to handle API responses and transform them into application-specific data structures.
- Refactored messageThunk to streamline message creation and updates, ensuring consistency with the new message structure.
- Enhanced error handling and state management during message processing.

* refactor(message): update message handling with new types and utility functions

- Refactored message operations to utilize new message types and improved utility functions for content retrieval.
- Introduced helper functions for finding message blocks, enhancing the structure and readability of message processing.
- Updated various providers to align with the new message structure, ensuring consistent handling of message content and blocks.
- Enhanced error handling and state management during message processing, improving overall reliability.

* refactor(message): wip create XxxBlock components

- Refactored message handling to utilize new message types, enhancing the overall structure and readability.
- Introduced new message blocks including CitationBlock, CodeBlock, ErrorBlock, FileBlock, ImageBlock, ThinkingBlock, ToolBlock, and TranslationBlock.
- Updated existing components to align with the new message structure, ensuring consistent handling of message content and blocks.
- Improved utility functions for finding and processing message blocks, enhancing the reliability of message operations.

* refactor(message): enhance message operations with new selectors and blocks

- Updated useMessageOperations to integrate new message block types and selectors for improved state management.
- Introduced new selectors for fetching topic messages and loading states, enhancing the efficiency of message retrieval.
- Refactored message handling in Inputbar and MessageContent components to align with the new message structure.
- Added CitationBlock and improved rendering logic for message blocks, ensuring consistent display of message content.
- Enhanced error handling and logging for message operations, improving overall reliability.

* refactor(message): enhance message update logic with block instructions

- Updated the updateMessage reducer to handle block instructions, allowing for more flexible message updates.
- Improved the fetchAndProcessAssistantResponseImpl function to track the last added block, ensuring proper handling of streaming content.
- Refactored stream processing to accommodate new callback signatures and improve error handling.
- Removed unused console logs and cleaned up code for better readability and maintainability.

* merge origin/main

* refactor(message): update message handling and block structure

- Replaced `prepareTopicMessages` with `loadTopicMessagesThunk` for improved message loading logic.
- Updated `MessageTools` to accept `ToolBlock` instead of `Message`, enhancing type safety.
- Introduced `resetMessage` utility to streamline message object creation and reset mutable fields.
- Refactored `findCitationBlocks` and related types for consistency in message block handling.
- Enhanced error handling in message update logic to ensure robustness during database operations.

* refactor(message): update message and block handling for improved clarity and performance

- Refactored message handling by removing unnecessary cloning of messages in `MessageContent`.
- Updated `Inputbar` to import `Message` from the new message types module.
- Simplified block rendering logic in `MessageBlockRenderer` by removing redundant checks.
- Adjusted `StreamProcessingService` to use the updated import path for `GroundingMetadata`.
- Enhanced message status handling in `newMessage` to include 'streaming' status.
- Removed deprecated `createUserMessageThunk` to streamline thunk actions.
- Updated type definitions in `newMessageTypes` for better clarity and consistency.

* refactor(message): enhance message block structure and type handling

- Updated `Markdown` component to utilize `MainTextMessageBlock` for improved type safety.
- Refactored `MessageContent` to remove error handling logic and streamline rendering.
- Adjusted `MessageError` to accept `ErrorMessageBlock` and simplified error display logic.
- Enhanced `MessageTools` to work with `ToolMessageBlock`, improving clarity in tool response handling.
- Updated `MainTextBlock` to pass the correct props to `Markdown`, ensuring consistent rendering.
- Refactored utility functions to align with new message block types, enhancing overall code clarity and maintainability.

* refactor: update message types import paths and enhance message handling logic

- Changed import paths for message types from 'newMessageTypes' to 'newMessage' across multiple files.
- Refactored message handling functions to utilize the new message structure, ensuring compatibility with the updated types.
- Improved logic for finding and processing main text blocks in messages.
- Updated related components and hooks to reflect the new message structure, enhancing overall code maintainability.

* refactor: improve stream processing and message handling logic

- Updated the `createStreamProcessor` function to handle null and undefined chunks more robustly.
- Introduced a helper function `handleBlockTransition` to streamline state transitions between message blocks.
- Enhanced error handling in the `onComplete` function to specifically manage abort errors and create error blocks when necessary.
- Improved final block status updates to ensure accurate tracking of message processing states.

* refactor: use rehype-sanitize for html tags

# Conflicts:
#	src/renderer/src/pages/home/Markdown/Markdown.tsx

* refactor: merge rehype plugins

* refactor(ModelList): extract NameSpan component and adjust styling for better layout

* feat: update Z.ai app configuration with additional styling and increment store version to 97

# Conflicts:
#	src/renderer/src/store/index.ts

* feat(FeatureMenus, Footer): replace Ant Design icons with Lucide icons and enhance layout

- Updated icons in FeatureMenus from Ant Design to Lucide for improved visual consistency.
- Refactored Footer component to use Lucide icons and adjusted layout for better alignment and spacing.
- Enhanced styling of Tag components for a more cohesive design.

* lint: fix code format

* chore(version): 1.2.5

* feat(locales): add locale cleanup functionality to after-pack script

- Introduced a new `remove-locales.js` script to handle the removal of unnecessary locale files based on the platform.
- Integrated the locale cleanup process into the `after-pack.js` script to ensure locales are managed during packaging.

* feat: support escaping the comma character in the API key. (#5088)

feat: support escaping the comma character in the API key.

* feat(Citations): enhance CitationsList with title and info icon, and update styling

* Revert "feat: add chat message translate copy button (#4620)"

This reverts commit 8b462935b4.

# Conflicts:
#	src/renderer/src/hooks/useMessageOperations.ts
#	src/renderer/src/pages/home/Inputbar/Inputbar.tsx

* refactor: update message status handling and improve message creation logic

- Introduced `AssistantMessageStatus` to standardize message statuses across the application.
- Updated various components and services to utilize the new status enum, replacing string literals for better type safety.
- Refactored message creation and processing functions to align with the new status definitions.
- Adjusted filtering logic in `useMessageOperations` to reflect changes in message status handling.
- Cleaned up unused code and comments for improved readability.

* refactor: enhance citation handling and web search integration

- Updated CitationBlock and MainTextBlock components to improve citation processing logic.
- Refactored citation data handling to accommodate new web search sources and formats.
- Introduced new chunk types for better handling of streaming responses, including text and web search results.
- Enhanced the integration of web search results into message blocks, ensuring accurate citation references.
- Updated related types and interfaces to reflect changes in citation and web search structures.

* refactor: improve message handling and citation integration

- Added debug logging to various components for better traceability during message processing.
- Refactored CitationBlock and MainTextBlock to streamline citation handling and improve integration with web search results.
- Updated Inputbar to include debug logs for message sending and dispatching actions.
- Enhanced the message block rendering logic to decouple citation references from main text blocks.
- Improved the handling of citation data and ensured accurate formatting across different sources.

* feat: add regenerate assistant response functionality

- Introduced `regenerateAssistantResponseThunk` to allow regeneration of assistant messages.
- Updated `useMessageOperations` to include the new `regenerateAssistantMessage` function.
- Refactored `MessageMenubar` to utilize the new regeneration feature.
- Adjusted `MessageImage` and `ImageBlock` components to align with updated props.
- Cleaned up unused code and comments across various files for improved readability.

* fix: deepseek-reasoner does not support successive user or assistant messages in MCP scenario (#5112)

* fix: deepseek-reasoner does not support successive user or assistant messages in MCP scenario.

* fix: @ts-ignore

* refactor: remove google analytics

* feat: add PostHogProvider for analytics integration

- Introduced PostHogProvider to manage data collection based on user settings.
- Wrapped the main application in PostHogProvider to enable analytics when data collection is allowed.

* refactor(AxiosProxy): improve proxy handling and initialization logic

- Changed cacheAxios from undefined to null for better initialization.
- Updated proxy handling to use ProxyAgent, ensuring axios instance is recreated when the proxy changes.
- Simplified axios instance creation by directly using the current proxy agent.

* refactor: remove search enhanceMode

* fix: 知识库和网络搜索使用输出语言问题 (#5129)

* feat(proxy): use os-proxy-config to get system proxy info instead of resolveProxy (#5123)

* feat(proxy): integrate os-proxy-config for system proxy management

- Added os-proxy-config dependency to manage system proxy settings.
- Refactored setSystemProxy method to utilize getSystemProxy for improved proxy handling.

* fix lint error

* chore(version): 1.2.6

* fix: zipfile dependencies

* chore(release): update default release tag to v1.0.0 and install setuptools for Mac build

* disable auto update in portable exe

* chore(electron-builder): add StartupWMClass for CherryStudio in liunx desktop configuration (#5158)

chore(electron-builder): add StartupWMClass for CherryStudio in desktop configuration

* fix(MinApp): integrate dynamic background color for MinappPopupContainer (#5142)

* fix(models): 更新OpenRouter模型ID和名称,简化模型组分类 (#5172)

* fix: purify minapp user agent tag (#5173)

* fix: electron-builder 新增配置导致的无法构建的问题  (#5175)

fix: electron-builder 新增配置导致的无法构建的问题

当前 electron-builder 的版本为 "26.0.13",但在 v26 之后,StartupWMClass 等配置标签要在 desktop > entry 下,而不是直接在 desktop 下,否则会导致无法构建打包

* Revert "fix(minapps): remove AI Studio entry from default mini apps list" (#5177)

This reverts commit aed9c04c20.

* refactor(Markdown): remove rehype-sanitize and implement custom element filtering

- Removed rehype-sanitize dependency and its related configuration.
- Introduced ALLOWED_ELEMENTS and DISALLOWED_ELEMENTS for custom HTML element filtering.
- Updated rehypePlugins logic to conditionally apply plugins based on message content.
- Added encodeHTML utility function for HTML entity encoding.

# Conflicts:
#	src/renderer/src/pages/home/Markdown/Markdown.tsx

* refactor: mcp buttons and mcp settings

* refactor: add MessageTranslate.tsx & MessageCitations.tsx

* refactor: add MessageContent.main.tsx { getModelUniqId } from '@renderer/services/ModelService' import { Message, Model } from '@renderer/types' import { getBriefInfo } from '@renderer/utils' import { formatCitations, withMessageThought } from '@renderer/utils/formats' import { encodeHTML } from '@renderer/utils/markdown' import { Flex } from 'antd' import { clone } from 'lodash' import { Search } from 'lucide-react' import React, { Fragment, useMemo } from 'react' import { useTranslation } from 'react-i18next' import BarLoader from 'react-spinners/BarLoader' import styled, { css } from 'styled-components'

* refactor: enhance translation handling by integrating TranslationMessageBlock into Markdown and MessageTranslate components, and streamline TranslationBlock to utilize MessageTranslate for rendering.

* refactor: introduce citation processing optimization checklist and enhance citation handling

- Added a new refactoring checklist for optimizing citation processing logic.
- Implemented a lookup map for citations in MainTextBlock to improve performance.
- Updated Citation interface to include optional content field.
- Modified chunk types for web and knowledge search responses to improve type safety.
- Enhanced citation formatting to include content from web search results.

* refactor: update web search handling in GeminiProvider and OpenAIProvider

* refactor: update prompt handling and citation processing logic

* refactor: standardize chunk type usage across providers and improve image handling in MessageImage component

* refactor: update message operations and API service for improved message handling

- Translated comment in useMessageOperations.ts for clarity.
- Refactored ApiService to utilize findMainTextBlocks for knowledge base checks.
- Enhanced messageThunk with multi-model dispatch logic for better assistant response handling.

* refactor: optimize knowledge base ID handling in ApiService

- Removed unused store import and streamlined knowledge base ID extraction logic.
- Enhanced fetchExternalTool function to utilize mainTextBlocks for knowledge base checks, improving clarity and efficiency.

* feat: add message lifecycle documentation and enhance citation handling in CitationBlock component

* feat: add SearchingSpinner component and enhance message handling with ThinkingMessageBlock

- Introduced a new SearchingSpinner component for better user feedback during processing.
- Updated Markdown and MessageThought components to support ThinkingMessageBlock.
- Enhanced CitationBlock to display the SearchingSpinner when processing citations.
- Refactored message handling to include thinking time metrics across various components and services.

* refactor: enhance ApiService and message handling for improved search functionality

- Streamlined knowledge base ID extraction using flatMap for better performance.
- Added early checks for extractResults in search functions to prevent errors and improve logging.
- Updated chunk types to include SEARCH_IN_PROGRESS_UNION and SEARCH_COMPLETE_UNION for better state management during searches.
- Refactored search execution logic to handle different search scenarios more efficiently.

* refactor: streamline message update logic and enhance block handling

- Simplified message update dispatching by using the store's dispatch directly.
- Improved block transition handling by replacing direct calls with a dedicated function.
- Refactored conditional logic for updating blocks to ensure accurate state management.

* feat: enhance StreamProcessingService with tool call progress handling

- Added onToolCallInProgress callback to StreamProcessorCallbacks for handling tool call progress updates.
- Updated createStreamProcessor function to accept an empty object as default for callbacks.
- Implemented logic to process MCP_TOOL_IN_PROGRESS and MCP_TOOL_COMPLETE chunks in message handling.
- Improved type definitions for MCPToolInProgressChunk to include responses.

* refactor: enhance AI providers with abort controller integration for improved request handling

- Added abort controller functionality to AnthropicProvider, GeminiProvider, and OpenAIProvider to manage request cancellations effectively.
- Updated API calls to include signal for aborting requests, ensuring better control over ongoing operations.
- Improved cleanup logic to handle abort scenarios gracefully.

* feat: add TODO for pause capability in WebSearchService

- Added a TODO comment in WebSearchService to implement pause functionality for network searches, enhancing future service capabilities.

* fix(WebdavBackupManager): update modal confirmation to use window.modal and center content

* refactor: improve message operations and API service for enhanced functionality

- Updated useMessageOperations to dispatch resendMessageThunk with topic ID instead of object for better clarity.
- Refactored ApiService to streamline knowledge base ID extraction using flatMap and added early checks for improved error handling.
- Enhanced message handling in messageThunk by integrating topic queue management and simplifying error handling logic.
- Cleaned up commented-out code for better readability and maintainability.

* fix: enhance message resending functionality and integrate abort signal for web searches

- Updated resendMessageThunk to reset assistant messages without deleting other messages, improving user experience.
- Integrated abort signal handling in WebSearchService and fetch functions to manage request cancellations effectively.
- Refactored fetchWebContents and fetchWebContent to accept an optional abort signal for better control over fetch operations.
- Added resetAssistantMessage utility to streamline the resetting of assistant messages for regeneration.

* fix: enhance chunk handling in AI providers for improved message processing

- Added onChunk calls in AnthropicProvider and GeminiProvider to ensure complete text messages are processed correctly.
- Updated OpenAIProvider to handle finish reasons more accurately, improving message completion handling.
- Removed outdated TODO comment in WebSearchService for better code clarity.

* feat: enhance SearchingSpinner component and add processing text localization

- Updated SearchingSpinner to accept a text prop for dynamic message rendering.
- Added "processing" text localization in English, Japanese, Russian, Chinese (Simplified and Traditional) for improved user experience.
- Integrated updated SearchingSpinner in CitationBlock and MainTextBlock components to display appropriate loading messages.

* fix(WebdavBackupManager): update modal confirmation to use window.modal and center content

fix sse no headers

add eventSourceInit

refactor: switch from @vitejs/plugin-react to @vitejs/plugin-react-swc for improved performance

perf: improve streaming performance (#4986)

feat(ProviderSettings): move model provider to the top when toggled

When the model provider is toggled (OFF to ON), it is moved to the top of the provider setting for convenience. The change is minimal.

fix(settings): handle undefined content limit in BasicSettings component (#5252)

feat: update os-proxy-config to 1.1.2 and delete the patch (#5255)

updte os-proxy-config to 1.1.2 and delete the patch

feat: 添加嵌入维度配置 (#3947)

fix(ci): Remove a deleted step which make the nightly build pipeline fail

These lines were deleted in `release.yml` in commit 75f98608.

build: fix nightly build error

build: remove sentry integration

refactor(GeminiProvider): streamline abort signal handling and improve stream processing #5276

需要处理 GeminiProvider processStream 函数代码

https://github.com/CherryHQ/cherry-studio/pull/5276/files

Update @modelcontextprotocol/sdk to v1.10.2 (#5266)

- Removed MCPStreamableHttpClient.ts as it is now provided by the SDK.
- Adjusted imports in MCPService.ts to use the SDK's implementation.
- Updated yarn.lock to reflect the new SDK version.

feat: add cherry-text-logo.svg and remove npm.svg; update MCPSettings and NpxSearch components

- Introduced a new cherry-text-logo.svg file for branding.
- Removed the deprecated npm.svg file.
- Refactored MCPSettings and NpxSearch components to enhance functionality and UI, including state management and layout adjustments.
- Updated translations in multiple locales to include new types for MCP servers.

style(settings): update border-radius to use CSS variable for consistency

feat(mcp): mcp setting add service description page

chore: update dependencies and clean up code

- Reintroduced @mozilla/readability, @shikijs/markdown-it, and @xyflow/react to package.json.
- Updated shiki version to 3.2.2 in both package.json and yarn.lock.
- Removed trailing whitespace in IpcChannel.ts and index.ts for code cleanliness.
- Added outline style to .ant-tabs-tab-btn in ant.scss for improved UI consistency.

feat(WindowService): add maximize functionality and disable electron-window-state maxmize (#5292)

* feat(WindowService): add maximize functionality and clean up window close logic

- Introduced a new `maximize` option in the window state configuration.
- Added `setupMaximize` method to handle window maximization based on the launch state.
- Removed redundant logic from the window close event handler for clarity.

* add code

* update code

Create pull_request_template.md

feat: enhance MinAppIcon component with sidebar prop

- Added optional sidebar prop to MinAppIcon for conditional styling.
- Updated Sidebar component to pass sidebar prop to MinAppIcon for consistent appearance in sidebar context.

refactor(MessageAttachments): move styled component definition inside the component for better encapsulation

feat: support portable config dir (#5039)

* feat: support portable config dir

* fix: remove redundant mkdir

feat(image): support grok-2-image image and gpt-4o-image (#4767)

* feat(image): support grok image

* feat: add gpt-4o-image

* feat: 添加 gpt-image-1 到生成图像模型列表

* refactor(GeminiProvider): remove redundant onChunk call in processStream function

* refactor(OpenAIProvider): update image generation response format and improve prompt handling

* feat(AiProvider): implement thought processing for incremental reasoning and update MessageContent component

* refactor(useMessageOperations): update topic handling in resendUserMessageWithEditThunk and improve MessageMenubar component structure

* refactor(Messages): streamline message rendering and update type imports for better clarity

* refactor(Messages):  improve message block rendering logic

* refactor(Messages): enhance thinking time calculations

* refactor(Messages): enhance message usage estimation logic

* refactor(OpenAIProvider): get image generation usage

* refactor(Messages): optimize citation handling and improve main text block rendering

* refactor(Messages): improve clipboard copy functionality and streamline API response handling

* refactor(Messages): update message state management and improve selector usage for topic messages

* refactor(upgrades): update citation data structure in upgradeToV7 function and remove unused comments in messageBlock.ts

* feat(OpenAIProvider): enhance link conversion for web search results and refactor related logic in ApiService and messageBlock

* feat(translation): implement streaming translation updates and refactor translation handling

- Added a new `getTranslationUpdater` function to manage streaming translation updates.
- Refactored `translate` methods across various providers to support incremental updates.
- Updated `fetchTranslate` to accept content directly instead of a message object.
- Removed the `MessageContent.main.tsx` file as part of the cleanup.
- Enhanced error handling and logging during translation processes.

* feat(message-operations): add appendAssistantResponse functionality and enhance message operations

- Introduced `appendAssistantResponse` to allow appending new assistant responses using a specified model.
- Updated `useMessageOperations` hook to include the new function and improved documentation for existing methods.
- Refactored `MessageMenubar` to utilize the new `appendAssistantResponse` function for message handling.
- Enhanced error handling and logging in message-related thunks for better debugging and state management.

* feat(message): refactor message handling and enhance file block integration

- Updated `message_blocks` schema to include `file.id` for better file association.
- Refactored `FilesPage` to improve file deletion logic, ensuring related message blocks are updated or deleted accordingly.
- Enhanced `Inputbar` and `MessageAttachments` components to utilize new message structure and improve file handling.
- Removed deprecated `MessageCitations` component to streamline message management.
- Updated various components to use the new `MessageInputBaseParams` type for consistency across message operations.

* refactor(tests): clean up and organize formats test suite

- Removed commented-out code and unnecessary imports to enhance readability.
- Organized test cases for `escapeDollarNumber`, `escapeBrackets`, `extractTitle`, and `removeSvgEmptyLines` for better structure.
- Maintained existing test functionality while improving overall code clarity.

* refactor(tests): comment out unused tests in formats test suite

- Commented out the `withGeminiGrounding` test suite to improve clarity and focus on active tests.
- Removed unnecessary imports and organized the test structure for better readability.
- Maintained existing functionality while enhancing overall code organization.

* refactor(components): remove role prop from Markdown component in MessageThought and MessageTranslate

- Removed the `role` prop from the `Markdown` component in both `MessageThought` and `MessageTranslate` for consistency and to simplify the component usage.
- Updated import statements in `export.ts` to use type imports for `Message` and `Topic`, enhancing type safety.
- Commented out unused mock dependencies in the formats test suite to improve clarity and focus on active tests.

* refactor(messages): update message selection and handling for improved consistency

- Replaced legacy message selectors with new message handling methods in `ChatFlowHistory`, `ChatNavigation`, and `MessageAnchorLine` components.
- Utilized `getMainTextContent` utility for consistent message content retrieval across components.
- Updated state management in `messageThunk` to set the current topic ID correctly.
- Enhanced markdown export functions to utilize new message structure for better content handling.

* fix(databases): correct syntax in message_blocks schema for proper key separation

- Updated the `message_blocks` schema to include a comma separator between `messageId` and `file.id` for accurate primary key definition.
- Ensured consistency in database schema definitions to prevent potential issues during data retrieval.

* refactor(messages): enhance loading state handling and improve message block rendering

- Introduced a new `LoadingBlock` component to manage loading states for different message block types using `BeatLoader`.
- Updated `MessageContent` to display loading indicators when messages are pending.
- Cleaned up commented-out code and improved the structure of message block rendering logic.
- Adjusted `throttledBlockUpdate` and `throttledBlockDbUpdate` to prevent unnecessary updates when block statuses are already successful.
- Added error handling improvements in `fetchExternalTool` and `fetchAndProcessAssistantResponseImpl` for better robustness.

* refactor(messages): improve loading state handling in message block rendering

- Integrated `MessageBlockStatus` for better management of message block statuses.
- Added `LoadingBlock` component to handle loading states during message processing.
- Updated `fetchAndProcessAssistantResponseImpl` to set the status of tool blocks to `PROCESSING` for improved state tracking.
- Cleaned up commented-out code to enhance readability and maintainability of the rendering logic.

* refactor(messages): streamline message handling for clearing user messages

* feat(message-operations): add createTopicBranch functionality to clone messages to a new topic

- Implemented `createTopicBranch` in `useMessageOperations` to facilitate cloning messages from a source topic to a new topic.
- Introduced `cloneMessagesToNewTopicThunk` for handling the cloning process, including unique ID generation and database updates.
- Updated `Messages` component to utilize the new cloning functionality, ensuring proper topic management and error handling.
- Cleaned up unused imports and commented-out code in `MessageMenubar` for improved readability.

* fix(Messages): remove unused message operations in Messages component

- Removed `createNewContext` from the destructured message operations in the `Messages` component to clean up unused functionality.
- Added `getUserMessage` import to enhance message handling capabilities.

* 优化格式化和测试:重构消息处理和格式化功能

- 在 `formats.ts` 中移除未使用的 `withGeminiGrounding` 函数,并更新相关类型导入。
- 在测试文件中添加了对 `withGenerateImage` 和 `addImageFileToContents` 函数的测试,确保它们正确处理消息块和图像元数据。
- 通过创建辅助函数来简化测试数据的生成,提高测试的可读性和一致性。
- 清理了测试中的注释代码,提升了代码的整洁性。

* 优化消息处理和类型导入:更新消息相关组件以使用新消息类型

- 在多个组件中更新消息导入,确保使用 `newMessage` 类型以提高类型安全性。
- 移除未使用的 `CodeBlock` 组件,简化代码结构。
- 在 `SearchResults` 组件中引入 `getMainTextContent` 函数,以改进消息内容处理。
- 清理 `Suggestions` 组件中的冗余代码,提升可读性。
- 更新 `Message` 组件以支持新的消息处理逻辑,确保与助手消息状态的兼容性。

* feat(PlaceholderBlock): introduce PlaceholderBlock and Spinner component for loading states

- Added a new `Spinner` component to provide a visual loading indicator using `BarLoader` and `Search` icon.
- Replaced the deprecated `SearchingSpinner` with the new `Spinner` component in `CitationBlock` and `PlaceholderBlock` for improved consistency in loading states.
- Removed the unused `LoadingBlock` component to streamline the codebase.
- Updated `MessageContent` to enhance rendering logic by removing commented-out code and improving readability.

* feat:upgradeToV7 del catch

* fix:mini/message lint error

* feat(CitationsList): refactor citations rendering with Collapse component

- Replaced the direct rendering of citations with a Collapse component for better UI organization.
- Utilized useMemo for optimized rendering of citation items.
- Updated styling in CitationsContainer for improved layout.
- Enhanced PlaceholderBlock to use BeatLoader for loading state indication.

* fix(messageThunk): improve logging and cloneMessagesToNewTopicThunk functionality

- Updated debug logging to provide clearer information about topic retrieval and cloning process.
- Enhanced the cloning logic to correctly map askId for assistant messages, ensuring proper linkage in the new topic.
- Added checks to ensure file modifications only occur if the file exists, improving robustness.
- Cleaned up comments and improved readability in the cloneMessagesToNewTopicThunk function.

* fix(GeminiProvider): enhance image generation logic and response configuration (#5447)

* feat(GeminiProvider): enhance image generation logic and response configuration

* refactor(GeminiProvider): improve image generation logic readability

* feat(Message): enhance message handling and introduce MessageContent component

- Updated createAssistantMessage to remove unnecessary 'blocks' property from overrides.
- Refactored MessageItem to manage MainTextMessageBlock state and improve message processing logic.
- Added new MessageContent component for rendering message content with mentions and Markdown support.

---------

Co-authored-by: lizhixuan <zhixuan.li@banosuperapp.com>
Co-authored-by: one <wangan.cs@gmail.com>
Co-authored-by: ousugo <dkzyxh@gmail.com>
Co-authored-by: kangfenmao <kangfenmao@qq.com>
Co-authored-by: chenxi <16267732+chenxi-null@users.noreply.github.com>
Co-authored-by: suyao <sy20010504@gmail.com>
Co-authored-by: beyondkmp <beyondkmp@gmail.com>
Co-authored-by: Chen Tao <70054568+eeee0717@users.noreply.github.com>
Co-authored-by: Asurada <43401755+ousugo@users.noreply.github.com>
Co-authored-by: Roland <shlroland1995@gmail.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
Co-authored-by: tchigher <34847046+tchigher@users.noreply.github.com>
2025-04-29 14:12:07 +08:00
Teo
8423fb5610 refactor(TopicsTab): Use onContextMenu instead of onMouseEnter (#5459)
refactor(TopicsTab): change mouse event from onMouseEnter to onContextMenu for setting target topic
2025-04-29 10:14:19 +08:00
kangfenmao
350c2735e4 refactor(Sidebar, McpSettingsNavbar): update icons and improve layout for better UI consistency 2025-04-29 09:35:02 +08:00
478 changed files with 40594 additions and 15409 deletions

86
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,86 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 7
target-branch: "main"
commit-message:
prefix: "chore"
include: "scope"
groups:
# 核心框架
core-framework:
patterns:
- "react"
- "react-dom"
- "electron"
- "typescript"
- "@types/react*"
- "@types/node"
update-types:
- "minor"
- "patch"
# Electron 生态和构建工具
electron-build:
patterns:
- "electron-*"
- "@electron*"
- "vite"
- "@vitejs/*"
- "dotenv-cli"
- "rollup-plugin-*"
- "@swc/*"
update-types:
- "minor"
- "patch"
# 测试工具
testing-tools:
patterns:
- "vitest"
- "@vitest/*"
- "playwright"
- "@playwright/*"
- "eslint*"
- "@eslint*"
- "prettier"
- "husky"
- "lint-staged"
update-types:
- "minor"
- "patch"
# CherryStudio 自定义包
cherrystudio-packages:
patterns:
- "@cherrystudio/*"
update-types:
- "minor"
- "patch"
# 兜底其他 dependencies
other-dependencies:
dependency-type: "production"
# 兜底其他 devDependencies
other-dev-dependencies:
dependency-type: "development"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 3
commit-message:
prefix: "ci"
include: "scope"
groups:
github-actions:
patterns:
- "*"
update-types:
- "minor"
- "patch"

View File

@@ -54,5 +54,5 @@ jobs:
days-before-pr-close: -1 # Completely disable closing for PRs
# Temporary to reduce the huge issues number
operations-per-run: 100
operations-per-run: 1000
debug-only: false

View File

@@ -52,6 +52,8 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v4
with:
ref: develop
- name: Install Node.js
uses: actions/setup-node@v4

View File

@@ -5,6 +5,7 @@ on:
pull_request:
branches:
- main
- develop
jobs:
build:

View File

@@ -26,6 +26,8 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v4
with:
ref: main
- name: Get release tag
id: get-tag

1
.gitignore vendored
View File

@@ -51,3 +51,4 @@ local
coverage
.vitest-cache
vitest.config.*.timestamp-*
YOUR_MEMORY_FILE_PATH

View File

@@ -1,92 +0,0 @@
diff --git a/out/electron/ElectronFramework.js b/out/electron/ElectronFramework.js
index 5a4b4546870ee9e770d5a50d79790d39baabd268..3f0ac05dfd6bbaeaf5f834341a823718bd10f55c 100644
--- a/out/electron/ElectronFramework.js
+++ b/out/electron/ElectronFramework.js
@@ -55,26 +55,27 @@ async function removeUnusedLanguagesIfNeeded(options) {
if (!wantedLanguages.length) {
return;
}
- const { dir, langFileExt } = getLocalesConfig(options);
+ const { dirs, langFileExt } = getLocalesConfig(options);
// noinspection SpellCheckingInspection
- await (0, tiny_async_pool_1.default)(builder_util_1.MAX_FILE_REQUESTS, await (0, fs_extra_1.readdir)(dir), async (file) => {
- if (!file.endsWith(langFileExt)) {
+ const deletedFiles = async (dir) => {
+ await (0, tiny_async_pool_1.default)(builder_util_1.MAX_FILE_REQUESTS, await (0, fs_extra_1.readdir)(dir), async (file) => {
+ if (!file.endsWith(langFileExt)) {
+ return;
+ }
+ const language = file.substring(0, file.length - langFileExt.length);
+ if (!wantedLanguages.includes(language)) {
+ return fs.rm(path.join(dir, file), { recursive: true, force: true });
+ }
return;
- }
- const language = file.substring(0, file.length - langFileExt.length);
- if (!wantedLanguages.includes(language)) {
- return fs.rm(path.join(dir, file), { recursive: true, force: true });
- }
- return;
- });
+ });
+ };
+ await Promise.all(dirs.map(deletedFiles));
function getLocalesConfig(options) {
const { appOutDir, packager } = options;
if (packager.platform === index_1.Platform.MAC) {
- return { dir: packager.getResourcesDir(appOutDir), langFileExt: ".lproj" };
- }
- else {
- return { dir: path.join(packager.getResourcesDir(appOutDir), "..", "locales"), langFileExt: ".pak" };
+ return { dirs: [packager.getResourcesDir(appOutDir), packager.getMacOsElectronFrameworkResourcesDir(appOutDir)], langFileExt: ".lproj" };
}
+ return { dirs: [path.join(packager.getResourcesDir(appOutDir), "..", "locales")], langFileExt: ".pak" };
}
}
class ElectronFramework {
diff --git a/out/node-module-collector/index.d.ts b/out/node-module-collector/index.d.ts
index 8e808be0fa0d5971b9f9605c8eb88f71630e34b7..1b97dccd8a150a67c4312d2ba4757960e624045b 100644
--- a/out/node-module-collector/index.d.ts
+++ b/out/node-module-collector/index.d.ts
@@ -2,6 +2,6 @@ import { NpmNodeModulesCollector } from "./npmNodeModulesCollector";
import { PnpmNodeModulesCollector } from "./pnpmNodeModulesCollector";
import { detect, PM, getPackageManagerVersion } from "./packageManager";
import { NodeModuleInfo } from "./types";
-export declare function getCollectorByPackageManager(rootDir: string): Promise<NpmNodeModulesCollector | PnpmNodeModulesCollector>;
+export declare function getCollectorByPackageManager(rootDir: string): Promise<PnpmNodeModulesCollector | NpmNodeModulesCollector>;
export declare function getNodeModules(rootDir: string): Promise<NodeModuleInfo[]>;
export { detect, getPackageManagerVersion, PM };
diff --git a/out/platformPackager.d.ts b/out/platformPackager.d.ts
index 2df1ba2725c54c7b0e8fed67ab52e94f0cdb17bc..c7ff756564cfd216d2c7d8f72f367527010c06f9 100644
--- a/out/platformPackager.d.ts
+++ b/out/platformPackager.d.ts
@@ -67,6 +67,7 @@ export declare abstract class PlatformPackager<DC extends PlatformSpecificBuildO
getElectronSrcDir(dist: string): string;
getElectronDestinationDir(appOutDir: string): string;
getResourcesDir(appOutDir: string): string;
+ getMacOsElectronFrameworkResourcesDir(appOutDir: string): string;
getMacOsResourcesDir(appOutDir: string): string;
private checkFileInPackage;
private sanityCheckPackage;
diff --git a/out/platformPackager.js b/out/platformPackager.js
index 6f799ce0d1cdb5f0b18a9c8187b2db84b3567aa9..879248e6c6786d3473e1a80e3930d3a8d0190aab 100644
--- a/out/platformPackager.js
+++ b/out/platformPackager.js
@@ -465,12 +465,13 @@ class PlatformPackager {
if (this.platform === index_1.Platform.MAC) {
return this.getMacOsResourcesDir(appOutDir);
}
- else if ((0, Framework_1.isElectronBased)(this.info.framework)) {
+ if ((0, Framework_1.isElectronBased)(this.info.framework)) {
return path.join(appOutDir, "resources");
}
- else {
- return appOutDir;
- }
+ return appOutDir;
+ }
+ getMacOsElectronFrameworkResourcesDir(appOutDir) {
+ return path.join(appOutDir, `${this.appInfo.productFilename}.app`, "Contents", "Frameworks", "Electron Framework.framework", "Resources");
}
getMacOsResourcesDir(appOutDir) {
return path.join(appOutDir, `${this.appInfo.productFilename}.app`, "Contents", "Resources");

View File

@@ -0,0 +1,159 @@
diff --git a/out/macPackager.js b/out/macPackager.js
index 852f6c4d16f86a7bb8a78bf1ed5a14647a279aa1..60e7f5f16a844541eb1909b215fcda1811e924b8 100644
--- a/out/macPackager.js
+++ b/out/macPackager.js
@@ -423,7 +423,7 @@ class MacPackager extends platformPackager_1.PlatformPackager {
}
appPlist.CFBundleName = appInfo.productName;
appPlist.CFBundleDisplayName = appInfo.productName;
- const minimumSystemVersion = this.platformSpecificBuildOptions.minimumSystemVersion;
+ const minimumSystemVersion = this.platformSpecificBuildOptions.LSMinimumSystemVersion;
if (minimumSystemVersion != null) {
appPlist.LSMinimumSystemVersion = minimumSystemVersion;
}
diff --git a/out/publish/updateInfoBuilder.js b/out/publish/updateInfoBuilder.js
index 7924c5b47d01f8dfccccb8f46658015fa66da1f7..1a1588923c3939ae1297b87931ba83f0ebc052d8 100644
--- a/out/publish/updateInfoBuilder.js
+++ b/out/publish/updateInfoBuilder.js
@@ -133,6 +133,7 @@ async function createUpdateInfo(version, event, releaseInfo) {
const customUpdateInfo = event.updateInfo;
const url = path.basename(event.file);
const sha512 = (customUpdateInfo == null ? null : customUpdateInfo.sha512) || (await (0, hash_1.hashFile)(event.file));
+ const minimumSystemVersion = customUpdateInfo == null ? null : customUpdateInfo.minimumSystemVersion;
const files = [{ url, sha512 }];
const result = {
// @ts-ignore
@@ -143,9 +144,13 @@ async function createUpdateInfo(version, event, releaseInfo) {
path: url /* backward compatibility, electron-updater 1.x - electron-updater 2.15.0 */,
// @ts-ignore
sha512 /* backward compatibility, electron-updater 1.x - electron-updater 2.15.0 */,
+ minimumSystemVersion,
...releaseInfo,
};
if (customUpdateInfo != null) {
+ if (customUpdateInfo.minimumSystemVersion) {
+ delete customUpdateInfo.minimumSystemVersion;
+ }
// file info or nsis web installer packages info
Object.assign("sha512" in customUpdateInfo ? files[0] : result, customUpdateInfo);
}
diff --git a/out/targets/ArchiveTarget.js b/out/targets/ArchiveTarget.js
index e1f52a5fa86fff6643b2e57eaf2af318d541f865..47cc347f154a24b365e70ae5e1f6d309f3582ed0 100644
--- a/out/targets/ArchiveTarget.js
+++ b/out/targets/ArchiveTarget.js
@@ -69,6 +69,9 @@ class ArchiveTarget extends core_1.Target {
}
}
}
+ if (updateInfo != null && this.packager.platformSpecificBuildOptions.minimumSystemVersion) {
+ updateInfo.minimumSystemVersion = this.packager.platformSpecificBuildOptions.minimumSystemVersion;
+ }
await packager.info.emitArtifactBuildCompleted({
updateInfo,
file: artifactPath,
diff --git a/out/targets/nsis/NsisTarget.js b/out/targets/nsis/NsisTarget.js
index e8bd7bb46c8a54b3f55cf3a853ef924195271e01..f956e9f3fe9eb903c78aef3502553b01de4b89b1 100644
--- a/out/targets/nsis/NsisTarget.js
+++ b/out/targets/nsis/NsisTarget.js
@@ -305,6 +305,9 @@ class NsisTarget extends core_1.Target {
if (updateInfo != null && isPerMachine && (oneClick || options.packElevateHelper)) {
updateInfo.isAdminRightsRequired = true;
}
+ if (updateInfo != null && this.packager.platformSpecificBuildOptions.minimumSystemVersion) {
+ updateInfo.minimumSystemVersion = this.packager.platformSpecificBuildOptions.minimumSystemVersion;
+ }
await packager.info.emitArtifactBuildCompleted({
file: installerPath,
updateInfo,
diff --git a/scheme.json b/scheme.json
index 433e2efc9cef156ff5444f0c4520362ed2ef9ea7..a89c7a9b0b608fef67902c49106a43ebd0fa8b61 100644
--- a/scheme.json
+++ b/scheme.json
@@ -1975,6 +1975,13 @@
],
"description": "The mime types in addition to specified in the file associations. Use it if you don't want to register a new mime type, but reuse existing."
},
+ "minimumSystemVersion": {
+ "description": "The minimum os kernel version required to install the application.",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"packageCategory": {
"description": "backward compatibility + to allow specify fpm-only category for all possible fpm targets in one place",
"type": [
@@ -2327,6 +2334,13 @@
"MacConfiguration": {
"additionalProperties": false,
"properties": {
+ "LSMinimumSystemVersion": {
+ "description": "The minimum version of macOS required for the app to run. Corresponds to `LSMinimumSystemVersion`.",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"additionalArguments": {
"anyOf": [
{
@@ -2737,7 +2751,7 @@
"type": "boolean"
},
"minimumSystemVersion": {
- "description": "The minimum version of macOS required for the app to run. Corresponds to `LSMinimumSystemVersion`.",
+ "description": "The minimum os kernel version required to install the application.",
"type": [
"null",
"string"
@@ -2959,6 +2973,13 @@
"MasConfiguration": {
"additionalProperties": false,
"properties": {
+ "LSMinimumSystemVersion": {
+ "description": "The minimum version of macOS required for the app to run. Corresponds to `LSMinimumSystemVersion`.",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"additionalArguments": {
"anyOf": [
{
@@ -3369,7 +3390,7 @@
"type": "boolean"
},
"minimumSystemVersion": {
- "description": "The minimum version of macOS required for the app to run. Corresponds to `LSMinimumSystemVersion`.",
+ "description": "The minimum os kernel version required to install the application.",
"type": [
"null",
"string"
@@ -6507,6 +6528,13 @@
"string"
]
},
+ "minimumSystemVersion": {
+ "description": "The minimum os kernel version required to install the application.",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"protocols": {
"anyOf": [
{
@@ -7376,6 +7404,13 @@
],
"description": "MAS (Mac Application Store) development options (`mas-dev` target)."
},
+ "minimumSystemVersion": {
+ "description": "The minimum os kernel version required to install the application.",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
"msi": {
"anyOf": [
{

View File

@@ -1,51 +0,0 @@
diff --git a/out/MacUpdater.js b/out/MacUpdater.js
index 8f18dc5416c91835ded4e47f2358fba680c129ac..a3fb43c2450dc3484bf099b5ea79a362a3b372cc 100644
--- a/out/MacUpdater.js
+++ b/out/MacUpdater.js
@@ -74,7 +74,7 @@ class MacUpdater extends AppUpdater_1.AppUpdater {
else {
files = files.filter(file => !isArm64(file));
}
- const zipFileInfo = (0, Provider_1.findFile)(files, "zip", ["pkg", "dmg"]);
+ const zipFileInfo = (0, Provider_1.findFile)(files, "zip", ["pkg", "dmg"], false /*has been filtered by myself*/);
if (zipFileInfo == null) {
throw (0, builder_util_runtime_1.newError)(`ZIP file not provided: ${(0, builder_util_runtime_1.safeStringifyJson)(files)}`, "ERR_UPDATER_ZIP_FILE_NOT_FOUND");
}
diff --git a/out/providers/Provider.js b/out/providers/Provider.js
index 9829dff7e95aa9baa0bfdf29f52e6f761c9b7243..6ecaade9e294c87c03bb42e77ff5463f2782cb3c 100644
--- a/out/providers/Provider.js
+++ b/out/providers/Provider.js
@@ -61,11 +61,18 @@ class Provider {
}
}
exports.Provider = Provider;
-function findFile(files, extension, not) {
+function findFile(files, extension, not, filterByArch = true) {
if (files.length === 0) {
throw (0, builder_util_runtime_1.newError)("No files provided", "ERR_UPDATER_NO_FILES_PROVIDED");
}
- const result = files.find(it => it.url.pathname.toLowerCase().endsWith(`.${extension.toLowerCase()}`));
+ const result = files
+ .filter(file => {
+ if (!filterByArch) {
+ return true;
+ }
+ return (process.arch == "arm64") === (file.url.pathname.includes("arm64") || file.info.url.includes("arm64"));
+ })
+ .find(it => it.url.pathname.toLowerCase().endsWith(`.${extension.toLowerCase()}`));
if (result != null) {
return result;
}
diff --git a/out/differentialDownloader/multipleRangeDownloader.js b/out/differentialDownloader/multipleRangeDownloader.js
index bf7d3a2982c62b94054fed4ef60455b20b26d622..3a924eddc946ec446654a112a33be4e2cea311d2 100644
--- a/out/differentialDownloader/multipleRangeDownloader.js
+++ b/out/differentialDownloader/multipleRangeDownloader.js
@@ -75,7 +75,7 @@ function doExecuteTasks(differentialDownloader, options, out, resolve, reject) {
return;
}
const contentType = (0, builder_util_runtime_1.safeGetHeader)(response, "content-type");
- const m = /^multipart\/.+?(?:; boundary=(?:(?:"(.+)")|(?:([^\s]+))))$/i.exec(contentType);
+ const m = /^multipart\/.+?\s*;\s*boundary=(?:"([^"]+)"|([^\s";]+))\s*$/i.exec(contentType);
if (m == null) {
reject(new Error(`Content-Type "multipart/byteranges" is expected, but got "${contentType}"`));
return;

View File

@@ -1,39 +0,0 @@
diff --git a/core.js b/core.js
index ebb071d31cd5a14792b62814df072c5971e83300..31e1062d4a7f2422ffec79cf96a35dbb69fe89cb 100644
--- a/core.js
+++ b/core.js
@@ -157,7 +157,7 @@ class APIClient {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': this.getUserAgent(),
- ...getPlatformHeaders(),
+ // ...getPlatformHeaders(),
...this.authHeaders(opts),
};
}
diff --git a/core.mjs b/core.mjs
index 9c1a0264dcd73a85de1cf81df4efab9ce9ee2ab7..33f9f1f237f2eb2667a05dae1a7e3dc916f6bfff 100644
--- a/core.mjs
+++ b/core.mjs
@@ -150,7 +150,7 @@ export class APIClient {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': this.getUserAgent(),
- ...getPlatformHeaders(),
+ // ...getPlatformHeaders(),
...this.authHeaders(opts),
};
}
diff --git a/error.mjs b/error.mjs
index 7d19f5578040afa004bc887aab1725e8703d2bac..59ec725b6142299a62798ac4bdedb63ba7d9932c 100644
--- a/error.mjs
+++ b/error.mjs
@@ -36,7 +36,7 @@ export class APIError extends OpenAIError {
if (!status || !headers) {
return new APIConnectionError({ message, cause: castToError(errorResponse) });
}
- const error = errorResponse?.['error'];
+ const error = errorResponse?.['error'] || errorResponse;
if (status === 400) {
return new BadRequestError(status, error, message, headers);
}

View File

@@ -0,0 +1,85 @@
diff --git a/core.js b/core.js
index 862d66101f441fb4f47dfc8cff5e2d39e1f5a11e..6464bebbf696c39d35f0368f061ea4236225c162 100644
--- a/core.js
+++ b/core.js
@@ -159,7 +159,7 @@ class APIClient {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': this.getUserAgent(),
- ...getPlatformHeaders(),
+ // ...getPlatformHeaders(),
...this.authHeaders(opts),
};
}
diff --git a/core.mjs b/core.mjs
index 05dbc6cfde51589a2b100d4e4b5b3c1a33b32b89..789fbb4985eb952a0349b779fa83b1a068af6e7e 100644
--- a/core.mjs
+++ b/core.mjs
@@ -152,7 +152,7 @@ export class APIClient {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': this.getUserAgent(),
- ...getPlatformHeaders(),
+ // ...getPlatformHeaders(),
...this.authHeaders(opts),
};
}
diff --git a/error.mjs b/error.mjs
index 7d19f5578040afa004bc887aab1725e8703d2bac..59ec725b6142299a62798ac4bdedb63ba7d9932c 100644
--- a/error.mjs
+++ b/error.mjs
@@ -36,7 +36,7 @@ export class APIError extends OpenAIError {
if (!status || !headers) {
return new APIConnectionError({ message, cause: castToError(errorResponse) });
}
- const error = errorResponse?.['error'];
+ const error = errorResponse?.['error'] || errorResponse;
if (status === 400) {
return new BadRequestError(status, error, message, headers);
}
diff --git a/resources/embeddings.js b/resources/embeddings.js
index aae578404cb2d09a39ac33fc416f1c215c45eecd..25c54b05bdae64d5c3b36fbb30dc7c8221b14034 100644
--- a/resources/embeddings.js
+++ b/resources/embeddings.js
@@ -36,6 +36,9 @@ class Embeddings extends resource_1.APIResource {
// No encoding_format specified, defaulting to base64 for performance reasons
// See https://github.com/openai/openai-node/pull/1312
let encoding_format = hasUserProvidedEncodingFormat ? body.encoding_format : 'base64';
+ if (body.model.includes('jina')) {
+ encoding_format = undefined;
+ }
if (hasUserProvidedEncodingFormat) {
Core.debug('Request', 'User defined encoding_format:', body.encoding_format);
}
@@ -47,7 +50,7 @@ class Embeddings extends resource_1.APIResource {
...options,
});
// if the user specified an encoding_format, return the response as-is
- if (hasUserProvidedEncodingFormat) {
+ if (hasUserProvidedEncodingFormat || body.model.includes('jina')) {
return response;
}
// in this stage, we are sure the user did not specify an encoding_format
diff --git a/resources/embeddings.mjs b/resources/embeddings.mjs
index 0df3c6cc79a520e54acb4c2b5f77c43b774035ff..aa488b8a11b2c413c0a663d9a6059d286d7b5faf 100644
--- a/resources/embeddings.mjs
+++ b/resources/embeddings.mjs
@@ -10,6 +10,9 @@ export class Embeddings extends APIResource {
// No encoding_format specified, defaulting to base64 for performance reasons
// See https://github.com/openai/openai-node/pull/1312
let encoding_format = hasUserProvidedEncodingFormat ? body.encoding_format : 'base64';
+ if (body.model.includes('jina')) {
+ encoding_format = undefined;
+ }
if (hasUserProvidedEncodingFormat) {
Core.debug('Request', 'User defined encoding_format:', body.encoding_format);
}
@@ -21,7 +24,7 @@ export class Embeddings extends APIResource {
...options,
});
// if the user specified an encoding_format, return the response as-is
- if (hasUserProvidedEncodingFormat) {
+ if (hasUserProvidedEncodingFormat || body.model.includes('jina')) {
return response;
}
// in this stage, we are sure the user did not specify an encoding_format

File diff suppressed because one or more lines are too long

948
.yarn/releases/yarn-4.9.1.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@@ -4,4 +4,4 @@ httpTimeout: 300000
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.6.0.cjs
yarnPath: .yarn/releases/yarn-4.9.1.cjs

View File

@@ -19,13 +19,15 @@ Cherry Studio is a desktop client that supports for multiple LLM providers, avai
# 📖 Guide
https://docs.cherry-ai.com
<https://docs.cherry-ai.com>
# 🌠 Screenshot
![](https://github.com/user-attachments/assets/082efa42-c4df-4863-a9cb-80435cecce0f)
![](https://github.com/user-attachments/assets/f8411a65-c51f-47d3-9273-62ae384cc6f1)
![](https://github.com/user-attachments/assets/0d235b3e-65ae-45ab-987f-8dbe003c52be)
![](https://github.com/user-attachments/assets/36dddb2c-e0fb-4a5f-9411-91447bab6e18)
![](https://github.com/user-attachments/assets/f549e8a0-2385-40b4-b52b-2039e39f2930)
![](https://github.com/user-attachments/assets/58e0237c-4d36-40de-b428-53051d982026)
# 🌟 Key Features
@@ -82,11 +84,11 @@ https://docs.cherry-ai.com
# 🌈 Theme
- Theme Gallery: https://cherrycss.com
- Aero Theme: https://github.com/hakadao/CherryStudio-Aero
- PaperMaterial Theme: https://github.com/rainoffallingstar/CherryStudio-PaperMaterial
- Claude dynamic-style: https://github.com/bjl101501/CherryStudio-Claudestyle-dynamic
- Maple Neon Theme: https://github.com/BoningtonChen/CherryStudio_themes
- Theme Gallery: <https://cherrycss.com>
- Aero Theme: <https://github.com/hakadao/CherryStudio-Aero>
- PaperMaterial Theme: <https://github.com/rainoffallingstar/CherryStudio-PaperMaterial>
- Claude dynamic-style: <https://github.com/bjl101501/CherryStudio-Claudestyle-dynamic>
- Maple Neon Theme: <https://github.com/BoningtonChen/CherryStudio_themes>
Welcome PR for more themes
@@ -94,6 +96,10 @@ Welcome PR for more themes
Refer to the [development documentation](docs/dev.md)
Refer to the [Architecture overview documentation](https://deepwiki.com/CherryHQ/cherry-studio)
Refer to the [Branching Strategy](docs/branching-strategy-en.md) for contribution guidelines
# 🤝 Contributing
We welcome contributions to Cherry Studio! Here are some ways you can contribute:
@@ -117,7 +123,7 @@ For more detailed guidelines, please refer to our [Contributing Guide](./CONTRIB
Thank you for your support and contributions!
## Related Projects
# 🔗 Related Projects
- [one-api](https://github.com/songquanpeng/one-api):LLM API management and distribution system, supporting mainstream models like OpenAI, Azure, and Anthropic. Features unified API interface, suitable for key management and secondary distribution.
@@ -126,7 +132,7 @@ Thank you for your support and contributions!
# 🚀 Contributors
<a href="https://github.com/CherryHQ/cherry-studio/graphs/contributors">
<img src="https://contrib.rocks/image?repo=kangfenmao/cherry-studio" />
<img src="https://contrib.rocks/image?repo=CherryHQ/cherry-studio" />
</a>
<br /><br />
@@ -144,7 +150,7 @@ Thank you for your support and contributions!
# ✉️ Contact
yinsenho@cherry-ai.com
<yinsenho@cherry-ai.com>
# ⭐️ Star History

View File

@@ -6,17 +6,19 @@
<p align="center">
<a href="https://github.com/CherryHQ/cherry-studio">English</a> | <a href="./README.zh.md">中文</a> | 日本語 <br>
</p>
<div align="center">
<a href="https://trendshift.io/repositories/11772" target="_blank"><img src="https://trendshift.io/api/badge/repositories/11772" alt="kangfenmao%2Fcherry-studio | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<a href="https://www.producthunt.com/posts/cherry-studio?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cherry&#0045;studio" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=496640&theme=light" alt="Cherry&#0032;Studio - AI&#0032;Chatbots&#0044;&#0032;AI&#0032;Desktop&#0032;Client | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
</div>
# 🍒 Cherry Studio
Cherry Studio は、複数の LLM プロバイダーをサポートするデスクトップクライアントで、Windows、Mac、Linux で利用可能です。
👏 [Telegram](https://t.me/CherryStudioAI)[Discord](https://discord.gg/wez8HtpxqQ) | [QQグループ(575014769)](https://qm.qq.com/q/lo0D4qVZKi)
❤️ Cherry Studio をお気に入りにしましたか?小さな星をつけてください 🌟 または [スポンサー](sponsor.md) をして開発をサポートしてください!❤️
❤️ Cherry Studio をお気に入りにしましたか?小さな星をつけてください 🌟 または [スポンサー](sponsor.md) をして開発をサポートしてください!
# 📖 ガイド
@@ -24,9 +26,11 @@ https://docs.cherry-ai.com
# 🌠 スクリーンショット
![](https://github.com/user-attachments/assets/082efa42-c4df-4863-a9cb-80435cecce0f)
![](https://github.com/user-attachments/assets/f8411a65-c51f-47d3-9273-62ae384cc6f1)
![](https://github.com/user-attachments/assets/0d235b3e-65ae-45ab-987f-8dbe003c52be)
![](https://github.com/user-attachments/assets/36dddb2c-e0fb-4a5f-9411-91447bab6e18)
![](https://github.com/user-attachments/assets/f549e8a0-2385-40b4-b52b-2039e39f2930)
![](https://github.com/user-attachments/assets/58e0237c-4d36-40de-b428-53051d982026)
# 🌟 主な機能
@@ -56,7 +60,7 @@ https://docs.cherry-ai.com
- 🔤 AI による翻訳機能
- 🎯 ドラッグ&ドロップによる整理
- 🔌 ミニプログラム対応
- ⚙️ MCPモデルコンテキストプロトコル サービス
- ⚙️ MCPモデルコンテキストプロトコルサービス
5. **優れたユーザー体験**
@@ -70,71 +74,78 @@ https://docs.cherry-ai.com
- [x] クイックポップアップ(クリップボードの読み取り、簡単な質問、説明、翻訳、要約)
- [x] 複数モデルの回答の比較
- [x] サービスプロバイダーが提供する SSO を使用したログインをサポート
- [x] すべてのモデルネットワークをサポート
- [x] サービスプロバイダーが提供する SSO を使用したログイン対応
- [x] すべてのモデルネットワーク対応
- [x] 最初の公式バージョンのリリース
- [ ] 錯誤修復と改善 (開発中...)
- [x] バグ修正と改善(進行中...
- [ ] プラグイン機能JavaScript
- [ ] ブラウザ拡張機能(テキストをハイライトして翻訳、要約、ナレッジベースに追加)
- [ ] iOS & Android クライアント
- [ ] AIート
- [ ] AI ノート
- [ ] 音声入出力AI コール)
- [ ] データバックアップカスタムバックアップコンテンツをサポート
- [ ] データバックアップカスタマイズ対応
# 🌈 テーマ
- テーマギャラリー: https://cherrycss.com
- Aero テーマ: https://github.com/hakadao/CherryStudio-Aero
- PaperMaterial テーマ: https://github.com/rainoffallingstar/CherryStudio-PaperMaterial
- Claude テーマ: https://github.com/bjl101501/CherryStudio-Claudestyle-dynamic
- メープルネオンテーマ: https://github.com/BoningtonChen/CherryStudio_themes
- テーマギャラリーhttps://cherrycss.com
- Aero テーマhttps://github.com/hakadao/CherryStudio-Aero
- PaperMaterial テーマhttps://github.com/rainoffallingstar/CherryStudio-PaperMaterial
- Claude テーマhttps://github.com/bjl101501/CherryStudio-Claudestyle-dynamic
- メープルネオンテーマhttps://github.com/BoningtonChen/CherryStudio_themes
より多くのテーマのPRを歓迎します
より多くのテーマの PR を歓迎します
# 🖥️ 開発
参考[開発ドキュメント](dev.md)
[開発ドキュメント](dev.md)を参照してください
[アーキテクチャ概要ドキュメント](https://deepwiki.com/CherryHQ/cherry-studio)を参照してください
[ブランチ戦略](branching-strategy-en.md)を参照して貢献ガイドラインを確認してください
# 🤝 貢献
Cherry Studio への貢献を歓迎します!以下の方法で貢献できます:
1. **コードの貢献**:新機能を開発するか、既存のコードを最適化します
2. **バグの修正**:見つけたバグを修正します
3. **問題の管理**GitHub の問題を管理するのを手伝います
4. **製品デザイン**:デザインの議論に参加します
5. **ドキュメントの作成**:ユーザーマニュアルやガイドを改善します
6. **コミュニティの参加**:ディスカッションに参加し、ユーザーを支援します
7. **使用の促進**Cherry Studio を広めます
1. **コードの貢献**:新機能を開発するか、既存のコードを最適化します
2. **バグの修正**:見つけたバグを修正します
3. **問題の管理**GitHub の問題を管理するのを手伝います
4. **製品デザイン**:デザインの議論に参加します
5. **ドキュメントの作成**:ユーザーマニュアルやガイドを改善します
6. **コミュニティの参加**:ディスカッションに参加し、ユーザーを支援します
7. **使用の促進**Cherry Studio を広めます
## 始め方
1. **リポジトリをフォーク**:フォークしてローカルマシンにクローンします
2. **ブランチを作成**:変更のためのブランチを作成します
3. **変更を提出**:変更をコミットしてプッシュします
4. **プルリクエストを開く**:変更内容と理由を説明します
1. **リポジトリをフォーク**:フォークしてローカルマシンにクローンします
2. **ブランチを作成**:変更のためのブランチを作成します
3. **変更を提出**:変更をコミットしてプッシュします
4. **プルリクエストを開く**:変更内容と理由を説明します
詳細なガイドラインについては、[貢献ガイド](../CONTRIBUTING.md)をご覧ください。
ご支援と貢献に感謝します!
## 関連頁版
# 🔗 関連プロジェクト
- [one-api](https://github.com/songquanpeng/one-api)LLM API の管理・配信システム。OpenAI、Azure、Anthropic などの主要モデルに対応し、統一 API インターフェースを提供。API キー管理と再配布に利用可能。
- [ublacklist](https://github.com/iorate/ublacklist)Google 検索結果から特定のサイトを非表示にします
# 🚀 コントリビューター
<a href="https://github.com/CherryHQ/cherry-studio/graphs/contributors">
<img src="https://contrib.rocks/image?repo=kangfenmao/cherry-studio" />
<img src="https://contrib.rocks/image?repo=CherryHQ/cherry-studio" />
</a>
<br /><br />
# コミュニティ
# 🌐 コミュニティ
[Telegram](https://t.me/CherryStudioAI) | [Email](mailto:support@cherry-ai.com) | [Twitter](https://x.com/kangfenmao)
# スポンサー
# スポンサー
[Buy Me a Coffee](sponsor.md)
[開発者を支援する](sponsor.md)
# 📃 ライセンス

View File

@@ -4,7 +4,8 @@
</a>
</h1>
<p align="center">
<a href="https://github.com/CherryHQ/cherry-studio">English</a> | 中文 | <a href="./README.ja.md">日本語</a><br></p>
<a href="https://github.com/CherryHQ/cherry-studio">English</a> | 中文 | <a href="./README.ja.md">日本語</a><br>
</p>
<div align="center">
<a href="https://trendshift.io/repositories/11772" target="_blank"><img src="https://trendshift.io/api/badge/repositories/11772" alt="kangfenmao%2Fcherry-studio | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<a href="https://www.producthunt.com/posts/cherry-studio?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cherry&#0045;studio" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=496640&theme=light" alt="Cherry&#0032;Studio - AI&#0032;Chatbots&#0044;&#0032;AI&#0032;Desktop&#0032;Client | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
@@ -18,15 +19,25 @@ Cherry Studio 是一款支持多个大语言模型LLM服务商的桌面客
❤️ 喜欢 Cherry Studio? 点亮小星星 🌟 或 [赞助开发者](sponsor.md)! ❤️
# GitCode✖Cherry Studio【新源力】贡献挑战赛
<p align="center">
<a href="https://gitcode.com/CherryHQ/cherry-studio/discussion/2">
<img src="https://raw.gitcode.com/user-images/assets/5007375/8d8d7559-1141-4691-b90f-d154558c6896/cherry-studio-gitcode.jpg" width="100%" alt="banner" />
</a>
</p>
# 📖 使用教程
https://docs.cherry-ai.com
# 🌠 界面
![](https://github.com/user-attachments/assets/082efa42-c4df-4863-a9cb-80435cecce0f)
![](https://github.com/user-attachments/assets/f8411a65-c51f-47d3-9273-62ae384cc6f1)
![](https://github.com/user-attachments/assets/0d235b3e-65ae-45ab-987f-8dbe003c52be)
![](https://github.com/user-attachments/assets/36dddb2c-e0fb-4a5f-9411-91447bab6e18)
![](https://github.com/user-attachments/assets/f549e8a0-2385-40b4-b52b-2039e39f2930)
![](https://github.com/user-attachments/assets/58e0237c-4d36-40de-b428-53051d982026)
# 🌟 主要特性
@@ -66,12 +77,12 @@ https://docs.cherry-ai.com
- 📝 完整的 Markdown 渲染
- 🤲 便捷的内容分享功能
# 📝 待辦事項
# 📝 待办事项
- [x] 快捷弹窗(读取剪贴板、快速提问、解释、翻译、总结)
- [x] 多模型回答对比
- [x] 支持使用服务供应商提供的 SSO 进行登
- [x] 全部模型支持连网(开发中...
- [x] 支持使用服务供应商提供的 SSO 进行登
- [x] 所有模型支持联网
- [x] 推出第一个正式版
- [x] 错误修复和改进(开发中...
- [ ] 插件功能JavaScript
@@ -85,9 +96,9 @@ https://docs.cherry-ai.com
- 主题库https://cherrycss.com
- Aero 主题https://github.com/hakadao/CherryStudio-Aero
- PaperMaterial 主题: https://github.com/rainoffallingstar/CherryStudio-PaperMaterial
- 仿Claude 主题: https://github.com/bjl101501/CherryStudio-Claudestyle-dynamic
- 霓虹枫叶字体主题: https://github.com/BoningtonChen/CherryStudio_themes
- PaperMaterial 主题https://github.com/rainoffallingstar/CherryStudio-PaperMaterial
- 仿 Claude 主题https://github.com/bjl101501/CherryStudio-Claudestyle-dynamic
- 霓虹枫叶主题:https://github.com/BoningtonChen/CherryStudio_themes
欢迎 PR 更多主题
@@ -95,37 +106,43 @@ https://docs.cherry-ai.com
参考[开发文档](dev.md)
参考[架构概览文档](https://deepwiki.com/CherryHQ/cherry-studio)
参考[分支策略](branching-strategy-zh.md)了解贡献指南
# 🤝 贡献
我们欢迎对 Cherry Studio 的贡献!您可以通过以下方式贡献:
1. **贡献代码**:开发新功能或优化现有代码
2. **修复错误**:提交您发现的错误修复
3. **维护问题**:帮助管理 GitHub 问题
4. **产品设计**:参与设计讨论
5. **撰写文档**:改进用户手册和指南
6. **社区参与**:加入讨论并帮助用户
7. **推广使用**:宣传 Cherry Studio
1. **贡献代码**:开发新功能或优化现有代码
2. **修复错误**:提交您发现的错误修复
3. **维护问题**:帮助管理 GitHub 问题
4. **产品设计**:参与设计讨论
5. **撰写文档**:改进用户手册和指南
6. **社区参与**:加入讨论并帮助用户
7. **推广使用**:宣传 Cherry Studio
## 入门
1. **Fork 仓库**Fork 并克隆到您的本地机器
2. **创建分支**:为您的更改创建分支
3. **提交更改**:提交并推送您的更改
4. **打开 Pull Request**:描述您的更改和原因
1. **Fork 仓库**Fork 并克隆到您的本地机器
2. **创建分支**:为您的更改创建分支
3. **提交更改**:提交并推送您的更改
4. **打开 Pull Request**:描述您的更改和原因
有关更详细的指南,请参阅我们的 [贡献指南](./CONTRIBUTING.zh.md)
有关更详细的指南,请参阅我们的 [贡献指南](./CONTRIBUTING.zh.md)
感谢您的支持和贡献!
## 相关项目
# 🔗 相关项目
- [one-api](https://github.com/songquanpeng/one-api)LLM API 管理及分发系统,支持 OpenAI、Azure、Anthropic 等主流模型,统一 API 接口,可用于密钥管理与二次分发。
- [ublacklist](https://github.com/iorate/ublacklist):屏蔽特定网站在 Google 搜索结果中显示
# 🚀 贡献者
<a href="https://github.com/CherryHQ/cherry-studio/graphs/contributors">
<img src="https://contrib.rocks/image?repo=kangfenmao/cherry-studio" />
<img src="https://contrib.rocks/image?repo=CherryHQ/cherry-studio" />
</a>
<br /><br />
@@ -135,7 +152,7 @@ https://docs.cherry-ai.com
# ☕ 赞助
[微信赞赏码](sponsor.md)
[赞助开发者](sponsor.md)
# 📃 许可证

View File

@@ -0,0 +1,71 @@
# 🌿 Branching Strategy
Cherry Studio implements a structured branching strategy to maintain code quality and streamline the development process.
## Main Branches
- `main`: Main development branch
- Contains the latest development code
- Direct commits are not allowed - changes must come through pull requests
- Code may contain features in development and might not be fully stable
- `release/*`: Release branches
- Created from `main` branch
- Contains stable code ready for release
- Only accepts documentation updates and bug fixes
- Thoroughly tested before production deployment
## Contributing Branches
When contributing to Cherry Studio, please follow these guidelines:
1. **Feature Branches:**
- Create from `main` branch
- Naming format: `feature/issue-number-brief-description`
- Submit PR back to `main`
2. **Bug Fix Branches:**
- Create from `main` branch
- Naming format: `fix/issue-number-brief-description`
- Submit PR back to `main`
3. **Documentation Branches:**
- Create from `main` branch
- Naming format: `docs/brief-description`
- Submit PR back to `main`
4. **Hotfix Branches:**
- Create from `main` branch
- Naming format: `hotfix/issue-number-brief-description`
- Submit PR to both `main` and relevant `release` branches
5. **Release Branches:**
- Create from `main` branch
- Naming format: `release/version-number`
- Used for final preparation work before version release
- Only accepts bug fixes and documentation updates
- After testing and preparation, merge back to `main` and tag with version
## Workflow Diagram
![](https://github.com/user-attachments/assets/61db64a2-fab1-4a16-8253-0c64c9df1a63)
## Pull Request Guidelines
- All PRs should be submitted to the `main` branch unless fixing a critical production issue
- Ensure your branch is up to date with the latest `main` changes before submitting
- Include relevant issue numbers in your PR description
- Make sure all tests pass and code meets our quality standards
- Add before/after screenshots if you add a new feature or modify a UI component
## Version Tag Management
- Major releases: v1.0.0, v2.0.0, etc.
- Feature releases: v1.1.0, v1.2.0, etc.
- Patch releases: v1.0.1, v1.0.2, etc.
- Hotfix releases: v1.0.1-hotfix, etc.

View File

@@ -0,0 +1,71 @@
# 🌿 分支策略
Cherry Studio 采用结构化的分支策略来维护代码质量并简化开发流程。
## 主要分支
- `main`:主开发分支
- 包含最新的开发代码
- 禁止直接提交 - 所有更改必须通过拉取请求Pull Request
- 此分支上的代码可能包含正在开发的功能,不一定完全稳定
- `release/*`:发布分支
-`main` 分支创建
- 包含准备发布的稳定代码
- 只接受文档更新和 bug 修复
- 经过完整测试后可以发布到生产环境
## 贡献分支
在为 Cherry Studio 贡献代码时,请遵循以下准则:
1. **功能开发分支:**
-`main` 分支创建
- 命名格式:`feature/issue-number-brief-description`
- 完成后提交 PR 到 `main` 分支
2. **Bug 修复分支:**
-`main` 分支创建
- 命名格式:`fix/issue-number-brief-description`
- 完成后提交 PR 到 `main` 分支
3. **文档更新分支:**
-`main` 分支创建
- 命名格式:`docs/brief-description`
- 完成后提交 PR 到 `main` 分支
4. **紧急修复分支:**
-`main` 分支创建
- 命名格式:`hotfix/issue-number-brief-description`
- 完成后需要同时合并到 `main` 和相关的 `release` 分支
5. **发布分支:**
-`main` 分支创建
- 命名格式:`release/version-number`
- 用于版本发布前的最终准备工作
- 只允许合并 bug 修复和文档更新
- 完成测试和准备工作后,将代码合并回 `main` 分支并打上版本标签
## 工作流程
![](https://github.com/user-attachments/assets/61db64a2-fab1-4a16-8253-0c64c9df1a63)
## 拉取请求PR指南
- 除非是修复生产环境的关键问题,否则所有 PR 都应该提交到 `main` 分支
- 提交 PR 前确保你的分支已经同步了最新的 `main` 分支内容
- 在 PR 描述中包含相关的 issue 编号
- 确保所有测试通过,且代码符合我们的质量标准
- 如果你添加了新功能或修改了 UI 组件,请附上更改前后的截图
## 版本标签管理
- 主要版本发布v1.0.0、v2.0.0 等
- 功能更新发布v1.1.0、v1.2.0 等
- 补丁修复发布v1.0.1、v1.0.2 等
- 紧急修复发布v1.0.1-hotfix 等

View File

@@ -0,0 +1,3 @@
# 消息的生命周期
![image](./message-lifecycle.png)

View File

@@ -0,0 +1,127 @@
# messageBlock.ts 使用指南
该文件定义了用于管理应用程序中所有 `MessageBlock` 实体的 Redux Slice。它使用 Redux Toolkit 的 `createSlice``createEntityAdapter` 来高效地处理规范化的状态,并提供了一系列 actions 和 selectors 用于与消息块数据交互。
## 核心目标
- **状态管理**: 集中管理所有 `MessageBlock` 的状态。`MessageBlock` 代表消息中的不同内容单元(如文本、代码、图片、引用等)。
- **规范化**: 使用 `createEntityAdapter``MessageBlock` 数据存储在规范化的结构中(`{ ids: [], entities: {} }`),这有助于提高性能和简化更新逻辑。
- **可预测性**: 提供明确的 actions 来修改状态,并通过 selectors 安全地访问状态。
## 关键概念
- **Slice (`createSlice`)**: Redux Toolkit 的核心 API用于创建包含 reducer 逻辑、action creators 和初始状态的 Redux 模块。
- **Entity Adapter (`createEntityAdapter`)**: Redux Toolkit 提供的工具,用于简化对规范化数据的 CRUD创建、读取、更新、删除操作。它会自动生成 reducer 函数和 selectors。
- **Selectors**: 用于从 Redux store 中派生和计算数据的函数。Selectors 可以被记忆化memoized以提高性能。
## State 结构
`messageBlocks` slice 的状态结构由 `createEntityAdapter` 定义,大致如下:
```typescript
{
ids: string[]; // 存储所有 MessageBlock ID 的有序列表
entities: { [id: string]: MessageBlock }; // 按 ID 存储 MessageBlock 对象的字典
loadingState: 'idle' | 'loading' | 'succeeded' | 'failed'; // (可选) 其他状态,如加载状态
error: string | null; // (可选) 错误信息
}
```
## Actions
该 slice 导出以下 actions (由 `createSlice``createEntityAdapter` 自动生成或自定义)
- **`upsertOneBlock(payload: MessageBlock)`**:
- 添加一个新的 `MessageBlock` 或更新一个已存在的 `MessageBlock`。如果 payload 中的 `id` 已存在,则执行更新;否则执行插入。
- **`upsertManyBlocks(payload: MessageBlock[])`**:
- 添加或更新多个 `MessageBlock`。常用于批量加载数据(例如,加载一个 Topic 的所有消息块)。
- **`removeOneBlock(payload: string)`**:
- 根据提供的 `id` (payload) 移除单个 `MessageBlock`
- **`removeManyBlocks(payload: string[])`**:
- 根据提供的 `id` 数组 (payload) 移除多个 `MessageBlock`。常用于删除消息或清空 Topic 时清理相关的块。
- **`removeAllBlocks()`**:
- 移除 state 中的所有 `MessageBlock` 实体。
- **`updateOneBlock(payload: { id: string; changes: Partial<MessageBlock> })`**:
- 更新一个已存在的 `MessageBlock``payload` 需要包含块的 `id` 和一个包含要更改的字段的 `changes` 对象。
- **`setMessageBlocksLoading(payload: 'idle' | 'loading')`**:
- (自定义) 设置 `loadingState` 属性。
- **`setMessageBlocksError(payload: string)`**:
- (自定义) 设置 `loadingState``'failed'` 并记录错误信息。
**使用示例 (在 Thunk 或其他 Dispatch 的地方):**
```typescript
import { upsertOneBlock, removeManyBlocks, updateOneBlock } from './messageBlock'
import store from './store' // 假设这是你的 Redux store 实例
// 添加或更新一个块
const newBlock: MessageBlock = {
/* ... block data ... */
}
store.dispatch(upsertOneBlock(newBlock))
// 更新一个块的内容
store.dispatch(updateOneBlock({ id: blockId, changes: { content: 'New content' } }))
// 删除多个块
const blockIdsToRemove = ['id1', 'id2']
store.dispatch(removeManyBlocks(blockIdsToRemove))
```
## Selectors
该 slice 导出由 `createEntityAdapter` 生成的基础 selectors并通过 `messageBlocksSelectors` 对象访问:
- **`messageBlocksSelectors.selectIds(state: RootState): string[]`**: 返回包含所有块 ID 的数组。
- **`messageBlocksSelectors.selectEntities(state: RootState): { [id: string]: MessageBlock }`**: 返回块 ID 到块对象的映射字典。
- **`messageBlocksSelectors.selectAll(state: RootState): MessageBlock[]`**: 返回包含所有块对象的数组。
- **`messageBlocksSelectors.selectTotal(state: RootState): number`**: 返回块的总数。
- **`messageBlocksSelectors.selectById(state: RootState, id: string): MessageBlock | undefined`**: 根据 ID 返回单个块对象,如果找不到则返回 `undefined`
**此外,还提供了一个自定义的、记忆化的 selector**
- **`selectFormattedCitationsByBlockId(state: RootState, blockId: string | undefined): Citation[]`**:
- 接收一个 `blockId`
- 如果该 ID 对应的块是 `CITATION` 类型,则提取并格式化其包含的引用信息(来自网页搜索、知识库等),进行去重和重新编号,最后返回一个 `Citation[]` 数组,用于在 UI 中显示。
- 如果块不存在或类型不匹配,返回空数组 `[]`
- 这个 selector 封装了处理不同引用来源Gemini, OpenAI, OpenRouter, Zhipu 等)的复杂逻辑。
**使用示例 (在 React 组件或 `useSelector` 中):**
```typescript
import { useSelector } from 'react-redux'
import { messageBlocksSelectors, selectFormattedCitationsByBlockId } from './messageBlock'
import type { RootState } from './store'
// 获取所有块
const allBlocks = useSelector(messageBlocksSelectors.selectAll)
// 获取特定 ID 的块
const specificBlock = useSelector((state: RootState) => messageBlocksSelectors.selectById(state, someBlockId))
// 获取特定引用块格式化后的引用列表
const formattedCitations = useSelector((state: RootState) => selectFormattedCitationsByBlockId(state, citationBlockId))
// 在组件中使用引用数据
// {formattedCitations.map(citation => ...)}
```
## 集成
`messageBlock.ts` slice 通常与 `messageThunk.ts` 中的 Thunks 紧密协作。Thunks 负责处理异步逻辑(如 API 调用、数据库操作),并在需要时 dispatch `messageBlock` slice 的 actions 来更新状态。例如,当 `messageThunk` 接收到流式响应时,它会 dispatch `upsertOneBlock``updateOneBlock` 来实时更新对应的 `MessageBlock`。同样,删除消息的 Thunk 会 dispatch `removeManyBlocks`
理解 `messageBlock.ts` 的职责是管理**状态本身**,而 `messageThunk.ts` 负责**触发状态变更**的异步流程,这对于维护清晰的应用架构至关重要。

View File

@@ -0,0 +1,105 @@
# messageThunk.ts 使用指南
该文件包含用于管理应用程序中消息流、处理助手交互以及同步 Redux 状态与 IndexedDB 数据库的核心 Thunk Action Creators。主要围绕 `Message``MessageBlock` 对象进行操作。
## 核心功能
1. **发送/接收消息**: 处理用户消息的发送,触发助手响应,并流式处理返回的数据,将其解析为不同的 `MessageBlock`
2. **状态管理**: 确保 Redux store 中的消息和消息块状态与 IndexedDB 中的持久化数据保持一致。
3. **消息操作**: 提供删除、重发、重新生成、编辑后重发、追加响应、克隆等消息生命周期管理功能。
4. **Block 处理**: 动态创建、更新和保存各种类型的 `MessageBlock`(文本、思考过程、工具调用、引用、图片、错误、翻译等)。
## 主要 Thunks
以下是一些关键的 Thunk 函数及其用途:
1. **`sendMessage(userMessage, userMessageBlocks, assistant, topicId)`**
- **用途**: 发送一条新的用户消息。
- **流程**:
- 保存用户消息 (`userMessage`) 及其块 (`userMessageBlocks`) 到 Redux 和 DB。
- 检查 `@mentions` 以确定是单模型响应还是多模型响应。
- 创建助手消息(们)的存根 (Stub)。
- 将存根添加到 Redux 和 DB。
- 将核心处理逻辑 `fetchAndProcessAssistantResponseImpl` 添加到该 `topicId` 的队列中以获取实际响应。
- **Block 相关**: 主要处理用户消息的初始 `MessageBlock` 保存。
2. **`fetchAndProcessAssistantResponseImpl(dispatch, getState, topicId, assistant, assistantMessage)`**
- **用途**: (内部函数) 获取并处理单个助手响应的核心逻辑,被 `sendMessage`, `resend...`, `regenerate...`, `append...` 等调用。
- **流程**:
- 设置 Topic 加载状态。
- 准备上下文消息。
- 调用 `fetchChatCompletion` API 服务。
- 使用 `createStreamProcessor` 处理流式响应。
- 通过各种回调 (`onTextChunk`, `onThinkingChunk`, `onToolCallComplete`, `onImageGenerated`, `onError`, `onComplete` 等) 处理不同类型的事件。
- **Block 相关**:
- 根据流事件创建初始 `UNKNOWN` 块。
- 实时创建和更新 `MAIN_TEXT``THINKING` 块,使用 `throttledBlockUpdate``throttledBlockDbUpdate` 进行节流更新。
- 创建 `TOOL`, `CITATION`, `IMAGE`, `ERROR` 等类型的块。
- 在事件完成时(如 `onTextComplete`, `onToolCallComplete`)将块状态标记为 `SUCCESS``ERROR`,并使用 `saveUpdatedBlockToDB` 保存最终状态。
- 使用 `handleBlockTransition` 管理非流式块(如 `TOOL`, `CITATION`)的添加和状态更新。
3. **`loadTopicMessagesThunk(topicId, forceReload)`**
- **用途**: 从数据库加载指定主题的所有消息及其关联的 `MessageBlock`
- **流程**:
- 从 DB 获取 `Topic` 及其 `messages` 列表。
- 根据消息 ID 列表从 DB 获取所有相关的 `MessageBlock`
- 使用 `upsertManyBlocks` 将块更新到 Redux。
- 将消息更新到 Redux。
- **Block 相关**: 负责将持久化的 `MessageBlock` 加载到 Redux 状态。
4. **删除 Thunks**
- `deleteSingleMessageThunk(topicId, messageId)`: 删除单个消息及其所有 `MessageBlock`
- `deleteMessageGroupThunk(topicId, askId)`: 删除一个用户消息及其所有相关的助手响应消息和它们的所有 `MessageBlock`
- `clearTopicMessagesThunk(topicId)`: 清空主题下的所有消息及其所有 `MessageBlock`
- **Block 相关**: 从 Redux 和 DB 中移除指定的 `MessageBlock`
5. **重发/重新生成 Thunks**
- `resendMessageThunk(topicId, userMessageToResend, assistant)`: 重发用户消息。会重置(清空 Block 并标记为 PENDING所有与该用户消息关联的助手响应然后重新请求生成。
- `resendUserMessageWithEditThunk(topicId, originalMessage, mainTextBlockId, editedContent, assistant)`: 用户编辑消息内容后重发。先更新用户消息的 `MAIN_TEXT` 块内容,然后调用 `resendMessageThunk`
- `regenerateAssistantResponseThunk(topicId, assistantMessageToRegenerate, assistant)`: 重新生成单个助手响应。重置该助手消息(清空 Block 并标记为 PENDING然后重新请求生成。
- **Block 相关**: 删除旧的 `MessageBlock`,并在重新生成过程中创建新的 `MessageBlock`
6. **`appendAssistantResponseThunk(topicId, existingAssistantMessageId, newModel, assistant)`**
- **用途**: 在已有的对话上下文中,针对同一个用户问题,使用新选择的模型追加一个新的助手响应。
- **流程**:
- 找到现有助手消息以获取原始 `askId`
- 创建使用 `newModel` 的新助手消息存根(使用相同的 `askId`)。
- 添加新存根到 Redux 和 DB。
-`fetchAndProcessAssistantResponseImpl` 添加到队列以生成新响应。
- **Block 相关**: 为新的助手响应创建全新的 `MessageBlock`
7. **`cloneMessagesToNewTopicThunk(sourceTopicId, branchPointIndex, newTopic)`**
- **用途**: 将源主题的部分消息(及其 Block克隆到一个**已存在**的新主题中。
- **流程**:
- 复制指定索引前的消息。
- 为所有克隆的消息和 Block 生成新的 UUID。
- 正确映射克隆消息之间的 `askId` 关系。
- 复制 `MessageBlock` 内容,更新其 `messageId` 指向新的消息 ID。
- 更新文件引用计数(如果 Block 是文件或图片)。
- 将克隆的消息和 Block 保存到新主题的 Redux 状态和 DB 中。
- **Block 相关**: 创建 `MessageBlock` 的副本,并更新其 ID 和 `messageId`
8. **`initiateTranslationThunk(messageId, topicId, targetLanguage, sourceBlockId?, sourceLanguage?)`**
- **用途**: 为指定消息启动翻译流程,创建一个初始的 `TRANSLATION` 类型的 `MessageBlock`
- **流程**:
- 创建一个状态为 `STREAMING``TranslationMessageBlock`
- 将其添加到 Redux 和 DB。
- 更新原消息的 `blocks` 列表以包含新的翻译块 ID。
- **Block 相关**: 创建并保存一个占位的 `TranslationMessageBlock`。实际翻译内容的获取和填充需要后续步骤。
## 内部机制和注意事项
- **数据库交互**: 通过 `saveMessageAndBlocksToDB`, `updateExistingMessageAndBlocksInDB`, `saveUpdatesToDB`, `saveUpdatedBlockToDB`, `throttledBlockDbUpdate` 等辅助函数与 IndexedDB (`db`) 交互,确保数据持久化。
- **状态同步**: Thunks 负责协调 Redux Store 和 IndexedDB 之间的数据一致性。
- **队列 (`getTopicQueue`)**: 使用 `AsyncQueue` 确保对同一主题的操作(尤其是 API 请求)按顺序执行,避免竞态条件。
- **节流 (`throttle`)**: 对流式响应中频繁的 Block 更新(文本、思考)使用 `lodash.throttle` 优化性能,减少 Redux dispatch 和 DB 写入次数。
- **错误处理**: `fetchAndProcessAssistantResponseImpl` 内的回调函数(特别是 `onError`)处理流处理和 API 调用中可能出现的错误,并创建 `ERROR` 类型的 `MessageBlock`
开发者在使用这些 Thunks 时,通常需要提供 `dispatch`, `getState` (由 Redux Thunk 中间件注入),以及如 `topicId`, `assistant` 配置对象, 相关的 `Message``MessageBlock` 对象/ID 等参数。理解每个 Thunk 的职责和它如何影响消息及块的状态至关重要。

View File

@@ -0,0 +1,156 @@
# useMessageOperations.ts 使用指南
该文件定义了一个名为 `useMessageOperations` 的自定义 React Hook。这个 Hook 的主要目的是为 React 组件提供一个便捷的接口用于执行与特定主题Topic相关的各种消息操作。它封装了调用 Redux Thunks (`messageThunk.ts`) 和 Actions (`newMessage.ts`, `messageBlock.ts`) 的逻辑,简化了组件与消息数据交互的代码。
## 核心目标
- **封装**: 将复杂的消息操作逻辑(如删除、重发、重新生成、编辑、翻译等)封装在易于使用的函数中。
- **简化**: 让组件可以直接调用这些操作函数,而无需直接与 Redux `dispatch` 或 Thunks 交互。
- **上下文关联**: 所有操作都与传入的 `topic` 对象相关联,确保操作作用于正确的主题。
## 如何使用
在你的 React 函数组件中,导入并调用 `useMessageOperations` Hook并传入当前活动的 `Topic` 对象。
```typescript
import React from 'react';
import { useMessageOperations } from '@renderer/hooks/useMessageOperations';
import type { Topic, Message, Assistant, Model } from '@renderer/types';
interface MyComponentProps {
currentTopic: Topic;
currentAssistant: Assistant;
}
function MyComponent({ currentTopic, currentAssistant }: MyComponentProps) {
const {
deleteMessage,
resendMessage,
regenerateAssistantMessage,
appendAssistantResponse,
getTranslationUpdater,
createTopicBranch,
// ... 其他操作函数
} = useMessageOperations(currentTopic);
const handleDelete = (messageId: string) => {
deleteMessage(messageId);
};
const handleResend = (message: Message) => {
resendMessage(message, currentAssistant);
};
const handleAppend = (existingMsg: Message, newModel: Model) => {
appendAssistantResponse(existingMsg, newModel, currentAssistant);
}
// ... 在组件中使用其他操作函数
return (
<div>
{/* Component UI */}
<button onClick={() => handleDelete('some-message-id')}>Delete Message</button>
{/* ... */}
</div>
);
}
```
## 返回值
`useMessageOperations(topic)` Hook 返回一个包含以下函数和值的对象:
- **`deleteMessage(id: string)`**:
- 删除指定 `id` 的单个消息。
- 内部调用 `deleteSingleMessageThunk`
- **`deleteGroupMessages(askId: string)`**:
- 删除与指定 `askId` 相关联的一组消息(通常是用户提问及其所有助手回答)。
- 内部调用 `deleteMessageGroupThunk`
- **`editMessage(messageId: string, updates: Partial<Message>)`**:
- 更新指定 `messageId` 的消息的部分属性。
- **注意**: 目前主要用于更新 Redux 状态
- 内部调用 `newMessagesActions.updateMessage`
- **`resendMessage(message: Message, assistant: Assistant)`**:
- 重新发送指定的用户消息 (`message`),这将触发其所有关联助手响应的重新生成。
- 内部调用 `resendMessageThunk`
- **`resendUserMessageWithEdit(message: Message, editedContent: string, assistant: Assistant)`**:
- 在用户消息的主要文本块被编辑后,重新发送该消息。
- 会先查找消息的 `MAIN_TEXT` 块 ID然后调用 `resendUserMessageWithEditThunk`
- **`clearTopicMessages(_topicId?: string)`**:
- 清除当前主题(或可选的指定 `_topicId`)下的所有消息。
- 内部调用 `clearTopicMessagesThunk`
- **`createNewContext()`**:
- 发出一个全局事件 (`EVENT_NAMES.NEW_CONTEXT`),通常用于通知 UI 清空显示,准备新的上下文。不直接修改 Redux 状态。
- **`displayCount`**:
- (非操作函数) 从 Redux store 中获取当前的 `displayCount` 值。
- **`pauseMessages()`**:
- 尝试中止当前主题中正在进行的消息生成(状态为 `processing``pending`)。
- 通过查找相关的 `askId` 并调用 `abortCompletion` 来实现。
- 同时会 dispatch `setTopicLoading` action 将加载状态设为 `false`
- **`resumeMessage(message: Message, assistant: Assistant)`**:
- 恢复/重新发送一个用户消息。目前实现为直接调用 `resendMessage`
- **`regenerateAssistantMessage(message: Message, assistant: Assistant)`**:
- 重新生成指定的**助手**消息 (`message`) 的响应。
- 内部调用 `regenerateAssistantResponseThunk`
- **`appendAssistantResponse(existingAssistantMessage: Message, newModel: Model, assistant: Assistant)`**:
- 针对 `existingAssistantMessage` 所回复的**同一用户提问**,使用 `newModel` 追加一个新的助手响应。
- 内部调用 `appendAssistantResponseThunk`
- **`getTranslationUpdater(messageId: string, targetLanguage: string, sourceBlockId?: string, sourceLanguage?: string)`**:
- **用途**: 获取一个用于逐步更新翻译块内容的函数。
- **流程**:
1. 内部调用 `initiateTranslationThunk` 来创建或获取一个 `TRANSLATION` 类型的 `MessageBlock`,并获取其 `blockId`
2. 返回一个**异步更新函数**。
- **返回的更新函数 `(accumulatedText: string, isComplete?: boolean) => void`**:
- 接收累积的翻译文本和完成状态。
- 调用 `updateOneBlock` 更新 Redux 中的翻译块内容和状态 (`STREAMING``SUCCESS`)。
- 调用 `throttledBlockDbUpdate` 将更新(节流地)保存到数据库。
- 如果初始化失败Thunk 返回 `undefined`),则此函数返回 `null`
- **`createTopicBranch(sourceTopicId: string, branchPointIndex: number, newTopic: Topic)`**:
- 创建一个主题分支,将 `sourceTopicId` 主题中 `branchPointIndex` 索引之前的消息克隆到 `newTopic` 中。
- **注意**: `newTopic` 对象必须是调用此函数**之前**已经创建并添加到 Redux 和数据库中的。
- 内部调用 `cloneMessagesToNewTopicThunk`
## 依赖
- **`topic: Topic`**: 必须传入当前操作上下文的主题对象。Hook 返回的操作函数将始终作用于这个主题的 `topic.id`
- **Redux `dispatch`**: Hook 内部使用 `useAppDispatch` 获取 `dispatch` 函数来调用 actions 和 thunks。
## 相关 Hooks
在同一文件中还定义了两个辅助 Hook
- **`useTopicMessages(topic: Topic)`**:
- 使用 `selectMessagesForTopic` selector 来获取并返回指定主题的消息列表。
- **`useTopicLoading(topic: Topic)`**:
- 使用 `selectNewTopicLoading` selector 来获取并返回指定主题的加载状态。
这些 Hook 可以与 `useMessageOperations` 结合使用,方便地在组件中获取消息数据、加载状态,并执行相关操作。

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 KiB

View File

@@ -45,6 +45,9 @@ win:
target:
- target: nsis
- target: portable
signtoolOptions:
sign: scripts/win-sign.js
verifyUpdateCodeSignature: false
nsis:
artifactName: ${productName}-${version}-${arch}-setup.${ext}
shortcutName: ${productName}
@@ -56,10 +59,12 @@ nsis:
buildUniversalInstaller: false
portable:
artifactName: ${productName}-${version}-${arch}-portable.${ext}
buildUniversalInstaller: false
mac:
entitlementsInherit: build/entitlements.mac.plist
notarize: false
artifactName: ${productName}-${version}-${arch}.${ext}
minimumSystemVersion: '20.1.0' # 最低支持 macOS 11.0
extendInfo:
- NSCameraUsageDescription: Application requests access to the device's camera.
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
@@ -89,9 +94,14 @@ afterSign: scripts/notarize.js
artifactBuildCompleted: scripts/artifact-build-completed.js
releaseInfo:
releaseNotes: |
新增对 grok-2-image 和 gpt-4o-image 图像支持
支持 Windows 便携版使用 data 目录存储数据
MCP 界面改版,新增描述信息显示
Mermaid 渲染逻辑优化
支持关闭公示渲染
修复 OpenAI 类型渲染错误
⚠️ 注意:升级前请备份数据,否则将无法降级
增加 TokenFlux 服务商
增加 Claude 4 模型支持
Grok 模型增加联网能力
小程序支持前进和后退
修复 Windows 用户 MCP 无法启动问题
修复无法搜索历史消息问题
修复 MCP 代理问题
修复精简备份恢复覆盖文件问题
修复@模型回复插入位置错误问题
修复搜索小程序崩溃问题

View File

@@ -73,7 +73,18 @@ export default defineConfig({
}
},
optimizeDeps: {
exclude: []
exclude: ['pyodide']
},
worker: {
format: 'es'
},
build: {
rollupOptions: {
input: {
index: resolve(__dirname, 'src/renderer/index.html'),
miniWindow: resolve(__dirname, 'src/renderer/miniWindow.html')
}
}
}
}
})

View File

@@ -1,6 +1,6 @@
{
"name": "CherryStudio",
"version": "1.2.10",
"version": "1.3.11",
"private": true,
"description": "A powerful AI assistant for producer.",
"main": "./out/main/index.js",
@@ -50,41 +50,41 @@
"test:renderer": "vitest run",
"test:renderer:ui": "vitest --ui",
"test:renderer:coverage": "vitest run --coverage",
"test:lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts",
"format": "prettier --write .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
"postinstall": "electron-builder install-app-deps",
"prepare": "husky"
},
"dependencies": {
"@cherrystudio/embedjs": "^0.1.28",
"@cherrystudio/embedjs-libsql": "^0.1.28",
"@cherrystudio/embedjs-loader-csv": "^0.1.28",
"@cherrystudio/embedjs-loader-image": "^0.1.28",
"@cherrystudio/embedjs-loader-markdown": "^0.1.28",
"@cherrystudio/embedjs-loader-msoffice": "^0.1.28",
"@cherrystudio/embedjs-loader-pdf": "^0.1.28",
"@cherrystudio/embedjs-loader-sitemap": "^0.1.28",
"@cherrystudio/embedjs-loader-web": "^0.1.28",
"@cherrystudio/embedjs-loader-xml": "^0.1.28",
"@cherrystudio/embedjs-openai": "^0.1.28",
"@cherrystudio/embedjs": "^0.1.31",
"@cherrystudio/embedjs-libsql": "^0.1.31",
"@cherrystudio/embedjs-loader-csv": "^0.1.31",
"@cherrystudio/embedjs-loader-image": "^0.1.31",
"@cherrystudio/embedjs-loader-markdown": "^0.1.31",
"@cherrystudio/embedjs-loader-msoffice": "^0.1.31",
"@cherrystudio/embedjs-loader-pdf": "^0.1.31",
"@cherrystudio/embedjs-loader-sitemap": "^0.1.31",
"@cherrystudio/embedjs-loader-web": "^0.1.31",
"@cherrystudio/embedjs-loader-xml": "^0.1.31",
"@cherrystudio/embedjs-openai": "^0.1.31",
"@electron-toolkit/utils": "^3.0.0",
"@electron/notarize": "^2.5.0",
"@langchain/community": "^0.3.36",
"@strongtz/win32-arm64-msvc": "^0.4.7",
"@tanstack/react-query": "^5.27.0",
"@types/react-infinite-scroll-component": "^5.0.0",
"adm-zip": "^0.5.16",
"archiver": "^7.0.1",
"async-mutex": "^0.5.0",
"bufferutil": "^4.0.9",
"color": "^5.0.0",
"diff": "^7.0.0",
"docx": "^9.0.2",
"electron-log": "^5.1.5",
"electron-store": "^8.2.0",
"electron-updater": "patch:electron-updater@npm%3A6.6.3#~/.yarn/patches/electron-updater-npm-6.6.3-9269dbaf84.patch",
"electron-updater": "6.6.4",
"electron-window-state": "^5.0.3",
"epub": "patch:epub@npm%3A1.3.0#~/.yarn/patches/epub-npm-1.3.0-8325494ffe.patch",
"extract-zip": "^2.0.1",
"fast-diff": "^1.3.0",
"fast-xml-parser": "^5.2.0",
"fetch-socks": "^1.3.2",
"fs-extra": "^11.2.0",
@@ -98,7 +98,6 @@
"tar": "^7.4.3",
"turndown": "^7.2.0",
"turndown-plugin-gfm": "^1.0.2",
"undici": "^7.4.0",
"webdav": "^5.8.0",
"ws": "^8.18.1",
"zipread": "^1.3.3"
@@ -108,7 +107,7 @@
"@agentic/searxng": "^7.3.3",
"@agentic/tavily": "^7.3.3",
"@ant-design/v5-patch-for-react-19": "^1.0.3",
"@anthropic-ai/sdk": "^0.38.0",
"@anthropic-ai/sdk": "^0.41.0",
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
"@electron-toolkit/eslint-config-ts": "^3.0.0",
"@electron-toolkit/preload": "^3.0.0",
@@ -116,18 +115,16 @@
"@emotion/is-prop-valid": "^1.3.1",
"@eslint-react/eslint-plugin": "^1.36.1",
"@eslint/js": "^9.22.0",
"@google/genai": "^0.10.0",
"@google/genai": "^0.13.0",
"@hello-pangea/dnd": "^16.6.0",
"@kangfenmao/keyv-storage": "^0.1.0",
"@modelcontextprotocol/sdk": "^1.10.2",
"@modelcontextprotocol/sdk": "^1.11.4",
"@mozilla/readability": "^0.6.0",
"@notionhq/client": "^2.2.15",
"@reduxjs/toolkit": "^2.2.5",
"@shikijs/markdown-it": "^3.2.2",
"@swc/plugin-styled-components": "^7.1.3",
"@tavily/core": "patch:@tavily/core@npm%3A0.3.1#~/.yarn/patches/@tavily-core-npm-0.3.1-fe69bf2bea.patch",
"@shikijs/markdown-it": "^3.4.2",
"@swc/plugin-styled-components": "^7.1.5",
"@tryfabric/martian": "^1.2.4",
"@types/adm-zip": "^0",
"@types/diff": "^7",
"@types/fs-extra": "^11",
"@types/lodash": "^4.17.5",
@@ -138,26 +135,28 @@
"@types/react": "^19.0.12",
"@types/react-dom": "^19.0.4",
"@types/react-infinite-scroll-component": "^5.0.0",
"@types/react-window": "^1",
"@types/tinycolor2": "^1",
"@types/ws": "^8",
"@uiw/codemirror-extensions-langs": "^4.23.12",
"@uiw/codemirror-themes-all": "^4.23.12",
"@uiw/react-codemirror": "^4.23.12",
"@vitejs/plugin-react-swc": "^3.9.0",
"@vitest/coverage-v8": "^3.1.1",
"@vitest/ui": "^3.1.1",
"@vitest/web-worker": "^3.1.3",
"@xyflow/react": "^12.4.4",
"antd": "^5.22.5",
"applescript": "^1.0.0",
"axios": "^1.7.3",
"babel-plugin-styled-components": "^2.1.4",
"browser-image-compression": "^2.0.2",
"dayjs": "^1.11.11",
"dexie": "^4.0.8",
"dexie-react-hooks": "^1.1.7",
"dotenv-cli": "^7.4.2",
"electron": "31.7.6",
"electron-builder": "26.0.13",
"electron": "35.4.0",
"electron-builder": "26.0.15",
"electron-devtools-installer": "^3.2.0",
"electron-icon-builder": "^2.0.1",
"electron-vite": "^2.3.0",
"electron-vite": "^3.1.0",
"emittery": "^1.0.3",
"emoji-picker-element": "^1.22.1",
"eslint": "^9.22.0",
@@ -171,12 +170,14 @@
"lodash": "^4.17.21",
"lru-cache": "^11.1.0",
"lucide-react": "^0.487.0",
"mermaid": "^11.6.0",
"mime": "^4.0.4",
"motion": "^12.10.5",
"npx-scope-finder": "^1.2.0",
"openai": "patch:openai@npm%3A4.87.3#~/.yarn/patches/openai-npm-4.87.3-2b30a7685f.patch",
"openai": "patch:openai@npm%3A4.96.0#~/.yarn/patches/openai-npm-4.96.0-0665b05cb9.patch",
"p-queue": "^8.1.0",
"prettier": "^3.5.3",
"rc-virtual-list": "^3.18.5",
"rc-virtual-list": "^3.18.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hotkeys-hook": "^4.6.1",
@@ -187,6 +188,7 @@
"react-router": "6",
"react-router-dom": "6",
"react-spinners": "^0.14.1",
"react-window": "^1.8.11",
"redux": "^5.0.1",
"redux-persist": "^6.0.0",
"rehype-katex": "^7.0.1",
@@ -196,12 +198,11 @@
"remark-gfm": "^4.0.0",
"remark-math": "^6.0.0",
"rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.77.2",
"shiki": "^3.2.2",
"sass": "^1.88.0",
"shiki": "^3.4.2",
"string-width": "^7.2.0",
"styled-components": "^6.1.11",
"tiny-pinyin": "^1.3.2",
"tinycolor2": "^1.6.0",
"tokenx": "^0.4.1",
"typescript": "^5.6.2",
"uuid": "^10.0.0",
@@ -214,12 +215,13 @@
"@langchain/openai@npm:>=0.1.0 <0.4.0": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch",
"node-gyp": "^9.1.0",
"libsql@npm:^0.4.4": "patch:libsql@npm%3A0.4.7#~/.yarn/patches/libsql-npm-0.4.7-444e260fb1.patch",
"openai@npm:^4.77.0": "patch:openai@npm%3A4.87.3#~/.yarn/patches/openai-npm-4.87.3-2b30a7685f.patch",
"openai@npm:^4.77.0": "patch:openai@npm%3A4.96.0#~/.yarn/patches/openai-npm-4.96.0-0665b05cb9.patch",
"pkce-challenge@npm:^4.1.0": "patch:pkce-challenge@npm%3A4.1.0#~/.yarn/patches/pkce-challenge-npm-4.1.0-fbc51695a3.patch",
"app-builder-lib@npm:26.0.13": "patch:app-builder-lib@npm%3A26.0.13#~/.yarn/patches/app-builder-lib-npm-26.0.13-a064c9e1d0.patch",
"shiki": "3.2.2"
"openai@npm:^4.87.3": "patch:openai@npm%3A4.96.0#~/.yarn/patches/openai-npm-4.96.0-0665b05cb9.patch",
"app-builder-lib@npm:26.0.15": "patch:app-builder-lib@npm%3A26.0.15#~/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch"
},
"packageManager": "yarn@4.6.0",
"packageManager": "yarn@4.9.1",
"lint-staged": {
"*.{js,jsx,ts,tsx,cjs,mjs,cts,mts}": [
"prettier --write",

View File

@@ -1 +0,0 @@
# Cherry Studio Artifacts

View File

@@ -1,19 +0,0 @@
{
"name": "@cherry-studio/artifacts",
"version": "0.1.0",
"description": "Cherry Studio Artifacts",
"main": "index.js",
"homepage": "https://github.com/kangfenmao/cherry-studio/blob/main/npm/artifacts",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"artifacts"
],
"author": "kangfenmao",
"license": "ISC"
}

View File

@@ -1,108 +0,0 @@
:root {
/* 莫兰迪色系:使用柔和、低饱和度的颜色 */
--primary-color: #b6b5a7; /* 莫兰迪灰褐色,用于背景文字 */
--secondary-color: #9a8f8f; /* 莫兰迪灰棕色,用于标题背景 */
--accent-color: #c5b4a0; /* 莫兰迪淡棕色,用于强调元素 */
--background-color: #e8e3de; /* 莫兰迪米色,用于页面背景 */
--text-color: #5b5b5b; /* 莫兰迪深灰色,用于主要文字 */
--light-text-color: #8c8c8c; /* 莫兰迪中灰色,用于次要文字 */
--divider-color: #d1cbc3; /* 莫兰迪浅灰色,用于分隔线 */
}
body,
html {
margin: 0;
padding: 0;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--background-color); /* 使用莫兰迪米色作为页面背景 */
font-family: 'Noto Sans SC', sans-serif;
color: var(--text-color); /* 使用莫兰迪深灰色作为主要文字颜色 */
}
.card {
width: 300px;
height: 500px;
background-color: #f2ede9; /* 莫兰迪浅米色,用于卡片背景 */
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
}
.header {
background-color: var(--secondary-color); /* 使用莫兰迪灰棕色作为标题背景 */
color: #f2ede9; /* 浅色文字与深色背景形成对比 */
padding: 20px;
text-align: left;
position: relative;
z-index: 1;
}
h1 {
font-family: 'Noto Serif SC', serif;
font-size: 20px;
margin: 0;
font-weight: 700;
}
.content {
padding: 30px 20px;
display: flex;
flex-direction: column;
flex-grow: 1;
}
.word {
text-align: left;
margin-bottom: 20px;
}
.word-main {
font-family: 'Noto Serif SC', serif;
font-size: 36px;
color: var(--text-color); /* 使用莫兰迪深灰色作为主要词汇颜色 */
margin-bottom: 10px;
position: relative;
}
.word-main::after {
content: '';
position: absolute;
left: 0;
bottom: -5px;
width: 50px;
height: 3px;
background-color: var(--accent-color); /* 使用莫兰迪淡棕色作为下划线 */
}
.word-sub {
font-size: 14px;
color: var(--light-text-color); /* 使用莫兰迪中灰色作为次要文字颜色 */
margin: 5px 0;
}
.divider {
width: 100%;
height: 1px;
background-color: var(--divider-color); /* 使用莫兰迪浅灰色作为分隔线 */
margin: 20px 0;
}
.explanation {
font-size: 18px;
line-height: 1.6;
text-align: left;
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.quote {
position: relative;
padding-left: 20px;
border-left: 3px solid var(--accent-color); /* 使用莫兰迪淡棕色作为引用边框 */
}
.background-text {
position: absolute;
font-size: 150px;
color: rgba(182, 181, 167, 0.15); /* 使用莫兰迪灰褐色的透明版本作为背景文字 */
z-index: 0;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-weight: bold;
}

View File

@@ -1,3 +0,0 @@
data/*
!data/.gitkeep

View File

@@ -1,3 +0,0 @@
# Cherry Studio Database
Cherry Studio 依赖的数据文件由这个数据库来生成,数据库文件请联系开发者获取

View File

@@ -1,13 +0,0 @@
{
"name": "@cherry-studio/database",
"packageManager": "yarn@4.6.0",
"dependencies": {
"csv-parser": "^3.0.0",
"sqlite3": "^5.1.7"
},
"scripts": {
"agents": "node src/agents.js",
"email": "yarn csv && node src/email.js",
"csv": "node src/csv.js"
}
}

View File

@@ -1,47 +0,0 @@
const sqlite3 = require('sqlite3').verbose()
const fs = require('fs')
// 连接到数据库
const db = new sqlite3.Database('./data/CherryStudio.sqlite3', (err) => {
if (err) {
console.error('Error connecting to the database:', err.message)
return
}
console.log('Connected to the database.')
})
// 查询数据并转换为JSON
db.all('SELECT * FROM agents', [], (err, rows) => {
if (err) {
console.error('Error querying the database:', err.message)
return
}
// 将 ID 类型转换为字符串
for (const row of rows) {
row.id = row.id.toString()
row.group = row.group.toString().split(',')
row.group = row.group.map((item) => item.trim().replace('\r\n', ''))
}
// 将查询结果转换为JSON字符串
const jsonData = JSON.stringify(rows, null, 2)
// 将JSON数据写入文件
fs.writeFile('../../src/renderer/src/config/agents.json', jsonData, (err) => {
if (err) {
console.error('Error writing to file:', err.message)
return
}
console.log('Data has been written to agents.json')
})
// 关闭数据库连接
db.close((err) => {
if (err) {
console.error('Error closing the database:', err.message)
return
}
console.log('Database connection closed.')
})
})

View File

@@ -1,77 +0,0 @@
const fs = require('fs')
const csv = require('csv-parser')
const sqlite3 = require('sqlite3').verbose()
// 连接到 SQLite 数据库
const db = new sqlite3.Database('./data/CherryStudio.sqlite3', (err) => {
if (err) {
console.error('Error opening database', err)
return
}
console.log('Connected to the SQLite database.')
})
// 创建一个数组来存储 CSV 数据
const results = []
// 读取 CSV 文件
fs.createReadStream('./data/data.csv')
.pipe(csv())
.on('data', (data) => results.push(data))
.on('end', () => {
// 准备 SQL 插入语句,使用 INSERT OR IGNORE
const stmt = db.prepare('INSERT OR IGNORE INTO emails (email, github, sent) VALUES (?, ?, ?)')
// 插入每一行数据
let inserted = 0
let skipped = 0
let emptyEmail = 0
db.serialize(() => {
// 开始一个事务以提高性能
db.run('BEGIN TRANSACTION')
results.forEach((row) => {
// 检查 email 是否为空
if (!row.email || row.email.trim() === '') {
emptyEmail++
return // 跳过这一行
}
stmt.run(row.email, row['user-href'], 0, function (err) {
if (err) {
console.error('Error inserting row', err)
} else {
if (this.changes === 1) {
inserted++
} else {
skipped++
}
}
})
})
// 提交事务
db.run('COMMIT', (err) => {
if (err) {
console.error('Error committing transaction', err)
} else {
console.log(
`Insertion complete. Inserted: ${inserted}, Skipped (duplicate): ${skipped}, Skipped (empty email): ${emptyEmail}`
)
}
// 完成插入
stmt.finalize()
// 关闭数据库连接
db.close((err) => {
if (err) {
console.error('Error closing database', err)
} else {
console.log('Database connection closed.')
}
})
})
})
})

View File

@@ -1,36 +0,0 @@
const sqlite3 = require('sqlite3').verbose()
// 连接到数据库
const db = new sqlite3.Database('./data/CherryStudio.sqlite3', (err) => {
if (err) {
console.error('Error connecting to the database:', err.message)
return
}
})
// 查询数据并转换为JSON
db.all('SELECT * FROM emails WHERE sent = 0', [], (err, rows) => {
if (err) {
console.error('Error querying the database:', err.message)
return
}
for (const row of rows) {
console.log(row.email)
// Update row set sent = 1
db.run('UPDATE emails SET sent = 1 WHERE id = ?', [row.id], (err) => {
if (err) {
console.error('Error updating the database:', err.message)
return
}
})
}
// 关闭数据库连接
db.close((err) => {
if (err) {
console.error('Error closing the database:', err.message)
return
}
})
})

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
export enum IpcChannel {
App_GetCacheSize = 'app:get-cache-size',
App_ClearCache = 'app:clear-cache',
App_SetLaunchOnBoot = 'app:set-launch-on-boot',
App_SetLanguage = 'app:set-language',
@@ -12,14 +13,17 @@ export enum IpcChannel {
App_SetTrayOnClose = 'app:set-tray-on-close',
App_RestartTray = 'app:restart-tray',
App_SetTheme = 'app:set-theme',
App_SetCustomCss = 'app:set-custom-css',
App_SetAutoUpdate = 'app:set-auto-update',
App_HandleZoomFactor = 'app:handle-zoom-factor',
App_IsBinaryExist = 'app:is-binary-exist',
App_GetBinaryPath = 'app:get-binary-path',
App_InstallUvBinary = 'app:install-uv-binary',
App_InstallBunBinary = 'app:install-bun-binary',
Notification_Send = 'notification:send',
Notification_OnClick = 'notification:on-click',
Webview_SetOpenLinkExternal = 'webview:set-open-link-external',
// Open
@@ -51,6 +55,7 @@ export enum IpcChannel {
Mcp_GetInstallInfo = 'mcp:get-install-info',
Mcp_ServersChanged = 'mcp:servers-changed',
Mcp_ServersUpdated = 'mcp:servers-updated',
Mcp_CheckConnectivity = 'mcp:check-connectivity',
//copilot
Copilot_GetAuthMessage = 'copilot:get-auth-message',
@@ -82,8 +87,6 @@ export enum IpcChannel {
Windows_ResetMinimumSize = 'window:reset-minimum-size',
Windows_SetMinimumSize = 'window:set-minimum-size',
SelectionMenu_Action = 'selection-menu:action',
KnowledgeBase_Create = 'knowledge-base:create',
KnowledgeBase_Reset = 'knowledge-base:reset',
KnowledgeBase_Delete = 'knowledge-base:delete',
@@ -105,12 +108,13 @@ export enum IpcChannel {
File_SelectFolder = 'file:selectFolder',
File_Create = 'file:create',
File_Write = 'file:write',
File_WriteWithId = 'file:writeWithId',
File_SaveImage = 'file:saveImage',
File_Base64Image = 'file:base64Image',
File_Download = 'file:download',
File_Copy = 'file:copy',
File_BinaryFile = 'file:binaryFile',
File_BinaryImage = 'file:binaryImage',
File_Base64File = 'file:base64File',
Fs_Read = 'fs:read',
Export_Word = 'export:word',
@@ -135,8 +139,10 @@ export enum IpcChannel {
System_GetDeviceType = 'system:getDeviceType',
System_GetHostname = 'system:getHostname',
// DevTools
System_ToggleDevTools = 'system:toggleDevTools',
// events
SelectionAction = 'selection-action',
BackupProgress = 'backup-progress',
ThemeChange = 'theme:change',
UpdateDownloadedCancelled = 'update-downloaded-cancelled',
@@ -154,7 +160,6 @@ export enum IpcChannel {
HideMiniWindow = 'hide-mini-window',
ShowMiniWindow = 'show-mini-window',
MiniWindowReload = 'miniwindow-reload',
ReduxStateChange = 'redux-state-change',
ReduxStoreReady = 'redux-store-ready',
@@ -162,5 +167,14 @@ export enum IpcChannel {
// Search Window
SearchWindow_Open = 'search-window:open',
SearchWindow_Close = 'search-window:close',
SearchWindow_OpenUrl = 'search-window:open-url'
SearchWindow_OpenUrl = 'search-window:open-url',
//Store Sync
StoreSync_Subscribe = 'store-sync:subscribe',
StoreSync_Unsubscribe = 'store-sync:unsubscribe',
StoreSync_OnUpdate = 'store-sync:on-update',
StoreSync_BroadcastSync = 'store-sync:broadcast-sync',
// Provider
Provider_AddKey = 'provider:add-key'
}

View File

@@ -134,6 +134,14 @@ export const textExts = [
'.tf' // Technology File
]
export const ZOOM_LEVELS = [0.25, 0.33, 0.5, 0.67, 0.75, 0.8, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2, 2.5, 3, 4, 5]
// 从 ZOOM_LEVELS 生成 Ant Design Select 所需的 options 结构
export const ZOOM_OPTIONS = ZOOM_LEVELS.map((level) => ({
value: level,
label: `${Math.round(level * 100)}%`
}))
export const ZOOM_SHORTCUTS = [
{
key: 'zoom_in',

View File

@@ -15,6 +15,8 @@ const BUN_PACKAGES = {
'darwin-x64': 'bun-darwin-x64.zip',
'win32-x64': 'bun-windows-x64.zip',
'win32-x64-baseline': 'bun-windows-x64-baseline.zip',
'win32-arm64': 'bun-windows-x64.zip',
'win32-arm64-baseline': 'bun-windows-x64-baseline.zip',
'linux-x64': 'bun-linux-x64.zip',
'linux-x64-baseline': 'bun-linux-x64-baseline.zip',
'linux-arm64': 'bun-linux-aarch64.zip',

View File

@@ -1,117 +0,0 @@
import Cocoa
import Foundation
class TextSelectionObserver: NSObject {
let workspace = NSWorkspace.shared
var lastSelectedText: String?
override init() {
super.init()
//
let observer = NSWorkspace.shared.notificationCenter
observer.addObserver(
self,
selector: #selector(handleSelectionChange),
name: NSWorkspace.didActivateApplicationNotification,
object: nil
)
//
var axObserver: AXObserver?
let error = AXObserverCreate(getpid(), { observer, element, notification, userData in
let selfPointer = userData!.load(as: TextSelectionObserver.self)
selfPointer.checkSelectedText()
}, &axObserver)
if error == .success, let axObserver = axObserver {
CFRunLoopAddSource(
RunLoop.main.getCFRunLoop(),
AXObserverGetRunLoopSource(axObserver),
.defaultMode
)
//
updateActiveAppObserver(axObserver)
}
}
@objc func handleSelectionChange(_ notification: Notification) {
//
var axObserver: AXObserver?
let error = AXObserverCreate(getpid(), { _, _, _, _ in }, &axObserver)
if error == .success, let axObserver = axObserver {
updateActiveAppObserver(axObserver)
}
}
func updateActiveAppObserver(_ axObserver: AXObserver) {
guard let app = workspace.frontmostApplication else { return }
let pid = app.processIdentifier
let element = AXUIElementCreateApplication(pid)
//
AXObserverAddNotification(
axObserver,
element,
kAXSelectedTextChangedNotification as CFString,
UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
)
}
func checkSelectedText() {
if let text = getSelectedText() {
if text.count > 0 && text != lastSelectedText {
print(text)
fflush(stdout)
lastSelectedText = text
}
}
}
func getSelectedText() -> String? {
guard let app = NSWorkspace.shared.frontmostApplication else { return nil }
let pid = app.processIdentifier
let axApp = AXUIElementCreateApplication(pid)
var focusedElement: AnyObject?
// Get focused element
let result = AXUIElementCopyAttributeValue(axApp, kAXFocusedUIElementAttribute as CFString, &focusedElement)
guard result == .success else { return nil }
// Try different approaches to get selected text
var selectedText: AnyObject?
// First try: Direct selected text
var textResult = AXUIElementCopyAttributeValue(focusedElement as! AXUIElement, kAXSelectedTextAttribute as CFString, &selectedText)
// Second try: Selected text in text area
if textResult != .success {
var selectedTextRange: AnyObject?
textResult = AXUIElementCopyAttributeValue(focusedElement as! AXUIElement, kAXSelectedTextRangeAttribute as CFString, &selectedTextRange)
if textResult == .success {
textResult = AXUIElementCopyAttributeValue(focusedElement as! AXUIElement, kAXValueAttribute as CFString, &selectedText)
}
}
// Third try: Get selected text from parent element
if textResult != .success {
var parent: AnyObject?
if AXUIElementCopyAttributeValue(focusedElement as! AXUIElement, kAXParentAttribute as CFString, &parent) == .success {
textResult = AXUIElementCopyAttributeValue(parent as! AXUIElement, kAXSelectedTextAttribute as CFString, &selectedText)
}
}
guard textResult == .success, let text = selectedText as? String else { return nil }
return text
}
}
let observer = TextSelectionObserver()
signal(SIGINT) { _ in
exit(0)
}
RunLoop.main.run()

View File

@@ -8,11 +8,6 @@ exports.default = function (buildResult) {
}
let oldFilePath = buildResult.file
if (oldFilePath.includes('-portable') && !oldFilePath.includes('-x64') && !oldFilePath.includes('-arm64')) {
console.log('[artifact build completed] delete portable file:', oldFilePath)
fs.unlinkSync(oldFilePath)
return
}
const newfilePath = oldFilePath.replace(/ /g, '-')
fs.renameSync(oldFilePath, newfilePath)
buildResult.file = newfilePath

View File

@@ -3,7 +3,7 @@ Object.defineProperty(exports, '__esModule', { value: true })
var fs = require('fs')
var path = require('path')
var translationsDir = path.join(__dirname, '../src/renderer/src/i18n/locales')
var baseLocale = 'zh-CN'
var baseLocale = 'en-us'
var baseFileName = ''.concat(baseLocale, '.json')
var baseFilePath = path.join(translationsDir, baseFileName)
/**

View File

@@ -7,7 +7,6 @@ exports.default = async function notarizing(context) {
}
if (!process.env.APPLE_ID || !process.env.APPLE_APP_SPECIFIC_PASSWORD || !process.env.APPLE_TEAM_ID) {
console.log('Skipping notarization')
return
}

View File

@@ -1,16 +1,16 @@
/**
* OCOOL_API_KEY=sk-abcxxxxxxxxxxxxxxxxxxxxxxx123 ts-node scripts/update-i18n.ts
* Paratera_API_KEY=sk-abcxxxxxxxxxxxxxxxxxxxxxxx123 ts-node scripts/update-i18n.ts
*/
// OCOOL API KEY
const OCOOL_API_KEY = process.env.OCOOL_API_KEY
const Paratera_API_KEY = process.env.Paratera_API_KEY
const INDEX = [
// 语言的名称 代码 用来翻译的模型
{ name: 'France', code: 'fr-fr', model: 'qwen2.5-32b-instruct' },
{ name: 'Spanish', code: 'es-es', model: 'qwen2.5-32b-instruct' },
{ name: 'Portuguese', code: 'pt-pt', model: 'qwen2.5-72b-instruct' },
{ name: 'Greek', code: 'el-gr', model: 'qwen-turbo' }
{ name: 'France', code: 'fr-fr', model: 'Qwen3-235B-A22B' },
{ name: 'Spanish', code: 'es-es', model: 'Qwen3-235B-A22B' },
{ name: 'Portuguese', code: 'pt-pt', model: 'Qwen3-235B-A22B' },
{ name: 'Greek', code: 'el-gr', model: 'Qwen3-235B-A22B' }
]
const fs = require('fs')
@@ -19,8 +19,8 @@ import OpenAI from 'openai'
const zh = JSON.parse(fs.readFileSync('src/renderer/src/i18n/locales/zh-cn.json', 'utf8')) as object
const openai = new OpenAI({
apiKey: OCOOL_API_KEY,
baseURL: 'https://one.ocoolai.com/v1'
apiKey: Paratera_API_KEY,
baseURL: 'https://llmapi.paratera.com/v1'
})
// 递归遍历翻译

19
scripts/win-sign.js Normal file
View File

@@ -0,0 +1,19 @@
const { execSync } = require('child_process')
exports.default = async function (configuration) {
if (process.env.WIN_SIGN) {
const { path } = configuration
if (configuration.path) {
try {
console.log('Start code signing...')
console.log('Signing file:', path)
const signCommand = `signtool sign /tr http://timestamp.comodoca.com /td sha256 /fd sha256 /a /v "${path}"`
execSync(signCommand, { stdio: 'inherit' })
console.log('Code signing completed')
} catch (error) {
console.error('Code signing failed:', error)
throw error
}
}
}
}

View File

@@ -12,12 +12,12 @@ export const DATA_PATH = getDataPath()
export const titleBarOverlayDark = {
height: 40,
color: 'rgba(0,0,0,0)',
symbolColor: '#ffffff'
color: 'rgba(255,255,255,0)',
symbolColor: '#fff'
}
export const titleBarOverlayLight = {
height: 40,
color: 'rgba(255,255,255,0)',
symbolColor: '#000000'
symbolColor: '#000'
}

View File

@@ -23,14 +23,14 @@ export default class EmbeddingsFactory {
azureOpenAIApiVersion: apiVersion,
azureOpenAIApiDeploymentName: model,
azureOpenAIApiInstanceName: getInstanceName(baseURL),
dimensions,
// dimensions,
batchSize
})
}
return new OpenAiEmbeddings({
model,
apiKey,
dimensions,
// dimensions,
batchSize,
configuration: { baseURL }
})

View File

@@ -11,7 +11,6 @@ export default class VoyageEmbeddings extends BaseEmbeddings {
if (!this.configuration.outputDimension) {
throw new Error('You need to pass in the optional dimensions parameter for this model')
}
console.log('VoyageEmbeddings', this.configuration)
this.model = new _VoyageEmbeddings(this.configuration)
}
override async getDimensions(): Promise<number> {

View File

@@ -1,10 +1,12 @@
import '@main/config'
import { electronApp, optimizer } from '@electron-toolkit/utils'
import { replaceDevtoolsFont } from '@main/utils/windowUtil'
import { IpcChannel } from '@shared/IpcChannel'
import { app, ipcMain } from 'electron'
import { app } from 'electron'
import installExtension, { REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS } from 'electron-devtools-installer'
import Logger from 'electron-log'
import { isDev } from './constant'
import { registerIpc } from './ipc'
import { configManager } from './services/ConfigManager'
import mcpService from './services/MCPService'
@@ -19,11 +21,29 @@ import { TrayService } from './services/TrayService'
import { windowService } from './services/WindowService'
import { setUserDataDir } from './utils/file'
Logger.initialize()
// in production mode, handle uncaught exception and unhandled rejection globally
if (!isDev) {
// handle uncaught exception
process.on('uncaughtException', (error) => {
Logger.error('Uncaught Exception:', error)
})
// handle unhandled rejection
process.on('unhandledRejection', (reason, promise) => {
Logger.error('Unhandled Rejection at:', promise, 'reason:', reason)
})
}
// Check for single instance lock
if (!app.requestSingleInstanceLock()) {
app.quit()
process.exit(0)
} else {
// Portable dir must be setup before app ready
setUserDataDir()
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
@@ -56,23 +76,14 @@ if (!app.requestSingleInstanceLock()) {
replaceDevtoolsFont(mainWindow)
setUserDataDir()
// Setup deep link for AppImage on Linux
await setupAppImageDeepLink()
if (process.env.NODE_ENV === 'development') {
if (isDev) {
installExtension([REDUX_DEVTOOLS, REACT_DEVELOPER_TOOLS])
.then((name) => console.log(`Added Extension: ${name}`))
.catch((err) => console.log('An error occurred: ', err))
}
ipcMain.handle(IpcChannel.System_GetDeviceType, () => {
return process.platform === 'darwin' ? 'mac' : process.platform === 'win32' ? 'windows' : 'linux'
})
ipcMain.handle(IpcChannel.System_GetHostname, () => {
return require('os').hostname()
})
})
registerProtocolClient(app)

View File

@@ -3,10 +3,12 @@ import { arch } from 'node:os'
import { isMac, isWin } from '@main/constant'
import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process'
import { handleZoomFactor } from '@main/utils/zoom'
import { IpcChannel } from '@shared/IpcChannel'
import { Shortcut, ThemeMode } from '@types'
import { BrowserWindow, ipcMain, nativeTheme, session, shell } from 'electron'
import log from 'electron-log'
import { Notification } from 'src/renderer/src/types/notification'
import { titleBarOverlayDark, titleBarOverlayLight } from './config'
import AppUpdater from './services/AppUpdater'
@@ -19,17 +21,19 @@ import FileStorage from './services/FileStorage'
import { GeminiService } from './services/GeminiService'
import KnowledgeService from './services/KnowledgeService'
import mcpService from './services/MCPService'
import NotificationService from './services/NotificationService'
import * as NutstoreService from './services/NutstoreService'
import ObsidianVaultService from './services/ObsidianVaultService'
import { ProxyConfig, proxyManager } from './services/ProxyManager'
import { searchService } from './services/SearchService'
import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService'
import storeSyncService from './services/StoreSyncService'
import { TrayService } from './services/TrayService'
import { setOpenLinkExternal } from './services/WebviewService'
import { windowService } from './services/WindowService'
import { getResourcePath } from './utils'
import { calculateDirectorySize, getResourcePath } from './utils'
import { decrypt, encrypt } from './utils/aes'
import { getConfigDir, getFilesDir } from './utils/file'
import { getCacheDir, getConfigDir, getFilesDir } from './utils/file'
import { compress, decompress } from './utils/zip'
const fileManager = new FileStorage()
@@ -39,6 +43,7 @@ const obsidianVaultService = new ObsidianVaultService()
export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
const appUpdater = new AppUpdater(mainWindow)
const notificationService = new NotificationService(mainWindow)
ipcMain.handle(IpcChannel.App_Info, () => ({
version: app.getVersion(),
@@ -120,11 +125,21 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
// theme
ipcMain.handle(IpcChannel.App_SetTheme, (_, theme: ThemeMode) => {
const updateTitleBarOverlay = () => {
if (!mainWindow?.setTitleBarOverlay) return
const isDark = nativeTheme.shouldUseDarkColors
mainWindow.setTitleBarOverlay(isDark ? titleBarOverlayDark : titleBarOverlayLight)
}
const broadcastThemeChange = () => {
const isDark = nativeTheme.shouldUseDarkColors
const effectiveTheme = isDark ? ThemeMode.dark : ThemeMode.light
BrowserWindow.getAllWindows().forEach((win) => win.webContents.send(IpcChannel.ThemeChange, effectiveTheme))
}
const notifyThemeChange = () => {
const windows = BrowserWindow.getAllWindows()
windows.forEach((win) =>
win.webContents.send(IpcChannel.ThemeChange, nativeTheme.shouldUseDarkColors ? ThemeMode.dark : ThemeMode.light)
)
updateTitleBarOverlay()
broadcastThemeChange()
}
if (theme === ThemeMode.auto) {
@@ -132,29 +147,18 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
nativeTheme.on('updated', notifyThemeChange)
} else {
nativeTheme.themeSource = theme
nativeTheme.removeAllListeners('updated')
nativeTheme.off('updated', notifyThemeChange)
}
mainWindow?.setTitleBarOverlay &&
mainWindow.setTitleBarOverlay(nativeTheme.shouldUseDarkColors ? titleBarOverlayDark : titleBarOverlayLight)
updateTitleBarOverlay()
configManager.setTheme(theme)
notifyThemeChange()
})
// custom css
ipcMain.handle(IpcChannel.App_SetCustomCss, (event, css: string) => {
if (css === configManager.getCustomCss()) return
configManager.setCustomCss(css)
// Broadcast to all windows including the mini window
const senderWindowId = event.sender.id
ipcMain.handle(IpcChannel.App_HandleZoomFactor, (_, delta: number, reset: boolean = false) => {
const windows = BrowserWindow.getAllWindows()
// 向其他窗口广播主题变化
windows.forEach((win) => {
if (win.webContents.id !== senderWindowId) {
win.webContents.send('custom-css:update', css)
}
})
handleZoomFactor(windows, delta, reset)
return configManager.getZoomFactor()
})
// clear cache
@@ -179,15 +183,46 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
}
})
// get cache size
ipcMain.handle(IpcChannel.App_GetCacheSize, async () => {
const cachePath = getCacheDir()
log.info(`Calculating cache size for path: ${cachePath}`)
try {
const sizeInBytes = await calculateDirectorySize(cachePath)
const sizeInMB = (sizeInBytes / (1024 * 1024)).toFixed(2)
return `${sizeInMB}`
} catch (error: any) {
log.error(`Failed to calculate cache size for ${cachePath}: ${error.message}`)
return '0'
}
})
// check for update
ipcMain.handle(IpcChannel.App_CheckForUpdate, async () => {
await appUpdater.checkForUpdates()
})
// notification
ipcMain.handle(IpcChannel.Notification_Send, async (_, notification: Notification) => {
await notificationService.sendNotification(notification)
})
ipcMain.handle(IpcChannel.Notification_OnClick, (_, notification: Notification) => {
mainWindow.webContents.send('notification-click', notification)
})
// zip
ipcMain.handle(IpcChannel.Zip_Compress, (_, text: string) => compress(text))
ipcMain.handle(IpcChannel.Zip_Decompress, (_, text: Buffer) => decompress(text))
// system
ipcMain.handle(IpcChannel.System_GetDeviceType, () => (isMac ? 'mac' : isWin ? 'windows' : 'linux'))
ipcMain.handle(IpcChannel.System_GetHostname, () => require('os').hostname())
ipcMain.handle(IpcChannel.System_ToggleDevTools, (e) => {
const win = BrowserWindow.fromWebContents(e.sender)
win && win.webContents.toggleDevTools()
})
// backup
ipcMain.handle(IpcChannel.Backup_Backup, backupManager.backup)
ipcMain.handle(IpcChannel.Backup_Restore, backupManager.restore)
@@ -211,11 +246,13 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
ipcMain.handle(IpcChannel.File_SelectFolder, fileManager.selectFolder)
ipcMain.handle(IpcChannel.File_Create, fileManager.createTempFile)
ipcMain.handle(IpcChannel.File_Write, fileManager.writeFile)
ipcMain.handle(IpcChannel.File_WriteWithId, fileManager.writeFileWithId)
ipcMain.handle(IpcChannel.File_SaveImage, fileManager.saveImage)
ipcMain.handle(IpcChannel.File_Base64Image, fileManager.base64Image)
ipcMain.handle(IpcChannel.File_Base64File, fileManager.base64File)
ipcMain.handle(IpcChannel.File_Download, fileManager.downloadFile)
ipcMain.handle(IpcChannel.File_Copy, fileManager.copyFile)
ipcMain.handle(IpcChannel.File_BinaryFile, fileManager.binaryFile)
ipcMain.handle(IpcChannel.File_BinaryImage, fileManager.binaryImage)
// fs
ipcMain.handle(IpcChannel.Fs_Read, FileService.readFile)
@@ -293,6 +330,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
ipcMain.handle(IpcChannel.Mcp_ListResources, mcpService.listResources)
ipcMain.handle(IpcChannel.Mcp_GetResource, mcpService.getResource)
ipcMain.handle(IpcChannel.Mcp_GetInstallInfo, mcpService.getInstallInfo)
ipcMain.handle(IpcChannel.Mcp_CheckConnectivity, mcpService.checkMcpConnectivity)
ipcMain.handle(IpcChannel.App_IsBinaryExist, (_, name: string) => isBinaryExists(name))
ipcMain.handle(IpcChannel.App_GetBinaryPath, (_, name: string) => getBinaryPath(name))
@@ -338,4 +376,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
ipcMain.handle(IpcChannel.Webview_SetOpenLinkExternal, (_, webviewId: number, isExternal: boolean) =>
setOpenLinkExternal(webviewId, isExternal)
)
// store sync
storeSyncService.registerIpcHandler()
}

View File

@@ -237,8 +237,7 @@ async function getPoisData(apiKey: string, ids: string[]): Promise<BravePoiRespo
throw new Error(`Brave API error: ${response.status} ${response.statusText}\n${await response.text()}`)
}
const poisResponse = (await response.json()) as BravePoiResponse
return poisResponse
return (await response.json()) as BravePoiResponse
}
async function getDescriptionsData(apiKey: string, ids: string[]): Promise<BraveDescription> {
@@ -257,8 +256,7 @@ async function getDescriptionsData(apiKey: string, ids: string[]): Promise<Brave
throw new Error(`Brave API error: ${response.status} ${response.statusText}\n${await response.text()}`)
}
const descriptionsData = (await response.json()) as BraveDescription
return descriptionsData
return (await response.json()) as BraveDescription
}
function formatLocalResults(poisData: BravePoiResponse, descData: BraveDescription): string {

View File

@@ -56,7 +56,6 @@ class DifyKnowledgeServer {
private config: DifyKnowledgeServerConfig
constructor(difyKey: string, args: string[]) {
console.log('DifyKnowledgeServer args', args)
if (args.length === 0) {
throw new Error('DifyKnowledgeServer requires at least one argument')
}
@@ -113,8 +112,6 @@ class DifyKnowledgeServer {
const errorDetails = JSON.stringify(parsed.error.format(), null, 2)
throw new Error(`无效的参数:\n${errorDetails}`)
}
console.log('DifyKnowledgeServer search_knowledge parsed', parsed.data)
return await this.performSearchKnowledge(
parsed.data.id,
parsed.data.query,

View File

@@ -209,20 +209,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
const validatedArgs = RequestPayloadSchema.parse(args)
if (request.params.name === 'fetch_html') {
const fetchResult = await Fetcher.html(validatedArgs)
return fetchResult
return await Fetcher.html(validatedArgs)
}
if (request.params.name === 'fetch_json') {
const fetchResult = await Fetcher.json(validatedArgs)
return fetchResult
return await Fetcher.json(validatedArgs)
}
if (request.params.name === 'fetch_txt') {
const fetchResult = await Fetcher.txt(validatedArgs)
return fetchResult
return await Fetcher.txt(validatedArgs)
}
if (request.params.name === 'fetch_markdown') {
const fetchResult = await Fetcher.markdown(validatedArgs)
return fetchResult
return await Fetcher.markdown(validatedArgs)
}
throw new Error('Tool not found')
})

View File

@@ -183,7 +183,6 @@ async function searchFiles(
}
} catch (error) {
// Skip invalid paths during search
continue
}
}
}

View File

@@ -2,6 +2,7 @@ import { getConfigDir } from '@main/utils/file'
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js'
import { Mutex } from 'async-mutex' // 引入 Mutex
import Logger from 'electron-log'
import { promises as fs } from 'fs'
import path from 'path'
@@ -355,9 +356,9 @@ class MemoryServer {
private async _initializeManager(memoryPath: string): Promise<void> {
try {
this.knowledgeGraphManager = await KnowledgeGraphManager.create(memoryPath)
console.log('KnowledgeGraphManager initialized successfully.')
Logger.log('KnowledgeGraphManager initialized successfully.')
} catch (error) {
console.error('Failed to initialize KnowledgeGraphManager:', error)
Logger.error('Failed to initialize KnowledgeGraphManager:', error)
// Server might be unusable, consider how to handle this state
// Maybe set a flag and return errors for all tool calls?
this.knowledgeGraphManager = null // Ensure it's null if init fails

View File

@@ -55,8 +55,8 @@ class SequentialThinkingServer {
const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId } =
thoughtData
let prefix = ''
let context = ''
let prefix: string
let context: string
if (isRevision) {
prefix = chalk.yellow('🔄 Revision')

View File

@@ -17,6 +17,10 @@ export default abstract class BaseReranker {
* Get Rerank Request Url
*/
protected getRerankUrl() {
if (this.base.rerankModelProvider === 'dashscope') {
return 'https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank'
}
let baseURL = this.base?.rerankBaseURL?.endsWith('/')
? this.base.rerankBaseURL.slice(0, -1)
: this.base.rerankBaseURL
@@ -28,6 +32,56 @@ export default abstract class BaseReranker {
return `${baseURL}/rerank`
}
/**
* Get Rerank Request Body
*/
protected getRerankRequestBody(query: string, searchResults: ExtractChunkData[]) {
const provider = this.base.rerankModelProvider
const documents = searchResults.map((doc) => doc.pageContent)
const topN = this.base.documentCount
if (provider === 'voyageai') {
return {
model: this.base.rerankModel,
query,
documents,
top_k: topN
}
} else if (provider === 'dashscope') {
return {
model: this.base.rerankModel,
input: {
query,
documents
},
parameters: {
top_n: topN
}
}
} else {
return {
model: this.base.rerankModel,
query,
documents,
top_n: topN
}
}
}
/**
* Extract Rerank Result
*/
protected extractRerankResult(data: any) {
const provider = this.base.rerankModelProvider
if (provider === 'dashscope') {
return data.output.results
} else if (provider === 'voyageai') {
return data.data
} else {
return data.results
}
}
/**
* Get Rerank Result
* @param searchResults

View File

@@ -1,14 +0,0 @@
import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces'
import { KnowledgeBaseParams } from '@types'
import BaseReranker from './BaseReranker'
export default class DefaultReranker extends BaseReranker {
constructor(base: KnowledgeBaseParams) {
super(base)
}
async rerank(): Promise<ExtractChunkData[]> {
throw new Error('Method not implemented.')
}
}

View File

@@ -4,7 +4,7 @@ import { KnowledgeBaseParams } from '@types'
import BaseReranker from './BaseReranker'
export default class JinaReranker extends BaseReranker {
export default class GeneralReranker extends BaseReranker {
constructor(base: KnowledgeBaseParams) {
super(base)
}
@@ -12,21 +12,15 @@ export default class JinaReranker extends BaseReranker {
public rerank = async (query: string, searchResults: ExtractChunkData[]): Promise<ExtractChunkData[]> => {
const url = this.getRerankUrl()
const requestBody = {
model: this.base.rerankModel,
query,
documents: searchResults.map((doc) => doc.pageContent),
top_n: this.base.topN
}
const requestBody = this.getRerankRequestBody(query, searchResults)
try {
const { data } = await AxiosProxy.axios.post(url, requestBody, { headers: this.defaultHeaders() })
const rerankResults = data.results
const rerankResults = this.extractRerankResult(data)
return this.getRerankResult(searchResults, rerankResults)
} catch (error: any) {
const errorDetails = this.formatErrorMessage(url, error, requestBody)
console.error('Jina Reranker API Error:', errorDetails)
throw new Error(`重排序请求失败: ${error.message}\n请求详情: ${errorDetails}`)
}
}

View File

@@ -1,13 +1,12 @@
import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces'
import { KnowledgeBaseParams } from '@types'
import BaseReranker from './BaseReranker'
import RerankerFactory from './RerankerFactory'
import GeneralReranker from './GeneralReranker'
export default class Reranker {
private sdk: BaseReranker
private sdk: GeneralReranker
constructor(base: KnowledgeBaseParams) {
this.sdk = RerankerFactory.create(base)
this.sdk = new GeneralReranker(base)
}
public async rerank(query: string, searchResults: ExtractChunkData[]): Promise<ExtractChunkData[]> {
return this.sdk.rerank(query, searchResults)

View File

@@ -1,20 +0,0 @@
import { KnowledgeBaseParams } from '@types'
import BaseReranker from './BaseReranker'
import DefaultReranker from './DefaultReranker'
import JinaReranker from './JinaReranker'
import SiliconFlowReranker from './SiliconFlowReranker'
import VoyageReranker from './VoyageReranker'
export default class RerankerFactory {
static create(base: KnowledgeBaseParams): BaseReranker {
if (base.rerankModelProvider === 'silicon') {
return new SiliconFlowReranker(base)
} else if (base.rerankModelProvider === 'jina') {
return new JinaReranker(base)
} else if (base.rerankModelProvider === 'voyageai') {
return new VoyageReranker(base)
}
return new DefaultReranker(base)
}
}

View File

@@ -1,36 +0,0 @@
import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces'
import axiosProxy from '@main/services/AxiosProxy'
import { KnowledgeBaseParams } from '@types'
import BaseReranker from './BaseReranker'
export default class SiliconFlowReranker extends BaseReranker {
constructor(base: KnowledgeBaseParams) {
super(base)
}
public rerank = async (query: string, searchResults: ExtractChunkData[]): Promise<ExtractChunkData[]> => {
const url = this.getRerankUrl()
const requestBody = {
model: this.base.rerankModel,
query,
documents: searchResults.map((doc) => doc.pageContent),
top_n: this.base.topN,
max_chunks_per_doc: this.base.chunkSize,
overlap_tokens: this.base.chunkOverlap
}
try {
const { data } = await axiosProxy.axios.post(url, requestBody, { headers: this.defaultHeaders() })
const rerankResults = data.results
return this.getRerankResult(searchResults, rerankResults)
} catch (error: any) {
const errorDetails = this.formatErrorMessage(url, error, requestBody)
console.error('SiliconFlow Reranker API 错误:', errorDetails)
throw new Error(`重排序请求失败: ${error.message}\n请求详情: ${errorDetails}`)
}
}
}

View File

@@ -1,40 +0,0 @@
import { ExtractChunkData } from '@cherrystudio/embedjs-interfaces'
import axiosProxy from '@main/services/AxiosProxy'
import { KnowledgeBaseParams } from '@types'
import BaseReranker from './BaseReranker'
export default class VoyageReranker extends BaseReranker {
constructor(base: KnowledgeBaseParams) {
super(base)
}
public rerank = async (query: string, searchResults: ExtractChunkData[]): Promise<ExtractChunkData[]> => {
const url = this.getRerankUrl()
const requestBody = {
model: this.base.rerankModel,
query,
documents: searchResults.map((doc) => doc.pageContent),
top_k: this.base.topN,
return_documents: false,
truncation: true
}
try {
const { data } = await axiosProxy.axios.post(url, requestBody, {
headers: {
...this.defaultHeaders()
}
})
const rerankResults = data.data
return this.getRerankResult(searchResults, rerankResults)
} catch (error: any) {
const errorDetails = this.formatErrorMessage(url, error, requestBody)
console.error('Voyage Reranker API Error:', errorDetails)
throw new Error(`重排序请求失败: ${error.message}\n请求详情: ${errorDetails}`)
}
}
}

View File

@@ -4,8 +4,8 @@ import archiver from 'archiver'
import { exec } from 'child_process'
import { app } from 'electron'
import Logger from 'electron-log'
import extract from 'extract-zip'
import * as fs from 'fs-extra'
import StreamZip from 'node-stream-zip'
import * as path from 'path'
import { createClient, CreateDirectoryOptions, FileStat } from 'webdav'
@@ -77,7 +77,8 @@ class BackupManager {
_: Electron.IpcMainInvokeEvent,
fileName: string,
data: string,
destinationPath: string = this.backupDir
destinationPath: string = this.backupDir,
skipBackupFile: boolean = false
): Promise<string> {
const mainWindow = windowService.getMainWindow()
@@ -104,23 +105,30 @@ class BackupManager {
onProgress({ stage: 'writing_data', progress: 20, total: 100 })
// 复制 Data 目录到临时目录
const sourcePath = path.join(app.getPath('userData'), 'Data')
const tempDataDir = path.join(this.tempDir, 'Data')
Logger.log('[BackupManager IPC] ', skipBackupFile)
// 获取源目录总大小
const totalSize = await this.getDirSize(sourcePath)
let copiedSize = 0
if (!skipBackupFile) {
// 复制 Data 目录到临时目录
const sourcePath = path.join(app.getPath('userData'), 'Data')
const tempDataDir = path.join(this.tempDir, 'Data')
// 使用流式复制
await this.copyDirWithProgress(sourcePath, tempDataDir, (size) => {
copiedSize += size
const progress = Math.min(50, Math.floor((copiedSize / totalSize) * 50))
onProgress({ stage: 'copying_files', progress, total: 100 })
})
// 获取源目录总大小
const totalSize = await this.getDirSize(sourcePath)
let copiedSize = 0
await this.setWritableRecursive(tempDataDir)
onProgress({ stage: 'preparing_compression', progress: 50, total: 100 })
// 使用流式复制
await this.copyDirWithProgress(sourcePath, tempDataDir, (size) => {
copiedSize += size
const progress = Math.min(50, Math.floor((copiedSize / totalSize) * 50))
onProgress({ stage: 'copying_files', progress, total: 100 })
})
await this.setWritableRecursive(tempDataDir)
onProgress({ stage: 'preparing_compression', progress: 50, total: 100 })
} else {
Logger.log('[BackupManager] Skip the backup of the file')
await fs.promises.mkdir(path.join(this.tempDir, 'Data')) // 不创建空 Data 目录会导致 restore 失败
}
// 创建输出文件流
const backupedFilePath = path.join(destinationPath, fileName)
@@ -231,15 +239,10 @@ class BackupManager {
Logger.log('[backup] step 1: unzip backup file', this.tempDir)
// 使用 extract-zip 解压
await extract(backupPath, {
dir: this.tempDir,
onEntry: () => {
// 这里可以处理进度,但 extract-zip 不提供总条目数信息
onProgress({ stage: 'extracting', progress: 15, total: 100 })
}
})
onProgress({ stage: 'extracting', progress: 25, total: 100 })
const zip = new StreamZip.async({ file: backupPath })
onProgress({ stage: 'extracting', progress: 15, total: 100 })
await zip.extract(null, this.tempDir)
onProgress({ stage: 'extracted', progress: 25, total: 100 })
Logger.log('[backup] step 2: read data.json')
// 读取 data.json
@@ -252,19 +255,26 @@ class BackupManager {
const sourcePath = path.join(this.tempDir, 'Data')
const destPath = path.join(app.getPath('userData'), 'Data')
// 获取源目录总大小
const totalSize = await this.getDirSize(sourcePath)
let copiedSize = 0
const dataExists = await fs.pathExists(sourcePath)
const dataFiles = dataExists ? await fs.readdir(sourcePath) : []
await this.setWritableRecursive(destPath)
await fs.remove(destPath)
if (dataExists && dataFiles.length > 0) {
// 获取源目录总大小
const totalSize = await this.getDirSize(sourcePath)
let copiedSize = 0
// 使用流式复制
await this.copyDirWithProgress(sourcePath, destPath, (size) => {
copiedSize += size
const progress = Math.min(85, 35 + Math.floor((copiedSize / totalSize) * 50))
onProgress({ stage: 'copying_files', progress, total: 100 })
})
await this.setWritableRecursive(destPath)
await fs.remove(destPath)
// 使用流式复制
await this.copyDirWithProgress(sourcePath, destPath, (size) => {
copiedSize += size
const progress = Math.min(85, 35 + Math.floor((copiedSize / totalSize) * 50))
onProgress({ stage: 'copying_files', progress, total: 100 })
})
} else {
Logger.log('[backup] skipBackupFile is true, skip restoring Data directory')
}
Logger.log('[backup] step 4: clean up temp directory')
// 清理临时目录
@@ -284,11 +294,20 @@ class BackupManager {
async backupToWebdav(_: Electron.IpcMainInvokeEvent, data: string, webdavConfig: WebDavConfig) {
const filename = webdavConfig.fileName || 'cherry-studio.backup.zip'
const backupedFilePath = await this.backup(_, filename, data)
const backupedFilePath = await this.backup(_, filename, data, undefined, webdavConfig.skipBackupFile)
const webdavClient = new WebDav(webdavConfig)
return await webdavClient.putFileContents(filename, fs.createReadStream(backupedFilePath), {
overwrite: true
})
try {
const result = await webdavClient.putFileContents(filename, fs.createReadStream(backupedFilePath), {
overwrite: true
})
// 上传成功后删除本地备份文件
await fs.remove(backupedFilePath)
return result
} catch (error) {
// 上传失败时也删除本地临时文件
await fs.remove(backupedFilePath).catch(() => {})
throw error
}
}
async restoreFromWebdav(_: Electron.IpcMainInvokeEvent, webdavConfig: WebDavConfig) {

View File

@@ -1,118 +0,0 @@
import { debounce, getResourcePath } from '@main/utils'
import { exec } from 'child_process'
import { screen } from 'electron'
import path from 'path'
import { windowService } from './WindowService'
export default class ClipboardMonitor {
private platform: string
private lastText: string
private user32: any
private observer: any
public onTextSelected: (text: string) => void
constructor() {
this.platform = process.platform
this.lastText = ''
this.onTextSelected = debounce((text: string) => this.handleTextSelected(text), 550)
if (this.platform === 'win32') {
this.setupWindows()
} else if (this.platform === 'darwin') {
this.setupMacOS()
}
}
setupMacOS() {
// 使用 Swift 脚本来监听文本选择
const scriptPath = path.join(getResourcePath(), 'textMonitor.swift')
// 启动 Swift 进程来监听文本选择
const process = exec(`swift ${scriptPath}`)
process?.stdout?.on('data', (data: string) => {
console.log('[ClipboardMonitor] MacOS data:', data)
const text = data.toString().trim()
if (text && text !== this.lastText) {
this.lastText = text
this.onTextSelected(text)
}
})
process.on('error', (error) => {
console.error('[ClipboardMonitor] MacOS error:', error)
})
}
setupWindows() {
// 使用 Windows API 监听文本选择事件
const ffi = require('ffi-napi')
const ref = require('ref-napi')
this.user32 = new ffi.Library('user32', {
SetWinEventHook: ['pointer', ['uint32', 'uint32', 'pointer', 'pointer', 'uint32', 'uint32', 'uint32']],
UnhookWinEvent: ['bool', ['pointer']]
})
// 定义事件常量
const EVENT_OBJECT_SELECTION = 0x8006
const WINEVENT_OUTOFCONTEXT = 0x0000
const WINEVENT_SKIPOWNTHREAD = 0x0001
const WINEVENT_SKIPOWNPROCESS = 0x0002
// 创建回调函数
const callback = ffi.Callback('void', ['pointer', 'uint32', 'pointer', 'long', 'long', 'uint32', 'uint32'], () => {
this.getSelectedText()
})
// 设置事件钩子
this.observer = this.user32.SetWinEventHook(
EVENT_OBJECT_SELECTION,
EVENT_OBJECT_SELECTION,
ref.NULL,
callback,
0,
0,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNTHREAD | WINEVENT_SKIPOWNPROCESS
)
}
getSelectedText() {
// Get selected text
if (this.platform === 'win32') {
const ref = require('ref-napi')
if (this.user32.OpenClipboard(ref.NULL)) {
// Get clipboard content
const text = this.user32.GetClipboardData(1) // CF_TEXT = 1
this.user32.CloseClipboard()
if (text && text !== this.lastText) {
this.lastText = text
this.onTextSelected(text)
}
}
}
}
private handleTextSelected(text: string) {
if (!text) return
console.log('[ClipboardMonitor] handleTextSelected', text)
windowService.setLastSelectedText(text)
const mousePosition = screen.getCursorScreenPoint()
windowService.showSelectionMenu({
x: mousePosition.x,
y: mousePosition.y + 10
})
}
dispose() {
if (this.platform === 'win32' && this.observer) {
this.user32.UnhookWinEvent(this.observer)
}
}
}

View File

@@ -44,14 +44,6 @@ export class ConfigManager {
this.set(ConfigKeys.Theme, theme)
}
getCustomCss(): string {
return this.store.get('customCss', '') as string
}
setCustomCss(css: string) {
this.store.set('customCss', css)
}
getLaunchToTray(): boolean {
return !!this.get(ConfigKeys.LaunchToTray, false)
}

View File

@@ -0,0 +1,77 @@
import { Menu, MenuItemConstructorOptions } from 'electron'
import { locales } from '../utils/locales'
import { configManager } from './ConfigManager'
class ContextMenu {
public contextMenu(w: Electron.BrowserWindow) {
w.webContents.on('context-menu', (_event, properties) => {
const template: MenuItemConstructorOptions[] = this.createEditMenuItems(properties)
const filtered = template.filter((item) => item.visible !== false)
if (filtered.length > 0) {
const menu = Menu.buildFromTemplate([...filtered, ...this.createInspectMenuItems(w)])
menu.popup()
}
})
}
private createInspectMenuItems(w: Electron.BrowserWindow): MenuItemConstructorOptions[] {
const locale = locales[configManager.getLanguage()]
const { common } = locale.translation
const template: MenuItemConstructorOptions[] = [
{
id: 'inspect',
label: common.inspect,
click: () => {
w.webContents.toggleDevTools()
},
enabled: true
}
]
return template
}
private createEditMenuItems(properties: Electron.ContextMenuParams): MenuItemConstructorOptions[] {
const locale = locales[configManager.getLanguage()]
const { common } = locale.translation
const hasText = properties.selectionText.trim().length > 0
const can = (type: string) => properties.editFlags[`can${type}`] && hasText
const template: MenuItemConstructorOptions[] = [
{
id: 'copy',
label: common.copy,
role: 'copy',
enabled: can('Copy'),
visible: properties.isEditable || hasText
},
{
id: 'paste',
label: common.paste,
role: 'paste',
enabled: properties.editFlags.canPaste,
visible: properties.isEditable
},
{
id: 'cut',
label: common.cut,
role: 'cut',
enabled: can('Cut'),
visible: properties.isEditable
}
]
// remove role from items that are not enabled
// https://github.com/electron/electron/issues/13554
template.forEach((item) => {
if (item.enabled === false) {
item.role = undefined
}
})
return template
}
}
export const contextMenu = new ContextMenu()

View File

@@ -1,5 +1,6 @@
import { AxiosRequestConfig } from 'axios'
import { app, safeStorage } from 'electron'
import Logger from 'electron-log'
import fs from 'fs/promises'
import path from 'path'
@@ -227,10 +228,10 @@ class CopilotService {
try {
await fs.access(this.tokenFilePath)
await fs.unlink(this.tokenFilePath)
console.log('Successfully logged out from Copilot')
Logger.log('Successfully logged out from Copilot')
} catch (error) {
// 文件不存在不是错误,只是记录一下
console.log('Token file not found, nothing to delete')
Logger.log('Token file not found, nothing to delete')
}
} catch (error) {
console.error('Failed to logout:', error)

View File

@@ -83,7 +83,7 @@ export class ExportService {
}
break
case 'text':
runs.push(new TextRun({ text: token.content, bold: isHeaderRow ? true : false }))
runs.push(new TextRun({ text: token.content, bold: isHeaderRow }))
break
case 'strong':
runs.push(new TextRun({ text: token.content, bold: true }))

View File

@@ -28,11 +28,16 @@ class FileStorage {
}
private initStorageDir = (): void => {
if (!fs.existsSync(this.storageDir)) {
fs.mkdirSync(this.storageDir, { recursive: true })
}
if (!fs.existsSync(this.tempDir)) {
fs.mkdirSync(this.tempDir, { recursive: true })
try {
if (!fs.existsSync(this.storageDir)) {
fs.mkdirSync(this.storageDir, { recursive: true })
}
if (!fs.existsSync(this.tempDir)) {
fs.mkdirSync(this.tempDir, { recursive: true })
}
} catch (error) {
logger.error('[FileStorage] Failed to initialize storage directories:', error)
throw error
}
}
@@ -263,7 +268,15 @@ class FileStorage {
}
}
public binaryFile = async (_: Electron.IpcMainInvokeEvent, id: string): Promise<{ data: Buffer; mime: string }> => {
public base64File = async (_: Electron.IpcMainInvokeEvent, id: string): Promise<{ data: string; mime: string }> => {
const filePath = path.join(this.storageDir, id)
const buffer = await fs.promises.readFile(filePath)
const base64 = buffer.toString('base64')
const mime = `application/${path.extname(filePath).slice(1)}`
return { data: base64, mime }
}
public binaryImage = async (_: Electron.IpcMainInvokeEvent, id: string): Promise<{ data: Buffer; mime: string }> => {
const filePath = path.join(this.storageDir, id)
const data = await fs.promises.readFile(filePath)
const mime = `image/${path.extname(filePath).slice(1)}`
@@ -315,7 +328,7 @@ class FileStorage {
fileName: string,
content: string,
options?: SaveDialogOptions
): Promise<string | null> => {
): Promise<string> => {
try {
const result: SaveDialogReturnValue = await dialog.showSaveDialog({
title: '保存文件',
@@ -323,14 +336,18 @@ class FileStorage {
...options
})
if (result.canceled) {
return Promise.reject(new Error('User canceled the save dialog'))
}
if (!result.canceled && result.filePath) {
await writeFileSync(result.filePath, content, { encoding: 'utf-8' })
}
return result.filePath
} catch (err) {
} catch (err: any) {
logger.error('[IPC - Error]', 'An error occurred saving the file:', err)
return null
return Promise.reject('An error occurred saving the file: ' + err?.message)
}
}
@@ -369,7 +386,11 @@ class FileStorage {
}
}
public downloadFile = async (_: Electron.IpcMainInvokeEvent, url: string): Promise<FileType> => {
public downloadFile = async (
_: Electron.IpcMainInvokeEvent,
url: string,
isUseContentType?: boolean
): Promise<FileType> => {
try {
const response = await fetch(url)
if (!response.ok) {
@@ -394,7 +415,7 @@ class FileStorage {
}
// 如果文件名没有后缀根据Content-Type添加后缀
if (!filename.includes('.')) {
if (isUseContentType || !filename.includes('.')) {
const contentType = response.headers.get('Content-Type')
const ext = this.getExtensionFromMimeType(contentType)
filename += ext
@@ -467,6 +488,25 @@ class FileStorage {
throw error
}
}
public writeFileWithId = async (_: Electron.IpcMainInvokeEvent, id: string, content: string): Promise<void> => {
try {
const filePath = path.join(this.storageDir, id)
logger.info('[FileStorage] Writing file:', filePath)
// 确保目录存在
if (!fs.existsSync(this.storageDir)) {
logger.info('[FileStorage] Creating storage directory:', this.storageDir)
fs.mkdirSync(this.storageDir, { recursive: true })
}
await fs.promises.writeFile(filePath, content, 'utf8')
logger.info('[FileStorage] File written successfully:', filePath)
} catch (error) {
logger.error('[FileStorage] Failed to write file:', error)
throw error
}
}
}
export default FileStorage

View File

@@ -8,9 +8,20 @@ export class GeminiService {
private static readonly FILE_LIST_CACHE_KEY = 'gemini_file_list'
private static readonly CACHE_DURATION = 3000
static async uploadFile(_: Electron.IpcMainInvokeEvent, file: FileType, apiKey: string): Promise<File> {
const sdk = new GoogleGenAI({ vertexai: false, apiKey })
const uploadResult = await sdk.files.upload({
static async uploadFile(
_: Electron.IpcMainInvokeEvent,
file: FileType,
{ apiKey, baseURL }: { apiKey: string; baseURL: string }
): Promise<File> {
const sdk = new GoogleGenAI({
vertexai: false,
apiKey,
httpOptions: {
baseUrl: baseURL
}
})
return await sdk.files.upload({
file: file.path,
config: {
mimeType: 'application/pdf',
@@ -18,7 +29,6 @@ export class GeminiService {
displayName: file.origin_name
}
})
return uploadResult
}
static async base64File(_: Electron.IpcMainInvokeEvent, file: FileType) {

View File

@@ -459,7 +459,7 @@ class KnowledgeService {
{ uniqueId, uniqueIds, base }: { uniqueId: string; uniqueIds: string[]; base: KnowledgeBaseParams }
): Promise<void> => {
const ragApplication = await this.getRagApplication(base)
console.log(`[ KnowledgeService Remove Item UniqueId: ${uniqueId}]`)
Logger.log(`[ KnowledgeService Remove Item UniqueId: ${uniqueId}]`)
for (const id of uniqueIds) {
await ragApplication.deleteLoader(id)
}

View File

@@ -1,15 +1,14 @@
import crypto from 'node:crypto'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { isLinux, isMac, isWin } from '@main/constant'
import { createInMemoryMCPServer } from '@main/mcpServers/factory'
import { makeSureDirExists } from '@main/utils'
import { buildFunctionCallToolName } from '@main/utils/mcp'
import { getBinaryName, getBinaryPath } from '@main/utils/process'
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { SSEClientTransport, SSEClientTransportOptions } from '@modelcontextprotocol/sdk/client/sse.js'
import { getDefaultEnvironment, StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
import {
StreamableHTTPClientTransport,
type StreamableHTTPClientTransportOptions
@@ -33,6 +32,7 @@ import { memoize } from 'lodash'
import { CacheService } from './CacheService'
import { CallBackServer } from './mcp/oauth/callback'
import { McpOAuthClientProvider } from './mcp/oauth/provider'
import getLoginShellEnvironment from './mcp/shell-env'
// Generic type for caching wrapped functions
type CachedFunction<T extends unknown[], R> = (...args: T) => Promise<R>
@@ -70,17 +70,7 @@ function withCache<T extends unknown[], R>(
class McpService {
private clients: Map<string, Client> = new Map()
private getServerKey(server: MCPServer): string {
return JSON.stringify({
baseUrl: server.baseUrl,
command: server.command,
args: server.args,
registryUrl: server.registryUrl,
env: server.env,
id: server.id
})
}
private pendingClients: Map<string, Promise<Client>> = new Map()
constructor() {
this.initClient = this.initClient.bind(this)
@@ -97,9 +87,26 @@ class McpService {
this.cleanup = this.cleanup.bind(this)
}
private getServerKey(server: MCPServer): string {
return JSON.stringify({
baseUrl: server.baseUrl,
command: server.command,
args: server.args,
registryUrl: server.registryUrl,
env: server.env,
id: server.id
})
}
async initClient(server: MCPServer): Promise<Client> {
const serverKey = this.getServerKey(server)
// If there's a pending initialization, wait for it
const pendingClient = this.pendingClients.get(serverKey)
if (pendingClient) {
return pendingClient
}
// Check if we already have a client for this server configuration
const existingClient = this.clients.get(serverKey)
if (existingClient) {
@@ -114,194 +121,232 @@ class McpService {
} else {
return existingClient
}
} catch (error) {
Logger.error(`[MCP] Error pinging server ${server.name}:`, error)
} catch (error: any) {
Logger.error(`[MCP] Error pinging server ${server.name}:`, error?.message)
this.clients.delete(serverKey)
}
}
// Create new client instance for each connection
const client = new Client({ name: 'Cherry Studio', version: app.getVersion() }, { capabilities: {} })
const args = [...(server.args || [])]
// Create a promise for the initialization process
const initPromise = (async () => {
try {
// Create new client instance for each connection
const client = new Client({ name: 'Cherry Studio', version: app.getVersion() }, { capabilities: {} })
// let transport: StdioClientTransport | SSEClientTransport | InMemoryTransport | StreamableHTTPClientTransport
const authProvider = new McpOAuthClientProvider({
serverUrlHash: crypto
.createHash('md5')
.update(server.baseUrl || '')
.digest('hex')
})
const args = [...(server.args || [])]
const initTransport = async (): Promise<
StdioClientTransport | SSEClientTransport | InMemoryTransport | StreamableHTTPClientTransport
> => {
// Create appropriate transport based on configuration
if (server.type === 'inMemory') {
Logger.info(`[MCP] Using in-memory transport for server: ${server.name}`)
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair()
// start the in-memory server with the given name and environment variables
const inMemoryServer = createInMemoryMCPServer(server.name, args, server.env || {})
try {
await inMemoryServer.connect(serverTransport)
Logger.info(`[MCP] In-memory server started: ${server.name}`)
} catch (error: Error | any) {
Logger.error(`[MCP] Error starting in-memory server: ${error}`)
throw new Error(`Failed to start in-memory server: ${error.message}`)
}
// set the client transport to the client
return clientTransport
} else if (server.baseUrl) {
if (server.type === 'streamableHttp') {
const options: StreamableHTTPClientTransportOptions = {
requestInit: {
headers: server.headers || {}
},
authProvider
}
return new StreamableHTTPClientTransport(new URL(server.baseUrl!), options)
} else if (server.type === 'sse') {
const options: SSEClientTransportOptions = {
eventSourceInit: {
fetch: (url, init) => fetch(url, { ...init, headers: server.headers || {} })
},
requestInit: {
headers: server.headers || {}
},
authProvider
}
return new SSEClientTransport(new URL(server.baseUrl!), options)
} else {
throw new Error('Invalid server type')
}
} else if (server.command) {
let cmd = server.command
if (server.command === 'npx') {
cmd = await getBinaryPath('bun')
Logger.info(`[MCP] Using command: ${cmd}`)
// add -x to args if args exist
if (args && args.length > 0) {
if (!args.includes('-y')) {
!args.includes('-y') && args.unshift('-y')
}
if (!args.includes('x')) {
args.unshift('x')
}
}
if (server.registryUrl) {
server.env = {
...server.env,
NPM_CONFIG_REGISTRY: server.registryUrl
}
// if the server name is mcp-auto-install, use the mcp-registry.json file in the bin directory
if (server.name.includes('mcp-auto-install')) {
const binPath = await getBinaryPath()
makeSureDirExists(binPath)
server.env.MCP_REGISTRY_PATH = path.join(binPath, '..', 'config', 'mcp-registry.json')
}
}
} else if (server.command === 'uvx' || server.command === 'uv') {
cmd = await getBinaryPath(server.command)
if (server.registryUrl) {
server.env = {
...server.env,
UV_DEFAULT_INDEX: server.registryUrl,
PIP_INDEX_URL: server.registryUrl
}
}
}
Logger.info(`[MCP] Starting server with command: ${cmd} ${args ? args.join(' ') : ''}`)
// Logger.info(`[MCP] Environment variables for server:`, server.env)
const stdioTransport = new StdioClientTransport({
command: cmd,
args,
env: {
...getDefaultEnvironment(),
PATH: await this.getEnhancedPath(process.env.PATH || ''),
...server.env
},
stderr: 'pipe'
// let transport: StdioClientTransport | SSEClientTransport | InMemoryTransport | StreamableHTTPClientTransport
const authProvider = new McpOAuthClientProvider({
serverUrlHash: crypto
.createHash('md5')
.update(server.baseUrl || '')
.digest('hex')
})
stdioTransport.stderr?.on('data', (data) =>
Logger.info(`[MCP] Stdio stderr for server: ${server.name} `, data.toString())
)
return stdioTransport
} else {
throw new Error('Either baseUrl or command must be provided')
}
}
const handleAuth = async (client: Client, transport: SSEClientTransport | StreamableHTTPClientTransport) => {
Logger.info(`[MCP] Starting OAuth flow for server: ${server.name}`)
// Create an event emitter for the OAuth callback
const events = new EventEmitter()
const initTransport = async (): Promise<
StdioClientTransport | SSEClientTransport | InMemoryTransport | StreamableHTTPClientTransport
> => {
// Create appropriate transport based on configuration
if (server.type === 'inMemory') {
Logger.info(`[MCP] Using in-memory transport for server: ${server.name}`)
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair()
// start the in-memory server with the given name and environment variables
const inMemoryServer = createInMemoryMCPServer(server.name, args, server.env || {})
try {
await inMemoryServer.connect(serverTransport)
Logger.info(`[MCP] In-memory server started: ${server.name}`)
} catch (error: Error | any) {
Logger.error(`[MCP] Error starting in-memory server: ${error}`)
throw new Error(`Failed to start in-memory server: ${error.message}`)
}
// set the client transport to the client
return clientTransport
} else if (server.baseUrl) {
if (server.type === 'streamableHttp') {
const options: StreamableHTTPClientTransportOptions = {
requestInit: {
headers: server.headers || {}
},
authProvider
}
return new StreamableHTTPClientTransport(new URL(server.baseUrl!), options)
} else if (server.type === 'sse') {
const options: SSEClientTransportOptions = {
eventSourceInit: {
fetch: async (url, init) => {
const headers = { ...(server.headers || {}), ...(init?.headers || {}) }
// Create a callback server
const callbackServer = new CallBackServer({
port: authProvider.config.callbackPort,
path: authProvider.config.callbackPath || '/oauth/callback',
events
})
// Get tokens from authProvider to make sure using the latest tokens
if (authProvider && typeof authProvider.tokens === 'function') {
try {
const tokens = await authProvider.tokens()
if (tokens && tokens.access_token) {
headers['Authorization'] = `Bearer ${tokens.access_token}`
}
} catch (error) {
Logger.error('Failed to fetch tokens:', error)
}
}
// Set a timeout to close the callback server
const timeoutId = setTimeout(() => {
Logger.warn(`[MCP] OAuth flow timed out for server: ${server.name}`)
callbackServer.close()
}, 300000) // 5 minutes timeout
return fetch(url, { ...init, headers })
}
},
requestInit: {
headers: server.headers || {}
},
authProvider
}
return new SSEClientTransport(new URL(server.baseUrl!), options)
} else {
throw new Error('Invalid server type')
}
} else if (server.command) {
let cmd = server.command
try {
// Wait for the authorization code
const authCode = await callbackServer.waitForAuthCode()
Logger.info(`[MCP] Received auth code: ${authCode}`)
if (server.command === 'npx') {
cmd = await getBinaryPath('bun')
Logger.info(`[MCP] Using command: ${cmd}`)
// Complete the OAuth flow
await transport.finishAuth(authCode)
// add -x to args if args exist
if (args && args.length > 0) {
if (!args.includes('-y')) {
args.unshift('-y')
}
if (!args.includes('x')) {
args.unshift('x')
}
}
if (server.registryUrl) {
server.env = {
...server.env,
NPM_CONFIG_REGISTRY: server.registryUrl
}
Logger.info(`[MCP] OAuth flow completed for server: ${server.name}`)
// if the server name is mcp-auto-install, use the mcp-registry.json file in the bin directory
if (server.name.includes('mcp-auto-install')) {
const binPath = await getBinaryPath()
makeSureDirExists(binPath)
server.env.MCP_REGISTRY_PATH = path.join(binPath, '..', 'config', 'mcp-registry.json')
}
}
} else if (server.command === 'uvx' || server.command === 'uv') {
cmd = await getBinaryPath(server.command)
if (server.registryUrl) {
server.env = {
...server.env,
UV_DEFAULT_INDEX: server.registryUrl,
PIP_INDEX_URL: server.registryUrl
}
}
}
const newTransport = await initTransport()
// Try to connect again
await client.connect(newTransport)
Logger.info(`[MCP] Starting server with command: ${cmd} ${args ? args.join(' ') : ''}`)
// Logger.info(`[MCP] Environment variables for server:`, server.env)
const loginShellEnv = await this.getLoginShellEnv()
Logger.info(`[MCP] Successfully authenticated with server: ${server.name}`)
} catch (oauthError) {
Logger.error(`[MCP] OAuth authentication failed for server ${server.name}:`, oauthError)
throw new Error(
`OAuth authentication failed: ${oauthError instanceof Error ? oauthError.message : String(oauthError)}`
)
} finally {
// Clear the timeout and close the callback server
clearTimeout(timeoutId)
callbackServer.close()
}
}
// Bun not support proxy https://github.com/oven-sh/bun/issues/16812
if (cmd.endsWith('bun')) {
this.removeProxyEnv(loginShellEnv)
}
try {
const transport = await initTransport()
try {
await client.connect(transport)
} catch (error: Error | any) {
if (error instanceof Error && (error.name === 'UnauthorizedError' || error.message.includes('Unauthorized'))) {
Logger.info(`[MCP] Authentication required for server: ${server.name}`)
await handleAuth(client, transport as SSEClientTransport | StreamableHTTPClientTransport)
} else {
throw error
const stdioTransport = new StdioClientTransport({
command: cmd,
args,
env: {
...loginShellEnv,
...server.env
},
stderr: 'pipe'
})
stdioTransport.stderr?.on('data', (data) =>
Logger.info(`[MCP] Stdio stderr for server: ${server.name} `, data.toString())
)
return stdioTransport
} else {
throw new Error('Either baseUrl or command must be provided')
}
}
const handleAuth = async (client: Client, transport: SSEClientTransport | StreamableHTTPClientTransport) => {
Logger.info(`[MCP] Starting OAuth flow for server: ${server.name}`)
// Create an event emitter for the OAuth callback
const events = new EventEmitter()
// Create a callback server
const callbackServer = new CallBackServer({
port: authProvider.config.callbackPort,
path: authProvider.config.callbackPath || '/oauth/callback',
events
})
// Set a timeout to close the callback server
const timeoutId = setTimeout(() => {
Logger.warn(`[MCP] OAuth flow timed out for server: ${server.name}`)
callbackServer.close()
}, 300000) // 5 minutes timeout
try {
// Wait for the authorization code
const authCode = await callbackServer.waitForAuthCode()
Logger.info(`[MCP] Received auth code: ${authCode}`)
// Complete the OAuth flow
await transport.finishAuth(authCode)
Logger.info(`[MCP] OAuth flow completed for server: ${server.name}`)
const newTransport = await initTransport()
// Try to connect again
await client.connect(newTransport)
Logger.info(`[MCP] Successfully authenticated with server: ${server.name}`)
} catch (oauthError) {
Logger.error(`[MCP] OAuth authentication failed for server ${server.name}:`, oauthError)
throw new Error(
`OAuth authentication failed: ${oauthError instanceof Error ? oauthError.message : String(oauthError)}`
)
} finally {
// Clear the timeout and close the callback server
clearTimeout(timeoutId)
callbackServer.close()
}
}
try {
const transport = await initTransport()
try {
await client.connect(transport)
} catch (error: Error | any) {
if (
error instanceof Error &&
(error.name === 'UnauthorizedError' || error.message.includes('Unauthorized'))
) {
Logger.info(`[MCP] Authentication required for server: ${server.name}`)
await handleAuth(client, transport as SSEClientTransport | StreamableHTTPClientTransport)
} else {
throw error
}
}
// Store the new client in the cache
this.clients.set(serverKey, client)
Logger.info(`[MCP] Activated server: ${server.name}`)
return client
} catch (error: any) {
Logger.error(`[MCP] Error activating server ${server.name}:`, error?.message)
throw new Error(`[MCP] Error activating server ${server.name}: ${error.message}`)
}
} finally {
// Clean up the pending promise when done
this.pendingClients.delete(serverKey)
}
})()
// Store the new client in the cache
this.clients.set(serverKey, client)
// Store the pending promise
this.pendingClients.set(serverKey, initPromise)
Logger.info(`[MCP] Activated server: ${server.name}`)
return client
} catch (error: any) {
Logger.error(`[MCP] Error activating server ${server.name}:`, error)
throw new Error(`[MCP] Error activating server ${server.name}: ${error.message}`)
}
return initPromise
}
async closeClient(serverKey: string) {
@@ -343,12 +388,32 @@ class McpService {
for (const [key] of this.clients) {
try {
await this.closeClient(key)
} catch (error) {
Logger.error(`[MCP] Failed to close client: ${error}`)
} catch (error: any) {
Logger.error(`[MCP] Failed to close client: ${error?.message}`)
}
}
}
/**
* Check connectivity for an MCP server
*/
public async checkMcpConnectivity(_: Electron.IpcMainInvokeEvent, server: MCPServer): Promise<boolean> {
Logger.info(`[MCP] Checking connectivity for server: ${server.name}`)
try {
const client = await this.initClient(server)
// Attempt to list tools as a way to check connectivity
await client.listTools()
Logger.info(`[MCP] Connectivity check successful for server: ${server.name}`)
return true
} catch (error) {
Logger.error(`[MCP] Connectivity check failed for server: ${server.name}`, error)
// Close the client if connectivity check fails to ensure a clean state for the next attempt
const serverKey = this.getServerKey(server)
await this.closeClient(serverKey)
return false
}
}
private async listToolsImpl(server: MCPServer): Promise<MCPTool[]> {
Logger.info(`[MCP] Listing tools for server: ${server.name}`)
const client = await this.initClient(server)
@@ -358,15 +423,15 @@ class McpService {
tools.map((tool: any) => {
const serverTool: MCPTool = {
...tool,
id: `f${nanoid()}`,
id: buildFunctionCallToolName(server.name, tool.name),
serverId: server.id,
serverName: server.name
}
serverTools.push(serverTool)
})
return serverTools
} catch (error) {
Logger.error(`[MCP] Failed to list tools for server: ${server.name}`, error)
} catch (error: any) {
Logger.error(`[MCP] Failed to list tools for server: ${server.name}`, error?.message)
return []
}
}
@@ -385,6 +450,35 @@ class McpService {
return cachedListTools(server)
}
/**
* Apply default parameters to tool arguments
*/
private applyDefaultParameters(
toolName: string,
server: MCPServer,
providedArgs: Record<string, any> = {}
): Record<string, any> {
const toolConfig = server.customToolConfigs?.find((config) => config.toolName === toolName)
if (!toolConfig) {
return providedArgs
}
const mergedArgs = { ...providedArgs }
toolConfig.parameters.forEach((paramConfig) => {
if (
paramConfig.defaultValue !== undefined &&
paramConfig.defaultValue !== null &&
paramConfig.defaultValue !== ''
) {
mergedArgs[paramConfig.name] = paramConfig.defaultValue
}
})
return mergedArgs
}
/**
* Call a tool on an MCP server
*/
@@ -401,8 +495,13 @@ class McpService {
Logger.error('[MCP] args parse error', args)
}
}
Logger.info('[MCP] Calling with args:', server.name, name, args)
const mergedArgs = this.applyDefaultParameters(name, server, args || {})
Logger.info('[MCP] Calling with merged args:', server.name, name, mergedArgs)
const client = await this.initClient(server)
const result = await client.callTool({ name, arguments: args }, undefined, {
const result = await client.callTool({ name, arguments: mergedArgs }, undefined, {
timeout: server.timeout ? server.timeout * 1000 : 60000 // Default timeout of 1 minute
})
return result as MCPCallToolResponse
@@ -425,19 +524,21 @@ class McpService {
* List prompts available on an MCP server
*/
private async listPromptsImpl(server: MCPServer): Promise<MCPPrompt[]> {
Logger.info(`[MCP] Listing prompts for server: ${server.name}`)
const client = await this.initClient(server)
Logger.info(`[MCP] Listing prompts for server: ${server.name}`)
try {
const { prompts } = await client.listPrompts()
const serverPrompts = prompts.map((prompt: any) => ({
return prompts.map((prompt: any) => ({
...prompt,
id: `p${nanoid()}`,
serverId: server.id,
serverName: server.name
}))
return serverPrompts
} catch (error) {
Logger.error(`[MCP] Failed to list prompts for server: ${server.name}`, error)
} catch (error: any) {
// -32601 is the code for the method not found
if (error?.code !== -32601) {
Logger.error(`[MCP] Failed to list prompts for server: ${server.name}`, error?.message)
}
return []
}
}
@@ -495,8 +596,8 @@ class McpService {
* List resources available on an MCP server (implementation)
*/
private async listResourcesImpl(server: MCPServer): Promise<MCPResource[]> {
Logger.info(`[MCP] Listing resources for server: ${server.name}`)
const client = await this.initClient(server)
Logger.info(`[MCP] Listing resources for server: ${server.name}`)
try {
const result = await client.listResources()
const resources = result.resources || []
@@ -506,8 +607,11 @@ class McpService {
serverName: server.name
}))
return serverResources
} catch (error) {
Logger.error(`[MCP] Failed to list resources for server: ${server.name}`, error)
} catch (error: any) {
// -32601 is the code for the method not found
if (error?.code !== -32601) {
Logger.error(`[MCP] Failed to list resources for server: ${server.name}`, error?.message)
}
return []
}
}
@@ -550,7 +654,7 @@ class McpService {
contents: contents
}
} catch (error: Error | any) {
Logger.error(`[MCP] Failed to get resource ${uri} from server: ${server.name}`, error)
Logger.error(`[MCP] Failed to get resource ${uri} from server: ${server.name}`, error.message)
throw new Error(`Failed to get resource ${uri} from server: ${server.name}: ${error.message}`)
}
}
@@ -574,164 +678,27 @@ class McpService {
return await cachedGetResource(server, uri)
}
private findPowerShellExecutable() {
const psPath = 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' // Standard WinPS path
const pwshPath = 'C:\\Program Files\\PowerShell\\7\\pwsh.exe'
if (fs.existsSync(psPath)) {
return psPath
private getLoginShellEnv = memoize(async (): Promise<Record<string, string>> => {
try {
const loginEnv = await getLoginShellEnvironment()
const pathSeparator = process.platform === 'win32' ? ';' : ':'
const cherryBinPath = path.join(os.homedir(), '.cherrystudio', 'bin')
loginEnv.PATH = `${loginEnv.PATH}${pathSeparator}${cherryBinPath}`
Logger.info('[MCP] Successfully fetched login shell environment variables:')
return loginEnv
} catch (error) {
Logger.error('[MCP] Failed to fetch login shell environment variables:', error)
return {}
}
if (fs.existsSync(pwshPath)) {
return pwshPath
}
return 'powershell.exe'
}
private getSystemPath = memoize(async (): Promise<string> => {
return new Promise((resolve, reject) => {
let command: string
let shell: string
if (process.platform === 'win32') {
shell = this.findPowerShellExecutable()
command = '$env:PATH'
} else {
// 尝试获取当前用户的默认 shell
let userShell = process.env.SHELL
if (!userShell) {
if (fs.existsSync('/bin/zsh')) {
userShell = '/bin/zsh'
} else if (fs.existsSync('/bin/bash')) {
userShell = '/bin/bash'
} else if (fs.existsSync('/bin/fish')) {
userShell = '/bin/fish'
} else {
userShell = '/bin/sh'
}
}
shell = userShell
// 根据不同的 shell 构建不同的命令
if (userShell.includes('zsh')) {
command =
'source /etc/zshenv 2>/dev/null || true; source ~/.zshenv 2>/dev/null || true; source /etc/zprofile 2>/dev/null || true; source ~/.zprofile 2>/dev/null || true; source /etc/zshrc 2>/dev/null || true; source ~/.zshrc 2>/dev/null || true; source /etc/zlogin 2>/dev/null || true; source ~/.zlogin 2>/dev/null || true; echo $PATH'
} else if (userShell.includes('bash')) {
command =
'source /etc/profile 2>/dev/null || true; source ~/.bash_profile 2>/dev/null || true; source ~/.bash_login 2>/dev/null || true; source ~/.profile 2>/dev/null || true; source ~/.bashrc 2>/dev/null || true; echo $PATH'
} else if (userShell.includes('fish')) {
command =
'source /etc/fish/config.fish 2>/dev/null || true; source ~/.config/fish/config.fish 2>/dev/null || true; source ~/.config/fish/config.local.fish 2>/dev/null || true; echo $PATH'
} else {
// 默认使用 zsh
shell = '/bin/zsh'
command =
'source /etc/zshenv 2>/dev/null || true; source ~/.zshenv 2>/dev/null || true; source /etc/zprofile 2>/dev/null || true; source ~/.zprofile 2>/dev/null || true; source /etc/zshrc 2>/dev/null || true; source ~/.zshrc 2>/dev/null || true; source /etc/zlogin 2>/dev/null || true; source ~/.zlogin 2>/dev/null || true; echo $PATH'
}
}
console.log(`Using shell: ${shell} with command: ${command}`)
const child = require('child_process').spawn(shell, ['-c', command], {
env: { ...process.env },
cwd: app.getPath('home')
})
let path = ''
child.stdout.on('data', (data: Buffer) => {
path += data.toString()
})
child.stderr.on('data', (data: Buffer) => {
console.error('Error getting PATH:', data.toString())
})
child.on('error', (error: Error) => {
reject(new Error(`Failed to get system PATH, ${error.message}`))
})
child.on('close', (code: number) => {
if (code === 0) {
const trimmedPath = path.trim()
resolve(trimmedPath)
} else {
reject(new Error(`Failed to get system PATH, exit code: ${code}`))
}
})
})
})
/**
* Get enhanced PATH including common tool locations
*/
private async getEnhancedPath(originalPath: string): Promise<string> {
let systemPath = ''
try {
systemPath = await this.getSystemPath()
} catch (error) {
Logger.error('[MCP] Failed to get system PATH:', error)
}
// 将原始 PATH 按分隔符分割成数组
const pathSeparator = process.platform === 'win32' ? ';' : ':'
const existingPaths = new Set(
[...systemPath.split(pathSeparator), ...originalPath.split(pathSeparator)].filter(Boolean)
)
const homeDir = process.env.HOME || process.env.USERPROFILE || ''
// 定义要添加的新路径
const newPaths: string[] = []
if (isMac) {
newPaths.push(
'/bin',
'/usr/bin',
'/usr/local/bin',
'/usr/local/sbin',
'/opt/homebrew/bin',
'/opt/homebrew/sbin',
'/usr/local/opt/node/bin',
`${homeDir}/.nvm/current/bin`,
`${homeDir}/.npm-global/bin`,
`${homeDir}/.yarn/bin`,
`${homeDir}/.cargo/bin`,
`${homeDir}/.cherrystudio/bin`,
'/opt/local/bin'
)
}
if (isLinux) {
newPaths.push(
'/bin',
'/usr/bin',
'/usr/local/bin',
`${homeDir}/.nvm/current/bin`,
`${homeDir}/.npm-global/bin`,
`${homeDir}/.yarn/bin`,
`${homeDir}/.cargo/bin`,
`${homeDir}/.cherrystudio/bin`,
'/snap/bin'
)
}
if (isWin) {
newPaths.push(
`${process.env.APPDATA}\\npm`,
`${homeDir}\\AppData\\Local\\Yarn\\bin`,
`${homeDir}\\.cargo\\bin`,
`${homeDir}\\.cherrystudio\\bin`
)
}
// 只添加不存在的路径
newPaths.forEach((path) => {
if (path && !existingPaths.has(path)) {
existingPaths.add(path)
}
})
// 转换回字符串
return Array.from(existingPaths).join(pathSeparator)
private removeProxyEnv(env: Record<string, string>) {
delete env.HTTPS_PROXY
delete env.HTTP_PROXY
delete env.grpc_proxy
delete env.http_proxy
delete env.https_proxy
}
}
const mcpService = new McpService()
export default mcpService
export default new McpService()

View File

@@ -0,0 +1,31 @@
import { BrowserWindow, Notification as ElectronNotification } from 'electron'
import { Notification } from 'src/renderer/src/types/notification'
import icon from '../../../build/icon.png?asset'
class NotificationService {
private window: BrowserWindow
constructor(window: BrowserWindow) {
// Initialize the service
this.window = window
}
public async sendNotification(notification: Notification) {
// 使用 Electron Notification API
const electronNotification = new ElectronNotification({
title: notification.title,
body: notification.message,
icon: icon
})
electronNotification.on('click', () => {
this.window.show()
this.window.webContents.send('notification-click', notification)
})
electronNotification.show()
}
}
export default NotificationService

View File

@@ -32,10 +32,9 @@ interface WebDAVResponse {
}
export async function getNutstoreSSOUrl() {
const url = await createOAuthUrl({
return await createOAuthUrl({
app: 'cherrystudio'
})
return url
}
export async function decryptToken(token: string) {

View File

@@ -1,4 +1,5 @@
import { app } from 'electron'
import Logger from 'electron-log'
import fs from 'fs'
import path from 'path'
@@ -155,7 +156,7 @@ class ObsidianVaultService {
return []
}
console.log('获取Vault文件结构:', vault.name, vault.path)
Logger.log('获取Vault文件结构:', vault.name, vault.path)
return this.getVaultStructure(vault.path)
} catch (error) {
console.error('获取Vault文件结构时发生错误:', error)

View File

@@ -6,6 +6,7 @@ import { promisify } from 'node:util'
import { app } from 'electron'
import Logger from 'electron-log'
import { handleProvidersProtocolUrl } from './urlschema/handle-providers'
import { handleMcpProtocolUrl } from './urlschema/mcp-install'
import { windowService } from './WindowService'
@@ -25,7 +26,6 @@ export function handleProtocolUrl(url: string) {
if (!url) return
// Process the URL that was used to open the app
// The url will be in the format: cherrystudio://data?param1=value1&param2=value2
console.log('Received URL:', url)
// Parse the URL and extract parameters
const urlObj = new URL(url)
@@ -35,6 +35,9 @@ export function handleProtocolUrl(url: string) {
case 'mcp':
handleMcpProtocolUrl(urlObj)
return
case 'providers':
handleProvidersProtocolUrl(urlObj)
return
}
// You can send the data to your renderer process

View File

@@ -1,8 +1,7 @@
import { ProxyConfig as _ProxyConfig, session } from 'electron'
import { socksDispatcher } from 'fetch-socks'
import { getSystemProxy } from 'os-proxy-config'
import { ProxyAgent as GeneralProxyAgent } from 'proxy-agent'
import { ProxyAgent, setGlobalDispatcher } from 'undici'
// import { ProxyAgent, setGlobalDispatcher } from 'undici'
type ProxyMode = 'system' | 'custom' | 'none'
@@ -121,23 +120,22 @@ export class ProxyManager {
return this.config.url || ''
}
setGlobalProxy() {
const proxyUrl = this.config.url
if (proxyUrl) {
const [protocol, address] = proxyUrl.split('://')
const [host, port] = address.split(':')
if (!protocol.includes('socks')) {
setGlobalDispatcher(new ProxyAgent(proxyUrl))
} else {
const dispatcher = socksDispatcher({
port: parseInt(port),
type: protocol === 'socks5' ? 5 : 4,
host: host
})
global[Symbol.for('undici.globalDispatcher.1')] = dispatcher
}
}
}
// setGlobalProxy() {
// const proxyUrl = this.config.url
// if (proxyUrl) {
// const [protocol, address] = proxyUrl.split('://')
// const [host, port] = address.split(':')
// if (!protocol.includes('socks')) {
// setGlobalDispatcher(new ProxyAgent(proxyUrl))
// } else {
// global[Symbol.for('undici.globalDispatcher.1')] = socksDispatcher({
// port: parseInt(port),
// type: protocol === 'socks5' ? 5 : 4,
// host: host
// })
// }
// }
// }
}
export const proxyManager = new ProxyManager()

View File

@@ -191,7 +191,7 @@ export const reduxService = new ReduxService()
try {
// 读取状态
const settings = await reduxService.select('state.settings')
console.log('settings', settings)
Logger.log('settings', settings)
// 派发 action
await reduxService.dispatch({
@@ -201,7 +201,7 @@ export const reduxService = new ReduxService()
// 订阅状态变化
const unsubscribe = await reduxService.subscribe('state.settings.apiKey', (newValue) => {
console.log('API key changed:', newValue)
Logger.log('API key changed:', newValue)
})
// 批量执行 actions
@@ -212,16 +212,16 @@ export const reduxService = new ReduxService()
// 同步方法虽然可能不是最新的数据,但响应更快
const apiKey = reduxService.selectSync('state.settings.apiKey')
console.log('apiKey', apiKey)
Logger.log('apiKey', apiKey)
// 处理保证是最新的数据
const apiKey1 = await reduxService.select('state.settings.apiKey')
console.log('apiKey1', apiKey1)
Logger.log('apiKey1', apiKey1)
// 取消订阅
unsubscribe()
} catch (error) {
console.error('Error:', error)
Logger.error('Error:', error)
}
}
*/

View File

@@ -0,0 +1,57 @@
// import Logger from 'electron-log'
// import { Operator } from 'opendal'
// export default class RemoteStorage {
// public instance: Operator | undefined
// /**
// *
// * @param scheme is the scheme for opendal services. Available value includes "azblob", "azdls", "cos", "gcs", "obs", "oss", "s3", "webdav", "webhdfs", "aliyun-drive", "alluxio", "azfile", "dropbox", "gdrive", "onedrive", "postgresql", "mysql", "redis", "swift", "mongodb", "alluxio", "b2", "seafile", "upyun", "koofr", "yandex-disk"
// * @param options is the options for given opendal services. Valid options depend on the scheme. Checkout https://docs.rs/opendal/latest/opendal/services/index.html for all valid options.
// *
// * For example, use minio as remote storage:
// *
// * ```typescript
// * const storage = new RemoteStorage('s3', {
// * endpoint: 'http://localhost:9000',
// * region: 'us-east-1',
// * bucket: 'testbucket',
// * access_key_id: 'user',
// * secret_access_key: 'password',
// * root: '/path/to/basepath',
// * })
// * ```
// */
// constructor(scheme: string, options?: Record<string, string> | undefined | null) {
// this.instance = new Operator(scheme, options)
// this.putFileContents = this.putFileContents.bind(this)
// this.getFileContents = this.getFileContents.bind(this)
// }
// public putFileContents = async (filename: string, data: string | Buffer) => {
// if (!this.instance) {
// return new Error('RemoteStorage client not initialized')
// }
// try {
// return await this.instance.write(filename, data)
// } catch (error) {
// Logger.error('[RemoteStorage] Error putting file contents:', error)
// throw error
// }
// }
// public getFileContents = async (filename: string) => {
// if (!this.instance) {
// throw new Error('RemoteStorage client not initialized')
// }
// try {
// return await this.instance.read(filename)
// } catch (error) {
// Logger.error('[RemoteStorage] Error getting file contents:', error)
// throw error
// }
// }
// }

View File

@@ -74,8 +74,7 @@ export class SearchService {
})
// Get the page content after ensuring it's fully loaded
const content = await window.webContents.executeJavaScript('document.documentElement.outerHTML')
return content
return await window.webContents.executeJavaScript('document.documentElement.outerHTML')
}
}

View File

@@ -1,3 +1,4 @@
import { handleZoomFactor } from '@main/utils/zoom'
import { Shortcut } from '@types'
import { BrowserWindow, globalShortcut } from 'electron'
import Logger from 'electron-log'
@@ -14,14 +15,11 @@ const windowOnHandlers = new Map<BrowserWindow, { onFocusHandler: () => void; on
function getShortcutHandler(shortcut: Shortcut) {
switch (shortcut.key) {
case 'zoom_in':
return (window: BrowserWindow) => handleZoom(0.1)(window)
return (window: BrowserWindow) => handleZoomFactor([window], 0.1)
case 'zoom_out':
return (window: BrowserWindow) => handleZoom(-0.1)(window)
return (window: BrowserWindow) => handleZoomFactor([window], -0.1)
case 'zoom_reset':
return (window: BrowserWindow) => {
window.webContents.setZoomFactor(1)
configManager.setZoomFactor(1)
}
return (window: BrowserWindow) => handleZoomFactor([window], 0, true)
case 'show_app':
return () => {
windowService.toggleMainWindow()
@@ -39,17 +37,6 @@ function formatShortcutKey(shortcut: string[]): string {
return shortcut.join('+')
}
function handleZoom(delta: number) {
return (window: BrowserWindow) => {
const currentZoom = configManager.getZoomFactor()
const newZoom = Number((currentZoom + delta).toFixed(1))
if (newZoom >= 0.1 && newZoom <= 5.0) {
window.webContents.setZoomFactor(newZoom)
configManager.setZoomFactor(newZoom)
}
}
}
const convertShortcutRecordedByKeyboardEventKeyValueToElectronGlobalShortcutFormat = (
shortcut: string | string[]
): string => {

View File

@@ -0,0 +1,116 @@
import { IpcChannel } from '@shared/IpcChannel'
import type { StoreSyncAction } from '@types'
import { BrowserWindow, ipcMain } from 'electron'
/**
* StoreSyncService class manages Redux store synchronization between multiple windows in the main process
* It uses singleton pattern to ensure only one sync service instance exists in the application
*
* Main features:
* 1. Manages window subscriptions for store sync
* 2. Handles IPC communication for store sync between windows
* 3. Broadcasts Redux actions from one window to all other windows
* 4. Adds metadata to synced actions to prevent infinite sync loops
*/
export class StoreSyncService {
private static instance: StoreSyncService
private windowIds: number[] = []
private isIpcHandlerRegistered = false
private constructor() {
return
}
/**
* Get the singleton instance of StoreSyncService
*/
public static getInstance(): StoreSyncService {
if (!StoreSyncService.instance) {
StoreSyncService.instance = new StoreSyncService()
}
return StoreSyncService.instance
}
/**
* Subscribe a window to store sync
* @param windowId ID of the window to subscribe
*/
public subscribe(windowId: number): void {
if (!this.windowIds.includes(windowId)) {
this.windowIds.push(windowId)
}
}
/**
* Unsubscribe a window from store sync
* @param windowId ID of the window to unsubscribe
*/
public unsubscribe(windowId: number): void {
this.windowIds = this.windowIds.filter((id) => id !== windowId)
}
/**
* Register IPC handlers for store sync communication
* Handles window subscription, unsubscription and action broadcasting
*/
public registerIpcHandler(): void {
if (this.isIpcHandlerRegistered) return
ipcMain.handle(IpcChannel.StoreSync_Subscribe, (event) => {
const windowId = BrowserWindow.fromWebContents(event.sender)?.id
if (windowId) {
this.subscribe(windowId)
}
})
ipcMain.handle(IpcChannel.StoreSync_Unsubscribe, (event) => {
const windowId = BrowserWindow.fromWebContents(event.sender)?.id
if (windowId) {
this.unsubscribe(windowId)
}
})
ipcMain.handle(IpcChannel.StoreSync_OnUpdate, (event, action: StoreSyncAction) => {
const sourceWindowId = BrowserWindow.fromWebContents(event.sender)?.id
if (!sourceWindowId) return
// Broadcast the action to all other windows
this.broadcastToOtherWindows(sourceWindowId, action)
})
this.isIpcHandlerRegistered = true
}
/**
* Broadcast a Redux action to all other windows except the source
* @param sourceWindowId ID of the window that originated the action
* @param action Redux action to broadcast
*/
private broadcastToOtherWindows(sourceWindowId: number, action: StoreSyncAction): void {
// Add metadata to indicate this action came from sync
const syncAction = {
...action,
meta: {
...action.meta,
fromSync: true,
source: `windowId:${sourceWindowId}`
}
}
// Send to all windows except the source
this.windowIds.forEach((windowId) => {
if (windowId !== sourceWindowId) {
const targetWindow = BrowserWindow.fromId(windowId)
if (targetWindow && !targetWindow.isDestroyed()) {
targetWindow.webContents.send(IpcChannel.StoreSync_BroadcastSync, syncAction)
} else {
this.unsubscribe(windowId)
}
}
})
}
}
// Export singleton instance
export default StoreSyncService.getInstance()

View File

@@ -6,12 +6,8 @@ import { session, shell, webContents } from 'electron'
*/
export function initSessionUserAgent() {
const wvSession = session.fromPartition('persist:webview')
const newChromeVersion = '135.0.7049.96'
const originUA = wvSession.getUserAgent()
const newUA = originUA
.replace(/CherryStudio\/\S+\s/, '')
.replace(/Electron\/\S+\s/, '')
.replace(/Chrome\/\d+\.\d+\.\d+\.\d+/, `Chrome/${newChromeVersion}`)
const newUA = originUA.replace(/CherryStudio\/\S+\s/, '').replace(/Electron\/\S+\s/, '')
wvSession.setUserAgent(newUA)
}

View File

@@ -3,15 +3,15 @@ import { isDev, isLinux, isMac, isWin } from '@main/constant'
import { getFilesDir } from '@main/utils/file'
import { IpcChannel } from '@shared/IpcChannel'
import { ThemeMode } from '@types'
import { app, BrowserWindow, ipcMain, Menu, MenuItem, nativeTheme, shell } from 'electron'
import { app, BrowserWindow, nativeTheme, shell } from 'electron'
import Logger from 'electron-log'
import windowStateKeeper from 'electron-window-state'
import { join } from 'path'
import icon from '../../../build/icon.png?asset'
import { titleBarOverlayDark, titleBarOverlayLight } from '../config'
import { locales } from '../utils/locales'
import { configManager } from './ConfigManager'
import { contextMenu } from './ContextMenu'
import { initSessionUserAgent } from './WebviewService'
export class WindowService {
@@ -22,9 +22,7 @@ export class WindowService {
//hacky-fix: store the focused status of mainWindow before miniWindow shows
//to restore the focus status when miniWindow hides
private wasMainWindowFocused: boolean = false
private selectionMenuWindow: BrowserWindow | null = null
private lastSelectedText: string = ''
private contextMenu: Menu | null = null
private lastRendererProcessCrashTime: number = 0
public static getInstance(): WindowService {
if (!WindowService.instance) {
@@ -66,7 +64,7 @@ export class WindowService {
transparent: isMac,
vibrancy: 'sidebar',
visualEffectState: 'active',
titleBarStyle: isLinux ? 'default' : 'hidden',
titleBarStyle: 'hidden',
titleBarOverlay: nativeTheme.shouldUseDarkColors ? titleBarOverlayDark : titleBarOverlayLight,
backgroundColor: isMac ? undefined : nativeTheme.shouldUseDarkColors ? '#181818' : '#FFFFFF',
darkTheme: nativeTheme.shouldUseDarkColors,
@@ -77,7 +75,9 @@ export class WindowService {
sandbox: false,
webSecurity: false,
webviewTag: true,
allowRunningInsecureContent: true
allowRunningInsecureContent: true,
zoomFactor: configManager.getZoomFactor(),
backgroundThrottling: false
}
})
@@ -103,9 +103,32 @@ export class WindowService {
this.setupWindowEvents(mainWindow)
this.setupWebContentsHandlers(mainWindow)
this.setupWindowLifecycleEvents(mainWindow)
this.setupMainWindowMonitor(mainWindow)
this.loadMainWindowContent(mainWindow)
}
private setupMainWindowMonitor(mainWindow: BrowserWindow) {
mainWindow.webContents.on('render-process-gone', (_, details) => {
Logger.error(`Renderer process crashed with: ${JSON.stringify(details)}`)
const currentTime = Date.now()
const lastCrashTime = this.lastRendererProcessCrashTime
this.lastRendererProcessCrashTime = currentTime
if (currentTime - lastCrashTime > 60 * 1000) {
// 如果大于1分钟则重启渲染进程
mainWindow.webContents.reload()
} else {
// 如果小于1分钟则退出应用, 可能是连续crash需要退出应用
app.exit(1)
}
})
mainWindow.webContents.on('unresponsive', () => {
// 在升级到electron 34后可以获取具体js stack trace,目前只打个日志监控下
// https://www.electronjs.org/blog/electron-34-0#unresponsive-renderer-javascript-call-stacks
Logger.error('Renderer process unresponsive')
})
}
private setupMaximize(mainWindow: BrowserWindow, isMaximized: boolean) {
if (isMaximized) {
// 如果是从托盘启动,则需要延迟最大化,否则显示的就不是重启前的最大化窗口了
@@ -118,18 +141,9 @@ export class WindowService {
}
private setupContextMenu(mainWindow: BrowserWindow) {
if (!this.contextMenu) {
const locale = locales[configManager.getLanguage()]
const { common } = locale.translation
this.contextMenu = new Menu()
this.contextMenu.append(new MenuItem({ label: common.copy, role: 'copy' }))
this.contextMenu.append(new MenuItem({ label: common.paste, role: 'paste' }))
this.contextMenu.append(new MenuItem({ label: common.cut, role: 'cut' }))
}
mainWindow.webContents.on('context-menu', () => {
this.contextMenu?.popup()
contextMenu.contextMenu(mainWindow)
app.on('browser-window-created', (_, win) => {
contextMenu.contextMenu(win)
})
// Dangerous API
@@ -138,13 +152,6 @@ export class WindowService {
webPreferences.preload = join(__dirname, '../preload/index.js')
})
}
// Handle webview context menu
mainWindow.webContents.on('did-attach-webview', (_, webContents) => {
webContents.on('context-menu', () => {
this.contextMenu?.popup()
})
})
}
private setupWindowEvents(mainWindow: BrowserWindow) {
@@ -179,6 +186,12 @@ export class WindowService {
mainWindow.webContents.setZoomFactor(configManager.getZoomFactor())
})
// set the zoom factor again when the window is going to restore
// minimize and restore will cause zoom reset
mainWindow.on('restore', () => {
mainWindow.webContents.setZoomFactor(configManager.getZoomFactor())
})
// ARCH: as `will-resize` is only for Win & Mac,
// linux has the same problem, use `resize` listener instead
// but `resize` will fliker the ui
@@ -193,10 +206,21 @@ export class WindowService {
// 当按下Escape键且窗口处于全屏状态时退出全屏
if (input.key === 'Escape' && !input.alt && !input.control && !input.meta && !input.shift) {
if (mainWindow.isFullScreen()) {
event.preventDefault()
mainWindow.setFullScreen(false)
// 获取 shortcuts 配置
const shortcuts = configManager.getShortcuts()
const exitFullscreenShortcut = shortcuts.find((s) => s.key === 'exit_fullscreen')
if (exitFullscreenShortcut == undefined) {
mainWindow.setFullScreen(false)
return
}
if (exitFullscreenShortcut?.enabled) {
event.preventDefault()
mainWindow.setFullScreen(false)
return
}
}
}
return
})
}
@@ -301,7 +325,7 @@ export class WindowService {
/**
* 上述逻辑以下:
* win/linux: 是开启托盘+设置关闭时最小化到托盘的情况
* win/linux: 是"开启托盘+设置关闭时最小化到托盘"的情况
* mac: 任何情况都会到这里因此需要单独处理mac
*/
@@ -424,7 +448,8 @@ export class WindowService {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
webSecurity: false,
webviewTag: true
webviewTag: true,
backgroundThrottling: false
}
})
@@ -462,16 +487,10 @@ export class WindowService {
this.miniWindow?.webContents.send(IpcChannel.ShowMiniWindow)
})
ipcMain.on(IpcChannel.MiniWindowReload, () => {
this.miniWindow?.reload()
})
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
this.miniWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '#/mini')
this.miniWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '/miniWindow.html')
} else {
this.miniWindow.loadFile(join(__dirname, '../renderer/index.html'), {
hash: '#/mini'
})
this.miniWindow.loadFile(join(__dirname, '../renderer/miniWindow.html'))
}
return this.miniWindow
@@ -484,10 +503,6 @@ export class WindowService {
return
}
if (this.selectionMenuWindow && !this.selectionMenuWindow.isDestroyed()) {
this.selectionMenuWindow.hide()
}
if (this.miniWindow && !this.miniWindow.isDestroyed()) {
this.wasMainWindowFocused = this.mainWindow?.isFocused() || false
@@ -534,74 +549,6 @@ export class WindowService {
public setPinMiniWindow(isPinned) {
this.isPinnedMiniWindow = isPinned
}
public showSelectionMenu(bounds: { x: number; y: number }) {
if (this.selectionMenuWindow && !this.selectionMenuWindow.isDestroyed()) {
this.selectionMenuWindow.setPosition(bounds.x, bounds.y)
this.selectionMenuWindow.show()
return
}
const theme = configManager.getTheme()
this.selectionMenuWindow = new BrowserWindow({
width: 280,
height: 40,
x: bounds.x,
y: bounds.y,
show: true,
autoHideMenuBar: true,
transparent: true,
frame: false,
alwaysOnTop: false,
skipTaskbar: true,
backgroundColor: isMac ? undefined : theme === 'dark' ? '#181818' : '#FFFFFF',
resizable: false,
vibrancy: 'popover',
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
webSecurity: false
}
})
// 点击其他地方时隐藏窗口
this.selectionMenuWindow.on('blur', () => {
this.selectionMenuWindow?.hide()
this.miniWindow?.webContents.send(IpcChannel.SelectionAction, {
action: 'home',
selectedText: this.lastSelectedText
})
})
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
this.selectionMenuWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] + '/src/windows/menu/menu.html')
} else {
this.selectionMenuWindow.loadFile(join(__dirname, '../renderer/src/windows/menu/menu.html'))
}
this.setupSelectionMenuEvents()
}
private setupSelectionMenuEvents() {
if (!this.selectionMenuWindow) return
ipcMain.removeHandler(IpcChannel.SelectionMenu_Action)
ipcMain.handle(IpcChannel.SelectionMenu_Action, (_, action) => {
this.selectionMenuWindow?.hide()
this.showMiniWindow()
setTimeout(() => {
this.miniWindow?.webContents.send(IpcChannel.SelectionAction, {
action,
selectedText: this.lastSelectedText
})
}, 100)
})
}
public setLastSelectedText(text: string) {
this.lastSelectedText = text
}
}
export const windowService = WindowService.getInstance()

View File

@@ -44,7 +44,7 @@ export class CallBackServer {
Logger.error('OAuth callback server error:', error)
})
const runningServer = new Promise<http.Server>((resolve, reject) => {
return new Promise<http.Server>((resolve, reject) => {
server.listen(port, () => {
Logger.info(`OAuth callback server listening on port ${port}`)
resolve(server)
@@ -54,7 +54,6 @@ export class CallBackServer {
reject(error)
})
})
return runningServer
}
get getServer(): Promise<http.Server> {

View File

@@ -0,0 +1,120 @@
import { spawn } from 'child_process'
import Logger from 'electron-log'
import os from 'os'
/**
* Spawns a login shell in the user's home directory to capture its environment variables.
* @returns {Promise<Object>} A promise that resolves with an object containing
* the environment variables, or rejects with an error.
*/
function getLoginShellEnvironment(): Promise<Record<string, string>> {
return new Promise((resolve, reject) => {
const homeDirectory = os.homedir()
if (!homeDirectory) {
return reject(new Error("Could not determine user's home directory."))
}
let shellPath = process.env.SHELL
let commandArgs
let shellCommandToGetEnv
const platform = os.platform()
if (platform === 'win32') {
// On Windows, 'cmd.exe' is the common shell.
// The 'set' command lists environment variables.
// We don't typically talk about "login shells" in the same way,
// but cmd will load the user's environment.
shellPath = process.env.COMSPEC || 'cmd.exe'
shellCommandToGetEnv = 'set'
commandArgs = ['/c', shellCommandToGetEnv] // /c Carries out the command specified by string and then terminates
} else {
// For POSIX systems (Linux, macOS)
if (!shellPath) {
// Fallback if process.env.SHELL is not set (less common for interactive users)
// Defaulting to bash, but this might not be the user's actual login shell.
// A more robust solution might involve checking /etc/passwd or similar,
// but that's more complex and often requires higher privileges or native modules.
Logger.warn("process.env.SHELL is not set. Defaulting to /bin/bash. This might not be the user's login shell.")
shellPath = '/bin/bash' // A common default
}
// -l: Make it a login shell. This sources profile files like .profile, .bash_profile, .zprofile etc.
// -i: Make it interactive. Some shells or profile scripts behave differently.
// 'env': The command to print environment variables.
// Using 'env -0' would be more robust for parsing if values contain newlines,
// but requires splitting by null character. For simplicity, we'll use 'env'.
shellCommandToGetEnv = 'env'
commandArgs = ['-ilc', shellCommandToGetEnv] // -i for interactive, -l for login, -c to execute command
}
Logger.log(`[ShellEnv] Spawning shell: ${shellPath} with args: ${commandArgs.join(' ')} in ${homeDirectory}`)
const child = spawn(shellPath, commandArgs, {
cwd: homeDirectory, // Run the command in the user's home directory
detached: true, // Allows the parent to exit independently of the child
stdio: ['ignore', 'pipe', 'pipe'], // stdin, stdout, stderr
shell: false // We are specifying the shell command directly
})
let output = ''
let errorOutput = ''
child.stdout.on('data', (data) => {
output += data.toString()
})
child.stderr.on('data', (data) => {
errorOutput += data.toString()
})
child.on('error', (error) => {
Logger.error(`Failed to start shell process: ${shellPath}`, error)
reject(new Error(`Failed to start shell: ${error.message}`))
})
child.on('close', (code) => {
if (code !== 0) {
const errorMessage = `Shell process exited with code ${code}. Shell: ${shellPath}. Args: ${commandArgs.join(' ')}. CWD: ${homeDirectory}. Stderr: ${errorOutput.trim()}`
Logger.error(errorMessage)
return reject(new Error(errorMessage))
}
if (errorOutput.trim()) {
// Some shells might output warnings or non-fatal errors to stderr
// during profile loading. Log it, but proceed if exit code is 0.
Logger.warn(`Shell process stderr output (even with exit code 0):\n${errorOutput.trim()}`)
}
const env: Record<string, string> = {}
const lines = output.split('\n')
lines.forEach((line) => {
const trimmedLine = line.trim()
if (trimmedLine) {
const separatorIndex = trimmedLine.indexOf('=')
if (separatorIndex > 0) {
// Ensure '=' is present and it's not the first character
const key = trimmedLine.substring(0, separatorIndex)
const value = trimmedLine.substring(separatorIndex + 1)
env[key] = value
}
}
})
if (Object.keys(env).length === 0 && output.length < 100) {
// Arbitrary small length check
// This might indicate an issue if no env vars were parsed or output was minimal
Logger.warn(
'Parsed environment is empty or output was very short. This might indicate an issue with shell execution or environment variable retrieval.'
)
Logger.warn('Raw output from shell:\n', output)
}
env.PATH = env.Path || env.PATH || ''
resolve(env)
})
})
}
export default getLoginShellEnvironment

View File

@@ -0,0 +1,37 @@
import { IpcChannel } from '@shared/IpcChannel'
import Logger from 'electron-log'
import { windowService } from '../WindowService'
export function handleProvidersProtocolUrl(url: URL) {
const params = new URLSearchParams(url.search)
switch (url.pathname) {
case '/api-keys': {
// jsonConfig example:
// {
// "id": "tokenflux",
// "baseUrl": "https://tokenflux.ai/v1",
// "apiKey": "sk-xxxx"
// }
// cherrystudio://providers/api-keys?data={base64Encode(JSON.stringify(jsonConfig))}
const data = params.get('data')
if (data) {
const stringify = Buffer.from(data, 'base64').toString('utf8')
Logger.info('get api keys from urlschema: ', stringify)
const jsonConfig = JSON.parse(stringify)
Logger.info('get api keys from urlschema: ', jsonConfig)
const mainWindow = windowService.getMainWindow()
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send(IpcChannel.Provider_AddKey, jsonConfig)
mainWindow.webContents.executeJavaScript(`window.navigate('/settings/provider?id=${jsonConfig.id}')`)
}
} else {
Logger.error('No data found in URL')
}
break
}
default:
console.error(`Unknown MCP protocol URL: ${url}`)
break
}
}

View File

@@ -81,6 +81,10 @@ export function getConfigDir() {
return path.join(os.homedir(), '.cherrystudio', 'config')
}
export function getCacheDir() {
return path.join(app.getPath('userData'), 'Cache')
}
export function getAppConfigDir(name: string) {
return path.join(getConfigDir(), name)
}

View File

@@ -1,4 +1,5 @@
import fs from 'node:fs'
import fsAsync from 'node:fs/promises'
import path from 'node:path'
import { app } from 'electron'
@@ -52,3 +53,20 @@ export function makeSureDirExists(dir: string) {
fs.mkdirSync(dir, { recursive: true })
}
}
export async function calculateDirectorySize(directoryPath: string): Promise<number> {
let totalSize = 0
const items = await fsAsync.readdir(directoryPath)
for (const item of items) {
const itemPath = path.join(directoryPath, item)
const stats = await fsAsync.stat(itemPath)
if (stats.isFile()) {
totalSize += stats.size
} else if (stats.isDirectory()) {
totalSize += await calculateDirectorySize(itemPath)
}
}
return totalSize
}

34
src/main/utils/mcp.ts Normal file
View File

@@ -0,0 +1,34 @@
export function buildFunctionCallToolName(serverName: string, toolName: string) {
const sanitizedServer = serverName.trim().replace(/-/g, '_')
const sanitizedTool = toolName.trim().replace(/-/g, '_')
// Combine server name and tool name
let name = sanitizedTool
if (!sanitizedTool.includes(sanitizedServer.slice(0, 7))) {
name = `${sanitizedServer.slice(0, 7) || ''}-${sanitizedTool || ''}`
}
// Replace invalid characters with underscores or dashes
// Keep a-z, A-Z, 0-9, underscores and dashes
name = name.replace(/[^a-zA-Z0-9_-]/g, '_')
// Ensure name starts with a letter or underscore (for valid JavaScript identifier)
if (!/^[a-zA-Z]/.test(name)) {
name = `tool-${name}`
}
// Remove consecutive underscores/dashes (optional improvement)
name = name.replace(/[_-]{2,}/g, '_')
// Truncate to 63 characters maximum
if (name.length > 63) {
name = name.slice(0, 63)
}
// Handle edge case: ensure we still have a valid name if truncation left invalid chars at edges
if (name.endsWith('_') || name.endsWith('-')) {
name = name.slice(0, -1)
}
return name
}

View File

@@ -1,5 +1,7 @@
import { BrowserWindow } from 'electron'
import { isDev, isWin } from '../constant'
function isTilingWindowManager() {
if (process.platform === 'darwin') {
return false
@@ -15,31 +17,59 @@ function isTilingWindowManager() {
return tilingSystems.some((system) => desktopEnv?.includes(system))
}
//see: https://github.com/electron/electron/issues/42055#issuecomment-2449365647
export const replaceDevtoolsFont = (browserWindow: BrowserWindow) => {
if (process.platform === 'win32') {
//only for windows and dev, don't do this in production to avoid performance issues
if (isWin && isDev) {
browserWindow.webContents.on('devtools-opened', () => {
const css = `
:root {
--sys-color-base: var(--ref-palette-neutral100);
--source-code-font-family: consolas;
--source-code-font-family: consolas !important;
--source-code-font-size: 12px;
--monospace-font-family: consolas;
--monospace-font-family: consolas !important;
--monospace-font-size: 12px;
--default-font-family: system-ui, sans-serif;
--default-font-size: 12px;
--ref-palette-neutral99: #ffffffff;
}
.-theme-with-dark-background {
.theme-with-dark-background {
--sys-color-base: var(--ref-palette-secondary25);
}
body {
--default-font-family: system-ui,sans-serif;
}`
--default-font-family: system-ui, sans-serif;
}
`
browserWindow.webContents.devToolsWebContents?.executeJavaScript(`
const overriddenStyle = document.createElement('style');
overriddenStyle.innerHTML = '${css.replaceAll('\n', ' ')}';
document.body.append(overriddenStyle);
document.body.classList.remove('platform-windows');`)
document.querySelectorAll('.platform-windows').forEach(el => el.classList.remove('platform-windows'));
addStyleToAutoComplete();
const observer = new MutationObserver((mutationList, observer) => {
for (const mutation of mutationList) {
if (mutation.type === 'childList') {
for (let i = 0; i < mutation.addedNodes.length; i++) {
const item = mutation.addedNodes[i];
if (item.classList.contains('editor-tooltip-host')) {
addStyleToAutoComplete();
}
}
}
}
});
observer.observe(document.body, {childList: true});
function addStyleToAutoComplete() {
document.querySelectorAll('.editor-tooltip-host').forEach(element => {
if (element.shadowRoot.querySelectorAll('[data-key="overridden-dev-tools-font"]').length === 0) {
const overriddenStyle = document.createElement('style');
overriddenStyle.setAttribute('data-key', 'overridden-dev-tools-font');
overriddenStyle.innerHTML = '.cm-tooltip-autocomplete ul[role=listbox] {font-family: consolas !important;}';
element.shadowRoot.append(overriddenStyle);
}
});
}
`)
})
}
}

View File

@@ -9,14 +9,13 @@ const gunzipPromise = util.promisify(zlib.gunzip)
/**
* 压缩字符串
* @param {string} string - 要压缩的 JSON 字符串
* @returns {Promise<Buffer>} 压缩后的 Buffer
* @param str
*/
export async function compress(str) {
try {
const buffer = Buffer.from(str, 'utf-8')
const compressedBuffer = await gzipPromise(buffer)
return compressedBuffer
return await gzipPromise(buffer)
} catch (error) {
logger.error('Compression failed:', error)
throw error

26
src/main/utils/zoom.ts Normal file
View File

@@ -0,0 +1,26 @@
import { BrowserWindow } from 'electron'
import { configManager } from '../services/ConfigManager'
export function handleZoomFactor(wins: BrowserWindow[], delta: number, reset: boolean = false) {
if (reset) {
wins.forEach((win) => {
win.webContents.setZoomFactor(1)
})
configManager.setZoomFactor(1)
return
}
if (delta === 0) {
return
}
const currentZoom = configManager.getZoomFactor()
const newZoom = Number((currentZoom + delta).toFixed(1))
if (newZoom >= 0.5 && newZoom <= 2.0) {
wins.forEach((win) => {
win.webContents.setZoomFactor(newZoom)
})
configManager.setZoomFactor(newZoom)
}
}

View File

@@ -2,7 +2,8 @@ import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces'
import { electronAPI } from '@electron-toolkit/preload'
import { IpcChannel } from '@shared/IpcChannel'
import { FileType, KnowledgeBaseParams, KnowledgeItem, MCPServer, Shortcut, WebDavConfig } from '@types'
import { contextBridge, ipcRenderer, OpenDialogOptions, shell } from 'electron'
import { contextBridge, ipcRenderer, OpenDialogOptions, shell, webUtils } from 'electron'
import { Notification } from 'src/renderer/src/types/notification'
import { CreateDirectoryOptions } from 'webdav'
// Custom APIs for renderer
@@ -19,21 +20,29 @@ const api = {
setTrayOnClose: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTrayOnClose, isActive),
restartTray: () => ipcRenderer.invoke(IpcChannel.App_RestartTray),
setTheme: (theme: 'light' | 'dark' | 'auto') => ipcRenderer.invoke(IpcChannel.App_SetTheme, theme),
setCustomCss: (css: string) => ipcRenderer.invoke(IpcChannel.App_SetCustomCss, css),
handleZoomFactor: (delta: number, reset: boolean = false) =>
ipcRenderer.invoke(IpcChannel.App_HandleZoomFactor, delta, reset),
setAutoUpdate: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetAutoUpdate, isActive),
openWebsite: (url: string) => ipcRenderer.invoke(IpcChannel.Open_Website, url),
getCacheSize: () => ipcRenderer.invoke(IpcChannel.App_GetCacheSize),
clearCache: () => ipcRenderer.invoke(IpcChannel.App_ClearCache),
notification: {
send: (notification: Notification) => ipcRenderer.invoke(IpcChannel.Notification_Send, notification)
},
system: {
getDeviceType: () => ipcRenderer.invoke(IpcChannel.System_GetDeviceType),
getHostname: () => ipcRenderer.invoke(IpcChannel.System_GetHostname)
},
devTools: {
toggle: () => ipcRenderer.invoke(IpcChannel.System_ToggleDevTools)
},
zip: {
compress: (text: string) => ipcRenderer.invoke(IpcChannel.Zip_Compress, text),
decompress: (text: Buffer) => ipcRenderer.invoke(IpcChannel.Zip_Decompress, text)
},
backup: {
backup: (fileName: string, data: string, destinationPath?: string) =>
ipcRenderer.invoke(IpcChannel.Backup_Backup, fileName, data, destinationPath),
backup: (fileName: string, data: string, destinationPath?: string, skipBackupFile?: boolean) =>
ipcRenderer.invoke(IpcChannel.Backup_Backup, fileName, data, destinationPath, skipBackupFile),
restore: (backupPath: string) => ipcRenderer.invoke(IpcChannel.Backup_Restore, backupPath),
backupToWebdav: (data: string, webdavConfig: WebDavConfig) =>
ipcRenderer.invoke(IpcChannel.Backup_BackupToWebdav, data, webdavConfig),
@@ -57,6 +66,7 @@ const api = {
get: (filePath: string) => ipcRenderer.invoke(IpcChannel.File_Get, filePath),
create: (fileName: string) => ipcRenderer.invoke(IpcChannel.File_Create, fileName),
write: (filePath: string, data: Uint8Array | string) => ipcRenderer.invoke(IpcChannel.File_Write, filePath, data),
writeWithId: (id: string, content: string) => ipcRenderer.invoke(IpcChannel.File_WriteWithId, id, content),
open: (options?: OpenDialogOptions) => ipcRenderer.invoke(IpcChannel.File_Open, options),
openPath: (path: string) => ipcRenderer.invoke(IpcChannel.File_OpenPath, path),
save: (path: string, content: string | NodeJS.ArrayBufferView, options?: any) =>
@@ -64,9 +74,11 @@ const api = {
selectFolder: () => ipcRenderer.invoke(IpcChannel.File_SelectFolder),
saveImage: (name: string, data: string) => ipcRenderer.invoke(IpcChannel.File_SaveImage, name, data),
base64Image: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_Base64Image, fileId),
download: (url: string) => ipcRenderer.invoke(IpcChannel.File_Download, url),
download: (url: string, isUseContentType?: boolean) => ipcRenderer.invoke(IpcChannel.File_Download, url, isUseContentType),
copy: (fileId: string, destPath: string) => ipcRenderer.invoke(IpcChannel.File_Copy, fileId, destPath),
binaryFile: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_BinaryFile, fileId)
binaryImage: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_BinaryImage, fileId),
base64File: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_Base64File, fileId),
getPathForFile: (file: File) => webUtils.getPathForFile(file)
},
fs: {
read: (path: string) => ipcRenderer.invoke(IpcChannel.Fs_Read, path)
@@ -104,15 +116,13 @@ const api = {
resetMinimumSize: () => ipcRenderer.invoke(IpcChannel.Windows_ResetMinimumSize)
},
gemini: {
uploadFile: (file: FileType, apiKey: string) => ipcRenderer.invoke(IpcChannel.Gemini_UploadFile, file, apiKey),
uploadFile: (file: FileType, { apiKey, baseURL }: { apiKey: string; baseURL: string }) =>
ipcRenderer.invoke(IpcChannel.Gemini_UploadFile, file, { apiKey, baseURL }),
base64File: (file: FileType) => ipcRenderer.invoke(IpcChannel.Gemini_Base64File, file),
retrieveFile: (file: FileType, apiKey: string) => ipcRenderer.invoke(IpcChannel.Gemini_RetrieveFile, file, apiKey),
listFiles: (apiKey: string) => ipcRenderer.invoke(IpcChannel.Gemini_ListFiles, apiKey),
deleteFile: (fileId: string, apiKey: string) => ipcRenderer.invoke(IpcChannel.Gemini_DeleteFile, fileId, apiKey)
},
selectionMenu: {
action: (action: string) => ipcRenderer.invoke(IpcChannel.SelectionMenu_Action, action)
},
config: {
set: (key: string, value: any) => ipcRenderer.invoke(IpcChannel.Config_Set, key, value),
get: (key: string) => ipcRenderer.invoke(IpcChannel.Config_Get, key)
@@ -143,7 +153,8 @@ const api = {
listResources: (server: MCPServer) => ipcRenderer.invoke(IpcChannel.Mcp_ListResources, server),
getResource: ({ server, uri }: { server: MCPServer; uri: string }) =>
ipcRenderer.invoke(IpcChannel.Mcp_GetResource, { server, uri }),
getInstallInfo: () => ipcRenderer.invoke(IpcChannel.Mcp_GetInstallInfo)
getInstallInfo: () => ipcRenderer.invoke(IpcChannel.Mcp_GetInstallInfo),
checkMcpConnectivity: (server: any) => ipcRenderer.invoke(IpcChannel.Mcp_CheckConnectivity, server)
},
shell: {
openExternal: (url: string, options?: Electron.OpenExternalOptions) => shell.openExternal(url, options)
@@ -158,7 +169,6 @@ const api = {
logout: () => ipcRenderer.invoke(IpcChannel.Copilot_Logout),
getUser: (token: string) => ipcRenderer.invoke(IpcChannel.Copilot_GetUser, token)
},
// Binary related APIs
isBinaryExist: (name: string) => ipcRenderer.invoke(IpcChannel.App_IsBinaryExist, name),
getBinaryPath: (name: string) => ipcRenderer.invoke(IpcChannel.App_GetBinaryPath, name),
@@ -189,6 +199,11 @@ const api = {
webview: {
setOpenLinkExternal: (webviewId: number, isExternal: boolean) =>
ipcRenderer.invoke(IpcChannel.Webview_SetOpenLinkExternal, webviewId, isExternal)
},
storeSync: {
subscribe: () => ipcRenderer.invoke(IpcChannel.StoreSync_Subscribe),
unsubscribe: () => ipcRenderer.invoke(IpcChannel.StoreSync_Unsubscribe),
onUpdate: (action: any) => ipcRenderer.invoke(IpcChannel.StoreSync_OnUpdate, action)
}
}

View File

@@ -0,0 +1,49 @@
import { vi } from 'vitest'
vi.mock('electron-log/renderer', () => {
return {
default: {
info: console.log,
error: console.error,
warn: console.warn,
debug: console.debug,
verbose: console.log,
silly: console.log,
log: console.log,
transports: {
console: {
level: 'info'
}
}
}
}
})
vi.stubGlobal('window', {
electron: {
ipcRenderer: {
on: vi.fn(), // Mocking ipcRenderer.on
send: vi.fn() // Mocking ipcRenderer.send
}
},
api: {
file: {
read: vi.fn().mockResolvedValue('[]'), // Mock file.read to return an empty array (you can customize this)
writeWithId: vi.fn().mockResolvedValue(undefined) // Mock file.writeWithId to do nothing
}
}
})
vi.mock('axios', () => ({
default: {
get: vi.fn().mockResolvedValue({ data: {} }), // Mocking axios GET request
post: vi.fn().mockResolvedValue({ data: {} }) // Mocking axios POST request
// You can add other axios methods like put, delete etc. as needed
}
}))
vi.stubGlobal('window', {
...global.window, // Copy other global properties
addEventListener: vi.fn(), // Mock addEventListener
removeEventListener: vi.fn() // You can also mock removeEventListener if needed
})

View File

@@ -1,42 +1,46 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1, width=device-width" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; connect-src blob: *; script-src 'self' 'unsafe-eval' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data: file: * blob:; frame-src * file:" />
<title>Cherry Studio</title>
<style>
html,
body {
margin: 0;
}
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1, width=device-width" />
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; connect-src blob: *; script-src 'self' 'unsafe-eval' 'unsafe-inline' *; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' *; font-src 'self' data: *; img-src 'self' data: file: * blob:; frame-src * file:" />
<title>Cherry Studio</title>
#spinner {
position: fixed;
width: 100vw;
height: 100vh;
flex-direction: row;
justify-content: center;
align-items: center;
display: none;
}
<style>
html,
body {
margin: 0;
}
#spinner img {
width: 100px;
border-radius: 50px;
}
</style>
</head>
#spinner {
position: fixed;
width: 100vw;
height: 100vh;
flex-direction: row;
justify-content: center;
align-items: center;
display: flex;
}
<body>
<div id="root"></div>
<div id="spinner">
<img src="/src/assets/images/logo.png" />
</div>
<script type="module" src="/src/init.ts"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
#spinner img {
width: 100px;
border-radius: 50px;
}
</style>
</head>
<body>
<div id="root"></div>
<div id="spinner">
<img src="/src/assets/images/logo.png" />
</div>
<script>
console.time('init')
</script>
<script type="module" src="/src/init.ts"></script>
<script type="module" src="/src/entryPoint.tsx"></script>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More