From c088378d7e890ab917b6f0dc830fa557c8df8c86 Mon Sep 17 00:00:00 2001 From: karl Date: Fri, 16 May 2025 16:50:10 +0800 Subject: [PATCH 01/23] fix: mcp params error(issues/6050) (#6058) --- src/renderer/src/utils/mcp-tools.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/src/utils/mcp-tools.ts b/src/renderer/src/utils/mcp-tools.ts index 4c446ffa78..8bdc499135 100644 --- a/src/renderer/src/utils/mcp-tools.ts +++ b/src/renderer/src/utils/mcp-tools.ts @@ -412,6 +412,7 @@ export function upsertMCPToolResponse( const cur = { ...results[index], response: resp.response, + arguments: resp.arguments, status: resp.status } results[index] = cur From 89f31ddd94798342e09f612db3385105bccac1fe Mon Sep 17 00:00:00 2001 From: SuiYunsy <104718844+SuiYunsy@users.noreply.github.com> Date: Fri, 16 May 2025 19:23:43 +0800 Subject: [PATCH 02/23] =?UTF-8?q?fix:=20=E7=A6=81=E6=AD=A2=E2=80=9C?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89CSS=E2=80=9D=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E6=A1=86=E7=9A=84=E6=8B=BC=E5=86=99=E6=A3=80=E6=9F=A5=20(#6064?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/settings/DisplaySettings/DisplaySettings.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx b/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx index ad93b8fa38..d58cf0c01a 100644 --- a/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx +++ b/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx @@ -237,6 +237,7 @@ const DisplaySettings: FC = () => { minHeight: 200, fontFamily: 'monospace' }} + spellCheck={false} /> From 523d6de25739027e69d5e9da550bcca76c9dd2ea Mon Sep 17 00:00:00 2001 From: SuYao Date: Fri, 16 May 2025 20:42:59 +0800 Subject: [PATCH 03/23] hotfix: openai websearch render and gemini think (#6055) --- src/renderer/src/providers/AiProvider/GeminiProvider.ts | 6 ++++-- .../src/providers/AiProvider/OpenAIResponseProvider.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/providers/AiProvider/GeminiProvider.ts b/src/renderer/src/providers/AiProvider/GeminiProvider.ts index 46ea431f6f..222abeaaf1 100644 --- a/src/renderer/src/providers/AiProvider/GeminiProvider.ts +++ b/src/renderer/src/providers/AiProvider/GeminiProvider.ts @@ -287,7 +287,8 @@ export default class GeminiProvider extends BaseProvider { if (reasoningEffort === undefined) { return { thinkingConfig: { - includeThoughts: false + includeThoughts: false, + thinkingBudget: 0 } as ThinkingConfig } } @@ -921,7 +922,8 @@ export default class GeminiProvider extends BaseProvider { config = { ...config, thinkingConfig: { - includeThoughts: false + includeThoughts: false, + thinkingBudget: 0 } as ThinkingConfig } } diff --git a/src/renderer/src/providers/AiProvider/OpenAIResponseProvider.ts b/src/renderer/src/providers/AiProvider/OpenAIResponseProvider.ts index baacc96c44..9e163c39c6 100644 --- a/src/renderer/src/providers/AiProvider/OpenAIResponseProvider.ts +++ b/src/renderer/src/providers/AiProvider/OpenAIResponseProvider.ts @@ -593,7 +593,7 @@ export abstract class BaseOpenAIProvider extends BaseProvider { onChunk({ type: ChunkType.LLM_WEB_SEARCH_COMPLETE, llm_web_search: { - source: WebSearchSource.OPENAI, + source: WebSearchSource.OPENAI_RESPONSE, results: chunk.part.annotations } }) From 2fca383e1eff4d0f5021bd6bb3f9f5e572f02f70 Mon Sep 17 00:00:00 2001 From: MyPrototypeWhat <43230886+MyPrototypeWhat@users.noreply.github.com> Date: Fri, 16 May 2025 21:56:40 +0800 Subject: [PATCH 04/23] 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. --- .../home/Messages/Blocks/ThinkingBlock.tsx | 65 +++++++++++-------- src/renderer/src/store/thunk/messageThunk.ts | 1 - 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx b/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx index 212ee53ee9..476c67e57e 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx @@ -5,7 +5,7 @@ import { lightbulbVariants } from '@renderer/utils/motionVariants' import { Collapse, message as antdMessage, Tooltip } from 'antd' import { Lightbulb } from 'lucide-react' import { motion } from 'motion/react' -import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -20,8 +20,6 @@ const ThinkingBlock: React.FC = ({ block }) => { const { t } = useTranslation() const { messageFont, fontSize, thoughtAutoCollapse } = useSettings() const [activeKey, setActiveKey] = useState<'thought' | ''>(thoughtAutoCollapse ? '' : 'thought') - const [thinkingTime, setThinkingTime] = useState(block.thinking_millsec || 0) - const intervalId = useRef(null) const isThinking = useMemo(() => block.status === MessageBlockStatus.STREAMING, [block.status]) @@ -55,28 +53,6 @@ const ThinkingBlock: React.FC = ({ block }) => { } }, [block.content, t]) - // FIXME: 这里统计的和请求处统计的有一定误差 - useEffect(() => { - if (isThinking) { - intervalId.current = setInterval(() => { - setThinkingTime((prev) => prev + 100) - }, 100) - } else if (intervalId.current) { - // 立即清除计时器 - clearInterval(intervalId.current) - intervalId.current = null - } - - return () => { - if (intervalId.current) { - clearInterval(intervalId.current) - intervalId.current = null - } - } - }, [isThinking]) - - const thinkingTimeSeconds = useMemo(() => (thinkingTime / 1000).toFixed(1), [thinkingTime]) - if (!block.content) { return null } @@ -101,9 +77,7 @@ const ThinkingBlock: React.FC = ({ block }) => { - {t(isThinking ? 'chat.thinking' : 'chat.deeply_thought', { - seconds: thinkingTimeSeconds - })} + {/* {isThinking && } */} {!isThinking && ( @@ -134,6 +108,41 @@ const ThinkingBlock: React.FC = ({ block }) => { ) } +const ThinkingTimeSeconds = memo( + ({ blockThinkingTime, isThinking }: { blockThinkingTime?: number; isThinking: boolean }) => { + const { t } = useTranslation() + + const [thinkingTime, setThinkingTime] = useState(blockThinkingTime || 0) + + // FIXME: 这里统计的和请求处统计的有一定误差 + useEffect(() => { + let timer: NodeJS.Timeout | null = null + if (isThinking) { + timer = setInterval(() => { + setThinkingTime((prev) => prev + 100) + }, 100) + } else if (timer) { + // 立即清除计时器 + clearInterval(timer) + timer = null + } + + return () => { + if (timer) { + clearInterval(timer) + timer = null + } + } + }, [isThinking]) + + const thinkingTimeSeconds = useMemo(() => (thinkingTime / 1000).toFixed(1), [thinkingTime]) + + return t(isThinking ? 'chat.thinking' : 'chat.deeply_thought', { + seconds: thinkingTimeSeconds + }) + } +) + const CollapseContainer = styled(Collapse)` margin-bottom: 15px; ` diff --git a/src/renderer/src/store/thunk/messageThunk.ts b/src/renderer/src/store/thunk/messageThunk.ts index 86707a938c..fd3a88d5b9 100644 --- a/src/renderer/src/store/thunk/messageThunk.ts +++ b/src/renderer/src/store/thunk/messageThunk.ts @@ -734,7 +734,6 @@ export const loadTopicMessagesThunk = try { const topic = await db.topics.get(topicId) - if (!topic) { await db.topics.add({ id: topicId, messages: [] }) } From 883bffd2db9cd8228261c80d7683580980d04a00 Mon Sep 17 00:00:00 2001 From: SuYao Date: Fri, 16 May 2025 22:21:05 +0800 Subject: [PATCH 05/23] fix: Update the state of the last message block as a fallback (#6076) --- .../src/providers/AiProvider/OpenAIProvider.ts | 13 ++++++++++--- src/renderer/src/store/thunk/messageThunk.ts | 8 ++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/providers/AiProvider/OpenAIProvider.ts b/src/renderer/src/providers/AiProvider/OpenAIProvider.ts index f9f78cebe4..597f85f5fc 100644 --- a/src/renderer/src/providers/AiProvider/OpenAIProvider.ts +++ b/src/renderer/src/providers/AiProvider/OpenAIProvider.ts @@ -718,9 +718,17 @@ export default class OpenAIProvider extends BaseOpenAIProvider { const usage = chunk.usage const originalFinishDelta = chunk.delta const originalFinishRawChunk = chunk.chunk - if (!isEmpty(finishReason)) { - onChunk({ type: ChunkType.TEXT_COMPLETE, text: content }) + if (content) { + onChunk({ type: ChunkType.TEXT_COMPLETE, text: content }) + } + if (thinkingContent) { + onChunk({ + type: ChunkType.THINKING_COMPLETE, + text: thinkingContent, + thinking_millsec: new Date().getTime() - time_first_token_millsec + }) + } if (usage) { finalUsage.completion_tokens += usage.completion_tokens || 0 finalUsage.prompt_tokens += usage.prompt_tokens || 0 @@ -812,7 +820,6 @@ export default class OpenAIProvider extends BaseOpenAIProvider { if (toolResults.length) { await processToolResults(toolResults, idx) } - onChunk({ type: ChunkType.BLOCK_COMPLETE, response: { diff --git a/src/renderer/src/store/thunk/messageThunk.ts b/src/renderer/src/store/thunk/messageThunk.ts index fd3a88d5b9..c2c51b25e4 100644 --- a/src/renderer/src/store/thunk/messageThunk.ts +++ b/src/renderer/src/store/thunk/messageThunk.ts @@ -622,6 +622,14 @@ const fetchAndProcessAssistantResponseImpl = async ( const contextForUsage = userMsgIndex !== -1 ? orderedMsgs.slice(0, userMsgIndex + 1) : [] const finalContextWithAssistant = [...contextForUsage, finalAssistantMsg] + if (lastBlockId) { + const changes: Partial = { + status: MessageBlockStatus.SUCCESS + } + dispatch(updateOneBlock({ id: lastBlockId, changes })) + saveUpdatedBlockToDB(lastBlockId, assistantMsgId, topicId, getState) + } + // 更新topic的name autoRenameTopic(assistant, topicId) From 5701b09c23d988145c785555a24ddb420562e44c Mon Sep 17 00:00:00 2001 From: SuYao Date: Sat, 17 May 2025 00:45:26 +0800 Subject: [PATCH 06/23] Hotfix/gemini openrouter (#6080) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: 添加对openai兼容情况下Gemini模型的思考配置支持 --- .../providers/AiProvider/OpenAIProvider.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/renderer/src/providers/AiProvider/OpenAIProvider.ts b/src/renderer/src/providers/AiProvider/OpenAIProvider.ts index 597f85f5fc..a7bed185d5 100644 --- a/src/renderer/src/providers/AiProvider/OpenAIProvider.ts +++ b/src/renderer/src/providers/AiProvider/OpenAIProvider.ts @@ -10,6 +10,7 @@ import { isSupportedReasoningEffortModel, isSupportedReasoningEffortOpenAIModel, isSupportedThinkingTokenClaudeModel, + isSupportedThinkingTokenGeminiModel, isSupportedThinkingTokenModel, isSupportedThinkingTokenQwenModel, isVisionModel, @@ -258,6 +259,19 @@ export default class OpenAIProvider extends BaseOpenAIProvider { return { thinking: { type: 'disabled' } } } + if (isSupportedThinkingTokenGeminiModel(model)) { + // openrouter没有提供一个不推理的选项,先隐藏 + if (this.provider.id === 'openrouter') { + return { reasoning: { maxTokens: 0, exclude: true } } + } + return { + thinkingConfig: { + includeThoughts: false, + thinkingBudget: 0 + } + } + } + return {} } const effortRatio = EFFORT_RATIO[reasoningEffort] @@ -313,6 +327,16 @@ export default class OpenAIProvider extends BaseOpenAIProvider { } } + // Gemini models + if (isSupportedThinkingTokenGeminiModel(model)) { + return { + thinkingConfig: { + thinkingBudget: budgetTokens, + includeThoughts: true + } + } + } + // Default case: no special thinking settings return {} } From f84358adfd3b7e8604f141ff49b4cd24135e4489 Mon Sep 17 00:00:00 2001 From: 1600822305 <161661698+1600822305@users.noreply.github.com> Date: Sat, 17 May 2025 08:09:30 +0800 Subject: [PATCH 07/23] =?UTF-8?q?fix:=20Update=20file=20API=20usage=20for?= =?UTF-8?q?=20Electron=2035.2.2=20and=20add=20translations=20f=E2=80=A6=20?= =?UTF-8?q?(#6087)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: Update file API usage for Electron 35.2.2 and add translations for file error messages * 修复Electron 35.2.2中的文件API问题 --- src/preload/index.ts | 5 +- 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 | 1 + src/renderer/src/i18n/locales/zh-tw.json | 1 + .../src/pages/home/Inputbar/Inputbar.tsx | 49 +++++++++++-------- src/renderer/src/utils/input.ts | 25 +++++++--- 8 files changed, 56 insertions(+), 28 deletions(-) diff --git a/src/preload/index.ts b/src/preload/index.ts index 2a2f378fa2..828557865d 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -2,7 +2,7 @@ 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 { CreateDirectoryOptions } from 'webdav' // Custom APIs for renderer @@ -73,7 +73,8 @@ const api = { download: (url: string) => ipcRenderer.invoke(IpcChannel.File_Download, url), copy: (fileId: string, destPath: string) => ipcRenderer.invoke(IpcChannel.File_Copy, fileId, destPath), binaryImage: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_BinaryImage, fileId), - base64File: (fileId: string) => ipcRenderer.invoke(IpcChannel.File_Base64File, 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) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 94ee6c07a9..2866739748 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -163,6 +163,7 @@ "input.estimated_tokens.tip": "Estimated tokens", "input.expand": "Expand", "input.file_not_supported": "Model does not support this file type", + "input.file_error": "Error processing file", "input.generate_image": "Generate image", "input.generate_image_not_supported": "The model does not support generating images.", "input.knowledge_base": "Knowledge Base", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index fef52b7d58..9be762b226 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -163,6 +163,7 @@ "input.estimated_tokens.tip": "推定トークン数", "input.expand": "展開", "input.file_not_supported": "モデルはこのファイルタイプをサポートしません", + "input.file_error": "ファイル処理エラー", "input.generate_image": "画像を生成する", "input.generate_image_not_supported": "モデルは画像の生成をサポートしていません。", "input.knowledge_base": "ナレッジベース", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index bda4157737..f749fb2d01 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -163,6 +163,7 @@ "input.estimated_tokens.tip": "Затраты токенов", "input.expand": "Развернуть", "input.file_not_supported": "Модель не поддерживает этот тип файла", + "input.file_error": "Ошибка обработки файла", "input.generate_image": "Сгенерировать изображение", "input.generate_image_not_supported": "Модель не поддерживает генерацию изображений.", "input.knowledge_base": "База знаний", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 6808ff9b8a..e787498e8b 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -163,6 +163,7 @@ "input.estimated_tokens.tip": "预估 token 数", "input.expand": "展开", "input.file_not_supported": "模型不支持此文件类型", + "input.file_error": "文件处理出错", "input.generate_image": "生成图片", "input.generate_image_not_supported": "模型不支持生成图片", "input.knowledge_base": "知识库", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 02a18f08f5..8b1eff5941 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -163,6 +163,7 @@ "input.estimated_tokens.tip": "預估 Token 數", "input.expand": "展開", "input.file_not_supported": "模型不支援此檔案類型", + "input.file_error": "檔案處理錯誤", "input.generate_image": "生成圖片", "input.generate_image_not_supported": "模型不支援生成圖片", "input.knowledge_base": "知識庫", diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 39621cc85d..552e374e4c 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -584,27 +584,33 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = if (event.clipboardData?.files && event.clipboardData.files.length > 0) { event.preventDefault() for (const file of event.clipboardData.files) { - if (file.path === '') { - // 图像生成也支持图像编辑 - if (file.type.startsWith('image/') && (isVisionModel(model) || isGenerateImageModel(model))) { - const tempFilePath = await window.api.file.create(file.name) - const arrayBuffer = await file.arrayBuffer() - const uint8Array = new Uint8Array(arrayBuffer) - await window.api.file.write(tempFilePath, uint8Array) - const selectedFile = await window.api.file.get(tempFilePath) - selectedFile && setFiles((prevFiles) => [...prevFiles, selectedFile]) - break - } else { - window.message.info({ - key: 'file_not_supported', - content: t('chat.input.file_not_supported') - }) - } - } + try { + // 使用新的API获取文件路径 + const filePath = window.api.file.getPathForFile(file) - if (file.path) { - if (supportExts.includes(getFileExtension(file.path))) { - const selectedFile = await window.api.file.get(file.path) + // 如果没有路径,可能是剪贴板中的图像数据 + if (!filePath) { + // 图像生成也支持图像编辑 + if (file.type.startsWith('image/') && (isVisionModel(model) || isGenerateImageModel(model))) { + const tempFilePath = await window.api.file.create(file.name) + const arrayBuffer = await file.arrayBuffer() + const uint8Array = new Uint8Array(arrayBuffer) + await window.api.file.write(tempFilePath, uint8Array) + const selectedFile = await window.api.file.get(tempFilePath) + selectedFile && setFiles((prevFiles) => [...prevFiles, selectedFile]) + break + } else { + window.message.info({ + key: 'file_not_supported', + content: t('chat.input.file_not_supported') + }) + } + continue + } + + // 有路径的情况 + if (supportExts.includes(getFileExtension(filePath))) { + const selectedFile = await window.api.file.get(filePath) selectedFile && setFiles((prevFiles) => [...prevFiles, selectedFile]) } else { window.message.info({ @@ -612,6 +618,9 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = content: t('chat.input.file_not_supported') }) } + } catch (error) { + Logger.error('[src/renderer/src/pages/home/Inputbar/Inputbar.tsx] onPaste:', error) + window.message.error(t('chat.input.file_error')) } } return diff --git a/src/renderer/src/utils/input.ts b/src/renderer/src/utils/input.ts index 9c638c2289..6952a24a51 100644 --- a/src/renderer/src/utils/input.ts +++ b/src/renderer/src/utils/input.ts @@ -3,14 +3,27 @@ import { FileType } from '@renderer/types' export const getFilesFromDropEvent = async (e: React.DragEvent): Promise => { if (e.dataTransfer.files.length > 0) { - const results = await Promise.allSettled([...e.dataTransfer.files].map((file) => window.api.file.get(file.path))) + // 使用新的API获取文件路径 + const filePromises = [...e.dataTransfer.files].map(async (file) => { + try { + // 使用新的webUtils.getPathForFile API获取文件路径 + const filePath = window.api.file.getPathForFile(file) + if (filePath) { + return window.api.file.get(filePath) + } + return null + } catch (error) { + Logger.error('[src/renderer/src/utils/input.ts] getFilesFromDropEvent - getPathForFile error:', error) + return null + } + }) + + const results = await Promise.allSettled(filePromises) const list: FileType[] = [] for (const result of results) { - if (result.status === 'fulfilled') { - if (result.value !== null) { - list.push(result.value) - } - } else { + if (result.status === 'fulfilled' && result.value !== null) { + list.push(result.value) + } else if (result.status === 'rejected') { Logger.error('[src/renderer/src/utils/input.ts] getFilesFromDropEvent:', result.reason) } } From 3c2e8e2f1df72b10cfa7b6eaf56f42db18c98c9d Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Sat, 17 May 2025 08:46:26 +0800 Subject: [PATCH 08/23] 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 --- src/main/index.ts | 4 +++- src/main/services/ConfigManager.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index fb79b1e842..f85803ed84 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,3 +1,5 @@ +import '@main/config' + import { electronApp, optimizer } from '@electron-toolkit/utils' import { replaceDevtoolsFont } from '@main/utils/windowUtil' import { IpcChannel } from '@shared/IpcChannel' @@ -42,7 +44,7 @@ if (!app.requestSingleInstanceLock()) { } 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. diff --git a/src/main/services/ConfigManager.ts b/src/main/services/ConfigManager.ts index 6242709385..bb0cbfc422 100644 --- a/src/main/services/ConfigManager.ts +++ b/src/main/services/ConfigManager.ts @@ -62,7 +62,7 @@ export class ConfigManager { } getTrayOnClose(): boolean { - return !!this.get(ConfigKeys.TrayOnClose, true) + return !!this.get(ConfigKeys.TrayOnClose, false) } setTrayOnClose(value: boolean) { From c2465c33b7c37fec585367d9938ccfce7a72f02f Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sat, 17 May 2025 09:35:25 +0800 Subject: [PATCH 09/23] 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. --- .../src/pages/home/Inputbar/Inputbar.tsx | 68 ++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 552e374e4c..47edb496ed 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -117,6 +117,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState([]) const [mentionModels, setMentionModels] = useState([]) const [isDragging, setIsDragging] = useState(false) + const [isFileDragging, setIsFileDragging] = useState(false) const [textareaHeight, setTextareaHeight] = useState() const startDragY = useRef(0) const startHeight = useRef(0) @@ -646,14 +647,44 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = [model, pasteLongTextAsFile, pasteLongTextThreshold, resizeTextArea, supportExts, t, text] ) + // 添加全局粘贴事件处理 + useEffect(() => { + const handleGlobalPaste = (event: ClipboardEvent) => { + if (document.activeElement === textareaRef.current?.resizableTextArea?.textArea) { + return + } + + onPaste(event) + } + + document.addEventListener('paste', handleGlobalPaste) + return () => { + document.removeEventListener('paste', handleGlobalPaste) + } + }, [onPaste]) + const handleDragOver = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() + setIsFileDragging(true) + } + + const handleDragEnter = (e: React.DragEvent) => { + e.preventDefault() + e.stopPropagation() + setIsFileDragging(true) + } + + const handleDragLeave = (e: React.DragEvent) => { + e.preventDefault() + e.stopPropagation() + setIsFileDragging(false) } const handleDrop = async (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() + setIsFileDragging(false) const files = await getFilesFromDropEvent(e).catch((err) => { Logger.error('[src/renderer/src/pages/home/Inputbar/Inputbar.tsx] handleDrop:', err) @@ -661,11 +692,22 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = }) if (files) { + let supportedFiles = 0 + files.forEach((file) => { if (supportExts.includes(getFileExtension(file.path))) { setFiles((prevFiles) => [...prevFiles, file]) + supportedFiles++ } }) + + // 如果有文件,但都不支持 + if (files.length > 0 && supportedFiles === 0) { + window.message.info({ + key: 'file_not_supported', + content: t('chat.input.file_not_supported') + }) + } } } @@ -881,12 +923,17 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = const showThinkingButton = isSupportedThinkingTokenModel(model) || isSupportedReasoningEffortModel(model) return ( - + {files.length > 0 && } {selectedKnowledgeBases.length > 0 && ( @@ -1067,6 +1114,23 @@ const InputBarContainer = styled.div` border-radius: 15px; padding-top: 6px; // 为拖动手柄留出空间 background-color: var(--color-background-opacity); + + &.file-dragging { + border: 2px dashed #2ecc71; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(46, 204, 113, 0.03); + border-radius: 14px; + z-index: 5; + pointer-events: none; + } + } ` const TextareaStyle: CSSProperties = { From 2894eec43804b3e6ad19dca9580502b3cefe27f8 Mon Sep 17 00:00:00 2001 From: Steven Ding <1139274654@qq.com> Date: Sat, 17 May 2025 15:55:36 +0800 Subject: [PATCH 10/23] fix: change minimax's URL (#6091) Signed-off-by: stevending1st --- src/renderer/src/config/minapps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/config/minapps.ts b/src/renderer/src/config/minapps.ts index 4e688c8748..4dc80ad751 100644 --- a/src/renderer/src/config/minapps.ts +++ b/src/renderer/src/config/minapps.ts @@ -163,7 +163,7 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ { id: 'minimax', name: '海螺', - url: 'https://hailuoai.com/', + url: 'https://chat.minimaxi.com/', logo: HailuoModelLogo }, { From ece9e2ef131266282b0230948021e8e66b83eb46 Mon Sep 17 00:00:00 2001 From: one Date: Sat, 17 May 2025 17:07:30 +0800 Subject: [PATCH 11/23] chore: reduce package size (#6092) --- package.json | 12 ++++++------ yarn.lock | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 3459bdb54d..aa4211b4f3 100644 --- a/package.json +++ b/package.json @@ -74,9 +74,6 @@ "@strongtz/win32-arm64-msvc": "^0.4.7", "@tanstack/react-query": "^5.27.0", "@types/react-infinite-scroll-component": "^5.0.0", - "@uiw/codemirror-extensions-langs": "^4.23.12", - "@uiw/codemirror-themes-all": "^4.23.12", - "@uiw/react-codemirror": "^4.23.12", "archiver": "^7.0.1", "async-mutex": "^0.5.0", "color": "^5.0.0", @@ -94,13 +91,10 @@ "got-scraping": "^4.1.1", "jsdom": "^26.0.0", "markdown-it": "^14.1.0", - "mermaid": "^11.6.0", "node-stream-zip": "^1.15.0", "officeparser": "^4.1.1", "os-proxy-config": "^1.1.2", "proxy-agent": "^6.5.0", - "rc-virtual-list": "^3.18.6", - "react-window": "^1.8.11", "tar": "^7.4.3", "turndown": "^7.2.0", "turndown-plugin-gfm": "^1.0.2", @@ -147,6 +141,9 @@ "@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", @@ -179,12 +176,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.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.6", "react": "^19.0.0", "react-dom": "^19.0.0", "react-hotkeys-hook": "^4.6.1", @@ -195,6 +194,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", diff --git a/yarn.lock b/yarn.lock index 8599eac0ff..cb38b0566c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3204,13 +3204,13 @@ __metadata: linkType: hard "@lezer/sass@npm:^1.0.0": - version: 1.0.7 - resolution: "@lezer/sass@npm:1.0.7" + version: 1.1.0 + resolution: "@lezer/sass@npm:1.1.0" dependencies: "@lezer/common": "npm:^1.2.0" "@lezer/highlight": "npm:^1.0.0" "@lezer/lr": "npm:^1.0.0" - checksum: 10c0/6308873c74e10e2f075945f85068333e1cd95f65743a9bc054456e4f55b0a68563ccdfefa9f372d9c234640eca41455877d6a8d4db9c0a09eea5beb71f065009 + checksum: 10c0/1b40310ad861b183fb525b1bcdfa27e2bd06e8ad913df4c044be4bfcda5bc5ddbb1d1b3675016bd7792ceaa934548ceb3dc9ad3d3d57b72a31babfe389ab9bab languageName: node linkType: hard From 548db37066d4a0b6e0ee57ef4cf2e5f8e956b784 Mon Sep 17 00:00:00 2001 From: George Zhao <38124587+CreatorZZY@users.noreply.github.com> Date: Sat, 17 May 2025 17:27:45 +0800 Subject: [PATCH 12/23] 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 --- src/renderer/src/pages/apps/AppsPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/apps/AppsPage.tsx b/src/renderer/src/pages/apps/AppsPage.tsx index 3ffecd9bcd..099649fade 100644 --- a/src/renderer/src/pages/apps/AppsPage.tsx +++ b/src/renderer/src/pages/apps/AppsPage.tsx @@ -1,7 +1,7 @@ import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { Center } from '@renderer/components/Layout' import { useMinapps } from '@renderer/hooks/useMinapps' -import { Empty, Input } from 'antd' +import { Input } from 'antd' import { isEmpty } from 'lodash' import { Search } from 'lucide-react' import React, { FC, useState } from 'react' @@ -53,7 +53,7 @@ const AppsPage: FC = () => { {isEmpty(filteredApps) ? (
- +
) : ( From e9ab193a24c03b8190f6d73643df1ee9fa27f411 Mon Sep 17 00:00:00 2001 From: "Rudbeckia.hirta.L" <83773602@qq.com> Date: Sat, 17 May 2025 19:20:40 +0800 Subject: [PATCH 13/23] feat: support skipping files during backup(slim backup) (#6075) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: support skipping files during backup * 修复 lint * 修复 lint --------- Co-authored-by: daisai.1 --- src/main/services/BackupManager.ts | 40 +++++++++++-------- src/preload/index.ts | 4 +- .../src/components/Popups/BackupPopup.tsx | 6 ++- 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 | 3 ++ 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 | 4 +- src/renderer/src/i18n/translate/es-es.json | 3 +- src/renderer/src/i18n/translate/fr-fr.json | 4 +- src/renderer/src/i18n/translate/pt-pt.json | 5 ++- .../settings/DataSettings/DataSettings.tsx | 32 ++++++++++++++- .../DataSettings/NutstoreSettings.tsx | 31 +++++++++++--- .../settings/DataSettings/WebDavSettings.tsx | 22 ++++++++-- src/renderer/src/services/BackupService.ts | 10 +++-- src/renderer/src/services/NutstoreService.ts | 8 +++- src/renderer/src/store/nutstore.ts | 17 ++++++-- src/renderer/src/store/settings.ts | 13 ++++++ src/renderer/src/types/index.ts | 1 + 20 files changed, 169 insertions(+), 42 deletions(-) diff --git a/src/main/services/BackupManager.ts b/src/main/services/BackupManager.ts index ea8521aa16..4d951f3698 100644 --- a/src/main/services/BackupManager.ts +++ b/src/main/services/BackupManager.ts @@ -77,7 +77,8 @@ class BackupManager { _: Electron.IpcMainInvokeEvent, fileName: string, data: string, - destinationPath: string = this.backupDir + destinationPath: string = this.backupDir, + skipBackupFile: boolean = false ): Promise { 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) @@ -279,7 +287,7 @@ 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) try { const result = await webdavClient.putFileContents(filename, fs.createReadStream(backupedFilePath), { diff --git a/src/preload/index.ts b/src/preload/index.ts index 828557865d..3679dd0802 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -37,8 +37,8 @@ const api = { 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), diff --git a/src/renderer/src/components/Popups/BackupPopup.tsx b/src/renderer/src/components/Popups/BackupPopup.tsx index 41eb268a16..dd19e0010b 100644 --- a/src/renderer/src/components/Popups/BackupPopup.tsx +++ b/src/renderer/src/components/Popups/BackupPopup.tsx @@ -1,6 +1,8 @@ import { backup } from '@renderer/services/BackupService' +import store from '@renderer/store' import { IpcChannel } from '@shared/IpcChannel' import { Modal, Progress } from 'antd' +import Logger from 'electron-log' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -20,6 +22,7 @@ const PopupContainer: React.FC = ({ resolve }) => { const [open, setOpen] = useState(true) const [progressData, setProgressData] = useState() const { t } = useTranslation() + const skipBackupFile = store.getState().settings.skipBackupFile useEffect(() => { const removeListener = window.electron.ipcRenderer.on(IpcChannel.BackupProgress, (_, data: ProgressData) => { @@ -32,7 +35,8 @@ const PopupContainer: React.FC = ({ resolve }) => { }, []) const onOk = async () => { - await backup() + Logger.log('[BackupManager] ', skipBackupFile) + await backup(skipBackupFile) setOpen(false) } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 2866739748..5c1e47f053 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -956,6 +956,8 @@ "app_knowledge.remove_all_confirm": "Deleting knowledge base files will reduce the storage space occupied, but will not delete the knowledge base vector data, after deletion, the source file will no longer be able to be opened. Continue?", "app_knowledge.remove_all_success": "Files removed successfully", "app_logs": "App Logs", + "backup.skip_file_data_title": "Slim Backup", + "backup.skip_file_data_help": "Skip backing up data files such as pictures and knowledge bases during backup, and only back up chat records and settings. Reduce space occupancy and speed up the backup speed.", "clear_cache": { "button": "Clear Cache", "confirm": "Clearing the cache will delete application cache data, including minapp data. This action is irreversible, continue?", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 9be762b226..ca3f98c4a3 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -953,6 +953,8 @@ "app_knowledge.remove_all": "ナレッジベースファイルを削除", "app_knowledge.remove_all_confirm": "ナレッジベースファイルを削除すると、ナレッジベース自体は削除されません。これにより、ストレージ容量を節約できます。続行しますか?", "app_knowledge.remove_all_success": "ファイル削除成功", + "backup.skip_file_data_title": "精簡バックアップ", + "backup.skip_file_data_help": "バックアップ時に、画像や知識ベースなどのデータファイルをバックアップ対象から除外し、チャット履歴と設定のみをバックアップします。スペースの占有を減らし、バックアップ速度を向上させます。", "app_logs": "アプリログ", "clear_cache": { "button": "キャッシュをクリア", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index f749fb2d01..003ac5a446 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -904,6 +904,7 @@ "restore": { "confirm": "Вы уверены, что хотите восстановить данные?", "confirm.button": "Выбрать файл резервной копии", + "content": "Операция восстановления перезапишет все текущие данные приложения данными из резервной копии. Это может занять некоторое время.", "progress": { "completed": "Восстановление завершено", @@ -954,6 +955,8 @@ "app_knowledge.remove_all_confirm": "Удаление файлов базы знаний не удалит саму базу знаний, что позволит уменьшить занимаемый объем памяти, продолжить?", "app_knowledge.remove_all_success": "Файлы удалены успешно", "app_logs": "Логи приложения", + "backup.skip_file_data_title": "Упрощенная резервная копия", + "backup.skip_file_data_help": "Пропустить при резервном копировании такие данные, как изображения, базы знаний и другие файлы данных, и сделать резервную копию только переписки и настроек. Это уменьшает использование места на диске и ускоряет процесс резервного копирования.", "clear_cache": { "button": "Очистка кэша", "confirm": "Очистка кэша удалит данные приложения. Это действие необратимо, продолжить?", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index e787498e8b..9bbdb5aa4e 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -956,6 +956,8 @@ "app_knowledge.remove_all_confirm": "删除知识库文件可以减少存储空间占用,但不会删除知识库向量化数据,删除之后将无法打开源文件,是否删除?", "app_knowledge.remove_all_success": "文件删除成功", "app_logs": "应用日志", + "backup.skip_file_data_title": "精简备份", + "backup.skip_file_data_help": "备份时跳过备份图片、知识库等数据文件,仅备份聊天记录和设置。减少空间占用, 加快备份速度。", "clear_cache": { "button": "清除缓存", "confirm": "清除缓存将删除应用缓存的数据,包括小程序数据。此操作不可恢复,是否继续?", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 8b1eff5941..085b50323a 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -956,6 +956,8 @@ "app_knowledge.remove_all_confirm": "刪除知識庫文件可以減少儲存空間佔用,但不會刪除知識庫向量化資料,刪除之後將無法開啟原始檔,是否刪除?", "app_knowledge.remove_all_success": "檔案刪除成功", "app_logs": "應用程式日誌", + "backup.skip_file_data_title": "精簡備份", + "backup.skip_file_data_help": "備份時跳過備份圖片、知識庫等數據文件,僅備份聊天記錄和設置。減少空間佔用, 加快備份速度。", "clear_cache": { "button": "清除快取", "confirm": "清除快取將刪除應用快取資料,包括小工具資料。此操作不可恢復,是否繼續?", diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 8f2cb2b295..394d271858 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -930,6 +930,8 @@ "app_knowledge.remove_all_confirm": "Η διαγραφή των αρχείων της βάσης γνώσεων μπορεί να μειώσει τη χρήση χώρου αποθήκευσης, αλλά δεν θα διαγράψει τα διανυσματωτικά δεδομένα της βάσης γνώσεων. Μετά τη διαγραφή, δεν θα μπορείτε να ανοίξετε τα αρχεία πηγή. Θέλετε να διαγράψετε;", "app_knowledge.remove_all_success": "Τα αρχεία διαγράφηκαν με επιτυχία", "app_logs": "Φάκελοι εφαρμογής", + "backup.skip_file_data_title": "Συμπυκνωμένο αντίγραφο ασφαλείας", + "backup.skip_file_data_help": "Κατά τη δημιουργία αντιγράφων ασφαλείας, παραλείψτε τις εικόνες, τις βάσεις γνώσεων και άλλα αρχεία δεδομένων. Δημιουργήστε αντίγραφα μόνο για το ιστορικό συνομιλιών και τις ρυθμίσεις. Αυτό θα μειώσει τη χρήση χώρου και θα επιταχύνει την ταχύτητα δημιουργίας αντιγράφων.", "clear_cache": { "button": "Καθαρισμός Μνήμης", "confirm": "Η διαγραφή της μνήμης θα διαγράψει τα στοιχεία καθαρισμού της εφαρμογής, συμπεριλαμβανομένων των στοιχείων πρόσθετων εφαρμογών. Αυτή η ενέργεια δεν είναι αναστρέψιμη. Θέλετε να συνεχίσετε;", @@ -1655,4 +1657,4 @@ "visualization": "προβολή" } } -} \ No newline at end of file +} diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 8dc1ad294a..f71f859ce2 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -107,6 +107,7 @@ "backup": { "confirm": "¿Está seguro de que desea realizar una copia de seguridad de los datos?", "confirm.button": "Seleccionar ubicación de copia de seguridad", + "confirm.file_checkbox": "El tamaño del archivo es {{size}}, ¿desea elegir el archivo de copia de seguridad?", "content": "Realizar una copia de seguridad de todos los datos, incluyendo registros de chat, configuraciones, bases de conocimiento y todos los demás datos. Tenga en cuenta que el proceso de copia de seguridad puede llevar algún tiempo, gracias por su paciencia.", "progress": { "completed": "Copia de seguridad completada", @@ -1655,4 +1656,4 @@ "visualization": "Visualización" } } -} \ No newline at end of file +} diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index d80f63c02b..725f89717c 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -930,6 +930,8 @@ "app_knowledge.remove_all_confirm": "La suppression des fichiers de la base de connaissances libérera de l'espace de stockage, mais ne supprimera pas les données vectorisées de la base de connaissances. Après la suppression, vous ne pourrez plus ouvrir les fichiers sources. Souhaitez-vous continuer ?", "app_knowledge.remove_all_success": "Fichiers supprimés avec succès", "app_logs": "Journaux de l'application", + "backup.skip_file_data_title": "Sauvegarde réduite", + "backup.skip_file_data_help": "Passer outre les fichiers de données tels que les images et les bases de connaissances lors de la sauvegarde, et ne sauvegarder que les conversations et les paramètres. Cela réduit l'occupation d'espace et accélère la vitesse de sauvegarde.", "clear_cache": { "button": "Effacer le cache", "confirm": "L'effacement du cache supprimera les données du cache de l'application, y compris les données des mini-programmes. Cette action ne peut pas être annulée, voulez-vous continuer ?", @@ -1655,4 +1657,4 @@ "visualization": "Visualisation" } } -} \ No newline at end of file +} diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 52ae447027..d4b5362404 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -107,6 +107,7 @@ "backup": { "confirm": "Tem certeza de que deseja fazer backup dos dados?", "confirm.button": "Escolher local de backup", + "confirm.file_checkbox": "Pule a cópia de segurança de arquivos de dados como imagens e banco de conhecimento e copie apenas as conversas e as configurações.", "content": "Fazer backup de todos os dados, incluindo registros de chat, configurações, base de conhecimento e todos os outros dados. Por favor, note que o processo de backup pode levar algum tempo. Agradecemos sua paciência.", "progress": { "completed": "Backup concluído", @@ -931,6 +932,8 @@ "app_knowledge.remove_all_confirm": "A exclusão dos arquivos da base de conhecimento reduzirá o uso do espaço de armazenamento, mas não excluirá os dados vetoriais da base de conhecimento. Após a exclusão, os arquivos originais não poderão ser abertos. Deseja excluir?", "app_knowledge.remove_all_success": "Arquivo excluído com sucesso", "app_logs": "Logs do aplicativo", + "backup.skip_file_data_title": "Backup simplificado", + "backup.skip_file_data_help": "Pule arquivos de dados como imagens e bancos de conhecimento durante o backup e realize apenas o backup das conversas e configurações. Diminua o consumo de espaço e aumente a velocidade do backup.", "clear_cache": { "button": "Limpar cache", "confirm": "Limpar cache removerá os dados armazenados em cache do aplicativo, incluindo dados de aplicativos minúsculos. Esta ação não pode ser desfeita, deseja continuar?", @@ -1656,4 +1659,4 @@ "visualization": "Visualização" } } -} \ No newline at end of file +} diff --git a/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx b/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx index ec31fd6ae7..90e25de51a 100644 --- a/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx @@ -14,15 +14,25 @@ import RestorePopup from '@renderer/components/Popups/RestorePopup' import { useTheme } from '@renderer/context/ThemeProvider' import { useKnowledgeFiles } from '@renderer/hooks/useKnowledgeFiles' import { reset } from '@renderer/services/BackupService' +import store, { useAppDispatch } from '@renderer/store' +import { setSkipBackupFile as _setSkipBackupFile } from '@renderer/store/settings' import { AppInfo } from '@renderer/types' import { formatFileSize } from '@renderer/utils' -import { Button, Typography } from 'antd' +import { Button, Switch, Typography } from 'antd' import { FileText, FolderCog, FolderInput, Sparkle } from 'lucide-react' import { FC, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..' +import { + SettingContainer, + SettingDivider, + SettingGroup, + SettingHelpText, + SettingRow, + SettingRowTitle, + SettingTitle +} from '..' import AgentsSubscribeUrlSettings from './AgentsSubscribeUrlSettings' import ExportMenuOptions from './ExportMenuSettings' import JoplinSettings from './JoplinSettings' @@ -42,6 +52,11 @@ const DataSettings: FC = () => { const { theme } = useTheme() const [menu, setMenu] = useState('data') + const _skipBackupFile = store.getState().settings.skipBackupFile + const [skipBackupFile, setSkipBackupFile] = useState(_skipBackupFile) + + const dispatch = useAppDispatch() + //joplin icon needs to be updated into iconfont const JoplinIcon = () => ( @@ -164,6 +179,11 @@ const DataSettings: FC = () => { }) } + const onSkipBackupFilesChange = (value: boolean) => { + setSkipBackupFile(value) + dispatch(_setSkipBackupFile(value)) + } + return ( @@ -208,6 +228,14 @@ const DataSettings: FC = () => { + + + {t('settings.data.backup.skip_file_data_title')} + + + + {t('settings.data.backup.skip_file_data_help')} + {t('settings.data.data.title')} diff --git a/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx b/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx index 8ef3432307..6b9784cea0 100644 --- a/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/NutstoreSettings.tsx @@ -17,25 +17,31 @@ import { useAppDispatch, useAppSelector } from '@renderer/store' import { setNutstoreAutoSync, setNutstorePath, + setNutstoreSkipBackupFile, setNutstoreSyncInterval, setNutstoreToken } from '@renderer/store/nutstore' import { modalConfirm } from '@renderer/utils' import { NUTSTORE_HOST } from '@shared/config/nutstore' -import { Button, Input, Select, Tooltip, Typography } from 'antd' +import { Button, Input, Select, Switch, Tooltip, Typography } from 'antd' import dayjs from 'dayjs' import { FC, useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { type FileStat } from 'webdav' -import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..' +import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..' const NutstoreSettings: FC = () => { const { theme } = useTheme() const { t } = useTranslation() - const { nutstoreToken, nutstorePath, nutstoreSyncInterval, nutstoreAutoSync, nutstoreSyncState } = useAppSelector( - (state) => state.nutstore - ) + const { + nutstoreToken, + nutstorePath, + nutstoreSyncInterval, + nutstoreAutoSync, + nutstoreSyncState, + nutstoreSkipBackupFile + } = useAppSelector((state) => state.nutstore) const dispatch = useAppDispatch() @@ -48,6 +54,8 @@ const NutstoreSettings: FC = () => { const [syncInterval, setSyncInterval] = useState(nutstoreSyncInterval) + const [nutSkipBackupFile, setNutSkipBackupFile] = useState(nutstoreSkipBackupFile) + const nutstoreSSOHandler = useNutstoreSSO() const [backupManagerVisible, setBackupManagerVisible] = useState(false) @@ -128,6 +136,11 @@ const NutstoreSettings: FC = () => { } } + const onSkipBackupFilesChange = (value: boolean) => { + setNutSkipBackupFile(value) + dispatch(setNutstoreSkipBackupFile(value)) + } + const handleClickPathChange = async () => { if (!nutstoreToken) { return @@ -287,6 +300,14 @@ const NutstoreSettings: FC = () => { )} + + + {t('settings.data.backup.skip_file_data_title')} + + + + {t('settings.data.backup.skip_file_data_help')} + )} <> diff --git a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx index 29221f9c05..104fa5cbd8 100644 --- a/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/WebDavSettings.tsx @@ -12,15 +12,16 @@ import { setWebdavMaxBackups as _setWebdavMaxBackups, setWebdavPass as _setWebdavPass, setWebdavPath as _setWebdavPath, + setWebdavSkipBackupFile as _setWebdavSkipBackupFile, setWebdavSyncInterval as _setWebdavSyncInterval, setWebdavUser as _setWebdavUser } from '@renderer/store/settings' -import { Button, Input, Select, Tooltip } from 'antd' +import { Button, Input, Select, Switch, Tooltip } from 'antd' import dayjs from 'dayjs' import { FC, useState } from 'react' import { useTranslation } from 'react-i18next' -import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..' +import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingRowTitle, SettingTitle } from '..' const WebDavSettings: FC = () => { const { @@ -29,13 +30,15 @@ const WebDavSettings: FC = () => { webdavPass: webDAVPass, webdavPath: webDAVPath, webdavSyncInterval: webDAVSyncInterval, - webdavMaxBackups: webDAVMaxBackups + webdavMaxBackups: webDAVMaxBackups, + webdavSkipBackupFile: webdDAVSkipBackupFile } = useSettings() const [webdavHost, setWebdavHost] = useState(webDAVHost) const [webdavUser, setWebdavUser] = useState(webDAVUser) const [webdavPass, setWebdavPass] = useState(webDAVPass) const [webdavPath, setWebdavPath] = useState(webDAVPath) + const [webdavSkipBackupFile, setWebdavSkipBackupFile] = useState(webdDAVSkipBackupFile) const [backupManagerVisible, setBackupManagerVisible] = useState(false) const [syncInterval, setSyncInterval] = useState(webDAVSyncInterval) @@ -67,6 +70,11 @@ const WebDavSettings: FC = () => { dispatch(_setWebdavMaxBackups(value)) } + const onSkipBackupFilesChange = (value: boolean) => { + setWebdavSkipBackupFile(value) + dispatch(_setWebdavSkipBackupFile(value)) + } + const renderSyncStatus = () => { if (!webdavHost) return null @@ -194,6 +202,14 @@ const WebDavSettings: FC = () => { 50 + + + {t('settings.data.backup.skip_file_data_title')} + + + + {t('settings.data.backup.skip_file_data_help')} + {webdavSync && syncInterval > 0 && ( <> diff --git a/src/renderer/src/services/BackupService.ts b/src/renderer/src/services/BackupService.ts index a40b94e0fb..e0720e36d3 100644 --- a/src/renderer/src/services/BackupService.ts +++ b/src/renderer/src/services/BackupService.ts @@ -6,12 +6,12 @@ import store from '@renderer/store' import { setWebDAVSyncState } from '@renderer/store/backup' import dayjs from 'dayjs' -export async function backup() { +export async function backup(skipBackupFile: boolean) { const filename = `cherry-studio.${dayjs().format('YYYYMMDDHHmm')}.zip` const fileContnet = await getBackupData() const selectFolder = await window.api.file.selectFolder() if (selectFolder) { - await window.api.backup.backup(filename, fileContnet, selectFolder) + await window.api.backup.backup(filename, fileContnet, selectFolder, skipBackupFile) window.message.success({ content: i18n.t('message.backup.success'), key: 'backup' }) } } @@ -83,7 +83,8 @@ export async function backupToWebdav({ store.dispatch(setWebDAVSyncState({ syncing: true, lastSyncError: null })) - const { webdavHost, webdavUser, webdavPass, webdavPath, webdavMaxBackups } = store.getState().settings + const { webdavHost, webdavUser, webdavPass, webdavPath, webdavMaxBackups, webdavSkipBackupFile } = + store.getState().settings let deviceType = 'unknown' let hostname = 'unknown' try { @@ -104,7 +105,8 @@ export async function backupToWebdav({ webdavUser, webdavPass, webdavPath, - fileName: finalFileName + fileName: finalFileName, + skipBackupFile: webdavSkipBackupFile }) if (success) { store.dispatch( diff --git a/src/renderer/src/services/NutstoreService.ts b/src/renderer/src/services/NutstoreService.ts index bec3f477e7..e576a37ff3 100644 --- a/src/renderer/src/services/NutstoreService.ts +++ b/src/renderer/src/services/NutstoreService.ts @@ -98,9 +98,13 @@ export async function backupToNutstore({ store.dispatch(setNutstoreSyncState({ syncing: true, lastSyncError: null })) const backupData = await getBackupData() - + const skipBackupFile = store.getState().nutstore.nutstoreSkipBackupFile try { - const isSuccess = await window.api.backup.backupToWebdav(backupData, { ...config, fileName: finalFileName }) + const isSuccess = await window.api.backup.backupToWebdav(backupData, { + ...config, + fileName: finalFileName, + skipBackupFile: skipBackupFile + }) if (isSuccess) { store.dispatch( diff --git a/src/renderer/src/store/nutstore.ts b/src/renderer/src/store/nutstore.ts index 97542a30be..354a93bd39 100644 --- a/src/renderer/src/store/nutstore.ts +++ b/src/renderer/src/store/nutstore.ts @@ -10,6 +10,7 @@ export interface NutstoreState { nutstoreAutoSync: boolean nutstoreSyncInterval: number nutstoreSyncState: NutstoreSyncState + nutstoreSkipBackupFile: boolean } const initialState: NutstoreState = { @@ -21,7 +22,8 @@ const initialState: NutstoreState = { lastSyncTime: null, syncing: false, lastSyncError: null - } + }, + nutstoreSkipBackupFile: false } const nutstoreSlice = createSlice({ @@ -42,11 +44,20 @@ const nutstoreSlice = createSlice({ }, setNutstoreSyncState: (state, action: PayloadAction>) => { state.nutstoreSyncState = { ...state.nutstoreSyncState, ...action.payload } + }, + setNutstoreSkipBackupFile: (state, action: PayloadAction) => { + state.nutstoreSkipBackupFile = action.payload } } }) -export const { setNutstoreToken, setNutstorePath, setNutstoreAutoSync, setNutstoreSyncInterval, setNutstoreSyncState } = - nutstoreSlice.actions +export const { + setNutstoreToken, + setNutstorePath, + setNutstoreAutoSync, + setNutstoreSyncInterval, + setNutstoreSyncState, + setNutstoreSkipBackupFile +} = nutstoreSlice.actions export default nutstoreSlice.reducer diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 81a06c756b..f68eac0a4a 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -77,6 +77,8 @@ export interface SettingsState { gridColumns: number gridPopoverTrigger: 'hover' | 'click' messageNavigation: 'none' | 'buttons' | 'anchor' + // 数据目录设置 + skipBackupFile: boolean // webdav 配置 host, user, pass, path webdavHost: string webdavUser: string @@ -85,6 +87,7 @@ export interface SettingsState { webdavAutoSync: boolean webdavSyncInterval: number webdavMaxBackups: number + webdavSkipBackupFile: boolean translateModelPrompt: string autoTranslateWithSpace: boolean showTranslateConfirm: boolean @@ -202,6 +205,7 @@ export const initialState: SettingsState = { gridColumns: 2, gridPopoverTrigger: 'click', messageNavigation: 'none', + skipBackupFile: false, webdavHost: '', webdavUser: '', webdavPass: '', @@ -209,6 +213,7 @@ export const initialState: SettingsState = { webdavAutoSync: false, webdavSyncInterval: 0, webdavMaxBackups: 0, + webdavSkipBackupFile: false, translateModelPrompt: TRANSLATE_PROMPT, autoTranslateWithSpace: false, showTranslateConfirm: true, @@ -356,6 +361,9 @@ const settingsSlice = createSlice({ setClickAssistantToShowTopic: (state, action: PayloadAction) => { state.clickAssistantToShowTopic = action.payload }, + setSkipBackupFile: (state, action: PayloadAction) => { + state.skipBackupFile = action.payload + }, setWebdavHost: (state, action: PayloadAction) => { state.webdavHost = action.payload }, @@ -377,6 +385,9 @@ const settingsSlice = createSlice({ setWebdavMaxBackups: (state, action: PayloadAction) => { state.webdavMaxBackups = action.payload }, + setWebdavSkipBackupFile: (state, action: PayloadAction) => { + state.webdavSkipBackupFile = action.payload + }, setCodeExecution: (state, action: PayloadAction<{ enabled?: boolean; timeoutMinutes?: number }>) => { if (action.payload.enabled !== undefined) { state.codeExecution.enabled = action.payload.enabled @@ -611,6 +622,7 @@ export const { setAutoCheckUpdate, setRenderInputMessageAsMarkdown, setClickAssistantToShowTopic, + setSkipBackupFile, setWebdavHost, setWebdavUser, setWebdavPass, @@ -618,6 +630,7 @@ export const { setWebdavAutoSync, setWebdavSyncInterval, setWebdavMaxBackups, + setWebdavSkipBackupFile, setCodeExecution, setCodeEditor, setCodePreview, diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 2a0d77260e..dabfd60491 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -313,6 +313,7 @@ export type WebDavConfig = { webdavPass: string webdavPath: string fileName?: string + skipBackupFile?: boolean } export type AppInfo = { From d7139cca023099a2fb6a183d7eedd46640f07e33 Mon Sep 17 00:00:00 2001 From: George Zhao <38124587+CreatorZZY@users.noreply.github.com> Date: Sat, 17 May 2025 19:48:40 +0800 Subject: [PATCH 14/23] 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 --- src/renderer/src/components/app/Navbar.tsx | 12 +++++++++--- src/renderer/src/components/app/Sidebar.tsx | 14 ++++++++++---- src/renderer/src/hooks/useFullscreen.ts | 18 ++++++++++++++++++ .../settings/MCPSettings/McpSettingsNavbar.tsx | 3 ++- 4 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 src/renderer/src/hooks/useFullscreen.ts diff --git a/src/renderer/src/components/app/Navbar.tsx b/src/renderer/src/components/app/Navbar.tsx index 7e74464612..85a5265560 100644 --- a/src/renderer/src/components/app/Navbar.tsx +++ b/src/renderer/src/components/app/Navbar.tsx @@ -1,5 +1,6 @@ import { isLinux, isMac, isWindows } from '@renderer/config/constant' import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor' +import { useFullscreen } from '@renderer/hooks/useFullscreen' import type { FC, PropsWithChildren } from 'react' import type { HTMLAttributes } from 'react' import styled from 'styled-components' @@ -25,7 +26,12 @@ export const NavbarCenter: FC = ({ children, ...props }) => { } export const NavbarRight: FC = ({ children, ...props }) => { - return {children} + const isFullscreen = useFullscreen() + return ( + + {children} + + ) } const NavbarContainer = styled.div` @@ -58,11 +64,11 @@ const NavbarCenterContainer = styled.div` color: var(--color-text-1); ` -const NavbarRightContainer = styled.div` +const NavbarRightContainer = styled.div<{ $isFullscreen: boolean }>` min-width: var(--topic-list-width); display: flex; align-items: center; padding: 0 12px; - padding-right: ${isWindows ? '140px' : isLinux ? '120px' : '12px'}; + padding-right: ${({ $isFullscreen }) => ($isFullscreen ? '12px' : isWindows ? '140px' : isLinux ? '120px' : '12px')}; justify-content: flex-end; ` diff --git a/src/renderer/src/components/app/Sidebar.tsx b/src/renderer/src/components/app/Sidebar.tsx index c319123d29..08f4c1fa22 100644 --- a/src/renderer/src/components/app/Sidebar.tsx +++ b/src/renderer/src/components/app/Sidebar.tsx @@ -3,6 +3,7 @@ import { isMac } from '@renderer/config/constant' import { AppLogo, UserAvatar } from '@renderer/config/env' import { useTheme } from '@renderer/context/ThemeProvider' import useAvatar from '@renderer/hooks/useAvatar' +import { useFullscreen } from '@renderer/hooks/useFullscreen' import { useMinappPopup } from '@renderer/hooks/useMinappPopup' import { useMinapps } from '@renderer/hooks/useMinapps' import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor' @@ -68,8 +69,13 @@ const Sidebar: FC = () => { }) } + const isFullscreen = useFullscreen() + return ( - + {isEmoji(avatar) ? ( {avatar} @@ -311,7 +317,7 @@ const PinnedApps: FC = () => { ) } -const Container = styled.div` +const Container = styled.div<{ $isFullscreen: boolean }>` display: flex; flex-direction: column; align-items: center; @@ -319,9 +325,9 @@ const Container = styled.div` padding-bottom: 12px; width: var(--sidebar-width); min-width: var(--sidebar-width); - height: ${isMac ? 'calc(100vh - var(--navbar-height))' : '100vh'}; + height: ${({ $isFullscreen }) => (isMac && !$isFullscreen ? 'calc(100vh - var(--navbar-height))' : '100vh')}; -webkit-app-region: drag !important; - margin-top: ${isMac ? 'var(--navbar-height)' : 0}; + margin-top: ${({ $isFullscreen }) => (isMac && !$isFullscreen ? 'var(--navbar-height)' : 0)}; .sidebar-avatar { margin-bottom: ${isMac ? '12px' : '12px'}; diff --git a/src/renderer/src/hooks/useFullscreen.ts b/src/renderer/src/hooks/useFullscreen.ts new file mode 100644 index 0000000000..4a5820ed8e --- /dev/null +++ b/src/renderer/src/hooks/useFullscreen.ts @@ -0,0 +1,18 @@ +import { IpcChannel } from '@shared/IpcChannel' +import { useEffect, useState } from 'react' + +export function useFullscreen() { + const [isFullscreen, setIsFullscreen] = useState(false) + + useEffect(() => { + const cleanup = window.electron.ipcRenderer.on(IpcChannel.FullscreenStatusChanged, (_, fullscreen) => { + setIsFullscreen(fullscreen) + }) + + return () => { + cleanup() + } + }, []) + + return isFullscreen +} diff --git a/src/renderer/src/pages/settings/MCPSettings/McpSettingsNavbar.tsx b/src/renderer/src/pages/settings/MCPSettings/McpSettingsNavbar.tsx index 3cfdf8af63..7986ea5766 100644 --- a/src/renderer/src/pages/settings/MCPSettings/McpSettingsNavbar.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/McpSettingsNavbar.tsx @@ -1,6 +1,7 @@ import { NavbarRight } from '@renderer/components/app/Navbar' import { HStack } from '@renderer/components/Layout' import { isLinux, isWindows } from '@renderer/config/constant' +import { useFullscreen } from '@renderer/hooks/useFullscreen' import { Button, Dropdown, Menu, type MenuProps } from 'antd' import { ChevronDown, Search } from 'lucide-react' import { useTranslation } from 'react-i18next' @@ -73,7 +74,7 @@ export const McpSettingsNavbar = () => { })) return ( - +