From b722dab56b43c660aee1371a110aace9f023a4c8 Mon Sep 17 00:00:00 2001 From: SuYao Date: Thu, 5 Jun 2025 22:49:29 +0800 Subject: [PATCH 01/60] fix(OpenAIProvider): ensure tool_calls are only yielded when present (#6861) This update modifies the OpenAIProvider to yield tool_calls only if they exist and have a length greater than zero, improving the handling of delta content. Additionally, a minor cleanup was performed by removing an unnecessary blank line in the code. --- src/renderer/src/providers/AiProvider/OpenAIProvider.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/renderer/src/providers/AiProvider/OpenAIProvider.ts b/src/renderer/src/providers/AiProvider/OpenAIProvider.ts index 833cc45a0..945a8b3ac 100644 --- a/src/renderer/src/providers/AiProvider/OpenAIProvider.ts +++ b/src/renderer/src/providers/AiProvider/OpenAIProvider.ts @@ -635,7 +635,7 @@ export default class OpenAIProvider extends BaseOpenAIProvider { if (delta?.content) { yield { type: 'text-delta', textDelta: delta.content } } - if (delta?.tool_calls) { + if (delta?.tool_calls && delta?.tool_calls.length > 0) { yield { type: 'tool-calls', delta: delta } } @@ -664,7 +664,6 @@ export default class OpenAIProvider extends BaseOpenAIProvider { for await (const chunk of readableStreamAsyncIterable(processedStream)) { const delta = chunk.type === 'finish' ? chunk.delta : chunk const rawChunk = chunk.type === 'finish' ? chunk.chunk : chunk - switch (chunk.type) { case 'reasoning': { if (time_first_token_millsec === 0) { From 4bcfbf785f0b63df9c79b3605b785a4d09553868 Mon Sep 17 00:00:00 2001 From: Doekin <105162544+Doekin@users.noreply.github.com> Date: Fri, 6 Jun 2025 00:29:47 +0800 Subject: [PATCH 02/60] feat: enable rendering and download of inline base64-encoded images (#6669) This commit introduces support for displaying and downloading inline base64-encoded images (specifically PNG and JPEG formats) within Markdown content. Key changes: - Modified 'urlTransform' in the Markdown component to allow 'data:image/png' and 'data:image/jpeg' URLs, enabling their rendering. - Updated the 'download' utility to handle 'data:' URLs, allowing users to save these inline images. Signed-off-by: Chan Lee --- .../src/pages/home/Markdown/Markdown.tsx | 8 +++- src/renderer/src/utils/download.ts | 37 ++++++++++++------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/renderer/src/pages/home/Markdown/Markdown.tsx b/src/renderer/src/pages/home/Markdown/Markdown.tsx index 8e2d64177..9c9884aba 100644 --- a/src/renderer/src/pages/home/Markdown/Markdown.tsx +++ b/src/renderer/src/pages/home/Markdown/Markdown.tsx @@ -12,7 +12,7 @@ import { findCitationInChildren, getCodeBlockId } from '@renderer/utils/markdown import { isEmpty } from 'lodash' import { type FC, memo, useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import ReactMarkdown, { type Components } from 'react-markdown' +import ReactMarkdown, { type Components, defaultUrlTransform } from 'react-markdown' import rehypeKatex from 'rehype-katex' // @ts-ignore rehype-mathjax is not typed import rehypeMathjax from 'rehype-mathjax' @@ -88,6 +88,11 @@ const Markdown: FC = ({ block }) => { } as Partial }, [onSaveCodeBlock]) + const urlTransform = useCallback((value: string) => { + if (value.startsWith('data:image/png') || value.startsWith('data:image/jpeg')) return value + return defaultUrlTransform(value) + }, []) + // if (role === 'user' && !renderInputMessageAsMarkdown) { // return

{messageContent}

// } @@ -103,6 +108,7 @@ const Markdown: FC = ({ block }) => { className="markdown" components={components} disallowedElements={DISALLOWED_ELEMENTS} + urlTransform={urlTransform} remarkRehypeOptions={{ footnoteLabel: t('common.footnotes'), footnoteLabelTagName: 'h4', diff --git a/src/renderer/src/utils/download.ts b/src/renderer/src/utils/download.ts index cbbbf22e5..5e207eff6 100644 --- a/src/renderer/src/utils/download.ts +++ b/src/renderer/src/utils/download.ts @@ -1,20 +1,31 @@ export const download = (url: string, filename?: string) => { - // 处理 file:// 协议 - if (url.startsWith('file://')) { + // 处理可直接通过 标签下载的 URL: + // - 本地文件 ( file:// ) + // - 对象 URL ( blob: ) + // - 相对安全的内联数据 ( data:image/png, data:image/jpeg ) + // (注: 其他 data 类型,如 data:text/html 或 data:image/svg+xml, + // 因其潜在安全风险,不在此处理,将由后续 fetch 逻辑处理或被 CSP 阻止。) + const SUPPORTED_PREFIXES = ['file://', 'blob:', 'data:image/png', 'data:image/jpeg'] + if (SUPPORTED_PREFIXES.some((prefix) => url.startsWith(prefix))) { const link = document.createElement('a') link.href = url - link.download = filename || url.split('/').pop() || 'download' - document.body.appendChild(link) - link.click() - link.remove() - return - } - // 处理 Blob URL - if (url.startsWith('blob:')) { - const link = document.createElement('a') - link.href = url - link.download = filename || `${Date.now()}_diagram.svg` + let resolvedFilename = filename + if (!resolvedFilename) { + if (url.startsWith('file://')) { + const pathname = new URL(url).pathname + resolvedFilename = decodeURIComponent(pathname.substring(pathname.lastIndexOf('/') + 1)) + } else if (url.startsWith('blob:')) { + resolvedFilename = `${Date.now()}_diagram.svg` + } else if (url.startsWith('data:')) { + const mimeMatch = url.match(/^data:([^;,]+)[;,]/) + const mimeType = mimeMatch && mimeMatch[1] + const extension = getExtensionFromMimeType(mimeType) + resolvedFilename = `${Date.now()}_download${extension}` + } else resolvedFilename = 'download' + } + link.download = resolvedFilename + document.body.appendChild(link) link.click() link.remove() From d80513d0118d2d97cc3d3c88c1fb58253feff048 Mon Sep 17 00:00:00 2001 From: one Date: Fri, 6 Jun 2025 00:41:03 +0800 Subject: [PATCH 03/60] refactor(CodePreview): improve the triggering timing for highlighting (#6866) --- .../components/CodeBlockView/CodePreview.tsx | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/renderer/src/components/CodeBlockView/CodePreview.tsx b/src/renderer/src/components/CodeBlockView/CodePreview.tsx index c0ebcd9a7..d3c56f295 100644 --- a/src/renderer/src/components/CodeBlockView/CodePreview.tsx +++ b/src/renderer/src/components/CodeBlockView/CodePreview.tsx @@ -134,26 +134,31 @@ const CodePreview = ({ children, language, setTools }: CodePreviewProps) => { return () => cleanupTokenizers(callerId) }, [callerId, cleanupTokenizers]) - // 处理第二次开始的代码高亮 + // 触发代码高亮 + // - 进入视口后触发第一次高亮 + // - 内容变化后触发之后的高亮 useEffect(() => { - if (prevCodeLengthRef.current > 0) { - setTimeout(highlightCode, 0) - } - }, [highlightCode]) - - // 视口检测逻辑,只处理第一次代码高亮 - useEffect(() => { - const codeElement = codeContentRef.current - if (!codeElement || prevCodeLengthRef.current > 0) return - let isMounted = true - const observer = new IntersectionObserver((entries) => { - if (entries[0].isIntersecting && isMounted) { - setTimeout(highlightCode, 0) - observer.disconnect() + if (prevCodeLengthRef.current > 0) { + setTimeout(highlightCode, 0) + return + } + + const codeElement = codeContentRef.current + if (!codeElement) return + + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].intersectionRatio > 0 && isMounted) { + setTimeout(highlightCode, 0) + observer.disconnect() + } + }, + { + rootMargin: '50px 0px 50px 0px' } - }) + ) observer.observe(codeElement) From b402cdf7ff2b8f08844a12b0b3a215080e5fa865 Mon Sep 17 00:00:00 2001 From: one Date: Fri, 6 Jun 2025 03:07:59 +0800 Subject: [PATCH 04/60] perf: improve responsiveness on streaming formulas (#6659) * perf: improve performance on streaming formulas * refactor: create throttlers for blocks * refactor: use LRU cache for better memory management --- src/renderer/src/store/thunk/messageThunk.ts | 127 ++++++++++++++----- 1 file changed, 92 insertions(+), 35 deletions(-) diff --git a/src/renderer/src/store/thunk/messageThunk.ts b/src/renderer/src/store/thunk/messageThunk.ts index 4ad208c9e..175e63c0f 100644 --- a/src/renderer/src/store/thunk/messageThunk.ts +++ b/src/renderer/src/store/thunk/messageThunk.ts @@ -38,6 +38,7 @@ import { getTopicQueue, waitForTopicQueue } from '@renderer/utils/queue' import { isOnHomePage } from '@renderer/utils/window' import { t } from 'i18next' import { throttle } from 'lodash' +import { LRUCache } from 'lru-cache' import type { AppDispatch, RootState } from '../index' import { removeManyBlocks, updateOneBlock, upsertManyBlocks, upsertOneBlock } from '../messageBlock' @@ -114,22 +115,88 @@ const updateExistingMessageAndBlocksInDB = async ( } } -// 更新单个块的逻辑,用于更新消息中的单个块 -const throttledBlockUpdate = throttle(async (id, blockUpdate) => { - // const state = store.getState() - // const block = state.messageBlocks.entities[id] - // throttle是异步函数,可能会在complete事件触发后才执行 - // if ( - // blockUpdate.status === MessageBlockStatus.STREAMING && - // (block?.status === MessageBlockStatus.SUCCESS || block?.status === MessageBlockStatus.ERROR) - // ) - // return +/** + * 消息块节流器。 + * 每个消息块有独立节流器,并发更新时不会互相影响 + */ +const blockUpdateThrottlers = new LRUCache>({ + max: 100, + ttl: 1000 * 60 * 5, + updateAgeOnGet: true +}) - store.dispatch(updateOneBlock({ id, changes: blockUpdate })) - await db.message_blocks.update(id, blockUpdate) -}, 150) +/** + * 消息块 RAF 缓存。 + * 用于管理 RAF 请求创建和取消。 + */ +const blockUpdateRafs = new LRUCache({ + max: 100, + ttl: 1000 * 60 * 5, + updateAgeOnGet: true +}) -const cancelThrottledBlockUpdate = throttledBlockUpdate.cancel +/** + * 获取或创建消息块专用的节流函数。 + */ +const getBlockThrottler = (id: string) => { + if (!blockUpdateThrottlers.has(id)) { + const throttler = throttle(async (blockUpdate: any) => { + const existingRAF = blockUpdateRafs.get(id) + if (existingRAF) { + cancelAnimationFrame(existingRAF) + } + + const rafId = requestAnimationFrame(() => { + store.dispatch(updateOneBlock({ id, changes: blockUpdate })) + blockUpdateRafs.delete(id) + }) + + blockUpdateRafs.set(id, rafId) + await db.message_blocks.update(id, blockUpdate) + }, 150) + + blockUpdateThrottlers.set(id, throttler) + } + + return blockUpdateThrottlers.get(id)! +} + +/** + * 更新单个消息块。 + */ +const throttledBlockUpdate = (id: string, blockUpdate: any) => { + const throttler = getBlockThrottler(id) + throttler(blockUpdate) +} + +/** + * 取消单个块的节流更新,移除节流器和 RAF。 + */ +const cancelThrottledBlockUpdate = (id: string) => { + const rafId = blockUpdateRafs.get(id) + if (rafId) { + cancelAnimationFrame(rafId) + blockUpdateRafs.delete(id) + } + + const throttler = blockUpdateThrottlers.get(id) + if (throttler) { + throttler.cancel() + blockUpdateThrottlers.delete(id) + } +} + +/** + * 批量清理多个消息块。 + */ +export const cleanupMultipleBlocks = (dispatch: AppDispatch, blockIds: string[]) => { + blockIds.forEach((id) => { + cancelThrottledBlockUpdate(id) + }) + if (blockIds.length > 0) { + dispatch(removeManyBlocks(blockIds)) + } +} // // 修改: 节流更新单个块的内容/状态到数据库 (仅用于 Text/Thinking Chunks) // export const throttledBlockDbUpdate = throttle( @@ -357,12 +424,12 @@ const fetchAndProcessAssistantResponseImpl = async ( } }, onTextComplete: async (finalText) => { - cancelThrottledBlockUpdate() if (lastBlockType === MessageBlockType.MAIN_TEXT && lastBlockId) { const changes = { content: finalText, status: MessageBlockStatus.SUCCESS } + cancelThrottledBlockUpdate(lastBlockId) dispatch(updateOneBlock({ id: lastBlockId, changes })) saveUpdatedBlockToDB(lastBlockId, assistantMsgId, topicId, getState) @@ -415,8 +482,6 @@ const fetchAndProcessAssistantResponseImpl = async ( } }, onThinkingComplete: (finalText, final_thinking_millsec) => { - cancelThrottledBlockUpdate() - if (lastBlockType === MessageBlockType.THINKING && lastBlockId) { const changes = { type: MessageBlockType.THINKING, @@ -424,6 +489,7 @@ const fetchAndProcessAssistantResponseImpl = async ( status: MessageBlockStatus.SUCCESS, thinking_millsec: final_thinking_millsec } + cancelThrottledBlockUpdate(lastBlockId) dispatch(updateOneBlock({ id: lastBlockId, changes })) saveUpdatedBlockToDB(lastBlockId, assistantMsgId, topicId, getState) } else { @@ -458,7 +524,6 @@ const fetchAndProcessAssistantResponseImpl = async ( } }, onToolCallComplete: (toolResponse: MCPToolResponse) => { - cancelThrottledBlockUpdate() const existingBlockId = toolCallIdToBlockIdMap.get(toolResponse.id) if (toolResponse.status === 'done' || toolResponse.status === 'error') { if (!existingBlockId) { @@ -476,6 +541,7 @@ const fetchAndProcessAssistantResponseImpl = async ( if (finalStatus === MessageBlockStatus.ERROR) { changes.error = { message: `Tool execution failed/error`, details: toolResponse.response } } + cancelThrottledBlockUpdate(existingBlockId) dispatch(updateOneBlock({ id: existingBlockId, changes })) saveUpdatedBlockToDB(existingBlockId, assistantMsgId, topicId, getState) } else { @@ -577,7 +643,6 @@ const fetchAndProcessAssistantResponseImpl = async ( } }, onError: async (error) => { - cancelThrottledBlockUpdate() console.dir(error, { depth: null }) const isErrorTypeAbort = isAbortError(error) let pauseErrorLanguagePlaceholder = '' @@ -610,6 +675,7 @@ const fetchAndProcessAssistantResponseImpl = async ( const changes: Partial = { status: isErrorTypeAbort ? MessageBlockStatus.PAUSED : MessageBlockStatus.ERROR } + cancelThrottledBlockUpdate(lastBlockId) dispatch(updateOneBlock({ id: lastBlockId, changes })) saveUpdatedBlockToDB(lastBlockId, assistantMsgId, topicId, getState) } @@ -631,8 +697,6 @@ const fetchAndProcessAssistantResponseImpl = async ( }) }, onComplete: async (status: AssistantMessageStatus, response?: Response) => { - cancelThrottledBlockUpdate() - const finalStateOnComplete = getState() const finalAssistantMsg = finalStateOnComplete.messages.entities[assistantMsgId] @@ -647,6 +711,7 @@ const fetchAndProcessAssistantResponseImpl = async ( const changes: Partial = { status: MessageBlockStatus.SUCCESS } + cancelThrottledBlockUpdate(lastBlockId) dispatch(updateOneBlock({ id: lastBlockId, changes })) saveUpdatedBlockToDB(lastBlockId, assistantMsgId, topicId, getState) } @@ -819,7 +884,7 @@ export const deleteSingleMessageThunk = try { dispatch(newMessagesActions.removeMessage({ topicId, messageId })) - dispatch(removeManyBlocks(blockIdsToDelete)) + cleanupMultipleBlocks(dispatch, blockIdsToDelete) await db.message_blocks.bulkDelete(blockIdsToDelete) const topic = await db.topics.get(topicId) if (topic) { @@ -862,7 +927,7 @@ export const deleteMessageGroupThunk = try { dispatch(newMessagesActions.removeMessagesByAskId({ topicId, askId })) - dispatch(removeManyBlocks(blockIdsToDelete)) + cleanupMultipleBlocks(dispatch, blockIdsToDelete) await db.message_blocks.bulkDelete(blockIdsToDelete) const topic = await db.topics.get(topicId) if (topic) { @@ -892,9 +957,7 @@ export const clearTopicMessagesThunk = const blockIdsToDelete = Array.from(blockIdsToDeleteSet) dispatch(newMessagesActions.clearTopicMessages(topicId)) - if (blockIdsToDelete.length > 0) { - dispatch(removeManyBlocks(blockIdsToDelete)) - } + cleanupMultipleBlocks(dispatch, blockIdsToDelete) await db.topics.update(topicId, { messages: [] }) if (blockIdsToDelete.length > 0) { @@ -968,9 +1031,7 @@ export const resendMessageThunk = } messagesToUpdateInRedux.forEach((update) => dispatch(newMessagesActions.updateMessage(update))) - if (allBlockIdsToDelete.length > 0) { - dispatch(removeManyBlocks(allBlockIdsToDelete)) - } + cleanupMultipleBlocks(dispatch, allBlockIdsToDelete) try { if (allBlockIdsToDelete.length > 0) { @@ -1069,9 +1130,7 @@ export const regenerateAssistantResponseThunk = ) // 6. Remove old blocks from Redux - if (blockIdsToDelete.length > 0) { - dispatch(removeManyBlocks(blockIdsToDelete)) - } + cleanupMultipleBlocks(dispatch, blockIdsToDelete) // 7. Update DB: Save the reset message state within the topic and delete old blocks // Fetch the current state *after* Redux updates to get the latest message list @@ -1516,9 +1575,7 @@ export const removeBlocksThunk = // 1. Update Redux state dispatch(newMessagesActions.updateMessage({ topicId, messageId, updates: { blocks: updatedBlockIds } })) - if (blockIdsToRemove.length > 0) { - dispatch(removeManyBlocks(blockIdsToRemove)) - } + cleanupMultipleBlocks(dispatch, blockIdsToRemove) const finalMessagesToSave = selectMessagesForTopic(getState(), topicId) From 61c58caf7827a9d89e19d87bb7934f627ac64ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=87=AA=E7=94=B1=E7=9A=84=E4=B8=96=E7=95=8C=E4=BA=BA?= <3196812536@qq.com> Date: Fri, 6 Jun 2025 03:42:52 +0800 Subject: [PATCH 05/60] hotfix: gemini-2.5-pro-preview-06-05 using error (#6870) --- src/renderer/src/providers/AiProvider/GeminiProvider.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/providers/AiProvider/GeminiProvider.ts b/src/renderer/src/providers/AiProvider/GeminiProvider.ts index af1adb375..1d69285f1 100644 --- a/src/renderer/src/providers/AiProvider/GeminiProvider.ts +++ b/src/renderer/src/providers/AiProvider/GeminiProvider.ts @@ -291,8 +291,7 @@ export default class GeminiProvider extends BaseProvider { if (reasoningEffort === undefined) { return { thinkingConfig: { - includeThoughts: false, - thinkingBudget: 0 + includeThoughts: false } as ThinkingConfig } } @@ -308,11 +307,11 @@ export default class GeminiProvider extends BaseProvider { } const { max } = findTokenLimit(model.id) || { max: 0 } + const budget = Math.floor(max * effortRatio) - // 如果thinking_budget是明确设置的值(包括0),使用该值 return { thinkingConfig: { - thinkingBudget: Math.floor(max * effortRatio), + ...(budget > 0 ? { thinkingBudget: budget } : {}), includeThoughts: true } as ThinkingConfig } From aa3c376deffc4726f549377fd7353f22985aaaa4 Mon Sep 17 00:00:00 2001 From: rainnoon <101509477+rainnoon@users.noreply.github.com> Date: Fri, 6 Jun 2025 04:05:17 +0800 Subject: [PATCH 06/60] fix(Inputbar): fix textarea expansion and collapse issues with long text (CherryHQ#6857) (#6873) fix(Inputbar): fix textarea expansion and collapse issues with long text (#6857) --- .../src/pages/home/Inputbar/Inputbar.tsx | 60 +++++++------------ 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 8277ae1de..62e16b148 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -723,50 +723,28 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = }, []) const onToggleExpended = () => { - if (textareaHeight) { - const textArea = textareaRef.current?.resizableTextArea?.textArea - if (textArea) { - textArea.style.height = 'auto' - setTextareaHeight(undefined) - setTimeout(() => { - textArea.style.height = `${textArea.scrollHeight}px` - }, 200) - return - } - } - - const isExpended = !expended - setExpend(isExpended) + const currentlyExpanded = expended || !!textareaHeight + const shouldExpand = !currentlyExpanded + setExpend(shouldExpand) const textArea = textareaRef.current?.resizableTextArea?.textArea - - if (textArea) { - if (isExpended) { - textArea.style.height = '70vh' - } else { - resetHeight() - } + if (!textArea) return + if (shouldExpand) { + textArea.style.height = '70vh' + setTextareaHeight(window.innerHeight * 0.7) + } else { + textArea.style.height = 'auto' + setTextareaHeight(undefined) + requestAnimationFrame(() => { + if (textArea) { + const contentHeight = textArea.scrollHeight + textArea.style.height = contentHeight > 400 ? '400px' : `${contentHeight}px` + } + }) } textareaRef.current?.focus() } - const resetHeight = () => { - if (expended) { - setExpend(false) - } - - setTextareaHeight(undefined) - - requestAnimationFrame(() => { - const textArea = textareaRef.current?.resizableTextArea?.textArea - if (textArea) { - textArea.style.height = 'auto' - const contentHeight = textArea.scrollHeight - textArea.style.height = contentHeight > 400 ? '400px' : `${contentHeight}px` - } - }) - } - const isExpended = expended || !!textareaHeight const showThinkingButton = isSupportedThinkingTokenModel(model) || isSupportedReasoningEffortModel(model) @@ -921,6 +899,8 @@ const InputBarContainer = styled.div` border-radius: 15px; padding-top: 6px; // 为拖动手柄留出空间 background-color: var(--color-background-opacity); + display: flex; + flex-direction: column; &.file-dragging { border: 2px dashed #2ecc71; @@ -954,6 +934,7 @@ const Textarea = styled(TextArea)` overflow: auto; width: 100%; box-sizing: border-box; + transition: height 0.2s ease; &.ant-input { line-height: 1.4; } @@ -968,6 +949,9 @@ const Toolbar = styled.div` margin-bottom: 4px; height: 30px; gap: 16px; + position: relative; + z-index: 2; + flex-shrink: 0; ` const ToolbarMenu = styled.div` From dfc32967ed30d2ea06daa8ab463cb7959f00f2d1 Mon Sep 17 00:00:00 2001 From: fullex <106392080+0xfullex@users.noreply.github.com> Date: Fri, 6 Jun 2025 08:29:00 +0800 Subject: [PATCH 07/60] fix(SelectionAssistant): support selection when alt key pressed (#6865) fix: support alt key selection --- package.json | 2 +- src/main/services/SelectionService.ts | 9 +++++++-- yarn.lock | 10 +++++----- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index fb1a42b38..0536e8940 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "officeparser": "^4.1.1", "os-proxy-config": "^1.1.2", "proxy-agent": "^6.5.0", - "selection-hook": "^0.9.21", + "selection-hook": "^0.9.22", "tar": "^7.4.3", "turndown": "^7.2.0", "webdav": "^5.8.0", diff --git a/src/main/services/SelectionService.ts b/src/main/services/SelectionService.ts index ea5bede2d..16aaac689 100644 --- a/src/main/services/SelectionService.ts +++ b/src/main/services/SelectionService.ts @@ -812,8 +812,8 @@ export class SelectionService { if (this.triggerMode === TriggerMode.Ctrlkey && this.isCtrlkey(data.vkCode)) { return } - //dont hide toolbar when shiftkey is pressed, because it's used for selection - if (this.isShiftkey(data.vkCode)) { + //dont hide toolbar when shiftkey or altkey is pressed, because it's used for selection + if (this.isShiftkey(data.vkCode) || this.isAltkey(data.vkCode)) { return } @@ -901,6 +901,11 @@ export class SelectionService { return vkCode === 160 || vkCode === 161 } + //check if the key is alt key + private isAltkey(vkCode: number) { + return vkCode === 164 || vkCode === 165 + } + /** * Create a preloaded action window for quick response * Action windows handle specific operations on selected text diff --git a/yarn.lock b/yarn.lock index 4a407de19..e9aadd59c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5667,7 +5667,7 @@ __metadata: remark-math: "npm:^6.0.0" rollup-plugin-visualizer: "npm:^5.12.0" sass: "npm:^1.88.0" - selection-hook: "npm:^0.9.21" + selection-hook: "npm:^0.9.22" shiki: "npm:^3.4.2" string-width: "npm:^7.2.0" styled-components: "npm:^6.1.11" @@ -16384,13 +16384,13 @@ __metadata: languageName: node linkType: hard -"selection-hook@npm:^0.9.21": - version: 0.9.21 - resolution: "selection-hook@npm:0.9.21" +"selection-hook@npm:^0.9.22": + version: 0.9.22 + resolution: "selection-hook@npm:0.9.22" dependencies: node-gyp: "npm:latest" node-gyp-build: "npm:^4.8.4" - checksum: 10c0/e458f8d37c049899e0f9450ca29f882ee01e11b15490f7ee2ddd088843e3e9a201fd5410083faebfbf11e1259a2a74b0b736ff8f1789b626a21ea48ef0ca8705 + checksum: 10c0/4a202485fc4aed250f4ab863fdca9235240d49e5629549f5cf72dd3087af81ee54d65c28fa7d215d7ca609e25132b125e10540acbab1f6f4a7d8a508e1468ea3 languageName: node linkType: hard From 67ab36e0eadaae5974fa6eb74a22d6b956ee59d4 Mon Sep 17 00:00:00 2001 From: one Date: Fri, 6 Jun 2025 09:24:11 +0800 Subject: [PATCH 08/60] refactor(SelectionToolbar): add transition effects to action buttons (#6869) * refactor(SelectionToolbar): add transition effects to action buttons * refactor: reduce transition duration --- .../src/windows/selection/toolbar/SelectionToolbar.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx b/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx index 770c27ed7..2dd0cd3f8 100644 --- a/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx +++ b/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx @@ -310,15 +310,22 @@ const ActionButton = styled.div` cursor: pointer; border-radius: 4px; padding: 4px 6px; + transition: all 0.1s ease-in-out; + will-change: color, background-color; + .btn-icon { width: 16px; height: 16px; color: var(--color-selection-toolbar-text); background-color: transparent; + transition: color 0.1s ease-in-out; + will-change: color; } .btn-title { color: var(--color-selection-toolbar-text); --font-size: 14px; + transition: color 0.1s ease-in-out; + will-change: color; } &:hover { color: var(--color-primary); From 14e6a800497d7934d2508e5b876049d9103e1074 Mon Sep 17 00:00:00 2001 From: one Date: Fri, 6 Jun 2025 12:34:02 +0800 Subject: [PATCH 09/60] fix(SelectionToolbar): prevent dragging the demo (#6888) --- .../src/windows/selection/toolbar/SelectionToolbar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx b/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx index 2dd0cd3f8..07103f4c6 100644 --- a/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx +++ b/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx @@ -227,7 +227,7 @@ const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => { return ( - + @@ -261,13 +261,13 @@ const Container = styled.div` box-sizing: border-box; ` -const LogoWrapper = styled.div` +const LogoWrapper = styled.div<{ $draggable: boolean }>` display: flex; align-items: center; justify-content: center; - -webkit-app-region: drag; margin-left: 5px; background-color: transparent; + ${({ $draggable }) => $draggable && ' -webkit-app-region: drag;'} ` const Logo = styled(Avatar)` From 59195fec1aa6a81d34a9da01145ae949552c98c9 Mon Sep 17 00:00:00 2001 From: fullex <106392080+0xfullex@users.noreply.github.com> Date: Fri, 6 Jun 2025 14:19:55 +0800 Subject: [PATCH 10/60] fix(SelectionAssistant): default disabled (#6897) fix: selection default off --- src/main/services/ConfigManager.ts | 2 +- src/renderer/src/store/selectionStore.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/services/ConfigManager.ts b/src/main/services/ConfigManager.ts index fbe871cbb..c4e9c6555 100644 --- a/src/main/services/ConfigManager.ts +++ b/src/main/services/ConfigManager.ts @@ -151,7 +151,7 @@ export class ConfigManager { // Selection Assistant: is enabled the selection assistant getSelectionAssistantEnabled(): boolean { - return this.get(ConfigKeys.SelectionAssistantEnabled, true) + return this.get(ConfigKeys.SelectionAssistantEnabled, false) } setSelectionAssistantEnabled(value: boolean) { diff --git a/src/renderer/src/store/selectionStore.ts b/src/renderer/src/store/selectionStore.ts index 09e6c8326..eecf9b7fe 100644 --- a/src/renderer/src/store/selectionStore.ts +++ b/src/renderer/src/store/selectionStore.ts @@ -18,7 +18,7 @@ export const defaultActionItems: ActionItem[] = [ ] export const initialState: SelectionState = { - selectionEnabled: true, + selectionEnabled: false, triggerMode: 'selected', isCompact: false, isAutoClose: false, From 8efa7d25f81ce43f1fc0a6f6c40fe53a66ca7098 Mon Sep 17 00:00:00 2001 From: SuYao Date: Fri, 6 Jun 2025 15:16:12 +0800 Subject: [PATCH 11/60] fix(Inputbar): remove unnecessary flex properties from Inputbar styles (#6902) --- src/renderer/src/pages/home/Inputbar/Inputbar.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 62e16b148..9b37287eb 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -899,8 +899,6 @@ const InputBarContainer = styled.div` border-radius: 15px; padding-top: 6px; // 为拖动手柄留出空间 background-color: var(--color-background-opacity); - display: flex; - flex-direction: column; &.file-dragging { border: 2px dashed #2ecc71; @@ -929,7 +927,6 @@ const Textarea = styled(TextArea)` padding: 0; border-radius: 0; display: flex; - flex: 1; resize: none !important; overflow: auto; width: 100%; From bc5cc4bf020209eff74d5adcef8feb5f23ffa3fb Mon Sep 17 00:00:00 2001 From: SuYao Date: Fri, 6 Jun 2025 15:18:16 +0800 Subject: [PATCH 12/60] hotfix: enhance OpenAI stream handling and error management (#6541) fix: enhance OpenAI stream handling and error management - Updated the `openAIChunkToTextDelta` function to include error handling with a try-catch block, improving robustness during stream processing. - Refined the `readableStreamAsyncIterable` function to ensure proper handling of stream completion and errors, including a return method for cleanup. - Adjusted type definitions for better clarity and consistency in the handling of async iterables. --- .../providers/AiProvider/OpenAIProvider.ts | 52 ++++++++++--------- src/renderer/src/utils/stream.ts | 32 +++++++++--- 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/src/renderer/src/providers/AiProvider/OpenAIProvider.ts b/src/renderer/src/providers/AiProvider/OpenAIProvider.ts index 945a8b3ac..1c2a49756 100644 --- a/src/renderer/src/providers/AiProvider/OpenAIProvider.ts +++ b/src/renderer/src/providers/AiProvider/OpenAIProvider.ts @@ -619,32 +619,36 @@ export default class OpenAIProvider extends BaseOpenAIProvider { } const reasoningTag = getAppropriateTag(model) async function* openAIChunkToTextDelta(stream: any): AsyncGenerator { - for await (const chunk of stream) { - if (window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)) { - break - } - - if (chunk.choices && chunk.choices.length > 0) { - const delta = chunk.choices[0]?.delta - if ( - (delta?.reasoning_content && delta?.reasoning_content !== '\n') || - (delta?.reasoning && delta?.reasoning !== '\n') - ) { - yield { type: 'reasoning', textDelta: delta.reasoning_content || delta.reasoning } - } - if (delta?.content) { - yield { type: 'text-delta', textDelta: delta.content } - } - if (delta?.tool_calls && delta?.tool_calls.length > 0) { - yield { type: 'tool-calls', delta: delta } - } - - const finishReason = chunk?.choices[0]?.finish_reason - if (!isEmpty(finishReason)) { - yield { type: 'finish', finishReason, usage: chunk.usage, delta, chunk } + try { + for await (const chunk of stream) { + if (window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)) { break } + + if (chunk.choices && chunk.choices.length > 0) { + const delta = chunk.choices[0]?.delta + if ( + (delta?.reasoning_content && delta?.reasoning_content !== '\n') || + (delta?.reasoning && delta?.reasoning !== '\n') + ) { + yield { type: 'reasoning', textDelta: delta.reasoning_content || delta.reasoning } + } + if (delta?.content) { + yield { type: 'text-delta', textDelta: delta.content } + } + if (delta?.tool_calls && delta?.tool_calls.length > 0) { + yield { type: 'tool-calls', delta: delta } + } + + const finishReason = chunk?.choices[0]?.finish_reason + if (!isEmpty(finishReason)) { + yield { type: 'finish', finishReason, usage: chunk.usage, delta, chunk } + } + } } + } catch (error) { + console.error('[openAIChunkToTextDelta] error', error) + throw error } } @@ -661,7 +665,7 @@ export default class OpenAIProvider extends BaseOpenAIProvider { }) // 3. 消费 processedStream,分发 onChunk - for await (const chunk of readableStreamAsyncIterable(processedStream)) { + for await (const chunk of readableStreamAsyncIterable(processedStream)) { const delta = chunk.type === 'finish' ? chunk.delta : chunk const rawChunk = chunk.type === 'finish' ? chunk.chunk : chunk switch (chunk.type) { diff --git a/src/renderer/src/utils/stream.ts b/src/renderer/src/utils/stream.ts index 77119db8f..e1d34cc94 100644 --- a/src/renderer/src/utils/stream.ts +++ b/src/renderer/src/utils/stream.ts @@ -1,12 +1,32 @@ -export function readableStreamAsyncIterable(stream: ReadableStream): AsyncIterable { +/** + * Most browsers don't yet have async iterable support for ReadableStream, + * and Node has a very different way of reading bytes from its "ReadableStream". + * + * This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490 + */ +export function readableStreamAsyncIterable(stream: any): AsyncIterableIterator { + if (stream[Symbol.asyncIterator]) return stream + const reader = stream.getReader() return { - [Symbol.asyncIterator](): AsyncIterator { - return { - async next(): Promise> { - return reader.read() as Promise> - } + async next() { + try { + const result = await reader.read() + if (result?.done) reader.releaseLock() // release lock when stream becomes closed + return result + } catch (e) { + reader.releaseLock() // release lock when stream becomes errored + throw e } + }, + async return() { + const cancelPromise = reader.cancel() + reader.releaseLock() + await cancelPromise + return { done: true, value: undefined } + }, + [Symbol.asyncIterator]() { + return this } } } From d47c93b4d8cc2318c5958b2ac1cabcc329a8ca04 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Fri, 6 Jun 2025 15:48:54 +0800 Subject: [PATCH 13/60] feat: add set feed url functionality for early access (#5723) * feat: add update channel functionality for beta testing - Introduced a new IPC channel for setting the update channel. - Implemented logic in AppUpdater to handle update channel changes. - Updated settings to include a beta testing toggle, allowing users to switch between stable and beta update channels. - Enhanced the settings UI to reflect the new beta testing option. * add i18n * update i18n * update i18n * refactor: rename update channel to feed URL and update related functionality - Changed IPC channel from App_SetUpdateChannel to App_SetFeedUrl. - Updated AppUpdater to set feed URL instead of update channel. - Modified preload and settings to reflect the new feed URL functionality. - Added constants for production and early access feed URLs. * refactor: remove setAutoUpdate method from API - Eliminated the setAutoUpdate method from the API object in preload index, streamlining the IPC communication interface. * refactor: update early access feed URL and improve tooltip descriptions - Changed EARLY_ACCESS_FEED_URL to point to the latest GitHub release. - Simplified the setEarlyAccess function to directly set the feed URL. - Added tooltips for early access settings in multiple languages to inform users about potential instability and the need for data backup. * feat(migrate): add early access setting to state configuration - Introduced a new state setting 'earlyAccess' and initialized it to false in the migration configuration. * fix(i18n): update early access tooltip translations for clarity - Revised the tooltip descriptions for the early access feature in English, Simplified Chinese, and Traditional Chinese to enhance clarity and ensure consistency in messaging regarding potential instability and the importance of data backup. * feat: introduce FeedUrl enum for centralized feed URL management - Added a new enum `FeedUrl` in the constants file to define production and early access feed URLs. - Updated relevant IPC handlers and services to utilize the `FeedUrl` enum for type safety and consistency. - Refactored the configuration manager to include methods for getting and setting the feed URL using the new enum. * feat(settings): initialize early access and auto-update settings in AboutSettings component - Added initialization for early access and auto-check update settings in the AboutSettings component to enhance user configuration options. --------- Co-authored-by: beyondkmp --- packages/shared/IpcChannel.ts | 1 + packages/shared/config/constant.ts | 5 +++++ src/main/ipc.ts | 5 +++++ src/main/services/AppUpdater.ts | 7 +++++++ src/main/services/ConfigManager.ts | 11 ++++++++++- src/preload/index.ts | 2 ++ src/renderer/src/hooks/useSettings.ts | 7 +++++++ 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 | 4 +++- src/renderer/src/i18n/locales/zh-cn.json | 2 ++ src/renderer/src/i18n/locales/zh-tw.json | 4 +++- src/renderer/src/pages/settings/AboutSettings.tsx | 15 +++++++++++++-- src/renderer/src/store/migrate.ts | 1 + src/renderer/src/store/settings.ts | 6 ++++++ 15 files changed, 69 insertions(+), 5 deletions(-) diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 8c74ffcae..9cc3a1d99 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -13,6 +13,7 @@ export enum IpcChannel { App_SetTrayOnClose = 'app:set-tray-on-close', App_SetTheme = 'app:set-theme', App_SetAutoUpdate = 'app:set-auto-update', + App_SetFeedUrl = 'app:set-feed-url', App_HandleZoomFactor = 'app:handle-zoom-factor', App_IsBinaryExist = 'app:is-binary-exist', diff --git a/packages/shared/config/constant.ts b/packages/shared/config/constant.ts index e1fca4e6d..cfba46df7 100644 --- a/packages/shared/config/constant.ts +++ b/packages/shared/config/constant.ts @@ -403,3 +403,8 @@ export const KB = 1024 export const MB = 1024 * KB export const GB = 1024 * MB export const defaultLanguage = 'en-US' + +export enum FeedUrl { + PRODUCTION = 'https://releases.cherry-ai.com', + EARLY_ACCESS = 'https://github.com/CherryHQ/cherry-studio/releases/latest/download' +} diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 2fd60377c..5339e8169 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -34,6 +34,7 @@ import { calculateDirectorySize, getResourcePath } from './utils' import { decrypt, encrypt } from './utils/aes' import { getCacheDir, getConfigDir, getFilesDir } from './utils/file' import { compress, decompress } from './utils/zip' +import { FeedUrl } from '@shared/config/constant' const fileManager = new FileStorage() const backupManager = new BackupManager() @@ -112,6 +113,10 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { configManager.setAutoUpdate(isActive) }) + ipcMain.handle(IpcChannel.App_SetFeedUrl, (_, feedUrl: FeedUrl) => { + appUpdater.setFeedUrl(feedUrl) + }) + ipcMain.handle(IpcChannel.Config_Set, (_, key: string, value: any, isNotify: boolean = false) => { configManager.set(key, value, isNotify) }) diff --git a/src/main/services/AppUpdater.ts b/src/main/services/AppUpdater.ts index 1733bc606..772c885a0 100644 --- a/src/main/services/AppUpdater.ts +++ b/src/main/services/AppUpdater.ts @@ -1,6 +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 { UpdateInfo } from 'builder-util-runtime' import { app, BrowserWindow, dialog } from 'electron' import logger from 'electron-log' @@ -20,6 +21,7 @@ export default class AppUpdater { autoUpdater.forceDevUpdateConfig = !app.isPackaged autoUpdater.autoDownload = configManager.getAutoUpdate() autoUpdater.autoInstallOnAppQuit = configManager.getAutoUpdate() + autoUpdater.setFeedURL(configManager.getFeedUrl()) // 检测下载错误 autoUpdater.on('error', (error) => { @@ -62,6 +64,11 @@ export default class AppUpdater { autoUpdater.autoInstallOnAppQuit = isActive } + public setFeedUrl(feedUrl: FeedUrl) { + autoUpdater.setFeedURL(feedUrl) + configManager.setFeedUrl(feedUrl) + } + public async checkForUpdates() { if (isWin && 'PORTABLE_EXECUTABLE_DIR' in process.env) { return { diff --git a/src/main/services/ConfigManager.ts b/src/main/services/ConfigManager.ts index c4e9c6555..573674bd7 100644 --- a/src/main/services/ConfigManager.ts +++ b/src/main/services/ConfigManager.ts @@ -1,4 +1,4 @@ -import { defaultLanguage, ZOOM_SHORTCUTS } from '@shared/config/constant' +import { defaultLanguage, FeedUrl, ZOOM_SHORTCUTS } from '@shared/config/constant' import { LanguageVarious, Shortcut, ThemeMode } from '@types' import { app } from 'electron' import Store from 'electron-store' @@ -16,6 +16,7 @@ export enum ConfigKeys { ClickTrayToShowQuickAssistant = 'clickTrayToShowQuickAssistant', EnableQuickAssistant = 'enableQuickAssistant', AutoUpdate = 'autoUpdate', + FeedUrl = 'feedUrl', EnableDataCollection = 'enableDataCollection', SelectionAssistantEnabled = 'selectionAssistantEnabled', SelectionAssistantTriggerMode = 'selectionAssistantTriggerMode', @@ -141,6 +142,14 @@ export class ConfigManager { this.set(ConfigKeys.AutoUpdate, value) } + getFeedUrl(): string { + return this.get(ConfigKeys.FeedUrl, FeedUrl.PRODUCTION) + } + + setFeedUrl(value: FeedUrl) { + this.set(ConfigKeys.FeedUrl, value) + } + getEnableDataCollection(): boolean { return this.get(ConfigKeys.EnableDataCollection, true) } diff --git a/src/preload/index.ts b/src/preload/index.ts index 5e9f3681c..4ac4b39e9 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -5,6 +5,7 @@ import { FileType, KnowledgeBaseParams, KnowledgeItem, MCPServer, Shortcut, Them import { contextBridge, ipcRenderer, OpenDialogOptions, shell, webUtils } from 'electron' import { Notification } from 'src/renderer/src/types/notification' import { CreateDirectoryOptions } from 'webdav' +import { FeedUrl } from '@shared/config/constant' import type { ActionItem } from '../renderer/src/types/selectionTypes' @@ -20,6 +21,7 @@ const api = { setLaunchToTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetLaunchToTray, isActive), setTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTray, isActive), setTrayOnClose: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTrayOnClose, isActive), + setFeedUrl: (feedUrl: FeedUrl) => ipcRenderer.invoke(IpcChannel.App_SetFeedUrl, feedUrl), setTheme: (theme: ThemeMode) => ipcRenderer.invoke(IpcChannel.App_SetTheme, theme), handleZoomFactor: (delta: number, reset: boolean = false) => ipcRenderer.invoke(IpcChannel.App_HandleZoomFactor, delta, reset), diff --git a/src/renderer/src/hooks/useSettings.ts b/src/renderer/src/hooks/useSettings.ts index c058a9523..10f9ee900 100644 --- a/src/renderer/src/hooks/useSettings.ts +++ b/src/renderer/src/hooks/useSettings.ts @@ -4,6 +4,7 @@ import { SendMessageShortcut, setAssistantIconType, setAutoCheckUpdate as _setAutoCheckUpdate, + setEarlyAccess as _setEarlyAccess, setLaunchOnBoot, setLaunchToTray, setPinTopicsToTop, @@ -19,6 +20,7 @@ import { setWindowStyle } from '@renderer/store/settings' import { SidebarIcon, ThemeMode, TranslateLanguageVarious } from '@renderer/types' +import { FeedUrl } from '@shared/config/constant' export function useSettings() { const settings = useAppSelector((state) => state.settings) @@ -58,6 +60,11 @@ export function useSettings() { window.api.setAutoUpdate(isAutoUpdate) }, + setEarlyAccess(isEarlyAccess: boolean) { + dispatch(_setEarlyAccess(isEarlyAccess)) + window.api.setFeedUrl(isEarlyAccess ? FeedUrl.EARLY_ACCESS : FeedUrl.PRODUCTION) + }, + setTheme(theme: ThemeMode) { dispatch(setTheme(theme)) }, diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 2e8d29bda..c31ae341e 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1345,6 +1345,8 @@ "general.emoji_picker": "Emoji Picker", "general.image_upload": "Image Upload", "general.auto_check_update.title": "Auto Update", + "general.early_access.title": "Early Access", + "general.early_access.tooltip": "Enable to use the latest version from GitHub, which may be slower. Please backup your data in advance.", "general.reset.button": "Reset", "general.reset.title": "Data Reset", "general.restore.button": "Restore", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 7b1bfce8e..8467a1c1b 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1758,6 +1758,8 @@ "content_limit_tooltip": "検索結果の内容長を制限し、制限を超える内容は切り捨てられます。" }, "general.auto_check_update.title": "自動更新", + "general.early_access.title": "早期アクセス", + "general.early_access.tooltip": "有効にすると、GitHub の最新バージョンを使用します。ダウンロード速度が遅く、不安定な場合があります。データを事前にバックアップしてください。", "quickPhrase": { "title": "クイックフレーズ", "add": "フレーズを追加", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 8fd2f9159..2aae5c5ba 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1757,7 +1757,9 @@ "content_limit": "Ограничение длины текста", "content_limit_tooltip": "Ограничьте длину содержимого результатов поиска, контент, превышающий ограничение, будет обрезан." }, - "general.auto_check_update.title": "Включить автообновление", + "general.auto_check_update.title": "Автоматическое обновление", + "general.early_access.title": "Ранний доступ", + "general.early_access.tooltip": "Включить для использования последней версии из GitHub, что может быть медленнее и нестабильно. Пожалуйста, сделайте резервную копию данных заранее.", "quickPhrase": { "title": "Быстрые фразы", "add": "Добавить фразу", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 228e738dc..04feec6f3 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1345,6 +1345,8 @@ "general.emoji_picker": "表情选择器", "general.image_upload": "图片上传", "general.auto_check_update.title": "自动更新", + "general.early_access.title": "抢先体验", + "general.early_access.tooltip": "开启后,将使用 GitHub 的最新版本,下载速度可能较慢,请务必提前备份数据", "general.reset.button": "重置", "general.reset.title": "重置数据", "general.restore.button": "恢复", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 02f02bf42..b205a7d8d 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1760,7 +1760,9 @@ "content_limit": "內容長度限制", "content_limit_tooltip": "限制搜尋結果的內容長度,超過限制的內容將被截斷" }, - "general.auto_check_update.title": "啟用自動更新", + "general.auto_check_update.title": "自動更新", + "general.early_access.title": "搶先體驗", + "general.early_access.tooltip": "開啟後,將使用 GitHub 的最新版本,下載速度可能較慢,請務必提前備份數據", "quickPhrase": { "title": "快捷短語", "add": "新增短語", diff --git a/src/renderer/src/pages/settings/AboutSettings.tsx b/src/renderer/src/pages/settings/AboutSettings.tsx index bd7e174f6..0c292497c 100644 --- a/src/renderer/src/pages/settings/AboutSettings.tsx +++ b/src/renderer/src/pages/settings/AboutSettings.tsx @@ -10,7 +10,7 @@ import { useAppDispatch } from '@renderer/store' import { setUpdateState } from '@renderer/store/runtime' import { ThemeMode } from '@renderer/types' import { compareVersions, runAsyncFunction } from '@renderer/utils' -import { Avatar, Button, Progress, Row, Switch, Tag } from 'antd' +import { Avatar, Button, Progress, Row, Switch, Tag, Tooltip } from 'antd' import { debounce } from 'lodash' import { Bug, FileCheck, Github, Globe, Mail, Rss } from 'lucide-react' import { FC, useEffect, useState } from 'react' @@ -25,7 +25,7 @@ const AboutSettings: FC = () => { const [version, setVersion] = useState('') const [isPortable, setIsPortable] = useState(false) const { t } = useTranslation() - const { autoCheckUpdate, setAutoCheckUpdate } = useSettings() + const { autoCheckUpdate, setAutoCheckUpdate, earlyAccess, setEarlyAccess } = useSettings() const { theme } = useTheme() const dispatch = useAppDispatch() const { update } = useRuntime() @@ -101,6 +101,10 @@ const AboutSettings: FC = () => { setVersion(appInfo.version) setIsPortable(appInfo.isPortable) }) + + // 初始化设置 + setEarlyAccess(earlyAccess) + setAutoCheckUpdate(autoCheckUpdate) }, []) return ( @@ -161,6 +165,13 @@ const AboutSettings: FC = () => { {t('settings.general.auto_check_update.title')} setAutoCheckUpdate(v)} /> + + + {t('settings.general.early_access.title')} + + setEarlyAccess(v)} /> + + )} diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index bf19e7b6d..abec96fa7 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -1480,6 +1480,7 @@ const migrateConfig = { state.paintings.tokenFluxPaintings = [] } state.settings.showTokens = true + state.settings.earlyAccess = false return state } catch (error) { return state diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 2f5e62754..429c48ef9 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -67,6 +67,7 @@ export interface SettingsState { pasteLongTextThreshold: number clickAssistantToShowTopic: boolean autoCheckUpdate: boolean + earlyAccess: boolean renderInputMessageAsMarkdown: boolean // 代码执行 codeExecution: { @@ -216,6 +217,7 @@ export const initialState: SettingsState = { pasteLongTextThreshold: 1500, clickAssistantToShowTopic: true, autoCheckUpdate: true, + earlyAccess: false, renderInputMessageAsMarkdown: false, codeExecution: { enabled: false, @@ -418,6 +420,9 @@ const settingsSlice = createSlice({ setAutoCheckUpdate: (state, action: PayloadAction) => { state.autoCheckUpdate = action.payload }, + setEarlyAccess: (state, action: PayloadAction) => { + state.earlyAccess = action.payload + }, setRenderInputMessageAsMarkdown: (state, action: PayloadAction) => { state.renderInputMessageAsMarkdown = action.payload }, @@ -707,6 +712,7 @@ export const { setAssistantIconType, setPasteLongTextAsFile, setAutoCheckUpdate, + setEarlyAccess, setRenderInputMessageAsMarkdown, setClickAssistantToShowTopic, setSkipBackupFile, From 3cb34d30a94a8eedffc37d8166cf0510393615e9 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Fri, 6 Jun 2025 15:48:27 +0800 Subject: [PATCH 14/60] refactor: remove isPreset messages and assistant.messages --- src/renderer/src/databases/upgrades.ts | 1 - src/renderer/src/i18n/locales/en-us.json | 10 -- src/renderer/src/i18n/locales/ja-jp.json | 10 -- src/renderer/src/i18n/locales/ru-ru.json | 10 -- src/renderer/src/i18n/locales/zh-cn.json | 10 -- src/renderer/src/i18n/locales/zh-tw.json | 10 -- src/renderer/src/i18n/translate/el-gr.json | 10 -- src/renderer/src/i18n/translate/es-es.json | 10 -- src/renderer/src/i18n/translate/fr-fr.json | 10 -- src/renderer/src/i18n/translate/pt-pt.json | 10 -- .../src/pages/home/Inputbar/Inputbar.tsx | 3 +- .../src/pages/home/Messages/Message.tsx | 6 - .../src/pages/home/Messages/MessageGroup.tsx | 15 +- .../src/pages/home/Messages/Messages.tsx | 1 - .../AssistantMessagesSettings.tsx | 170 ------------------ .../settings/AssistantSettings/index.tsx | 12 -- .../providers/AiProvider/AnthropicProvider.ts | 10 +- .../providers/AiProvider/GeminiProvider.ts | 12 +- .../providers/AiProvider/OpenAIProvider.ts | 10 +- .../AiProvider/OpenAIResponseProvider.ts | 10 +- src/renderer/src/services/AssistantService.ts | 48 ----- src/renderer/src/store/agents.ts | 3 +- src/renderer/src/store/assistants.ts | 3 +- src/renderer/src/types/index.ts | 2 - src/renderer/src/types/newMessage.ts | 1 - .../src/utils/__tests__/export.test.ts | 1 - .../src/utils/__tests__/formats.test.ts | 1 - 27 files changed, 22 insertions(+), 377 deletions(-) delete mode 100644 src/renderer/src/pages/settings/AssistantSettings/AssistantMessagesSettings.tsx diff --git a/src/renderer/src/databases/upgrades.ts b/src/renderer/src/databases/upgrades.ts index fae8a7471..cb1e770db 100644 --- a/src/renderer/src/databases/upgrades.ts +++ b/src/renderer/src/databases/upgrades.ts @@ -281,7 +281,6 @@ export async function upgradeToV7(tx: Transaction): Promise { modelId: oldMessage.modelId, model: oldMessage.model, type: oldMessage.type === 'clear' ? 'clear' : undefined, - isPreset: oldMessage.isPreset, useful: oldMessage.useful, askId: oldMessage.askId, mentions: oldMessage.mentions, diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index c31ae341e..a9aa935df 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -30,16 +30,7 @@ "agent": "Export Agent" }, "delete.popup.content": "Are you sure you want to delete this agent?", - "edit.message.add.title": "Add", - "edit.message.assistant.placeholder": "Enter assistant message", - "edit.message.assistant.title": "Assistant", - "edit.message.empty.content": "Conversation input content cannot be empty", - "edit.message.group.title": "Message Group", - "edit.message.title": "Preset messages", - "edit.message.user.placeholder": "Enter user message", - "edit.message.user.title": "User", "edit.model.select.title": "Select Model", - "edit.settings.hide_preset_messages": "Hide Preset Message", "edit.title": "Edit Agent", "manage.title": "Manage Agents", "my_agents": "My Agents", @@ -76,7 +67,6 @@ "settings.mcp.noServersAvailable": "No MCP servers available. Add servers in settings", "settings.mcp.description": "Default enabled MCP servers", "settings.model": "Model Settings", - "settings.preset_messages": "Preset Messages", "settings.prompt": "Prompt Settings", "settings.reasoning_effort": "Reasoning effort", "settings.reasoning_effort.off": "Off", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 8467a1c1b..0c558b7d4 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -30,16 +30,7 @@ "agent": "エージェントをエクスポート" }, "delete.popup.content": "このエージェントを削除してもよろしいですか?", - "edit.message.add.title": "追加", - "edit.message.assistant.placeholder": "アシスタントのメッセージを入力", - "edit.message.assistant.title": "アシスタント", - "edit.message.empty.content": "会話の入力内容が空です", - "edit.message.group.title": "メッセージグループ", - "edit.message.title": "プリセットメッセージ", - "edit.message.user.placeholder": "ユーザーメッセージを入力", - "edit.message.user.title": "ユーザー", "edit.model.select.title": "モデルを選択", - "edit.settings.hide_preset_messages": "プリセットメッセージを非表示", "edit.title": "エージェントを編集", "manage.title": "エージェントを管理", "my_agents": "マイエージェント", @@ -76,7 +67,6 @@ "settings.default_model": "デフォルトモデル", "settings.knowledge_base": "ナレッジベース設定", "settings.model": "モデル設定", - "settings.preset_messages": "プリセットメッセージ", "settings.prompt": "プロンプト設定", "settings.reasoning_effort": "思考連鎖の長さ", "settings.reasoning_effort.off": "オフ", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 2aae5c5ba..b461be3c7 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -11,16 +11,7 @@ "add.prompt.variables.tip": "Доступные переменные: {{date}}, {{time}}, {{datetime}}, {{system}}, {{arch}}, {{language}}, {{model_name}}", "add.title": "Создать агента", "delete.popup.content": "Вы уверены, что хотите удалить этого агента?", - "edit.message.add.title": "Добавить", - "edit.message.assistant.placeholder": "Введите сообщение ассистента", - "edit.message.assistant.title": "Ассистент", - "edit.message.empty.content": "Содержание вводимого сообщения не может быть пустым", - "edit.message.group.title": "Группа сообщений", - "edit.message.title": "Предустановленные сообщения", - "edit.message.user.placeholder": "Введите сообщение пользователя", - "edit.message.user.title": "Пользователь", "edit.model.select.title": "Выбрать модель", - "edit.settings.hide_preset_messages": "Скрыть предустановленные сообщения", "edit.title": "Редактировать агента", "manage.title": "Редактировать агентов", "my_agents": "Мои агенты", @@ -76,7 +67,6 @@ "settings.default_model": "Модель по умолчанию", "settings.knowledge_base": "Настройки базы знаний", "settings.model": "Настройки модели", - "settings.preset_messages": "Предустановленные сообщения", "settings.prompt": "Настройки промптов", "settings.reasoning_effort.off": "Выключить", "settings.reasoning_effort.high": "Стараюсь думать", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 04feec6f3..4f9b6c2fa 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -30,16 +30,7 @@ "agent": "导出智能体" }, "delete.popup.content": "确定要删除此智能体吗?", - "edit.message.add.title": "添加", - "edit.message.assistant.placeholder": "输入助手消息", - "edit.message.assistant.title": "助手", - "edit.message.empty.content": "会话输入内容不能为空", - "edit.message.group.title": "消息组", - "edit.message.title": "预设消息", - "edit.message.user.placeholder": "输入用户消息", - "edit.message.user.title": "用户", "edit.model.select.title": "选择模型", - "edit.settings.hide_preset_messages": "隐藏预设消息", "edit.title": "编辑智能体", "manage.title": "管理智能体", "my_agents": "我的智能体", @@ -83,7 +74,6 @@ "settings.tool_use_mode.function": "函数", "settings.tool_use_mode.prompt": "提示词", "settings.model": "模型设置", - "settings.preset_messages": "预设消息", "settings.prompt": "提示词设置", "settings.reasoning_effort": "思维链长度", "settings.reasoning_effort.off": "关闭", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index b205a7d8d..61bd018af 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -30,16 +30,7 @@ "agent": "匯出智慧代理人" }, "delete.popup.content": "確定要刪除此智慧代理人嗎?", - "edit.message.add.title": "新增", - "edit.message.assistant.placeholder": "輸入助手訊息", - "edit.message.assistant.title": "助手", - "edit.message.empty.content": "會話輸入內容不能為空", - "edit.message.group.title": "訊息分組", - "edit.message.title": "預設訊息", - "edit.message.user.placeholder": "輸入使用者訊息", - "edit.message.user.title": "使用者", "edit.model.select.title": "選擇模型", - "edit.settings.hide_preset_messages": "隱藏預設訊息", "edit.title": "編輯智慧代理人", "manage.title": "管理智慧代理人", "my_agents": "我的智慧代理人", @@ -76,7 +67,6 @@ "settings.default_model": "預設模型", "settings.knowledge_base": "知識庫設定", "settings.model": "模型設定", - "settings.preset_messages": "預設訊息", "settings.prompt": "提示詞設定", "settings.reasoning_effort": "思維鏈長度", "settings.reasoning_effort.off": "關閉", diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 5ea7d99b9..c54f7e525 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -10,16 +10,7 @@ "add.prompt.placeholder": "Εισαγάγετε φράση προκαλέσεως", "add.title": "Δημιουργία νέου ειδικού", "delete.popup.content": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτόν τον ειδικό;", - "edit.message.add.title": "Προσθήκη", - "edit.message.assistant.placeholder": "Εισαγάγετε μήνυμα βοηθού", - "edit.message.assistant.title": "Βοηθός", - "edit.message.empty.content": "Το περιεχόμενο του συνομιλητή δεν μπορεί να είναι κενό.", - "edit.message.group.title": "Ομάδα μηνυμάτων", - "edit.message.title": "Προεπιλογές μηνυμάτων", - "edit.message.user.placeholder": "Εισαγάγετε μήνυμα χρήστη", - "edit.message.user.title": "Χρήστης", "edit.model.select.title": "Επιλογή μοντέλου", - "edit.settings.hide_preset_messages": "Απόκρυψη προεπιλογών μηνυμάτων", "edit.title": "Επεξεργασία ειδικού", "manage.title": "Διαχείριση ειδικών", "my_agents": "Οι ειδικοί μου", @@ -64,7 +55,6 @@ "settings.default_model": "Προεπιλεγμένο μοντέλο", "settings.knowledge_base": "Ρυθμίσεις βάσης γνώσεων", "settings.model": "Ρυθμίσεις μοντέλου", - "settings.preset_messages": "Προεπιλεγμένα μηνύματα", "settings.prompt": "Ρυθμίσεις προκαλύμματος", "settings.reasoning_effort": "Μήκος λογισμικού αλυσίδας", "settings.reasoning_effort.high": "Μεγάλο", diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index d0a1c588d..949612433 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -10,16 +10,7 @@ "add.prompt.placeholder": "Ingrese la palabra clave", "add.title": "Crear agente inteligente", "delete.popup.content": "¿Está seguro de que desea eliminar este agente inteligente?", - "edit.message.add.title": "Agregar", - "edit.message.assistant.placeholder": "Ingrese el mensaje del asistente", - "edit.message.assistant.title": "Asistente", - "edit.message.empty.content": "El contenido de la sesión de chat no puede estar vacío", - "edit.message.group.title": "Grupo de mensajes", - "edit.message.title": "Mensaje predeterminado", - "edit.message.user.placeholder": "Ingrese el mensaje del usuario", - "edit.message.user.title": "Usuario", "edit.model.select.title": "Seleccionar modelo", - "edit.settings.hide_preset_messages": "Ocultar mensajes predeterminados", "edit.title": "Editar agente inteligente", "manage.title": "Administrar agentes inteligentes", "my_agents": "Mis agentes inteligentes", @@ -64,7 +55,6 @@ "settings.default_model": "Modelo Predeterminado", "settings.knowledge_base": "Configuración de Base de Conocimientos", "settings.model": "Configuración de Modelo", - "settings.preset_messages": "Mensajes Preestablecidos", "settings.prompt": "Configuración de Palabras Clave", "settings.reasoning_effort": "Longitud de Cadena de Razonamiento", "settings.reasoning_effort.high": "Largo", diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 272954f0e..30870432d 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -10,16 +10,7 @@ "add.prompt.placeholder": "Entrer le mot-clé", "add.title": "Créer un agent intelligent", "delete.popup.content": "Êtes-vous sûr de vouloir supprimer cet agent intelligent ?", - "edit.message.add.title": "Ajouter", - "edit.message.assistant.placeholder": "Entrer le message de l'assistant", - "edit.message.assistant.title": "Assistant", - "edit.message.empty.content": "Le contenu de la session ne peut pas être vide", - "edit.message.group.title": "Groupe de messages", - "edit.message.title": "Messages prédéfinis", - "edit.message.user.placeholder": "Entrer le message de l'utilisateur", - "edit.message.user.title": "Utilisateur", "edit.model.select.title": "Sélectionner un modèle", - "edit.settings.hide_preset_messages": "Masquer les messages prédéfinis", "edit.title": "Modifier l'agent intelligent", "manage.title": "Gérer les agents intelligents", "my_agents": "Mes agents intelligents", @@ -64,7 +55,6 @@ "settings.default_model": "Modèle par défaut", "settings.knowledge_base": "Paramètres de la base de connaissances", "settings.model": "Paramètres du modèle", - "settings.preset_messages": "Messages prédéfinis", "settings.prompt": "Paramètres de l'invite", "settings.reasoning_effort": "Longueur de la chaîne de raisonnement", "settings.reasoning_effort.high": "Long", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 5e97076f6..2399d39ea 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -10,16 +10,7 @@ "add.prompt.placeholder": "Digite o Prompt", "add.title": "Criar Agente Inteligente", "delete.popup.content": "Tem certeza de que deseja excluir este agente inteligente?", - "edit.message.add.title": "Adicionar", - "edit.message.assistant.placeholder": "Digite a Mensagem do Assistente", - "edit.message.assistant.title": "Assistente", - "edit.message.empty.content": "O conteúdo da sessão não pode estar vazio", - "edit.message.group.title": "Grupo de Mensagens", - "edit.message.title": "Mensagens Padrão", - "edit.message.user.placeholder": "Digite a Mensagem do Usuário", - "edit.message.user.title": "Usuário", "edit.model.select.title": "Selecionar Modelo", - "edit.settings.hide_preset_messages": "Ocultar Mensagens Padrão", "edit.title": "Editar Agente Inteligente", "manage.title": "Gerenciar Agentes Inteligentes", "my_agents": "Meus Agentes Inteligentes", @@ -64,7 +55,6 @@ "settings.default_model": "Modelo Padrão", "settings.knowledge_base": "Configurações da Base de Conhecimento", "settings.model": "Configurações do Modelo", - "settings.preset_messages": "Mensagens Pré-definidas", "settings.prompt": "Configurações de Prompt", "settings.reasoning_effort": "Comprimento da Cadeia de Raciocínio", "settings.reasoning_effort.high": "Longo", diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 9b37287eb..0f8c23d27 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -18,7 +18,7 @@ import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime' import { useMessageStyle, useSettings } from '@renderer/hooks/useSettings' import { useShortcut, useShortcutDisplay } from '@renderer/hooks/useShortcuts' import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon' -import { addAssistantMessagesToTopic, getDefaultTopic } from '@renderer/services/AssistantService' +import { getDefaultTopic } from '@renderer/services/AssistantService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import FileManager from '@renderer/services/FileManager' import { checkRateLimit, getUserMessage } from '@renderer/services/MessagesService' @@ -408,7 +408,6 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = const topic = getDefaultTopic(assistant.id) await db.topics.add({ id: topic.id, messages: [] }) - await addAssistantMessagesToTopic({ assistant, topic }) // Clear previous state // Reset to assistant default model diff --git a/src/renderer/src/pages/home/Messages/Message.tsx b/src/renderer/src/pages/home/Messages/Message.tsx index 864815045..6fecd2df7 100644 --- a/src/renderer/src/pages/home/Messages/Message.tsx +++ b/src/renderer/src/pages/home/Messages/Message.tsx @@ -29,7 +29,6 @@ interface Props { assistant?: Assistant index?: number total?: number - hidePresetMessages?: boolean hideMenuBar?: boolean style?: React.CSSProperties isGrouped?: boolean @@ -42,7 +41,6 @@ const MessageItem: FC = ({ topic, // assistant, index, - hidePresetMessages, hideMenuBar = false, isGrouped, isStreaming = false, @@ -122,10 +120,6 @@ const MessageItem: FC = ({ return () => unsubscribes.forEach((unsub) => unsub()) }, [message.id, messageHighlightHandler]) - if (hidePresetMessages && message.isPreset) { - return null - } - if (message.type === 'clear') { return ( EventEmitter.emit(EVENT_NAMES.NEW_CONTEXT)}> diff --git a/src/renderer/src/pages/home/Messages/MessageGroup.tsx b/src/renderer/src/pages/home/Messages/MessageGroup.tsx index 0e7e6751a..26a18822d 100644 --- a/src/renderer/src/pages/home/Messages/MessageGroup.tsx +++ b/src/renderer/src/pages/home/Messages/MessageGroup.tsx @@ -19,11 +19,10 @@ import SelectableMessage from './MessageSelect' interface Props { messages: (Message & { index: number })[] topic: Topic - hidePresetMessages?: boolean registerMessageElement?: (id: string, element: HTMLElement | null) => void } -const MessageGroup = ({ messages, topic, hidePresetMessages, registerMessageElement }: Props) => { +const MessageGroup = ({ messages, topic, registerMessageElement }: Props) => { const { editMessage } = useMessageOperations(topic) const { multiModelMessageStyle: multiModelMessageStyleSetting, gridColumns, gridPopoverTrigger } = useSettings() const { isMultiSelectMode } = useChatContext(topic) @@ -168,7 +167,6 @@ const MessageGroup = ({ messages, topic, hidePresetMessages, registerMessageElem message, topic, index: message.index, - hidePresetMessages, style: { paddingTop: isGrouped && ['horizontal', 'grid'].includes(multiModelMessageStyle) ? 0 : 15 } @@ -220,16 +218,7 @@ const MessageGroup = ({ messages, topic, hidePresetMessages, registerMessageElem ) }, - [ - isGrid, - isGrouped, - topic, - hidePresetMessages, - multiModelMessageStyle, - isHorizontal, - selectedMessageId, - gridPopoverTrigger - ] + [isGrid, isGrouped, topic, multiModelMessageStyle, isHorizontal, selectedMessageId, gridPopoverTrigger] ) return ( diff --git a/src/renderer/src/pages/home/Messages/Messages.tsx b/src/renderer/src/pages/home/Messages/Messages.tsx index 47b844252..cae4237ff 100644 --- a/src/renderer/src/pages/home/Messages/Messages.tsx +++ b/src/renderer/src/pages/home/Messages/Messages.tsx @@ -289,7 +289,6 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic, o key={key} messages={groupMessages} topic={topic} - hidePresetMessages={assistant.settings?.hideMessages} registerMessageElement={registerMessageElement} /> ))} diff --git a/src/renderer/src/pages/settings/AssistantSettings/AssistantMessagesSettings.tsx b/src/renderer/src/pages/settings/AssistantSettings/AssistantMessagesSettings.tsx deleted file mode 100644 index a74e3823e..000000000 --- a/src/renderer/src/pages/settings/AssistantSettings/AssistantMessagesSettings.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import { DeleteOutlined, PlusOutlined } from '@ant-design/icons' -import { Assistant, AssistantMessage, AssistantSettings } from '@renderer/types' -import { Button, Card, Col, Divider, Form as FormAntd, FormInstance, Row, Space, Switch } from 'antd' -import TextArea from 'antd/es/input/TextArea' -import { FC, useMemo, useRef, useState } from 'react' -import { useTranslation } from 'react-i18next' -import styled from 'styled-components' - -interface Props { - assistant: Assistant - updateAssistant: (assistant: Assistant) => void - updateAssistantSettings: (settings: Partial) => void -} - -const AssistantMessagesSettings: FC = ({ assistant, updateAssistant, updateAssistantSettings }) => { - const { t } = useTranslation() - const [form] = Form.useForm() - const formRef = useRef(null) - const [messages, setMessagess] = useState(assistant?.messages || []) - const [hideMessages, setHideMessages] = useState(assistant?.settings?.hideMessages || false) - - const showSaveButton = useMemo(() => { - const originalMessages = assistant?.messages || [] - if (originalMessages.length !== messages.length) return true - - return messages.some((msg, index) => { - const originalMsg = originalMessages[index] - return !originalMsg || msg.content.trim() !== originalMsg.content.trim() - }) - }, [messages, assistant?.messages]) - - const onSave = () => { - // 检查是否有空对话组 - for (let i = 0; i < messages.length; i += 2) { - const userContent = messages[i].content.trim() - const assistantContent = messages[i + 1]?.content.trim() - if (userContent === '' || assistantContent === '') { - window.modal.error({ - centered: true, - content: t('agents.edit.message.empty.content') - }) - return - } - } - - // 过滤掉空消息并将消息分组 - const filteredMessagess = messages.reduce((acc, conv, index) => { - if (index % 2 === 0) { - const userContent = conv.content.trim() - const assistantContent = messages[index + 1]?.content.trim() - if (userContent !== '' || assistantContent !== '') { - acc.push({ role: 'user', content: userContent }, { role: 'assistant', content: assistantContent }) - } - } - return acc - }, [] as AssistantMessage[]) - - updateAssistant({ - ...assistant, - messages: filteredMessagess - }) - - window.message.success({ content: t('message.save.success.title'), key: 'save-messages' }) - } - - const addMessages = () => { - setMessagess([...messages, { role: 'user', content: '' }, { role: 'assistant', content: '' }]) - } - - const updateMessages = (index: number, role: 'user' | 'assistant', content: string) => { - const newMessagess = [...messages] - newMessagess[index] = { role, content } - setMessagess(newMessagess) - } - - const deleteMessages = (index: number) => { - const newMessagess = [...messages] - newMessagess.splice(index, 2) // 删除用户和助手的对话 - setMessagess(newMessagess) - } - - return ( - -
- - { - setHideMessages(checked) - updateAssistantSettings({ hideMessages: checked }) - }} - /> - - - - {messages.map( - (_, index) => - index % 2 === 0 && ( - } type="text" danger onClick={() => deleteMessages(index)} />}> - - - - - -