From 06d495c7e183b7221b8c448794a780e3d449f7f5 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Fri, 13 Jun 2025 10:52:25 +0800 Subject: [PATCH 01/23] feat: Enhance AppUpdater for Windows installation directory support (#7135) - Added support for setting the installation directory for the autoUpdater on Windows using NsisUpdater. - Imported the 'path' module to dynamically determine the installation path based on the executable location. - This change improves the updater's functionality and ensures a smoother installation experience for Windows users. Co-authored-by: beyondkmp --- src/main/services/AppUpdater.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/services/AppUpdater.ts b/src/main/services/AppUpdater.ts index 772c885a01..bb87480a64 100644 --- a/src/main/services/AppUpdater.ts +++ b/src/main/services/AppUpdater.ts @@ -5,7 +5,8 @@ import { FeedUrl } from '@shared/config/constant' import { UpdateInfo } from 'builder-util-runtime' import { app, BrowserWindow, dialog } from 'electron' import logger from 'electron-log' -import { AppUpdater as _AppUpdater, autoUpdater } from 'electron-updater' +import { AppUpdater as _AppUpdater, autoUpdater, NsisUpdater } from 'electron-updater' +import path from 'path' import icon from '../../../build/icon.png?asset' import { configManager } from './ConfigManager' @@ -56,6 +57,10 @@ export default class AppUpdater { logger.info('下载完成', releaseInfo) }) + if (isWin) { + ;(autoUpdater as NsisUpdater).installDirectory = path.dirname(app.getPath('exe')) + } + this.autoUpdater = autoUpdater } From 3b3b3c961e375345817736ebc5adefccb60467e4 Mon Sep 17 00:00:00 2001 From: one Date: Fri, 13 Jun 2025 11:02:22 +0800 Subject: [PATCH 02/23] refactor(CodeEditor): remove the right border of gutters (#7137) refactor: remove the right border of gutters --- src/renderer/src/assets/styles/markdown.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/src/assets/styles/markdown.scss b/src/renderer/src/assets/styles/markdown.scss index ea76b47031..0c80d9f68a 100644 --- a/src/renderer/src/assets/styles/markdown.scss +++ b/src/renderer/src/assets/styles/markdown.scss @@ -321,6 +321,7 @@ mjx-container { .cm-gutters { line-height: 1.6; + border-right: none; } .cm-content { From faf14ff10b4fd24129b878016b733a284b7ec1ce Mon Sep 17 00:00:00 2001 From: one Date: Fri, 13 Jun 2025 13:52:50 +0800 Subject: [PATCH 03/23] fix(MermaidPreview): re-render mermaid on display change (#7058) * fix(MermaidPreview): re-render mermaid on display change * test: add tests for MermaidPreview --- .../CodeBlockView/MermaidPreview.tsx | 48 +++- .../__tests__/MermaidPreview.test.tsx | 221 ++++++++++++++++++ 2 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 src/renderer/src/components/__tests__/MermaidPreview.test.tsx diff --git a/src/renderer/src/components/CodeBlockView/MermaidPreview.tsx b/src/renderer/src/components/CodeBlockView/MermaidPreview.tsx index 0928df8d68..d461b2899c 100644 --- a/src/renderer/src/components/CodeBlockView/MermaidPreview.tsx +++ b/src/renderer/src/components/CodeBlockView/MermaidPreview.tsx @@ -22,6 +22,7 @@ const MermaidPreview: React.FC = ({ children, setTools }) => { const diagramId = useRef(`mermaid-${nanoid(6)}`).current const [error, setError] = useState(null) const [isRendering, setIsRendering] = useState(false) + const [isVisible, setIsVisible] = useState(true) // 使用通用图像工具 const { handleZoom, handleCopyImage, handleDownload } = usePreviewToolHandlers(mermaidRef, { @@ -75,10 +76,55 @@ const MermaidPreview: React.FC = ({ children, setTools }) => { [renderMermaid] ) + /** + * 监听可见性变化,用于触发重新渲染。 + * 这是为了解决 `MessageGroup` 组件的 `fold` 布局中被 `display: none` 隐藏的图标无法正确渲染的问题。 + * 监听时向上遍历到第一个有 `fold` className 的父节点为止(也就是目前的 `MessageWrapper`)。 + * FIXME: 将来 mermaid-js 修复此问题后可以移除这里的相关逻辑。 + */ + useEffect(() => { + if (!mermaidRef.current) return + + const checkVisibility = () => { + const element = mermaidRef.current + if (!element) return + + const currentlyVisible = element.offsetParent !== null + setIsVisible(currentlyVisible) + } + + // 初始检查 + checkVisibility() + + const observer = new MutationObserver(() => { + checkVisibility() + }) + + let targetElement = mermaidRef.current.parentElement + while (targetElement) { + observer.observe(targetElement, { + attributes: true, + attributeFilter: ['class', 'style'] + }) + + if (targetElement.className?.includes('fold')) { + break + } + + targetElement = targetElement.parentElement + } + + return () => { + observer.disconnect() + } + }, []) + // 触发渲染 useEffect(() => { if (isLoadingMermaid) return + if (mermaidRef.current?.offsetParent === null) return + if (children) { setIsRendering(true) debouncedRender(children) @@ -90,7 +136,7 @@ const MermaidPreview: React.FC = ({ children, setTools }) => { return () => { debouncedRender.cancel() } - }, [children, isLoadingMermaid, debouncedRender]) + }, [children, isLoadingMermaid, debouncedRender, isVisible]) const isLoading = isLoadingMermaid || isRendering diff --git a/src/renderer/src/components/__tests__/MermaidPreview.test.tsx b/src/renderer/src/components/__tests__/MermaidPreview.test.tsx new file mode 100644 index 0000000000..3f76fc5eb8 --- /dev/null +++ b/src/renderer/src/components/__tests__/MermaidPreview.test.tsx @@ -0,0 +1,221 @@ +import { render, screen, waitFor } from '@testing-library/react' +import { act } from 'react' +import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest' + +import MermaidPreview from '../CodeBlockView/MermaidPreview' + +const mocks = vi.hoisted(() => ({ + useMermaid: vi.fn(), + usePreviewToolHandlers: vi.fn(), + usePreviewTools: vi.fn() +})) + +// Mock hooks +vi.mock('@renderer/hooks/useMermaid', () => ({ + useMermaid: () => mocks.useMermaid() +})) + +vi.mock('@renderer/components/CodeToolbar', () => ({ + usePreviewToolHandlers: () => mocks.usePreviewToolHandlers(), + usePreviewTools: () => mocks.usePreviewTools() +})) + +// Mock nanoid +vi.mock('@reduxjs/toolkit', () => ({ + nanoid: () => 'test-id-123456' +})) + +// Mock lodash debounce +vi.mock('lodash', async () => { + const actual = await import('lodash') + return { + ...actual, + debounce: vi.fn((fn) => { + const debounced = (...args: any[]) => fn(...args) + debounced.cancel = vi.fn() + return debounced + }) + } +}) + +// Mock antd components +vi.mock('antd', () => ({ + Flex: ({ children, vertical, ...props }: any) => ( +
+ {children} +
+ ), + Spin: ({ children, spinning, indicator }: any) => ( +
+ {spinning && indicator} + {children} +
+ ) +})) + +describe('MermaidPreview', () => { + const mockMermaid = { + parse: vi.fn(), + render: vi.fn() + } + + beforeEach(() => { + vi.clearAllMocks() + + mocks.useMermaid.mockReturnValue({ + mermaid: mockMermaid, + isLoading: false, + error: null + }) + + mocks.usePreviewToolHandlers.mockReturnValue({ + handleZoom: vi.fn(), + handleCopyImage: vi.fn(), + handleDownload: vi.fn() + }) + + mocks.usePreviewTools.mockReturnValue({}) + + mockMermaid.parse.mockResolvedValue(true) + mockMermaid.render.mockResolvedValue({ + svg: 'test diagram' + }) + + // Mock MutationObserver + global.MutationObserver = vi.fn().mockImplementation(() => ({ + observe: vi.fn(), + disconnect: vi.fn(), + takeRecords: vi.fn() + })) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + describe('visibility detection', () => { + it('should not render mermaid when element has display: none', async () => { + const mermaidCode = 'graph TD\nA-->B' + + const { container } = render({mermaidCode}) + + // Mock offsetParent to be null (simulating display: none) + const mermaidElement = container.querySelector('.mermaid') + if (mermaidElement) { + Object.defineProperty(mermaidElement, 'offsetParent', { + get: () => null, + configurable: true + }) + } + + // Re-render to trigger the effect + render({mermaidCode}) + + // Should not call mermaid render when offsetParent is null + expect(mockMermaid.render).not.toHaveBeenCalled() + + const svgElement = mermaidElement?.querySelector('svg.flowchart') + expect(svgElement).not.toBeInTheDocument() + }) + + it('should setup MutationObserver to monitor parent elements', () => { + const mermaidCode = 'graph TD\nA-->B' + + render({mermaidCode}) + + expect(global.MutationObserver).toHaveBeenCalledWith(expect.any(Function)) + }) + + it('should observe parent elements up to fold className', () => { + const mermaidCode = 'graph TD\nA-->B' + + // Create a DOM structure that simulates MessageGroup fold layout + const foldContainer = document.createElement('div') + foldContainer.className = 'fold selected' + + const messageWrapper = document.createElement('div') + messageWrapper.className = 'message-wrapper' + + const codeBlock = document.createElement('div') + codeBlock.className = 'code-block' + + foldContainer.appendChild(messageWrapper) + messageWrapper.appendChild(codeBlock) + document.body.appendChild(foldContainer) + + render({mermaidCode}, { + container: codeBlock + }) + + const observerInstance = (global.MutationObserver as Mock).mock.results[0]?.value + expect(observerInstance.observe).toHaveBeenCalled() + + // Cleanup + document.body.removeChild(foldContainer) + }) + + it('should trigger re-render when visibility changes from hidden to visible', async () => { + const mermaidCode = 'graph TD\nA-->B' + + const { container, rerender } = render({mermaidCode}) + + const mermaidElement = container.querySelector('.mermaid') + + // Initially hidden (offsetParent is null) + Object.defineProperty(mermaidElement, 'offsetParent', { + get: () => null, + configurable: true + }) + + // Clear previous calls + mockMermaid.render.mockClear() + + // Re-render with hidden state + rerender({mermaidCode}) + + // Should not render when hidden + expect(mockMermaid.render).not.toHaveBeenCalled() + + // Now make it visible + Object.defineProperty(mermaidElement, 'offsetParent', { + get: () => document.body, + configurable: true + }) + + // Simulate MutationObserver callback + const observerCallback = (global.MutationObserver as Mock).mock.calls[0][0] + act(() => { + observerCallback([]) + }) + + // Re-render to trigger visibility change effect + rerender({mermaidCode}) + + await waitFor(() => { + expect(mockMermaid.render).toHaveBeenCalledWith('mermaid-test-id-123456', mermaidCode, expect.any(Object)) + + const svgElement = mermaidElement?.querySelector('svg.flowchart') + expect(svgElement).toBeInTheDocument() + expect(svgElement).toHaveClass('flowchart') + }) + }) + + it('should handle mermaid loading state', () => { + mocks.useMermaid.mockReturnValue({ + mermaid: mockMermaid, + isLoading: true, + error: null + }) + + const mermaidCode = 'graph TD\nA-->B' + + render({mermaidCode}) + + // Should not render when mermaid is loading + expect(mockMermaid.render).not.toHaveBeenCalled() + + // Should show loading state + expect(screen.getByTestId('spin')).toHaveAttribute('data-spinning', 'true') + }) + }) +}) From c7fd1ac373014934d9fc77333a318e098e315aba Mon Sep 17 00:00:00 2001 From: one Date: Fri, 13 Jun 2025 17:24:24 +0800 Subject: [PATCH 04/23] fix(TopicRenaming): captured activeTopic.id is outdated and causes accidental topic changing after renaming (#7157) * fix(TopicRenaming): captured activeTopic.id is outdated and causes accidental topic changing after renaming * fix: prevent topic changing on auto renaming * fix: filter out main text on summarizing --- src/renderer/src/hooks/useTopic.ts | 4 ++-- src/renderer/src/pages/home/Tabs/TopicsTab.tsx | 2 -- src/renderer/src/services/ApiService.ts | 5 ++++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/hooks/useTopic.ts b/src/renderer/src/hooks/useTopic.ts index 02868a3a90..c6913bd7eb 100644 --- a/src/renderer/src/hooks/useTopic.ts +++ b/src/renderer/src/hooks/useTopic.ts @@ -121,7 +121,7 @@ export const autoRenameTopic = async (assistant: Assistant, topicId: string) => startTopicRenaming(topicId) const data = { ...topic, name: topicName } as Topic - _setActiveTopic(data) + topic.id === _activeTopic.id && _setActiveTopic(data) store.dispatch(updateTopic({ assistantId: assistant.id, topic: data })) } finally { finishTopicRenaming(topicId) @@ -138,7 +138,7 @@ export const autoRenameTopic = async (assistant: Assistant, topicId: string) => const summaryText = await fetchMessagesSummary({ messages: topic.messages, assistant }) if (summaryText) { const data = { ...topic, name: summaryText } - _setActiveTopic(data) + topic.id === _activeTopic.id && _setActiveTopic(data) store.dispatch(updateTopic({ assistantId: assistant.id, topic: data })) } } finally { diff --git a/src/renderer/src/pages/home/Tabs/TopicsTab.tsx b/src/renderer/src/pages/home/Tabs/TopicsTab.tsx index 04930a624f..6d370484fb 100644 --- a/src/renderer/src/pages/home/Tabs/TopicsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/TopicsTab.tsx @@ -197,7 +197,6 @@ const Topics: FC = ({ assistant: _assistant, activeTopic, setActiveTopic if (summaryText) { const updatedTopic = { ...topic, name: summaryText, isNameManuallyEdited: false } updateTopic(updatedTopic) - topic.id === activeTopic.id && setActiveTopic(updatedTopic) } else { window.message?.error(t('message.error.fetchTopicName')) } @@ -221,7 +220,6 @@ const Topics: FC = ({ assistant: _assistant, activeTopic, setActiveTopic if (name && topic?.name !== name) { const updatedTopic = { ...topic, name, isNameManuallyEdited: true } updateTopic(updatedTopic) - topic.id === activeTopic.id && setActiveTopic(updatedTopic) } } }, diff --git a/src/renderer/src/services/ApiService.ts b/src/renderer/src/services/ApiService.ts index d9becd6952..02763079d4 100644 --- a/src/renderer/src/services/ApiService.ts +++ b/src/renderer/src/services/ApiService.ts @@ -416,7 +416,10 @@ export async function fetchTranslate({ content, assistant, onResponse }: FetchTr export async function fetchMessagesSummary({ messages, assistant }: { messages: Message[]; assistant: Assistant }) { const prompt = (getStoreSetting('topicNamingPrompt') as string) || i18n.t('prompts.title') const model = getTopNamingModel() || assistant.model || getDefaultModel() - const userMessages = takeRight(messages, 5) + const userMessages = takeRight(messages, 5).map((message) => ({ + ...message, + content: getMainTextContent(message) + })) const provider = getProviderByModel(model) From 94118667274319aa20c639ab4b7342381aefe478 Mon Sep 17 00:00:00 2001 From: MyPrototypeWhat <43230886+MyPrototypeWhat@users.noreply.github.com> Date: Fri, 13 Jun 2025 17:55:40 +0800 Subject: [PATCH 05/23] =?UTF-8?q?refactor(ImageBlock):=20enhance=20loading?= =?UTF-8?q?=20state=20presentation=20and=20improve=20=E2=80=A6=20(#7160)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(ImageBlock): enhance loading state presentation and improve layout responsiveness - Wrapped the loading spinner in a new SpinnerWrapper for better alignment and presentation during streaming and processing states. - Updated the ImageBlockGroup to use `repeat(auto-fit, minmax(...))` for more flexible grid layout, improving responsiveness across different screen sizes. These changes enhance the user experience by providing a clearer loading indication and a more adaptable layout for image blocks. * style(ImageBlockGroup): comment out child styling for future adjustments - Commented out the child styling rules in ImageBlockGroup to allow for potential layout modifications without removing the code entirely. - This change prepares the component for further enhancements while maintaining existing functionality. * refactor(ImageBlock): replace loading spinner with Ant Design Skeleton component - Updated the loading state presentation in ImageBlock by replacing the custom spinner with Ant Design's Skeleton component for a more consistent UI experience. - Removed the SpinnerWrapper and simplified the return statement for better readability. - This change enhances the visual feedback during image loading while maintaining the component's functionality. --------- Co-authored-by: lizhixuan --- .../src/pages/home/Messages/Blocks/ImageBlock.tsx | 9 +++------ src/renderer/src/pages/home/Messages/Blocks/index.tsx | 9 ++++----- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/renderer/src/pages/home/Messages/Blocks/ImageBlock.tsx b/src/renderer/src/pages/home/Messages/Blocks/ImageBlock.tsx index 5229b12304..8cecea1ad8 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/ImageBlock.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/ImageBlock.tsx @@ -1,6 +1,6 @@ -import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring' import ImageViewer from '@renderer/components/ImageViewer' import { type ImageMessageBlock, MessageBlockStatus } from '@renderer/types/newMessage' +import { Skeleton } from 'antd' import React from 'react' import styled from 'styled-components' @@ -10,7 +10,7 @@ interface Props { const ImageBlock: React.FC = ({ block }) => { if (block.status === MessageBlockStatus.STREAMING || block.status === MessageBlockStatus.PROCESSING) - return + return if (block.status === MessageBlockStatus.SUCCESS) { const images = block.metadata?.generateImageResponse?.images?.length ? block.metadata?.generateImageResponse?.images @@ -28,9 +28,7 @@ const ImageBlock: React.FC = ({ block }) => { ))} ) - } else { - return <> - } + } else return null } const Container = styled.div` display: flex; @@ -38,5 +36,4 @@ const Container = styled.div` gap: 10px; margin-top: 8px; ` - export default React.memo(ImageBlock) diff --git a/src/renderer/src/pages/home/Messages/Blocks/index.tsx b/src/renderer/src/pages/home/Messages/Blocks/index.tsx index 7949356b25..b469f03264 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/index.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/index.tsx @@ -164,15 +164,14 @@ export default React.memo(MessageBlockRenderer) const ImageBlockGroup = styled.div` display: grid; - grid-template-columns: repeat(3, minmax(200px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 8px; - width: 100%; max-width: 960px; - > * { + /* > * { min-width: 200px; - } + } */ @media (min-width: 1536px) { - grid-template-columns: repeat(4, minmax(250px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); max-width: 1280px; > * { min-width: 250px; From afc4731b9d2266a23192efb579c6c8f669c11191 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sat, 14 Jun 2025 08:01:36 +0800 Subject: [PATCH 06/23] feat: clean up Windows license files (#7133) * feat: enable minification in build configurations and clean up Windows license files - Added minification option to the build configurations in electron.vite.config.ts to optimize output size. - Updated after-pack.js to remove unnecessary license files on Windows, improving the packaging process. * refactor: remove minification from build configurations in electron.vite.config.ts - Eliminated the minification option from the build settings in electron.vite.config.ts to streamline the build process. - This change may improve build times and simplify configuration management. --------- Co-authored-by: beyondkmp --- scripts/after-pack.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/after-pack.js b/scripts/after-pack.js index 073120e584..a764642308 100644 --- a/scripts/after-pack.js +++ b/scripts/after-pack.js @@ -36,6 +36,11 @@ exports.default = async function (context) { keepPackageNodeFiles(node_modules_path, '@libsql', ['win32-x64-msvc']) } } + + if (platform === 'windows') { + fs.rmSync(path.join(context.appOutDir, 'LICENSE.electron.txt'), { force: true }) + fs.rmSync(path.join(context.appOutDir, 'LICENSES.chromium.html'), { force: true }) + } } /** From f5e1885ffab8ff6d946f86e8dbc67e62c74d74fd Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sat, 14 Jun 2025 10:01:47 +0800 Subject: [PATCH 07/23] chore(electron.vite.config): update Rollup configuration for single file packaging (#7183) - Modified the Rollup options to disable code splitting and enable inline dynamic imports, ensuring a single file output for the build process. This change optimizes the packaging of the Electron application. --- electron.vite.config.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 7f4a4e3a66..9b22ffc33b 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -19,7 +19,13 @@ export default defineConfig({ }, build: { rollupOptions: { - external: ['@libsql/client', 'bufferutil', 'utf-8-validate'] + external: ['@libsql/client', 'bufferutil', 'utf-8-validate'], + output: { + // 彻底禁用代码分割 - 返回 null 强制单文件打包 + manualChunks: undefined, + // 内联所有动态导入,这是关键配置 + inlineDynamicImports: true + } }, sourcemap: process.env.NODE_ENV === 'development' }, From 27354d82e2a31f1f4abc4570d5db095772a02749 Mon Sep 17 00:00:00 2001 From: fullex <106392080+0xfullex@users.noreply.github.com> Date: Sat, 14 Jun 2025 11:43:13 +0800 Subject: [PATCH 08/23] fix(SelectionAssistant): make add custom action button bigger (#7185) fix: make add custom action button bigger --- src/renderer/src/i18n/locales/en-us.json | 1 + src/renderer/src/i18n/locales/ja-jp.json | 1 + src/renderer/src/i18n/locales/ru-ru.json | 1 + src/renderer/src/i18n/locales/zh-cn.json | 3 ++- src/renderer/src/i18n/locales/zh-tw.json | 1 + .../components/SettingsActionsListHeader.tsx | 9 ++++++++- 6 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index a7201ec1cb..2ac02dce93 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1962,6 +1962,7 @@ }, "actions": { "title": "Actions", + "custom": "Custom Action", "reset": { "button": "Reset", "tooltip": "Reset to default actions. Custom actions will not be deleted.", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 7425bb6428..45acc4c188 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1962,6 +1962,7 @@ }, "actions": { "title": "機能設定", + "custom": "カスタム機能", "reset": { "button": "リセット", "tooltip": "デフォルト機能にリセット(カスタム機能は保持)", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index c51e1b9fad..c2f3a90c9a 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1962,6 +1962,7 @@ }, "actions": { "title": "Действия", + "custom": "Пользовательское действие", "reset": { "button": "Сбросить", "tooltip": "Сбросить стандартные действия. Пользовательские останутся.", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index f2cee104a4..cd17d37bbe 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1927,7 +1927,7 @@ "selected": "划词", "selected_note": "划词后立即显示工具栏", "ctrlkey": "Ctrl 键", - "ctrlkey_note": "划词后,再 按住 Ctrl键,才显示工具栏", + "ctrlkey_note": "划词后,再 长按 Ctrl键,才显示工具栏", "shortcut": "快捷键", "shortcut_note": "划词后,使用快捷键显示工具栏。请在快捷键设置页面中设置取词快捷键并启用。", "shortcut_link": "前往快捷键设置" @@ -1962,6 +1962,7 @@ }, "actions": { "title": "功能", + "custom": "自定义功能", "reset": { "button": "重置", "tooltip": "重置为默认功能,自定义功能不会被删除", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 1a6d1371e5..f1504dd503 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1962,6 +1962,7 @@ }, "actions": { "title": "功能", + "custom": "自訂功能", "reset": { "button": "重設", "tooltip": "重設為預設功能,自訂功能不會被刪除", diff --git a/src/renderer/src/pages/settings/SelectionAssistantSettings/components/SettingsActionsListHeader.tsx b/src/renderer/src/pages/settings/SelectionAssistantSettings/components/SettingsActionsListHeader.tsx index 69017b36df..01d721baf9 100644 --- a/src/renderer/src/pages/settings/SelectionAssistantSettings/components/SettingsActionsListHeader.tsx +++ b/src/renderer/src/pages/settings/SelectionAssistantSettings/components/SettingsActionsListHeader.tsx @@ -32,7 +32,14 @@ const SettingsActionsListHeader = memo(({ customItemsCount, maxCustomItems, onRe ? t('selection.settings.actions.add_tooltip.disabled', { max: maxCustomItems }) : t('selection.settings.actions.add_tooltip.enabled') }> - ) From c9f12c2e491e5382deee33e8b617aa885723e02f Mon Sep 17 00:00:00 2001 From: Wang Jiyuan <59059173+EurFelux@users.noreply.github.com> Date: Sat, 14 Jun 2025 13:08:32 +0800 Subject: [PATCH 09/23] feat: add prompt variable "username" (#7174) --- src/renderer/src/i18n/locales/en-us.json | 2 +- src/renderer/src/i18n/locales/ja-jp.json | 2 +- src/renderer/src/i18n/locales/ru-ru.json | 2 +- src/renderer/src/i18n/locales/zh-cn.json | 2 +- src/renderer/src/i18n/locales/zh-tw.json | 2 +- src/renderer/src/i18n/translate/el-gr.json | 2 +- src/renderer/src/i18n/translate/es-es.json | 2 +- src/renderer/src/i18n/translate/fr-fr.json | 2 +- src/renderer/src/i18n/translate/pt-pt.json | 2 +- src/renderer/src/utils/prompt.ts | 10 ++++++++++ 10 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 2ac02dce93..12a74af7a9 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -10,7 +10,7 @@ "add.prompt.placeholder": "Enter prompt", "add.prompt.variables.tip": { "title": "Available variables", - "content": "{{date}}:\tDate\n{{time}}:\tTime\n{{datetime}}:\tDate and time\n{{system}}:\tOperating system\n{{arch}}:\tCPU architecture\n{{language}}:\tLanguage\n{{model_name}}:\tModel name" + "content": "{{date}}:\tDate\n{{time}}:\tTime\n{{datetime}}:\tDate and time\n{{system}}:\tOperating system\n{{arch}}:\tCPU architecture\n{{language}}:\tLanguage\n{{model_name}}:\tModel name\n{{username}}:\tUsername" }, "add.title": "Create Agent", "import": { diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 45acc4c188..def989512b 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -10,7 +10,7 @@ "add.prompt.placeholder": "プロンプトを入力", "add.prompt.variables.tip": { "title": "利用可能な変数", - "content": "{{date}}:\t日付\n{{time}}:\t時間\n{{datetime}}:\t日付と時間\n{{system}}:\tオペレーティングシステム\n{{arch}}:\tCPUアーキテクチャ\n{{language}}:\t言語\n{{model_name}}:\tモデル名" + "content": "{{date}}:\t日付\n{{time}}:\t時間\n{{datetime}}:\t日付と時間\n{{system}}:\tオペレーティングシステム\n{{arch}}:\tCPUアーキテクチャ\n{{language}}:\t言語\n{{model_name}}:\tモデル名\n{{username}}:\tユーザー名" }, "add.title": "エージェントを作成", "import": { diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index c2f3a90c9a..846be3a147 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -10,7 +10,7 @@ "add.prompt.placeholder": "Введите промпт", "add.prompt.variables.tip": { "title": "Доступные переменные", - "content": "{{date}}:\tДата\n{{time}}:\tВремя\n{{datetime}}:\tДата и время\n{{system}}:\tОперационная система\n{{arch}}:\tАрхитектура процессора\n{{language}}:\tЯзык\n{{model_name}}:\tНазвание модели" + "content": "{{date}}:\tДата\n{{time}}:\tВремя\n{{datetime}}:\tДата и время\n{{system}}:\tОперационная система\n{{arch}}:\tАрхитектура процессора\n{{language}}:\tЯзык\n{{model_name}}:\tНазвание модели\n{{username}}:\tИмя пользователя" }, "add.title": "Создать агента", "delete.popup.content": "Вы уверены, что хотите удалить этого агента?", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index cd17d37bbe..dbb8c5099a 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -10,7 +10,7 @@ "add.prompt.placeholder": "输入提示词", "add.prompt.variables.tip": { "title": "可用的变量", - "content": "{{date}}:\t日期\n{{time}}:\t时间\n{{datetime}}:\t日期和时间\n{{system}}:\t操作系统\n{{arch}}:\tCPU架构\n{{language}}:\t语言\n{{model_name}}:\t模型名称" + "content": "{{date}}:\t日期\n{{time}}:\t时间\n{{datetime}}:\t日期和时间\n{{system}}:\t操作系统\n{{arch}}:\tCPU架构\n{{language}}:\t语言\n{{model_name}}:\t模型名称\n{{username}}:\t用户名" }, "add.title": "创建智能体", "import": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index f1504dd503..5bd97f5a1f 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -10,7 +10,7 @@ "add.prompt.placeholder": "輸入提示詞", "add.prompt.variables.tip": { "title": "可用的變數", - "content": "{{date}}:\t日期\n{{time}}:\t時間\n{{datetime}}:\t日期和時間\n{{system}}:\t作業系統\n{{arch}}:\tCPU架構\n{{language}}:\t語言\n{{model_name}}:\t模型名稱" + "content": "{{date}}:\t日期\n{{time}}:\t時間\n{{datetime}}:\t日期和時間\n{{system}}:\t作業系統\n{{arch}}:\tCPU架構\n{{language}}:\t語言\n{{model_name}}:\t模型名稱\n{{username}}:\t使用者名稱" }, "add.title": "建立智慧代理人", "import": { diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 7d60a58e3f..28c2ccb274 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -10,7 +10,7 @@ "add.prompt.placeholder": "Εισαγάγετε φράση προκαλέσεως", "add.prompt.variables.tip": { "title": "Διαθέσιμες μεταβλητές", - "content": "{{date}}:\tΗμερομηνία\n{{time}}:\tΏρα\n{{datetime}}:\tΗμερομηνία και ώρα\n{{system}}:\tΛειτουργικό σύστημα\n{{arch}}:\tΑρχιτεκτονική CPU\n{{language}}:\tΓλώσσα\n{{model_name}}:\tΌνομα μοντέλου" + "content": "{{date}}:\tΗμερομηνία\n{{time}}:\tΏρα\n{{datetime}}:\tΗμερομηνία και ώρα\n{{system}}:\tΛειτουργικό σύστημα\n{{arch}}:\tΑρχιτεκτονική CPU\n{{language}}:\tΓλώσσα\n{{model_name}}:\tΌνομα μοντέλου\n{{username}}:\tΌνομα χρήστη" }, "add.title": "Δημιουργία νέου ειδικού", "delete.popup.content": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτόν τον ειδικό;", diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index b3083a93ba..b55bc39ed3 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -10,7 +10,7 @@ "add.prompt.placeholder": "Ingrese la palabra clave", "add.prompt.variables.tip": { "title": "Variables disponibles", - "content": "{{date}}:\tFecha\n{{time}}:\tHora\n{{datetime}}:\tFecha y hora\n{{system}}:\tSistema operativo\n{{arch}}:\tArquitectura de CPU\n{{language}}:\tIdioma\n{{model_name}}:\tNombre del modelo" + "content": "{{date}}:\tFecha\n{{time}}:\tHora\n{{datetime}}:\tFecha y hora\n{{system}}:\tSistema operativo\n{{arch}}:\tArquitectura de CPU\n{{language}}:\tIdioma\n{{model_name}}:\tNombre del modelo\n{{username}}:\tNombre de usuario" }, "add.title": "Crear agente inteligente", "delete.popup.content": "¿Está seguro de que desea eliminar este agente inteligente?", diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 0718050d06..04be562105 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -10,7 +10,7 @@ "add.prompt.placeholder": "Entrer le mot-clé", "add.prompt.variables.tip": { "title": "Variables disponibles", - "content": "{{date}}:\tDate\n{{time}}:\tHeure\n{{datetime}}:\tDate et heure\n{{system}}:\tSystème d'exploitation\n{{arch}}:\tArchitecture du processeur\n{{language}}:\tLangue\n{{model_name}}:\tNom du modèle" + "content": "{{date}}:\tDate\n{{time}}:\tHeure\n{{datetime}}:\tDate et heure\n{{system}}:\tSystème d'exploitation\n{{arch}}:\tArchitecture du processeur\n{{language}}:\tLangue\n{{model_name}}:\tNom du modèle\n{{username}}:\tNom d'utilisateur" }, "add.title": "Créer un agent intelligent", "delete.popup.content": "Êtes-vous sûr de vouloir supprimer cet agent intelligent ?", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 146e8d305b..f32a3dc930 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -10,7 +10,7 @@ "add.prompt.placeholder": "Digite o Prompt", "add.prompt.variables.tip": { "title": "Variáveis disponíveis", - "content": "{{date}}:\tData\n{{time}}:\tHora\n{{datetime}}:\tData e hora\n{{system}}:\tSistema operativo\n{{arch}}:\tArquitetura da CPU\n{{language}}:\tIdioma\n{{model_name}}:\tNome do modelo" + "content": "{{date}}:\tData\n{{time}}:\tHora\n{{datetime}}:\tData e hora\n{{system}}:\tSistema operativo\n{{arch}}:\tArquitetura da CPU\n{{language}}:\tIdioma\n{{model_name}}:\tNome do modelo\n{{username}}:\tNome de utilizador" }, "add.title": "Criar Agente Inteligente", "delete.popup.content": "Tem certeza de que deseja excluir este agente inteligente?", diff --git a/src/renderer/src/utils/prompt.ts b/src/renderer/src/utils/prompt.ts index 072635e430..05b10748d7 100644 --- a/src/renderer/src/utils/prompt.ts +++ b/src/renderer/src/utils/prompt.ts @@ -204,6 +204,16 @@ export const buildSystemPrompt = async (userSystemPrompt: string, tools?: MCPToo userSystemPrompt = userSystemPrompt.replace(/{{model_name}}/g, 'Unknown Model') } } + + if (userSystemPrompt.includes('{{username}}')) { + try { + const username = store.getState().settings.userName || 'Unknown Username' + userSystemPrompt = userSystemPrompt.replace(/{{username}}/g, username) + } catch (error) { + console.error('Failed to get username:', error) + userSystemPrompt = userSystemPrompt.replace(/{{username}}/g, 'Unknown Username') + } + } } if (tools && tools.length > 0) { From 2a0484ede226faf4966fcae82f8b12fdb2ae8e06 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Sat, 14 Jun 2025 13:18:47 +0800 Subject: [PATCH 10/23] chore(release): update fetch depth in GitHub Actions workflow - Changed the fetch depth to 0 in the release workflow to ensure all history is available for tagging. This adjustment improves the accuracy of the release process. --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bea18d50b5..7a007e4e91 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - name: Check out Git repository uses: actions/checkout@v4 with: - ref: main + fetch-depth: 0 - name: Get release tag id: get-tag @@ -149,4 +149,4 @@ jobs: token: ${{ secrets.REPO_DISPATCH_TOKEN }} repository: CherryHQ/cherry-studio-docs event-type: update-download-version - client-payload: '{"version": "${{ steps.get-tag.outputs.tag }}"}' \ No newline at end of file + client-payload: '{"version": "${{ steps.get-tag.outputs.tag }}"}' From e4e4dcbd1e3c89d9f5004a0be207130bb872456e Mon Sep 17 00:00:00 2001 From: Wang Jiyuan <59059173+EurFelux@users.noreply.github.com> Date: Sat, 14 Jun 2025 13:35:32 +0800 Subject: [PATCH 11/23] fix: model_name prompt var always use default model (#7178) * fix: model_name prompt var always use default mode * fix: incorrect model name --- .../aiCore/clients/anthropic/AnthropicAPIClient.ts | 2 +- .../src/aiCore/clients/gemini/GeminiAPIClient.ts | 2 +- .../src/aiCore/clients/openai/OpenAIApiClient.ts | 2 +- .../aiCore/clients/openai/OpenAIResponseAPIClient.ts | 2 +- src/renderer/src/utils/prompt.ts | 12 ++++++++---- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/renderer/src/aiCore/clients/anthropic/AnthropicAPIClient.ts b/src/renderer/src/aiCore/clients/anthropic/AnthropicAPIClient.ts index ffbda737c6..29cf86399a 100644 --- a/src/renderer/src/aiCore/clients/anthropic/AnthropicAPIClient.ts +++ b/src/renderer/src/aiCore/clients/anthropic/AnthropicAPIClient.ts @@ -453,7 +453,7 @@ export class AnthropicAPIClient extends BaseApiClient< }) if (this.useSystemPromptForTools) { - systemPrompt = await buildSystemPrompt(systemPrompt, mcpTools) + systemPrompt = await buildSystemPrompt(systemPrompt, mcpTools, assistant) } const systemMessage: TextBlockParam | undefined = systemPrompt diff --git a/src/renderer/src/aiCore/clients/gemini/GeminiAPIClient.ts b/src/renderer/src/aiCore/clients/gemini/GeminiAPIClient.ts index bc848df7f9..1a67696e33 100644 --- a/src/renderer/src/aiCore/clients/gemini/GeminiAPIClient.ts +++ b/src/renderer/src/aiCore/clients/gemini/GeminiAPIClient.ts @@ -452,7 +452,7 @@ export class GeminiAPIClient extends BaseApiClient< }) if (this.useSystemPromptForTools) { - systemInstruction = await buildSystemPrompt(assistant.prompt || '', mcpTools) + systemInstruction = await buildSystemPrompt(assistant.prompt || '', mcpTools, assistant) } let messageContents: Content diff --git a/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts b/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts index 07359c837f..f9c4372c7b 100644 --- a/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts +++ b/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts @@ -420,7 +420,7 @@ export class OpenAIAPIClient extends OpenAIBaseClient< }) if (this.useSystemPromptForTools) { - systemMessage.content = await buildSystemPrompt(systemMessage.content || '', mcpTools) + systemMessage.content = await buildSystemPrompt(systemMessage.content || '', mcpTools, assistant) } // 3. 处理用户消息 diff --git a/src/renderer/src/aiCore/clients/openai/OpenAIResponseAPIClient.ts b/src/renderer/src/aiCore/clients/openai/OpenAIResponseAPIClient.ts index 0fdd65f709..a0f4d8077d 100644 --- a/src/renderer/src/aiCore/clients/openai/OpenAIResponseAPIClient.ts +++ b/src/renderer/src/aiCore/clients/openai/OpenAIResponseAPIClient.ts @@ -290,7 +290,7 @@ export class OpenAIResponseAPIClient extends OpenAIBaseClient< }) if (this.useSystemPromptForTools) { - systemMessageInput.text = await buildSystemPrompt(systemMessageInput.text || '', mcpTools) + systemMessageInput.text = await buildSystemPrompt(systemMessageInput.text || '', mcpTools, assistant) } systemMessageContent.push(systemMessageInput) systemMessage.content = systemMessageContent diff --git a/src/renderer/src/utils/prompt.ts b/src/renderer/src/utils/prompt.ts index 05b10748d7..7ae0b7327f 100644 --- a/src/renderer/src/utils/prompt.ts +++ b/src/renderer/src/utils/prompt.ts @@ -1,5 +1,6 @@ import store from '@renderer/store' -import { MCPTool } from '@renderer/types' +import { Assistant, MCPTool } from '@renderer/types' + export const SYSTEM_PROMPT = `In this environment you have access to a set of tools you can use to answer the user's question. \ You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. @@ -147,7 +148,11 @@ ${availableTools} ` } -export const buildSystemPrompt = async (userSystemPrompt: string, tools?: MCPTool[]): Promise => { +export const buildSystemPrompt = async ( + userSystemPrompt: string, + tools?: MCPTool[], + assistant?: Assistant +): Promise => { if (typeof userSystemPrompt === 'string') { const now = new Date() if (userSystemPrompt.includes('{{date}}')) { @@ -197,8 +202,7 @@ export const buildSystemPrompt = async (userSystemPrompt: string, tools?: MCPToo if (userSystemPrompt.includes('{{model_name}}')) { try { - const modelName = store.getState().llm.defaultModel.name - userSystemPrompt = userSystemPrompt.replace(/{{model_name}}/g, modelName) + userSystemPrompt = userSystemPrompt.replace(/{{model_name}}/g, assistant?.model?.name || 'Unknown Model') } catch (error) { console.error('Failed to get model name:', error) userSystemPrompt = userSystemPrompt.replace(/{{model_name}}/g, 'Unknown Model') From 9138aecdf000dd6d681815e43c89db53691c7bdb Mon Sep 17 00:00:00 2001 From: Wang Jiyuan <59059173+EurFelux@users.noreply.github.com> Date: Sat, 14 Jun 2025 13:37:48 +0800 Subject: [PATCH 12/23] fix: missing topic prompt on resend/regenerate and duplicate prevention (#7173) * fix: completion doesn't include topic prompt * fix: Multiple additions of topic prompts * fix: improve logic * fix: improve logic --- src/renderer/src/pages/home/Inputbar/Inputbar.tsx | 8 ++++---- src/renderer/src/pages/home/Messages/Message.tsx | 7 +++++-- .../src/pages/home/Messages/MessageMenubar.tsx | 15 +++++++++++---- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 8690d99d84..1509f03395 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -191,16 +191,16 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = ) } - if (topic.prompt) { - assistant.prompt = assistant.prompt ? `${assistant.prompt}\n${topic.prompt}` : topic.prompt - } + const assistantWithTopicPrompt = topic.prompt + ? { ...assistant, prompt: `${assistant.prompt}\n${topic.prompt}` } + : assistant baseUserMessage.usage = await estimateUserPromptUsage(baseUserMessage) const { message, blocks } = getUserMessage(baseUserMessage) currentMessageId.current = message.id - dispatch(_sendMessage(message, blocks, assistant, topic.id)) + dispatch(_sendMessage(message, blocks, assistantWithTopicPrompt, topic.id)) // Clear input setText('') diff --git a/src/renderer/src/pages/home/Messages/Message.tsx b/src/renderer/src/pages/home/Messages/Message.tsx index baa41390e0..19fcbdaded 100644 --- a/src/renderer/src/pages/home/Messages/Message.tsx +++ b/src/renderer/src/pages/home/Messages/Message.tsx @@ -80,14 +80,17 @@ const MessageItem: FC = ({ const handleEditResend = useCallback( async (blocks: MessageBlock[]) => { + const assistantWithTopicPrompt = topic.prompt + ? { ...assistant, prompt: `${assistant.prompt}\n${topic.prompt}` } + : assistant try { - await resendUserMessageWithEdit(message, blocks, assistant) + await resendUserMessageWithEdit(message, blocks, assistantWithTopicPrompt) stopEditing() } catch (error) { console.error('Failed to resend message:', error) } }, - [message, resendUserMessageWithEdit, assistant, stopEditing] + [message, resendUserMessageWithEdit, assistant, stopEditing, topic.prompt] ) const handleEditCancel = useCallback(() => { diff --git a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx index 68b901ddec..8210a23b19 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -15,6 +15,7 @@ import type { Model } from '@renderer/types' import type { Assistant, Topic } from '@renderer/types' import type { Message } from '@renderer/types/newMessage' import { captureScrollableDivAsBlob, captureScrollableDivAsDataURL } from '@renderer/utils' +import { copyMessageAsPlainText } from '@renderer/utils/copy' import { exportMarkdownToJoplin, exportMarkdownToSiyuan, @@ -23,7 +24,6 @@ import { exportMessageToNotion, messageToMarkdown } from '@renderer/utils/export' -import { copyMessageAsPlainText } from '@renderer/utils/copy' // import { withMessageThought } from '@renderer/utils/formats' import { removeTrailingDoubleSpaces } from '@renderer/utils/markdown' import { findMainTextBlocks, findTranslationBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find' @@ -124,10 +124,13 @@ const MessageMenubar: FC = (props) => { const handleResendUserMessage = useCallback( async (messageUpdate?: Message) => { if (!loading) { - await resendMessage(messageUpdate ?? message, assistant) + const assistantWithTopicPrompt = topic.prompt + ? { ...assistant, prompt: `${assistant.prompt}\n${topic.prompt}` } + : assistant + await resendMessage(messageUpdate ?? message, assistantWithTopicPrompt) } }, - [assistant, loading, message, resendMessage] + [assistant, loading, message, resendMessage, topic.prompt] ) const { startEditing } = useMessageEditing() @@ -316,8 +319,12 @@ const MessageMenubar: FC = (props) => { // const _message = resetAssistantMessage(message, selectedModel) // editMessage(message.id, { ..._message }) // REMOVED + const assistantWithTopicPrompt = topic.prompt + ? { ...assistant, prompt: `${assistant.prompt}\n${topic.prompt}` } + : assistant + // Call the function from the hook - regenerateAssistantMessage(message, assistant) + regenerateAssistantMessage(message, assistantWithTopicPrompt) } const onMentionModel = async (e: React.MouseEvent) => { From bd4333ab9a1dd2d75822f1260b9e4fb4823930ab Mon Sep 17 00:00:00 2001 From: Wang Jiyuan <59059173+EurFelux@users.noreply.github.com> Date: Sat, 14 Jun 2025 14:18:25 +0800 Subject: [PATCH 13/23] fix: transparent background on translate dropdown (#7189) --- src/renderer/src/pages/home/Messages/MessageMenubar.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx index 8210a23b19..c71d2304ad 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -406,7 +406,8 @@ const MessageMenubar: FC = (props) => { menu={{ style: { maxHeight: 250, - overflowY: 'auto' + overflowY: 'auto', + backgroundClip: 'border-box' }, items: [ ...TranslateLanguageOptions.map((item) => ({ From 0a498460d6218b536c1cb02c589986878f243244 Mon Sep 17 00:00:00 2001 From: Wang Jiyuan <59059173+EurFelux@users.noreply.github.com> Date: Sat, 14 Jun 2025 14:57:31 +0800 Subject: [PATCH 14/23] fix: remove margin-bottom for loading animation (#7191) * fix: remove margin-bottom for loading animation * fix: just need to remove the margin-bottom of the last block --- src/renderer/src/assets/styles/index.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/renderer/src/assets/styles/index.scss b/src/renderer/src/assets/styles/index.scss index b91b3c3a54..9974f19596 100644 --- a/src/renderer/src/assets/styles/index.scss +++ b/src/renderer/src/assets/styles/index.scss @@ -136,6 +136,10 @@ ul { display: flow-root; } + .block-wrapper:last-child > *:last-child { + margin-bottom: 0; + } + .message-content-container > *:last-child { margin-bottom: 0; } From c644e4afa8eb266d6da73a6b7f29f3d64b3dbc8b Mon Sep 17 00:00:00 2001 From: Wang Jiyuan <59059173+EurFelux@users.noreply.github.com> Date: Sat, 14 Jun 2025 14:59:29 +0800 Subject: [PATCH 15/23] feat: add prompt variables docs on topic naming modal popup (#7175) --- .../settings/ModelSettings/TopicNamingModalPopup.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/settings/ModelSettings/TopicNamingModalPopup.tsx b/src/renderer/src/pages/settings/ModelSettings/TopicNamingModalPopup.tsx index 56799bca43..3e28bd3239 100644 --- a/src/renderer/src/pages/settings/ModelSettings/TopicNamingModalPopup.tsx +++ b/src/renderer/src/pages/settings/ModelSettings/TopicNamingModalPopup.tsx @@ -1,8 +1,9 @@ +import { QuestionCircleOutlined } from '@ant-design/icons' import { HStack } from '@renderer/components/Layout' import { useSettings } from '@renderer/hooks/useSettings' import { useAppDispatch } from '@renderer/store' import { setEnableTopicNaming, setTopicNamingPrompt } from '@renderer/store/settings' -import { Button, Divider, Input, Modal, Switch } from 'antd' +import { Button, Divider, Flex, Input, Modal, Popover, Switch } from 'antd' import { useState } from 'react' import { useTranslation } from 'react-i18next' @@ -36,6 +37,8 @@ const PopupContainer: React.FC = ({ resolve }) => { TopicNamingModalPopup.hide = onCancel + const promptVarsContent =
{t('agents.add.prompt.variables.tip.content')}
+ return ( = ({ resolve }) => {
-
{t('settings.models.topic_naming_prompt')}
+ +
{t('settings.models.topic_naming_prompt')}
+ + + +
Date: Sat, 14 Jun 2025 15:57:39 +0800 Subject: [PATCH 16/23] fix: update app-builder-lib patch and add excludeReBuildModules option (#7193) --- ...p-builder-lib-npm-26.0.15-360e5b0476.patch | 215 ++++++++++++------ yarn.lock | 4 +- 2 files changed, 142 insertions(+), 77 deletions(-) diff --git a/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch b/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch index d4381aa11c..cb835faf39 100644 --- a/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch +++ b/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch @@ -1,75 +1,56 @@ -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/out/configuration.d.ts b/out/configuration.d.ts +index 7ad2646c023e7980021a010fc505aad7d1b52831..60849d54420d2b42e1b0977a4143ca6f598abab1 100644 +--- a/out/configuration.d.ts ++++ b/out/configuration.d.ts +@@ -173,6 +173,10 @@ export interface CommonConfiguration { + * [Experimental] Configuration for concurrent builds. + */ + readonly concurrency?: Concurrency | null; ++ /** ++ * The modules to exclude from the rebuild. ++ */ ++ readonly excludeReBuildModules?: Array | null; + } + export interface Configuration extends CommonConfiguration, PlatformSpecificBuildOptions, Hooks { + /** +diff --git a/out/util/yarn.js b/out/util/yarn.js +index 1ee20f8b252a8f28d0c7b103789cf0a9a427aec1..c2878ec54d57da50bf14225e0c70c9c88664eb8a 100644 +--- a/out/util/yarn.js ++++ b/out/util/yarn.js +@@ -140,6 +140,7 @@ async function rebuild(config, { appDir, projectDir }, options) { + arch, + platform, + buildFromSource, ++ ignoreModules: config.excludeReBuildModules || undefined, + projectRootPath: projectDir, + mode: config.nativeRebuilder || "sequential", + disablePreGypCopy: true, diff --git a/scheme.json b/scheme.json -index 433e2efc9cef156ff5444f0c4520362ed2ef9ea7..a89c7a9b0b608fef67902c49106a43ebd0fa8b61 100644 +index 433e2efc9cef156ff5444f0c4520362ed2ef9ea7..c8827e218f66c45bdaec86b898fb932b9b98764c 100644 --- a/scheme.json +++ b/scheme.json -@@ -1975,6 +1975,13 @@ +@@ -1825,6 +1825,20 @@ + "string" + ] + }, ++ "excludeReBuildModules": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "description": "The modules to exclude from the rebuild." ++ }, + "executableArgs": { + "anyOf": [ + { +@@ -1975,6 +1989,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." }, @@ -83,7 +64,7 @@ index 433e2efc9cef156ff5444f0c4520362ed2ef9ea7..a89c7a9b0b608fef67902c49106a43eb "packageCategory": { "description": "backward compatibility + to allow specify fpm-only category for all possible fpm targets in one place", "type": [ -@@ -2327,6 +2334,13 @@ +@@ -2327,6 +2348,13 @@ "MacConfiguration": { "additionalProperties": false, "properties": { @@ -97,7 +78,28 @@ index 433e2efc9cef156ff5444f0c4520362ed2ef9ea7..a89c7a9b0b608fef67902c49106a43eb "additionalArguments": { "anyOf": [ { -@@ -2737,7 +2751,7 @@ +@@ -2527,6 +2555,20 @@ + "string" + ] + }, ++ "excludeReBuildModules": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "description": "The modules to exclude from the rebuild." ++ }, + "executableName": { + "description": "The executable name. Defaults to `productName`.", + "type": [ +@@ -2737,7 +2779,7 @@ "type": "boolean" }, "minimumSystemVersion": { @@ -106,7 +108,7 @@ index 433e2efc9cef156ff5444f0c4520362ed2ef9ea7..a89c7a9b0b608fef67902c49106a43eb "type": [ "null", "string" -@@ -2959,6 +2973,13 @@ +@@ -2959,6 +3001,13 @@ "MasConfiguration": { "additionalProperties": false, "properties": { @@ -120,7 +122,28 @@ index 433e2efc9cef156ff5444f0c4520362ed2ef9ea7..a89c7a9b0b608fef67902c49106a43eb "additionalArguments": { "anyOf": [ { -@@ -3369,7 +3390,7 @@ +@@ -3159,6 +3208,20 @@ + "string" + ] + }, ++ "excludeReBuildModules": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "description": "The modules to exclude from the rebuild." ++ }, + "executableName": { + "description": "The executable name. Defaults to `productName`.", + "type": [ +@@ -3369,7 +3432,7 @@ "type": "boolean" }, "minimumSystemVersion": { @@ -129,7 +152,28 @@ index 433e2efc9cef156ff5444f0c4520362ed2ef9ea7..a89c7a9b0b608fef67902c49106a43eb "type": [ "null", "string" -@@ -6507,6 +6528,13 @@ +@@ -6381,6 +6444,20 @@ + "string" + ] + }, ++ "excludeReBuildModules": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "description": "The modules to exclude from the rebuild." ++ }, + "executableName": { + "description": "The executable name. Defaults to `productName`.", + "type": [ +@@ -6507,6 +6584,13 @@ "string" ] }, @@ -143,7 +187,28 @@ index 433e2efc9cef156ff5444f0c4520362ed2ef9ea7..a89c7a9b0b608fef67902c49106a43eb "protocols": { "anyOf": [ { -@@ -7376,6 +7404,13 @@ +@@ -7153,6 +7237,20 @@ + "string" + ] + }, ++ "excludeReBuildModules": { ++ "anyOf": [ ++ { ++ "items": { ++ "type": "string" ++ }, ++ "type": "array" ++ }, ++ { ++ "type": "null" ++ } ++ ], ++ "description": "The modules to exclude from the rebuild." ++ }, + "executableName": { + "description": "The executable name. Defaults to `productName`.", + "type": [ +@@ -7376,6 +7474,13 @@ ], "description": "MAS (Mac Application Store) development options (`mas-dev` target)." }, diff --git a/yarn.lock b/yarn.lock index 74b503ec1c..5f27227669 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6036,7 +6036,7 @@ __metadata: "app-builder-lib@patch:app-builder-lib@npm%3A26.0.15#~/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch": version: 26.0.15 - resolution: "app-builder-lib@patch:app-builder-lib@npm%3A26.0.15#~/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch::version=26.0.15&hash=b02ae9" + resolution: "app-builder-lib@patch:app-builder-lib@npm%3A26.0.15#~/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch::version=26.0.15&hash=1e6426" dependencies: "@develar/schema-utils": "npm:~2.6.5" "@electron/asar": "npm:3.4.1" @@ -6074,7 +6074,7 @@ __metadata: peerDependencies: dmg-builder: 26.0.15 electron-builder-squirrel-windows: 26.0.15 - checksum: 10c0/616072842c01f9f65283c95bf5642106c32bc3c6679672955f57b48bae9c28de10e18f2005d0e6e46cb2cb560dda3869ebf1412d3db50b7872c5f660581ad6db + checksum: 10c0/99dab1abe455f964152e5e37fceb08b389858ac5c8c0147887b73100f3bd9f9ebbf3760432b376983ce732e2c42dd866d27010809a3b658d9281501e0f82f343 languageName: node linkType: hard From fd9ff4a4326eeb4011fb96056902a32df7aa44c0 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sat, 14 Jun 2025 19:39:28 +0800 Subject: [PATCH 17/23] fix: update app-builder-lib patch and adjust minimumSystemVersion handling (#7197) - Updated the resolution and checksum for the app-builder-lib patch in yarn.lock. - Modified macPackager.js and updateInfoBuilder.js to correctly reference LSMinimumSystemVersion. - Enhanced ArchiveTarget.js and NsisTarget.js to include minimumSystemVersion in updateInfo if specified. --- ...p-builder-lib-npm-26.0.15-360e5b0476.patch | 84 +++++++++++++++---- yarn.lock | 4 +- 2 files changed, 70 insertions(+), 18 deletions(-) diff --git a/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch b/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch index cb835faf39..e9ca84e6cd 100644 --- a/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch +++ b/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch @@ -1,18 +1,70 @@ -diff --git a/out/configuration.d.ts b/out/configuration.d.ts -index 7ad2646c023e7980021a010fc505aad7d1b52831..60849d54420d2b42e1b0977a4143ca6f598abab1 100644 ---- a/out/configuration.d.ts -+++ b/out/configuration.d.ts -@@ -173,6 +173,10 @@ export interface CommonConfiguration { - * [Experimental] Configuration for concurrent builds. - */ - readonly concurrency?: Concurrency | null; -+ /** -+ * The modules to exclude from the rebuild. -+ */ -+ readonly excludeReBuildModules?: Array | null; - } - export interface Configuration extends CommonConfiguration, PlatformSpecificBuildOptions, Hooks { - /** +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/out/util/yarn.js b/out/util/yarn.js index 1ee20f8b252a8f28d0c7b103789cf0a9a427aec1..c2878ec54d57da50bf14225e0c70c9c88664eb8a 100644 --- a/out/util/yarn.js @@ -26,7 +78,7 @@ index 1ee20f8b252a8f28d0c7b103789cf0a9a427aec1..c2878ec54d57da50bf14225e0c70c9c8 mode: config.nativeRebuilder || "sequential", disablePreGypCopy: true, diff --git a/scheme.json b/scheme.json -index 433e2efc9cef156ff5444f0c4520362ed2ef9ea7..c8827e218f66c45bdaec86b898fb932b9b98764c 100644 +index 433e2efc9cef156ff5444f0c4520362ed2ef9ea7..0167441bf928a92f59b5dbe70b2317a74dda74c9 100644 --- a/scheme.json +++ b/scheme.json @@ -1825,6 +1825,20 @@ diff --git a/yarn.lock b/yarn.lock index 5f27227669..38b82f8c93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6036,7 +6036,7 @@ __metadata: "app-builder-lib@patch:app-builder-lib@npm%3A26.0.15#~/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch": version: 26.0.15 - resolution: "app-builder-lib@patch:app-builder-lib@npm%3A26.0.15#~/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch::version=26.0.15&hash=1e6426" + resolution: "app-builder-lib@patch:app-builder-lib@npm%3A26.0.15#~/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch::version=26.0.15&hash=1f4887" dependencies: "@develar/schema-utils": "npm:~2.6.5" "@electron/asar": "npm:3.4.1" @@ -6074,7 +6074,7 @@ __metadata: peerDependencies: dmg-builder: 26.0.15 electron-builder-squirrel-windows: 26.0.15 - checksum: 10c0/99dab1abe455f964152e5e37fceb08b389858ac5c8c0147887b73100f3bd9f9ebbf3760432b376983ce732e2c42dd866d27010809a3b658d9281501e0f82f343 + checksum: 10c0/5de2bd593b21e464585ffa3424e053d41f8569b14ba2a00f29f84cb0b83347a7da3653587f9ef8b5d2f6d1e5bfc4081956b9d72f180d65960db49b5ac84b73d4 languageName: node linkType: hard From 163e28d9ba480217184710823f5f1c7e8b48ed09 Mon Sep 17 00:00:00 2001 From: one Date: Sat, 14 Jun 2025 21:24:34 +0800 Subject: [PATCH 18/23] fix(model): qwen3 model detection (#7201) --- src/renderer/src/config/models.ts | 8 +++-- .../src/utils/__tests__/naming.test.ts | 33 +++++++++++++++++++ src/renderer/src/utils/naming.ts | 14 ++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/config/models.ts b/src/renderer/src/config/models.ts index 78f4ff3d0b..3c849cc854 100644 --- a/src/renderer/src/config/models.ts +++ b/src/renderer/src/config/models.ts @@ -145,6 +145,7 @@ import YoudaoLogo from '@renderer/assets/images/providers/netease-youdao.svg' import NomicLogo from '@renderer/assets/images/providers/nomic.png' import { getProviderByModel } from '@renderer/services/AssistantService' import { Model } from '@renderer/types' +import { getBaseModelName } from '@renderer/utils' import OpenAI from 'openai' import { WEB_SEARCH_PROMPT_FOR_OPENROUTER } from './prompts' @@ -2484,9 +2485,10 @@ export function isSupportedThinkingTokenQwenModel(model?: Model): boolean { return false } + const baseName = getBaseModelName(model.id, '/').toLowerCase() + return ( - model.id.toLowerCase().startsWith('qwen3') || - model.id.toLowerCase().startsWith('qwen/qwen3') || + baseName.startsWith('qwen3') || [ 'qwen-plus-latest', 'qwen-plus-0428', @@ -2494,7 +2496,7 @@ export function isSupportedThinkingTokenQwenModel(model?: Model): boolean { 'qwen-turbo-latest', 'qwen-turbo-0428', 'qwen-turbo-2025-04-28' - ].includes(model.id.toLowerCase()) + ].includes(baseName) ) } diff --git a/src/renderer/src/utils/__tests__/naming.test.ts b/src/renderer/src/utils/__tests__/naming.test.ts index 4a76334bfe..1d4560a3ab 100644 --- a/src/renderer/src/utils/__tests__/naming.test.ts +++ b/src/renderer/src/utils/__tests__/naming.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest' import { firstLetter, generateColorFromChar, + getBaseModelName, getBriefInfo, getDefaultGroupName, getFirstCharacter, @@ -157,6 +158,38 @@ describe('naming', () => { }) }) + describe('getBaseModelName', () => { + it('should extract base model name with single delimiter', () => { + expect(getBaseModelName('DeepSeek/DeepSeek-R1')).toBe('DeepSeek-R1') + expect(getBaseModelName('openai/gpt-4.1')).toBe('gpt-4.1') + expect(getBaseModelName('anthropic/claude-3.5-sonnet')).toBe('claude-3.5-sonnet') + }) + + it('should extract base model name with multiple levels', () => { + expect(getBaseModelName('Pro/deepseek-ai/DeepSeek-R1')).toBe('DeepSeek-R1') + expect(getBaseModelName('org/team/group/model')).toBe('model') + }) + + it('should return original id if no delimiter found', () => { + expect(getBaseModelName('deepseek-r1')).toBe('deepseek-r1') + expect(getBaseModelName('deepseek-r1:free')).toBe('deepseek-r1:free') + }) + + it('should handle edge cases', () => { + // 验证空字符串的情况 + expect(getBaseModelName('')).toBe('') + // 验证以分隔符结尾的字符串 + expect(getBaseModelName('model/')).toBe('') + expect(getBaseModelName('model/name/')).toBe('') + // 验证以分隔符开头的字符串 + expect(getBaseModelName('/model')).toBe('model') + expect(getBaseModelName('/path/to/model')).toBe('model') + // 验证连续分隔符的情况 + expect(getBaseModelName('model//name')).toBe('name') + expect(getBaseModelName('model///name')).toBe('name') + }) + }) + describe('generateColorFromChar', () => { it('should generate a valid hex color code', () => { // 验证生成有效的十六进制颜色代码 diff --git a/src/renderer/src/utils/naming.ts b/src/renderer/src/utils/naming.ts index f475fa7421..df178104de 100644 --- a/src/renderer/src/utils/naming.ts +++ b/src/renderer/src/utils/naming.ts @@ -46,6 +46,20 @@ export const getDefaultGroupName = (id: string, provider?: string): string => { return str } +/** + * 从模型 ID 中提取基础名称。 + * 例如: + * - 'deepseek/deepseek-r1' => 'deepseek-r1' + * - 'deepseek-ai/deepseek/deepseek-r1' => 'deepseek-r1' + * @param {string} id 模型 ID + * @param {string} [delimiter='/'] 分隔符,默认为 '/' + * @returns {string} 基础名称 + */ +export const getBaseModelName = (id: string, delimiter: string = '/'): string => { + const parts = id.split(delimiter) + return parts[parts.length - 1] +} + /** * 用于获取 avatar 名字的辅助函数,会取出字符串的第一个字符,支持表情符号。 * @param {string} str 输入字符串 From 1c354ffa0a6e8daf8cf1abcdcf0dc61d180f679e Mon Sep 17 00:00:00 2001 From: Doekin <105162544+Doekin@users.noreply.github.com> Date: Sat, 14 Jun 2025 22:39:32 +0800 Subject: [PATCH 19/23] fix(ImageGenerationMiddleware): correctly process image URLs (#7198) --- .../middleware/feat/ImageGenerationMiddleware.ts | 14 ++++++++++++-- src/renderer/src/types/chunk.ts | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/aiCore/middleware/feat/ImageGenerationMiddleware.ts b/src/renderer/src/aiCore/middleware/feat/ImageGenerationMiddleware.ts index 560a9e0aac..324382b918 100644 --- a/src/renderer/src/aiCore/middleware/feat/ImageGenerationMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/feat/ImageGenerationMiddleware.ts @@ -97,11 +97,21 @@ export const ImageGenerationMiddleware: CompletionsMiddleware = ) } - const b64_json_array = response.data?.map((item) => `data:image/png;base64,${item.b64_json}`) || [] + let imageType: 'url' | 'base64' = 'base64' + const imageList = + response.data?.reduce((acc: string[], image) => { + if (image.url) { + acc.push(image.url) + imageType = 'url' + } else if (image.b64_json) { + acc.push(`data:image/png;base64,${image.b64_json}`) + } + return acc + }, []) || [] enqueue({ type: ChunkType.IMAGE_COMPLETE, - image: { type: 'base64', images: b64_json_array } + image: { type: imageType, images: imageList } }) const usage = (response as any).usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } diff --git a/src/renderer/src/types/chunk.ts b/src/renderer/src/types/chunk.ts index 4cb4755382..746c8999cb 100644 --- a/src/renderer/src/types/chunk.ts +++ b/src/renderer/src/types/chunk.ts @@ -137,7 +137,7 @@ export interface ImageCompleteChunk { /** * The image content of the chunk */ - image?: { type: 'base64'; images: string[] } + image?: { type: 'url' | 'base64'; images: string[] } } export interface ThinkingDeltaChunk { From 06b543039fc2aae0e32deaf814cd842c65477f6d Mon Sep 17 00:00:00 2001 From: one Date: Sat, 14 Jun 2025 22:58:49 +0800 Subject: [PATCH 20/23] chore(ci): remove --fix from lint (#7159) * chore(ci): remove --fix from lint * fix: lint errors --- .github/workflows/pr-ci.yml | 2 +- src/main/services/AppUpdater.ts | 2 +- src/renderer/src/pages/paintings/AihubmixPage.tsx | 2 +- src/renderer/src/pages/paintings/SiliconPage.tsx | 2 +- .../src/pages/settings/ProviderSettings/ProviderSetting.tsx | 2 +- src/renderer/src/utils/__tests__/markdown.test.ts | 4 ++-- src/renderer/src/utils/markdown.ts | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index 3200140f77..2fd3cf1749 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -44,4 +44,4 @@ jobs: run: yarn build:check - name: Lint Check - run: yarn lint + run: yarn test:lint diff --git a/src/main/services/AppUpdater.ts b/src/main/services/AppUpdater.ts index bb87480a64..203a0d5f1c 100644 --- a/src/main/services/AppUpdater.ts +++ b/src/main/services/AppUpdater.ts @@ -1,7 +1,7 @@ import { isWin } from '@main/constant' import { locales } from '@main/utils/locales' -import { IpcChannel } from '@shared/IpcChannel' import { FeedUrl } from '@shared/config/constant' +import { IpcChannel } from '@shared/IpcChannel' import { UpdateInfo } from 'builder-util-runtime' import { app, BrowserWindow, dialog } from 'electron' import logger from 'electron-log' diff --git a/src/renderer/src/pages/paintings/AihubmixPage.tsx b/src/renderer/src/pages/paintings/AihubmixPage.tsx index 0b6ad7d722..6e3ff6100a 100644 --- a/src/renderer/src/pages/paintings/AihubmixPage.tsx +++ b/src/renderer/src/pages/paintings/AihubmixPage.tsx @@ -1,4 +1,5 @@ import { PlusOutlined, RedoOutlined } from '@ant-design/icons' +import AiProvider from '@renderer/aiCore' import IcImageUp from '@renderer/assets/images/paintings/ic_ImageUp.svg' import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar' import { HStack } from '@renderer/components/Layout' @@ -11,7 +12,6 @@ import { usePaintings } from '@renderer/hooks/usePaintings' import { useAllProviders } from '@renderer/hooks/useProvider' import { useRuntime } from '@renderer/hooks/useRuntime' import { useSettings } from '@renderer/hooks/useSettings' -import AiProvider from '@renderer/aiCore' import FileManager from '@renderer/services/FileManager' import { translateText } from '@renderer/services/TranslateService' import { useAppDispatch } from '@renderer/store' diff --git a/src/renderer/src/pages/paintings/SiliconPage.tsx b/src/renderer/src/pages/paintings/SiliconPage.tsx index 6c4331e373..4631c7293f 100644 --- a/src/renderer/src/pages/paintings/SiliconPage.tsx +++ b/src/renderer/src/pages/paintings/SiliconPage.tsx @@ -1,4 +1,5 @@ import { PlusOutlined, RedoOutlined } from '@ant-design/icons' +import AiProvider from '@renderer/aiCore' import ImageSize1_1 from '@renderer/assets/images/paintings/image-size-1-1.svg' import ImageSize1_2 from '@renderer/assets/images/paintings/image-size-1-2.svg' import ImageSize3_2 from '@renderer/assets/images/paintings/image-size-3-2.svg' @@ -16,7 +17,6 @@ import { usePaintings } from '@renderer/hooks/usePaintings' import { useAllProviders } from '@renderer/hooks/useProvider' import { useRuntime } from '@renderer/hooks/useRuntime' import { useSettings } from '@renderer/hooks/useSettings' -import AiProvider from '@renderer/aiCore' import { getProviderByModel } from '@renderer/services/AssistantService' import FileManager from '@renderer/services/FileManager' import { translateText } from '@renderer/services/TranslateService' diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx index 1555f899ab..2a1515ff7f 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx @@ -1,4 +1,5 @@ import { CheckOutlined, LoadingOutlined } from '@ant-design/icons' +import { isOpenAIProvider } from '@renderer/aiCore/clients/ApiClientFactory' import OpenAIAlert from '@renderer/components/Alert/OpenAIAlert' import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon' import { HStack } from '@renderer/components/Layout' @@ -7,7 +8,6 @@ import { PROVIDER_CONFIG } from '@renderer/config/providers' import { useTheme } from '@renderer/context/ThemeProvider' import { useAllProviders, useProvider, useProviders } from '@renderer/hooks/useProvider' import i18n from '@renderer/i18n' -import { isOpenAIProvider } from '@renderer/aiCore/clients/ApiClientFactory' import { checkApi, formatApiKeys } from '@renderer/services/ApiService' import { checkModelsHealth, getModelCheckSummary } from '@renderer/services/HealthCheckService' import { isProviderSupportAuth } from '@renderer/services/ProviderService' diff --git a/src/renderer/src/utils/__tests__/markdown.test.ts b/src/renderer/src/utils/__tests__/markdown.test.ts index de555aa53f..cbde058d24 100644 --- a/src/renderer/src/utils/__tests__/markdown.test.ts +++ b/src/renderer/src/utils/__tests__/markdown.test.ts @@ -7,9 +7,9 @@ import { convertMathFormula, findCitationInChildren, getCodeBlockId, + markdownToPlainText, removeTrailingDoubleSpaces, - updateCodeBlock, - markdownToPlainText + updateCodeBlock } from '../markdown' describe('markdown', () => { diff --git a/src/renderer/src/utils/markdown.ts b/src/renderer/src/utils/markdown.ts index c3c6229064..e4881ed062 100644 --- a/src/renderer/src/utils/markdown.ts +++ b/src/renderer/src/utils/markdown.ts @@ -1,8 +1,8 @@ import remarkParse from 'remark-parse' import remarkStringify from 'remark-stringify' +import removeMarkdown from 'remove-markdown' import { unified } from 'unified' import { visit } from 'unist-util-visit' -import removeMarkdown from 'remove-markdown' /** * 更彻底的查找方法,递归搜索所有子元素 From fc62a5bdc284fb38e9d1044d7174958e79c2e649 Mon Sep 17 00:00:00 2001 From: Chen Tao <70054568+eeee0717@users.noreply.github.com> Date: Sat, 14 Jun 2025 23:01:45 +0800 Subject: [PATCH 21/23] fix: 7127 (#7196) --- src/main/reranker/BaseReranker.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/reranker/BaseReranker.ts b/src/main/reranker/BaseReranker.ts index f956a0573f..c129e19115 100644 --- a/src/main/reranker/BaseReranker.ts +++ b/src/main/reranker/BaseReranker.ts @@ -21,10 +21,13 @@ export default abstract class BaseReranker { 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 - // 必须携带/v1,否则会404 + let baseURL = this.base.rerankBaseURL + + if (baseURL && baseURL.endsWith('/')) { + // `/` 结尾强制使用rerankBaseURL + return `${baseURL}rerank` + } + if (baseURL && !baseURL.endsWith('/v1')) { baseURL = `${baseURL}/v1` } From 9ea4d1f99f957dd7a2c829a68501064cc6718138 Mon Sep 17 00:00:00 2001 From: Wang Jiyuan <59059173+EurFelux@users.noreply.github.com> Date: Sat, 14 Jun 2025 23:11:52 +0800 Subject: [PATCH 22/23] fix: send message shortcut doesn't work when editing existing message (#6934) * fix: send message shortcut doesn't work when editing existing message * fix: resend shortcut only apply on user msg --- .../src/pages/home/Inputbar/Inputbar.tsx | 2 +- .../src/pages/home/Messages/MessageEditor.tsx | 58 +++++++++++++++---- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 1509f03395..f9faa45169 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -309,7 +309,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = }, [knowledgeBases, openKnowledgeFileList, quickPanel, t, inputbarToolsRef]) const handleKeyDown = (event: React.KeyboardEvent) => { - const isEnterPressed = event.keyCode == 13 + const isEnterPressed = event.key === 'Enter' // 按下Tab键,自动选中${xxx} if (event.key === 'Tab' && inputFocus) { diff --git a/src/renderer/src/pages/home/Messages/MessageEditor.tsx b/src/renderer/src/pages/home/Messages/MessageEditor.tsx index 698991e92f..8a5bd974dd 100644 --- a/src/renderer/src/pages/home/Messages/MessageEditor.tsx +++ b/src/renderer/src/pages/home/Messages/MessageEditor.tsx @@ -40,7 +40,7 @@ const MessageBlockEditor: FC = ({ message, onSave, onResend, onCancel }) const model = assistant.model || assistant.defaultModel const isVision = useMemo(() => isVisionModel(model), [model]) const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision]) - const { pasteLongTextAsFile, pasteLongTextThreshold, fontSize } = useSettings() + const { pasteLongTextAsFile, pasteLongTextThreshold, fontSize, sendMessageShortcut } = useSettings() const { t } = useTranslation() const textareaRef = useRef(null) const attachmentButtonRef = useRef(null) @@ -137,9 +137,8 @@ const MessageBlockEditor: FC = ({ message, onSave, onResend, onCancel }) } } - const handleClick = async (withResend?: boolean) => { - if (isProcessing) return - setIsProcessing(true) + // 处理编辑区块并上传文件 + const processEditedBlocks = async () => { const updatedBlocks = [...editedBlocks] if (files && files.length) { const uploadedFiles = await FileManager.uploadFiles(files) @@ -153,10 +152,48 @@ const MessageBlockEditor: FC = ({ message, onSave, onResend, onCancel }) } }) } - if (withResend) { - onResend(updatedBlocks) - } else { - onSave(updatedBlocks) + return updatedBlocks + } + + const handleSave = async () => { + if (isProcessing) return + setIsProcessing(true) + const updatedBlocks = await processEditedBlocks() + onSave(updatedBlocks) + } + + const handleResend = async () => { + if (isProcessing) return + setIsProcessing(true) + const updatedBlocks = await processEditedBlocks() + onResend(updatedBlocks) + } + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (message.role !== 'user') { + return + } + + const isEnterPressed = event.key === 'Enter' + + if (isEnterPressed && !event.shiftKey && sendMessageShortcut === 'Enter') { + handleResend() + return event.preventDefault() + } + + if (sendMessageShortcut === 'Shift+Enter' && isEnterPressed && event.shiftKey) { + handleResend() + return event.preventDefault() + } + + if (sendMessageShortcut === 'Ctrl+Enter' && isEnterPressed && event.ctrlKey) { + handleResend() + return event.preventDefault() + } + + if (sendMessageShortcut === 'Command+Enter' && isEnterPressed && event.metaKey) { + handleResend() + return event.preventDefault() } } @@ -175,6 +212,7 @@ const MessageBlockEditor: FC = ({ message, onSave, onResend, onCancel }) handleTextChange(block.id, e.target.value) resizeTextArea() }} + onKeyDown={handleKeyDown} autoFocus contextMenu="true" spellCheck={false} @@ -240,13 +278,13 @@ const MessageBlockEditor: FC = ({ message, onSave, onResend, onCancel }) - handleClick()}> + {message.role === 'user' && ( - handleClick(true)}> + From 9001a96fff1be53ef0e41067e1a9b2a43b33314c Mon Sep 17 00:00:00 2001 From: Aichaellee <793708025@qq.com> Date: Fri, 13 Jun 2025 18:24:40 +0800 Subject: [PATCH 23/23] feat:add lanyun mcp server --- .../settings/MCPSettings/SyncServersPopup.tsx | 12 ++ .../settings/MCPSettings/providers/lanyun.ts | 178 ++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 src/renderer/src/pages/settings/MCPSettings/providers/lanyun.ts diff --git a/src/renderer/src/pages/settings/MCPSettings/SyncServersPopup.tsx b/src/renderer/src/pages/settings/MCPSettings/SyncServersPopup.tsx index f25a1c8735..ee86520144 100644 --- a/src/renderer/src/pages/settings/MCPSettings/SyncServersPopup.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/SyncServersPopup.tsx @@ -8,6 +8,7 @@ import styled from 'styled-components' import { getModelScopeToken, saveModelScopeToken, syncModelScopeServers } from './modelscopeSyncUtils' import { getTokenFluxToken, saveTokenFluxToken, syncTokenFluxServers, TOKENFLUX_HOST } from './providers/tokenflux' +import { getTokenLanYunToken, LANYUN_KEY_HOST, saveTokenLanYunToken, syncTokenLanYunServers } from './providers/lanyun' // Provider configuration interface interface ProviderConfig { @@ -45,6 +46,17 @@ const providers: ProviderConfig[] = [ getToken: getTokenFluxToken, saveToken: saveTokenFluxToken, syncServers: syncTokenFluxServers + }, + { + key: 'lanyun', + name: '蓝耘科技', + description: '蓝耘科技云平台 MCP 服务', + discoverUrl: 'https://mcp.lanyun.net', + apiKeyUrl: LANYUN_KEY_HOST, + tokenFieldName: 'tokenLanyunToken', + getToken: getTokenLanYunToken, + saveToken: saveTokenLanYunToken, + syncServers: syncTokenLanYunServers } ] diff --git a/src/renderer/src/pages/settings/MCPSettings/providers/lanyun.ts b/src/renderer/src/pages/settings/MCPSettings/providers/lanyun.ts new file mode 100644 index 0000000000..acd424de90 --- /dev/null +++ b/src/renderer/src/pages/settings/MCPSettings/providers/lanyun.ts @@ -0,0 +1,178 @@ +import type { MCPServer } from '@renderer/types' +import i18next from 'i18next' + +// Token storage constants and utilities +const TOKEN_STORAGE_KEY = 'tokenLanyunToken' +export const TOKENLANYUN_HOST = 'https://mcp.lanyun.net' +export const LANYUN_MCP_HOST = TOKENLANYUN_HOST + '/mcp/manager/selectListByApiKey' +export const LANYUN_KEY_HOST = TOKENLANYUN_HOST + '/#/manage/apiKey' + +export const saveTokenLanYunToken = (token: string): void => { + localStorage.setItem(TOKEN_STORAGE_KEY, token) +} + +export const getTokenLanYunToken = (): string | null => { + return localStorage.getItem(TOKEN_STORAGE_KEY) +} + +export const clearTokenLanYunToken = (): void => { + localStorage.removeItem(TOKEN_STORAGE_KEY) +} + +export const hasTokenLanYunToken = (): boolean => { + return !!getTokenLanYunToken() +} + +interface TokenLanYunServer { + id: string + /** + * locales 字段用于存储多语言信息。 + * 其中 key(lang)为语言代码(如 'zh', 'en'), + * value 为该语言下的 name 和 description。 + * 例如: + * { + * "zh": { name: "文档处理工具", description: "..." }, + * "en": { name: "Document Processor", description: "..." } + * } + */ + locales?: { + [lang: string]: { + description?: string + name?: string + } + } + chineseName?: string + description?: string + operationalUrls?: { url: string }[] + tags?: string[] + logoUrl?: string +} + +interface TokenLanYunSyncResult { + success: boolean + message: string + addedServers: MCPServer[] + errorDetails?: string +} + +// Function to fetch and process TokenLanYun servers +export const syncTokenLanYunServers = async ( + token: string, + existingServers: MCPServer[] +): Promise => { + const t = i18next.t + + try { + const response = await fetch(LANYUN_MCP_HOST, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + } + }) + + // Handle authentication errors + if (response.status === 401 || response.status === 403) { + clearTokenLanYunToken() + return { + success: false, + message: t('settings.mcp.sync.unauthorized', 'Sync Unauthorized'), + addedServers: [] + } + } + + // Handle server errors + if (response.status === 500 || !response.ok) { + return { + success: false, + message: t('settings.mcp.sync.error'), + addedServers: [], + errorDetails: `Status: ${response.status}` + } + } + + // Process successful response + const data = await response.json() + if (data.code === 401) { + return { + success: false, + message: t('settings.mcp.sync.unauthorized', 'Sync Unauthorized'), + addedServers: [], + errorDetails: `Status: ${response.status}` + } + } + if (data.code === 500) { + return { + success: false, + message: t('settings.mcp.sync.error'), + addedServers: [], + errorDetails: `Status: ${response.status}` + } + } + + const servers: TokenLanYunServer[] = data.data || [] + + if (servers.length === 0) { + return { + success: true, + message: t('settings.mcp.sync.noServersAvailable', 'No MCP servers available'), + addedServers: [] + } + } + + // Transform Token servers to MCP servers format + const addedServers: MCPServer[] = [] + console.log('TokenLanYun servers:', servers) + for (const server of servers) { + try { + if (!server.operationalUrls?.[0]?.url) continue + + // If any existing server id contains '@lanyun', clear them before adding new ones + // if (existingServers.some((s) => s.id.startsWith('@lanyun'))) { + // for (let i = existingServers.length - 1; i >= 0; i--) { + // if (existingServers[i].id.startsWith('@lanyun')) { + // existingServers.splice(i, 1) + // } + // } + // } + // Skip if server already exists after clearing + if (existingServers.some((s) => s.id === `@lanyun/${server.id}`)) continue + + const mcpServer: MCPServer = { + id: `@lanyun/${server.id}`, + name: + server.chineseName || server.locales?.zh?.name || server.locales?.en?.name || `LanYun Server ${server.id}`, + description: server.description || '', + type: 'sse', + baseUrl: server.operationalUrls[0].url, + command: '', + args: [], + env: {}, + isActive: true, + provider: '蓝耘科技', + providerUrl: server.operationalUrls[0].url, + logoUrl: server.logoUrl || '', + tags: server.tags ?? (server.chineseName ? [server.chineseName] : []) + } + + addedServers.push(mcpServer) + } catch (err) { + console.error('Error processing LanYun server:', err) + } + } + + return { + success: true, + message: t('settings.mcp.sync.success', { count: addedServers.length }), + addedServers + } + } catch (error) { + console.error('TokenLanyun sync error:', error) + return { + success: false, + message: t('settings.mcp.sync.error'), + addedServers: [], + errorDetails: String(error) + } + } +}