From ff74e9035d9512fff93758031e8653c5e981cbed Mon Sep 17 00:00:00 2001 From: SuYao Date: Sat, 6 Sep 2025 17:50:47 +0800 Subject: [PATCH 01/66] fix: add message processing status check to Message component (#9982) --- src/renderer/src/pages/home/Messages/Message.tsx | 4 +++- src/renderer/src/utils/messageUtils/is.ts | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/pages/home/Messages/Message.tsx b/src/renderer/src/pages/home/Messages/Message.tsx index 9421bce445..0e86e9b232 100644 --- a/src/renderer/src/pages/home/Messages/Message.tsx +++ b/src/renderer/src/pages/home/Messages/Message.tsx @@ -14,6 +14,7 @@ import { estimateMessageUsage } from '@renderer/services/TokenService' import { Assistant, Topic } from '@renderer/types' import type { Message, MessageBlock } from '@renderer/types/newMessage' import { classNames } from '@renderer/utils' +import { isMessageProcessing } from '@renderer/utils/messageUtils/is' import { Divider } from 'antd' import React, { Dispatch, FC, memo, SetStateAction, useCallback, useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' @@ -115,7 +116,8 @@ const MessageItem: FC = ({ const isLastMessage = index === 0 || !!isGrouped const isAssistantMessage = message.role === 'assistant' - const showMenubar = !hideMenuBar && !isEditing + const isProcessing = isMessageProcessing(message) + const showMenubar = !hideMenuBar && !isEditing && !isProcessing const messageHighlightHandler = useCallback( (highlight: boolean = true) => { diff --git a/src/renderer/src/utils/messageUtils/is.ts b/src/renderer/src/utils/messageUtils/is.ts index 7b2535e518..b874f08ae2 100644 --- a/src/renderer/src/utils/messageUtils/is.ts +++ b/src/renderer/src/utils/messageUtils/is.ts @@ -1,10 +1,12 @@ import { + AssistantMessageStatus, type CitationMessageBlock, type CodeMessageBlock, type ErrorMessageBlock, type FileMessageBlock, type ImageMessageBlock, type MainTextMessageBlock, + Message, type MessageBlock, MessageBlockType, type PlaceholderMessageBlock, @@ -147,3 +149,11 @@ export function isCitationBlock(block: MessageBlock): block is CitationMessageBl export function isPlaceholderBlock(block: MessageBlock): block is PlaceholderMessageBlock { return block.type === MessageBlockType.UNKNOWN } + +export function isMessageProcessing(message: Message): boolean { + return ( + message.status === AssistantMessageStatus.PROCESSING || + message.status === AssistantMessageStatus.PENDING || + message.status === AssistantMessageStatus.SEARCHING + ) +} From 2361c1b211ec11f2c4a21e4bb4161966357bf1eb Mon Sep 17 00:00:00 2001 From: SuYao Date: Sat, 6 Sep 2025 22:29:36 +0800 Subject: [PATCH 02/66] fix: implement Anthropic thinking budget calculation in reasoning logic (#9991) --- .../aiCore/prepareParams/parameterBuilder.ts | 16 +++++++-- src/renderer/src/aiCore/utils/reasoning.ts | 34 ++++++++++++------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts index d72010d03b..58b57792fb 100644 --- a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts +++ b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts @@ -9,17 +9,19 @@ import { isOpenRouterBuiltInWebSearchModel, isReasoningModel, isSupportedReasoningEffortModel, + isSupportedThinkingTokenClaudeModel, isSupportedThinkingTokenModel, isWebSearchModel } from '@renderer/config/models' import { getAssistantSettings, getDefaultModel } from '@renderer/services/AssistantService' -import type { Assistant, MCPTool, Provider } from '@renderer/types' +import { type Assistant, type MCPTool, type Provider } from '@renderer/types' import type { StreamTextParams } from '@renderer/types/aiCoreTypes' import type { ModelMessage } from 'ai' import { stepCountIs } from 'ai' import { setupToolsConfig } from '../utils/mcp' import { buildProviderOptions } from '../utils/options' +import { getAnthropicThinkingBudget } from '../utils/reasoning' import { getTemperature, getTopP } from './modelParameters' const logger = loggerService.withContext('parameterBuilder') @@ -55,7 +57,7 @@ export async function buildStreamTextParams( const model = assistant.model || getDefaultModel() - const { maxTokens } = getAssistantSettings(assistant) + let { maxTokens } = getAssistantSettings(assistant) // 这三个变量透传出来,交给下面启用插件/中间件 // 也可以在外部构建好再传入buildStreamTextParams @@ -88,6 +90,16 @@ export async function buildStreamTextParams( enableGenerateImage }) + // NOTE: ai-sdk会把maxToken和budgetToken加起来 + if ( + enableReasoning && + maxTokens !== undefined && + isSupportedThinkingTokenClaudeModel(model) && + (provider.type === 'anthropic' || provider.type === 'aws-bedrock') + ) { + maxTokens -= getAnthropicThinkingBudget(assistant, model) + } + // 构建基础参数 const params: StreamTextParams = { messages: sdkMessages, diff --git a/src/renderer/src/aiCore/utils/reasoning.ts b/src/renderer/src/aiCore/utils/reasoning.ts index 507b2cd9ce..385d8183c5 100644 --- a/src/renderer/src/aiCore/utils/reasoning.ts +++ b/src/renderer/src/aiCore/utils/reasoning.ts @@ -310,6 +310,26 @@ export function getOpenAIReasoningParams(assistant: Assistant, model: Model): Re return {} } +export function getAnthropicThinkingBudget(assistant: Assistant, model: Model): number { + const { maxTokens, reasoning_effort: reasoningEffort } = getAssistantSettings(assistant) + if (maxTokens === undefined || reasoningEffort === undefined) { + return 0 + } + const effortRatio = EFFORT_RATIO[reasoningEffort] + + const budgetTokens = Math.max( + 1024, + Math.floor( + Math.min( + (findTokenLimit(model.id)?.max! - findTokenLimit(model.id)?.min!) * effortRatio + + findTokenLimit(model.id)?.min!, + (maxTokens || DEFAULT_MAX_TOKENS) * effortRatio + ) + ) + ) + return budgetTokens +} + /** * 获取 Anthropic 推理参数 * 从 AnthropicAPIClient 中提取的逻辑 @@ -331,19 +351,7 @@ export function getAnthropicReasoningParams(assistant: Assistant, model: Model): // Claude 推理参数 if (isSupportedThinkingTokenClaudeModel(model)) { - const { maxTokens } = getAssistantSettings(assistant) - const effortRatio = EFFORT_RATIO[reasoningEffort] - - const budgetTokens = Math.max( - 1024, - Math.floor( - Math.min( - (findTokenLimit(model.id)?.max! - findTokenLimit(model.id)?.min!) * effortRatio + - findTokenLimit(model.id)?.min!, - (maxTokens || DEFAULT_MAX_TOKENS) * effortRatio - ) - ) - ) + const budgetTokens = getAnthropicThinkingBudget(assistant, model) return { thinking: { From d09743d254aa4ff32a589ff957c4389f9cbc4934 Mon Sep 17 00:00:00 2001 From: Pleasure1234 <3196812536@qq.com> Date: Sat, 6 Sep 2025 23:15:51 +0800 Subject: [PATCH 03/66] fix: improve note sorting behavior for drag and drop operations (#9971) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: improve note sorting behavior for drag and drop operations - Skip automatic sorting when performing same-level drag reordering - Preserve treePath during same-level moves to maintain manual ordering - Return special indicator for manual reorder operations to prevent conflicts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * fix: type safety issue --------- Co-authored-by: Claude --- src/renderer/src/pages/notes/NotesPage.tsx | 6 ++-- src/renderer/src/services/NotesService.ts | 33 ++++++++++++++----- src/renderer/src/services/NotesTreeService.ts | 21 +++++++----- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/renderer/src/pages/notes/NotesPage.tsx b/src/renderer/src/pages/notes/NotesPage.tsx index 64782721f4..1ca06c000e 100644 --- a/src/renderer/src/pages/notes/NotesPage.tsx +++ b/src/renderer/src/pages/notes/NotesPage.tsx @@ -563,8 +563,10 @@ const NotesPage: FC = () => { const handleMoveNode = useCallback( async (sourceNodeId: string, targetNodeId: string, position: 'before' | 'after' | 'inside') => { try { - await moveNode(sourceNodeId, targetNodeId, position) - await sortAllLevels(sortType) + const result = await moveNode(sourceNodeId, targetNodeId, position) + if (result.success && result.type !== 'manual_reorder') { + await sortAllLevels(sortType) + } } catch (error) { logger.error('Failed to move nodes:', error as Error) } diff --git a/src/renderer/src/services/NotesService.ts b/src/renderer/src/services/NotesService.ts index fa3607270e..4e27a41366 100644 --- a/src/renderer/src/services/NotesService.ts +++ b/src/renderer/src/services/NotesService.ts @@ -19,6 +19,8 @@ const NOTES_TREE_ID = 'notes-tree-structure' const logger = loggerService.withContext('NotesService') +export type MoveNodeResult = { success: false } | { success: true; type: 'file_system_move' | 'manual_reorder' } + /** * 初始化/同步笔记树结构 */ @@ -182,7 +184,7 @@ export async function moveNode( sourceNodeId: string, targetNodeId: string, position: 'before' | 'after' | 'inside' -): Promise { +): Promise { try { const tree = await getNotesTree() @@ -192,19 +194,19 @@ export async function moveNode( if (!sourceNode || !targetNode) { logger.error(`Move nodes failed: node not found (source: ${sourceNodeId}, target: ${targetNodeId})`) - return false + return { success: false } } // 不允许文件夹被放入文件中 if (position === 'inside' && targetNode.type === 'file' && sourceNode.type === 'folder') { logger.error('Move nodes failed: cannot move a folder inside a file') - return false + return { success: false } } // 不允许将节点移动到自身内部 if (position === 'inside' && isParentNode(tree, sourceNodeId, targetNodeId)) { logger.error('Move nodes failed: cannot move a node inside itself or its descendants') - return false + return { success: false } } let targetPath: string = '' @@ -215,7 +217,7 @@ export async function moveNode( targetPath = targetNode.externalPath } else { logger.error('Cannot move node inside a file node') - return false + return { success: false } } } else { const targetParent = findParentNode(tree, targetNodeId) @@ -226,6 +228,20 @@ export async function moveNode( } } + // 检查是否为同级拖动排序 + const sourceParent = findParentNode(tree, sourceNodeId) + const sourceDir = sourceParent ? sourceParent.externalPath : getFileDirectory(sourceNode.externalPath!) + + const isSameLevelReorder = position !== 'inside' && sourceDir === targetPath + + if (isSameLevelReorder) { + // 同级拖动排序:跳过文件系统操作,只更新树结构 + logger.debug(`Same level reorder detected, skipping file system operations`) + const success = await moveNodeInTree(tree, sourceNodeId, targetNodeId, position) + // 返回一个特殊标识,告诉调用方这是手动排序,不需要重新自动排序 + return success ? { success: true, type: 'manual_reorder' } : { success: false } + } + // 构建新的文件路径 const sourceName = sourceNode.externalPath!.split('/').pop()! const sourceNameWithoutExt = sourceName.replace(sourceNode.type === 'file' ? MARKDOWN_EXT : '', '') @@ -250,14 +266,15 @@ export async function moveNode( logger.debug(`Moved external ${sourceNode.type} to: ${newPath}`) } catch (error) { logger.error(`Failed to move external ${sourceNode.type}:`, error as Error) - return false + return { success: false } } } - return await moveNodeInTree(tree, sourceNodeId, targetNodeId, position) + const success = await moveNodeInTree(tree, sourceNodeId, targetNodeId, position) + return success ? { success: true, type: 'file_system_move' } : { success: false } } catch (error) { logger.error('Move nodes failed:', error as Error) - return false + return { success: false } } } diff --git a/src/renderer/src/services/NotesTreeService.ts b/src/renderer/src/services/NotesTreeService.ts index 5ce0bc0d5c..4159948323 100644 --- a/src/renderer/src/services/NotesTreeService.ts +++ b/src/renderer/src/services/NotesTreeService.ts @@ -89,8 +89,9 @@ export async function moveNodeInTree( return false } - // 先保存源节点的副本,以防操作失败需要恢复(暂未实现恢复逻辑) - // const sourceNodeCopy = { ...sourceNode } + // 在移除节点之前先获取源节点的父节点信息,用于后续判断是否为同级排序 + const sourceParent = findParentNode(tree, sourceNodeId) + const targetParent = findParentNode(tree, targetNodeId) // 从原位置移除节点(不保存数据库,只在内存中操作) const removed = removeNodeFromTreeInMemory(tree, sourceNodeId) @@ -110,7 +111,6 @@ export async function moveNodeInTree( sourceNode.treePath = `${targetNode.treePath}/${sourceNode.name}` } else { - const targetParent = findParentNode(tree, targetNodeId) const targetList = targetParent ? targetParent.children! : tree const targetIndex = targetList.findIndex((node) => node.id === targetNodeId) @@ -123,11 +123,16 @@ export async function moveNodeInTree( const insertIndex = position === 'before' ? targetIndex : targetIndex + 1 targetList.splice(insertIndex, 0, sourceNode) - // 更新节点路径 - if (targetParent) { - sourceNode.treePath = `${targetParent.treePath}/${sourceNode.name}` - } else { - sourceNode.treePath = `/${sourceNode.name}` + // 检查是否为同级排序,如果是则保持原有的 treePath + const isSameLevelReorder = sourceParent === targetParent + + // 只有在跨级移动时才更新节点路径 + if (!isSameLevelReorder) { + if (targetParent) { + sourceNode.treePath = `${targetParent.treePath}/${sourceNode.name}` + } else { + sourceNode.treePath = `/${sourceNode.name}` + } } } From b33b14b4b7f9ae766a2a5630a2a5711ff66cc9d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?George=C2=B7Dong?= <98630204+GeorgeDong32@users.noreply.github.com> Date: Sun, 7 Sep 2025 10:17:01 +0800 Subject: [PATCH 04/66] fix(parameter-builder): avoid enabling built-in web search when external provider is configured (#9995) --- .../src/aiCore/prepareParams/parameterBuilder.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts index 58b57792fb..c379a44eb0 100644 --- a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts +++ b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts @@ -67,11 +67,14 @@ export async function buildStreamTextParams( assistant.settings?.reasoning_effort !== undefined) || (isReasoningModel(model) && (!isSupportedThinkingTokenModel(model) || !isSupportedReasoningEffortModel(model))) + // 判断是否使用内置搜索 + // 条件:没有外部搜索提供商 && (用户开启了内置搜索 || 模型强制使用内置搜索) + const hasExternalSearch = !!options.webSearchProviderId const enableWebSearch = - (assistant.enableWebSearch && isWebSearchModel(model)) || - isOpenRouterBuiltInWebSearchModel(model) || - model.id.includes('sonar') || - false + !hasExternalSearch && + ((assistant.enableWebSearch && isWebSearchModel(model)) || + isOpenRouterBuiltInWebSearchModel(model) || + model.id.includes('sonar')) const enableUrlContext = assistant.enableUrlContext || false From 3607341413d5f0915bbecd9c818c7e0c376f92cb Mon Sep 17 00:00:00 2001 From: RieN 7z Date: Sun, 7 Sep 2025 10:19:17 +0800 Subject: [PATCH 05/66] fix: limit titlebar-area-* in mac (#9974) * fix: limit titlebar-area-* in mac * fix: unused padding-right --- src/renderer/src/components/Tab/TabContainer.tsx | 6 +++--- src/renderer/src/components/app/Navbar.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/components/Tab/TabContainer.tsx b/src/renderer/src/components/Tab/TabContainer.tsx index f3b5874b1c..ba5be5b1fd 100644 --- a/src/renderer/src/components/Tab/TabContainer.tsx +++ b/src/renderer/src/components/Tab/TabContainer.tsx @@ -1,7 +1,7 @@ import { PlusOutlined } from '@ant-design/icons' import { Sortable, useDndReorder } from '@renderer/components/dnd' import Scrollbar from '@renderer/components/Scrollbar' -import { isLinux, isMac, isWin } from '@renderer/config/constant' +import { isMac } from '@renderer/config/constant' import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' import { useTheme } from '@renderer/context/ThemeProvider' import { useFullscreen } from '@renderer/hooks/useFullscreen' @@ -290,9 +290,9 @@ const TabsBar = styled.div<{ $isFullscreen: boolean }>` align-items: center; gap: 5px; padding-left: ${({ $isFullscreen }) => (!$isFullscreen && isMac ? 'env(titlebar-area-x)' : '15px')}; - padding-right: ${({ $isFullscreen }) => ($isFullscreen ? '12px' : isWin ? '140px' : isLinux ? '120px' : '12px')}; + padding-right: ${({ $isFullscreen }) => ($isFullscreen ? '12px' : '0')}; height: var(--navbar-height); - min-height: env(titlebar-area-height); + min-height: ${({ $isFullscreen }) => (!$isFullscreen && isMac ? 'env(titlebar-area-height)' : '')}; position: relative; -webkit-app-region: drag; diff --git a/src/renderer/src/components/app/Navbar.tsx b/src/renderer/src/components/app/Navbar.tsx index af53ec407d..5eabba9a1f 100644 --- a/src/renderer/src/components/app/Navbar.tsx +++ b/src/renderer/src/components/app/Navbar.tsx @@ -69,7 +69,7 @@ const NavbarContainer = styled.div` min-width: 100%; display: flex; flex-direction: row; - min-height: env(titlebar-area-height); + min-height: ${isMac ? 'env(titlebar-area-height)' : 'var(--navbar-height)'}; max-height: var(--navbar-height); margin-left: ${isMac ? 'calc(var(--sidebar-width) * -1)' : 0}; padding-left: ${isMac ? 'env(titlebar-area-x)' : 0}; From f3787beade080b10c6ce9755f4b11a10bd0d3365 Mon Sep 17 00:00:00 2001 From: Phantom <59059173+EurFelux@users.noreply.github.com> Date: Sun, 7 Sep 2025 13:33:32 +0800 Subject: [PATCH 06/66] ci(workflow): remove code review on sync & add more env info (#9985) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ci(workflow): 简化claude代码审查工作流的触发条件 移除synchronize事件触发,仅保留pull_request opened事件 添加PR编号和仓库信息到审查提示中 --- .github/workflows/claude-code-review.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 9553ef6a91..5535f2a725 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -2,7 +2,7 @@ name: Claude Code Review on: pull_request: - types: [opened, synchronize] + types: [opened] # Optional: Only run on specific file changes # paths: # - "src/**/*.ts" @@ -45,6 +45,9 @@ jobs: - Security concerns - Test coverage + PR number: ${{ github.event.number }} + Repo: ${{ github.repository }} + Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback. Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR. From 8b7776b5457d8bcd4b92ffc298645bf1ac413720 Mon Sep 17 00:00:00 2001 From: Phantom <59059173+EurFelux@users.noreply.github.com> Date: Sun, 7 Sep 2025 13:36:01 +0800 Subject: [PATCH 07/66] CI: launch claude translate for all comments (#9983) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci(claude-translator): 临时禁用issues触发并更新条件 暂时禁用issues触发以解决上游bug 更新issue_comment的触发条件,简化claude_args参数 修改翻译结果的原始内容标题格式 * ci: 更新Claude翻译器工作流的名称和并发组配置 * fix(workflow): 修正Claude翻译工作流中的拼写错误 * ci(workflow): 更新claude-translator触发条件 添加对评论发送者类型的检查,避免机器人触发工作流 --- .github/workflows/claude-translator.yml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/claude-translator.yml b/.github/workflows/claude-translator.yml index d5f7ea40d8..0aeb635fc3 100644 --- a/.github/workflows/claude-translator.yml +++ b/.github/workflows/claude-translator.yml @@ -1,19 +1,24 @@ -name: English Translator +name: Claude Translator concurrency: - group: translator-${{ github.event.issue.number }} + group: translator-${{ github.event.comment.id || github.event.issue.number }} cancel-in-progress: false on: - issues: - types: [opened] + # temporally disable this because upstream bug. + # issues: + # types: [opened] issue_comment: types: [created, edited] jobs: translate: + # disable issues for now because upstream bug + # (github.event_name == 'issues' && github.event.issue.author_association == 'COLLABORATOR' && !contains(github.event.issue.body, 'This issue was translated by Claude.')) || + if: | - (github.event_name == 'issues' && github.event.issue.author_association == 'COLLABORATOR' && !contains(github.event.issue.body, 'This issue/comment was translated by Claude.')) || - (github.event_name == 'issue_comment' && github.event.comment.author_association == 'COLLABORATOR' && !contains(github.event.issue.body, 'This issue/comment was translated by Claude.')) + (github.event_name == 'issue_comment' && github.event.sender.type != 'Bot') && + ((github.event_name == 'issue_comment' && github.event.action == 'created' && !contains(github.event.comment.body, 'This issue was translated by Claude')) || + (github.event_name == 'issue_comment' && github.event.action == 'edited')) runs-on: ubuntu-latest permissions: contents: read @@ -32,7 +37,7 @@ jobs: id: claude with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - claude_args: '--allowed-tools mcp__github_comment__update_claude_comment,Bash(gh issue:*),Bash(gh api:repos/*/issues:*)' + claude_args: 'Bash(gh issue:*),Bash(gh api:repos/*/issues:*)' prompt: | 你是一个多语言翻译助手。请完成以下任务: @@ -50,7 +55,7 @@ jobs: ---
- **Original Content:** + Original Content [原始内容]
From c4e22a23eac5ca6d570fb6eb7bcd21b254c1b140 Mon Sep 17 00:00:00 2001 From: Phantom <59059173+EurFelux@users.noreply.github.com> Date: Sun, 7 Sep 2025 13:36:33 +0800 Subject: [PATCH 08/66] fix: add responsive design for sidebar tabs to prevent content cutoff (#9913) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add responsive design for sidebar tabs to prevent content cutoff at minimum width - Add responsive CSS variables and media queries for narrow screens - Update Container, AssistantItem, and TopicListItem to use responsive widths - Ensures settings tab content remains visible at minimum window width (520px) - Addresses issue where right sidebar content gets cut off on smaller screens Fixes #9894 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Pleasure1234 * fix(i18n): Auto update translations for PR #9913 * feat(样式): 添加响应式断点配置和媒体查询工具 添加 breakpoints.scss 文件定义断点变量和 CSS 变量 在 style.ts 中新增 breakpoints 配置和 media 工具函数,用于生成响应式媒体查询 * style: 添加 breakpoints.scss 到主样式文件 * style(styles): 添加响应式断点变量并更新引用 将 breakpoints.scss 重命名为 responsive.scss 并添加断点变量定义 更新 index.scss 中的引用以使用新的文件名 * style(color.scss): 移除未使用的窄屏幕助手宽度变量 * refactor(styles): 将布局变量从color.scss移动到responsive.scss并添加响应式设计 将布局相关的CSS变量从color.scss迁移到responsive.scss以更好地组织代码 添加针对大屏幕的响应式布局调整 * refactor(布局): 简化容器宽度设置并添加过渡动画 移除响应式宽度媒体查询,统一使用变量控制宽度 为容器和标签内容添加宽度过渡动画效果 调整导航栏图标顺序和样式 * style: 移除响应式宽度媒体查询以简化样式 * fix(style): 将媒体查询条件从min-width改为max-width * refactor(style): 移除未使用的响应式样式工具代码 清理不再使用的断点常量和媒体查询工具函数,这些代码当前未被项目使用 --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Pleasure1234 Co-authored-by: GitHub Action --- src/renderer/src/assets/styles/color.scss | 15 ----- src/renderer/src/assets/styles/index.scss | 1 + .../src/assets/styles/responsive.scss | 27 +++++++++ src/renderer/src/pages/home/ChatNavbar.tsx | 10 ++-- .../home/Tabs/components/AssistantItem.tsx | 1 + src/renderer/src/pages/home/Tabs/index.tsx | 5 +- src/renderer/src/utils/style.ts | 60 +++++++++++++++++++ 7 files changed, 97 insertions(+), 22 deletions(-) create mode 100644 src/renderer/src/assets/styles/responsive.scss diff --git a/src/renderer/src/assets/styles/color.scss b/src/renderer/src/assets/styles/color.scss index 517160e76c..96eb426be3 100644 --- a/src/renderer/src/assets/styles/color.scss +++ b/src/renderer/src/assets/styles/color.scss @@ -58,16 +58,6 @@ --navbar-background-mac: rgba(20, 20, 20, 0.55); --navbar-background: #1f1f1f; - --navbar-height: 44px; - --sidebar-width: 50px; - --status-bar-height: 40px; - --input-bar-height: 100px; - - --assistants-width: 275px; - --topic-list-width: 275px; - --settings-width: 250px; - --scrollbar-width: 5px; - --chat-background: transparent; --chat-background-user: rgba(255, 255, 255, 0.08); --chat-background-assistant: transparent; @@ -146,11 +136,6 @@ --chat-text-user: var(--color-text); } -[navbar-position='left'] { - --navbar-height: 42px; - --list-item-border-radius: 20px; -} - [navbar-position='left'][theme-mode='light'] { --color-list-item: #eee; --color-list-item-hover: #f5f5f5; diff --git a/src/renderer/src/assets/styles/index.scss b/src/renderer/src/assets/styles/index.scss index 974457dc4d..9efd4b67e9 100644 --- a/src/renderer/src/assets/styles/index.scss +++ b/src/renderer/src/assets/styles/index.scss @@ -6,6 +6,7 @@ @use './container.scss'; @use './animation.scss'; @use './richtext.scss'; +@use './responsive.scss'; @import '../fonts/icon-fonts/iconfont.css'; @import '../fonts/ubuntu/ubuntu.css'; @import '../fonts/country-flag-fonts/flag.css'; diff --git a/src/renderer/src/assets/styles/responsive.scss b/src/renderer/src/assets/styles/responsive.scss new file mode 100644 index 0000000000..c5ed80433d --- /dev/null +++ b/src/renderer/src/assets/styles/responsive.scss @@ -0,0 +1,27 @@ +// xl, xxl, default style +:root { + --navbar-height: 44px; + --sidebar-width: 50px; + --status-bar-height: 40px; + --input-bar-height: 100px; + + --assistants-width: 275px; + --topic-list-width: 275px; + --settings-width: 250px; + + --scrollbar-width: 5px; +} + +[navbar-position='left'] { + --navbar-height: 42px; + --list-item-border-radius: 20px; +} + +// lg +@media (max-width: 1080px) { + :root { + --assistants-width: 210px; + --topic-list-width: 210px; + --settings-width: 210px; + } +} diff --git a/src/renderer/src/pages/home/ChatNavbar.tsx b/src/renderer/src/pages/home/ChatNavbar.tsx index 4b4f94909f..63ed16f989 100644 --- a/src/renderer/src/pages/home/ChatNavbar.tsx +++ b/src/renderer/src/pages/home/ChatNavbar.tsx @@ -98,16 +98,16 @@ const HeaderNavbar: FC = ({ activeAssistant, setActiveAssistant, activeTo - - SearchPopup.show()}> - - - + + SearchPopup.show()}> + + + {topicPosition === 'right' && !showTopics && ( diff --git a/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx b/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx index 1d8e12626a..7951700aa6 100644 --- a/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx +++ b/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx @@ -389,6 +389,7 @@ const Container = styled.div` border-radius: var(--list-item-border-radius); border: 0.5px solid transparent; width: calc(var(--assistants-width) - 20px); + &:hover { background-color: var(--color-list-item-hover); } diff --git a/src/renderer/src/pages/home/Tabs/index.tsx b/src/renderer/src/pages/home/Tabs/index.tsx index f7314d746a..9362288df7 100644 --- a/src/renderer/src/pages/home/Tabs/index.tsx +++ b/src/renderer/src/pages/home/Tabs/index.tsx @@ -153,8 +153,8 @@ const HomeTabs: FC = ({ const Container = styled.div` display: flex; flex-direction: column; - max-width: var(--assistants-width); - min-width: var(--assistants-width); + width: var(--assistants-width); + transition: width 0.3s; height: calc(100vh - var(--navbar-height)); &.right { @@ -179,6 +179,7 @@ const Container = styled.div` const TabContent = styled.div` display: flex; + transition: width 0.3s; flex: 1; flex-direction: column; overflow-y: hidden; diff --git a/src/renderer/src/utils/style.ts b/src/renderer/src/utils/style.ts index 8050904cd1..a95fa93dd2 100644 --- a/src/renderer/src/utils/style.ts +++ b/src/renderer/src/utils/style.ts @@ -141,3 +141,63 @@ export function getForegroundColor(backgroundColor: HexColor): HexColor { return luminance > 0.179 ? '#000000' : '#FFFFFF' } + +// 用于ts方式控制响应式样式,暂时没用上 +// 目前应该设计到lg就足够 +// 应该和 file://./../assets/styles/responsive.scss 保持一致 +/** + * 断点配置对象,定义了不同屏幕尺寸的最小宽度(单位:像素) + * + * @property {number} xs - 超小屏幕断点,起始于 0px + * @property {number} sm - 小屏幕断点,起始于 576px + * @property {number} md - 中等屏幕断点,起始于 768px + * @property {number} lg - 大屏幕断点,起始于 1080px + * @property {number} xl - 超大屏幕断点,起始于 1200px + * @property {number} xxl - 超超大屏幕断点,起始于 1400px + */ +// export const breakpoints = { +// xs: 0, +// sm: 576, +// md: 768, +// lg: 1080, +// xl: 1200, +// xxl: 1400 +// } as const + +// type MediaQueryFunction = (styles: string) => string +// type MediaQueries = Record + +/** + * 媒体查询工具对象,用于生成响应式样式的媒体查询字符串 + * + * @example + * // 使用示例: + * ```ts + * const styles = { + * color: 'red', + * [media.md]: ` + * color: blue; + * `, + * [media.lg]: ` + * color: green; + * ` + * } + * ``` + * + * 生成的CSS将包含: + * ```css + * color: red; + * @media (max-width: 768px) { color: blue; } + * @media (max-width: 992px) { color: green; } + * ``` + */ +// Not using for now +// export const media = objectKeys(breakpoints).reduce((acc, label) => { +// const key = label +// acc[key] = (styles: string): string => ` +// @media (max-width: ${breakpoints[key]}px) { +// ${styles} +// } +// ` +// return acc +// }, {} as MediaQueries) From 4a5f374b7c437661bb23f7aa8f5f0d2af940f0ff Mon Sep 17 00:00:00 2001 From: SuYao Date: Sun, 7 Sep 2025 13:50:52 +0800 Subject: [PATCH 09/66] refactor(Messages): remove clean up clearTopic function (#9993) --- src/renderer/src/pages/home/Messages/Messages.tsx | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/renderer/src/pages/home/Messages/Messages.tsx b/src/renderer/src/pages/home/Messages/Messages.tsx index 9c655c6d66..0a8bd5dc9b 100644 --- a/src/renderer/src/pages/home/Messages/Messages.tsx +++ b/src/renderer/src/pages/home/Messages/Messages.tsx @@ -10,7 +10,7 @@ import useScrollPosition from '@renderer/hooks/useScrollPosition' import { useSettings } from '@renderer/hooks/useSettings' import { useShortcut } from '@renderer/hooks/useShortcuts' import { useTimer } from '@renderer/hooks/useTimer' -import { autoRenameTopic, getTopic } from '@renderer/hooks/useTopic' +import { autoRenameTopic } from '@renderer/hooks/useTopic' import SelectionBox from '@renderer/pages/home/Messages/SelectionBox' import { getDefaultTopic } from '@renderer/services/AssistantService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' @@ -61,7 +61,7 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic, o const [isLoadingMore, setIsLoadingMore] = useState(false) const [isProcessingContext, setIsProcessingContext] = useState(false) - const { updateTopic, addTopic } = useAssistant(assistant.id) + const { addTopic } = useAssistant(assistant.id) const { showPrompt, messageNavigation } = useSettings() const { t } = useTranslation() const dispatch = useAppDispatch() @@ -105,22 +105,15 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic, o const clearTopic = useCallback( async (data: Topic) => { - const defaultTopic = getDefaultTopic(assistant.id) - if (data && data.id !== topic.id) { await clearTopicMessages(data.id) - updateTopic({ ...data, name: defaultTopic.name } as Topic) return } await clearTopicMessages() - setDisplayMessages([]) - - const _topic = getTopic(assistant, topic.id) - _topic && updateTopic({ ..._topic, name: defaultTopic.name } as Topic) }, - [assistant, clearTopicMessages, topic.id, updateTopic] + [clearTopicMessages, topic.id] ) useEffect(() => { From 0187f1780e7815b446512bfecc2f27da7b4babfd Mon Sep 17 00:00:00 2001 From: Phantom <59059173+EurFelux@users.noreply.github.com> Date: Sun, 7 Sep 2025 16:39:22 +0800 Subject: [PATCH 10/66] Refactor/migrate zod v4 (#10002) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * build: 更新 zod 依赖至 v4.1.5 * refactor: 将zod导入从'zod/v4'更新为'zod' 统一使用zod主包而非v4子路径,简化依赖管理 * refactor: 统一使用命名导入方式导入zod库 * refactor(mcpServers): 更新RequestPayloadSchema的url和headers类型定义 * refactor(mcp): 将默认值从pipe移动到string以简化schema 默认值'stdio'从pipe操作中移动到string操作,使schema定义更清晰 * refactor(Markdown): 简化 CitationSchema 中 url 的验证方式 * refactor: 将类型断言改为使用 satisfies 操作符 * build: 更新 aiCore 的 zod 依赖至 v4.1.5 * refactor: 更新zod导入方式并简化基础provider类型定义 将zod从'zod/v4'更新为'zod'导入 将baseProviderSchema替换为更简洁的接口类型定义 * fix(providers): 优化provider配置的schema定义并添加注释 调整creator函数的类型定义,增加输入输出类型约束 --- package.json | 2 +- packages/aiCore/package.json | 2 +- packages/aiCore/src/core/options/xai.ts | 2 +- packages/aiCore/src/core/providers/schemas.ts | 25 +++++++++++-------- .../langchain/embeddings/JinaEmbeddings.ts | 2 +- src/main/mcpServers/dify-knowledge.ts | 2 +- src/main/mcpServers/fetch.ts | 4 +-- src/main/mcpServers/filesystem.ts | 2 +- .../services/knowledge/LangChainFramework.ts | 2 +- .../src/aiCore/tools/MemorySearchTool.ts | 2 +- .../pages/home/Markdown/CitationTooltip.tsx | 2 +- src/renderer/src/types/index.ts | 2 +- src/renderer/src/types/mcp.ts | 7 +++--- src/renderer/src/types/tool.ts | 2 +- src/renderer/src/utils/error.ts | 2 +- yarn.lock | 14 +++++------ 16 files changed, 38 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index 7d92b048fc..f5c4e71032 100644 --- a/package.json +++ b/package.json @@ -327,7 +327,7 @@ "yjs": "^13.6.27", "youtubei.js": "^15.0.1", "zipread": "^1.3.3", - "zod": "^3.25.74" + "zod": "^4.1.5" }, "resolutions": { "@codemirror/language": "6.11.3", diff --git a/packages/aiCore/package.json b/packages/aiCore/package.json index 2e02700563..020c95d7cb 100644 --- a/packages/aiCore/package.json +++ b/packages/aiCore/package.json @@ -45,7 +45,7 @@ "@ai-sdk/provider": "^2.0.0", "@ai-sdk/provider-utils": "^3.0.4", "@ai-sdk/xai": "^2.0.9", - "zod": "^3.25.0" + "zod": "^4.1.5" }, "devDependencies": { "tsdown": "^0.12.9", diff --git a/packages/aiCore/src/core/options/xai.ts b/packages/aiCore/src/core/options/xai.ts index 8d82f587e8..7fe5672778 100644 --- a/packages/aiCore/src/core/options/xai.ts +++ b/packages/aiCore/src/core/options/xai.ts @@ -1,7 +1,7 @@ // copy from @ai-sdk/xai/xai-chat-options.ts // 如果@ai-sdk/xai暴露出了xaiProviderOptions就删除这个文件 -import * as z from 'zod/v4' +import { z } from 'zod' const webSourceSchema = z.object({ type: z.literal('web'), diff --git a/packages/aiCore/src/core/providers/schemas.ts b/packages/aiCore/src/core/providers/schemas.ts index 0c1c847d98..e4b8d8aa64 100644 --- a/packages/aiCore/src/core/providers/schemas.ts +++ b/packages/aiCore/src/core/providers/schemas.ts @@ -10,8 +10,8 @@ import { createGoogleGenerativeAI } from '@ai-sdk/google' import { createOpenAI, type OpenAIProviderSettings } from '@ai-sdk/openai' import { createOpenAICompatible } from '@ai-sdk/openai-compatible' import { createXai } from '@ai-sdk/xai' -import { customProvider, type Provider } from 'ai' -import * as z from 'zod' +import { customProvider, Provider } from 'ai' +import { z } from 'zod' /** * 基础 Provider IDs @@ -38,14 +38,12 @@ export const baseProviderIdSchema = z.enum(baseProviderIds) */ export type BaseProviderId = z.infer -export const baseProviderSchema = z.object({ - id: baseProviderIdSchema, - name: z.string(), - creator: z.function().args(z.any()).returns(z.any()) as z.ZodType<(options: any) => Provider>, - supportsImageGeneration: z.boolean() -}) - -export type BaseProvider = z.infer +type BaseProvider = { + id: BaseProviderId + name: string + creator: (options: any) => Provider + supportsImageGeneration: boolean +} /** * 基础 Providers 定义 @@ -148,7 +146,12 @@ export const providerConfigSchema = z .object({ id: customProviderIdSchema, // 只允许自定义ID name: z.string().min(1), - creator: z.function().optional(), + creator: z + .function({ + input: z.any(), + output: z.any() + }) + .optional(), import: z.function().optional(), creatorFunctionName: z.string().optional(), supportsImageGeneration: z.boolean().default(false), diff --git a/src/main/knowledge/langchain/embeddings/JinaEmbeddings.ts b/src/main/knowledge/langchain/embeddings/JinaEmbeddings.ts index 0a6c5f1f84..f0380ff360 100644 --- a/src/main/knowledge/langchain/embeddings/JinaEmbeddings.ts +++ b/src/main/knowledge/langchain/embeddings/JinaEmbeddings.ts @@ -1,7 +1,7 @@ import { Embeddings, type EmbeddingsParams } from '@langchain/core/embeddings' import { chunkArray } from '@langchain/core/utils/chunk_array' import { getEnvironmentVariable } from '@langchain/core/utils/env' -import z from 'zod/v4' +import { z } from 'zod' const jinaModelSchema = z.union([ z.literal('jina-clip-v2'), diff --git a/src/main/mcpServers/dify-knowledge.ts b/src/main/mcpServers/dify-knowledge.ts index 83a352fd4f..04f010ce16 100644 --- a/src/main/mcpServers/dify-knowledge.ts +++ b/src/main/mcpServers/dify-knowledge.ts @@ -3,7 +3,7 @@ import { loggerService } from '@logger' import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js' import { net } from 'electron' -import * as z from 'zod/v4' +import { z } from 'zod' const logger = loggerService.withContext('DifyKnowledgeServer') diff --git a/src/main/mcpServers/fetch.ts b/src/main/mcpServers/fetch.ts index e55b114776..f170cc54c0 100644 --- a/src/main/mcpServers/fetch.ts +++ b/src/main/mcpServers/fetch.ts @@ -8,8 +8,8 @@ import TurndownService from 'turndown' import { z } from 'zod' export const RequestPayloadSchema = z.object({ - url: z.string().url(), - headers: z.record(z.string()).optional() + url: z.url(), + headers: z.record(z.string(), z.string()).optional() }) export type RequestPayload = z.infer diff --git a/src/main/mcpServers/filesystem.ts b/src/main/mcpServers/filesystem.ts index 3b3c5ed799..9ec5ced0b0 100644 --- a/src/main/mcpServers/filesystem.ts +++ b/src/main/mcpServers/filesystem.ts @@ -8,7 +8,7 @@ import fs from 'fs/promises' import { minimatch } from 'minimatch' import os from 'os' import path from 'path' -import * as z from 'zod/v4' +import { z } from 'zod' const logger = loggerService.withContext('MCP:FileSystemServer') diff --git a/src/main/services/knowledge/LangChainFramework.ts b/src/main/services/knowledge/LangChainFramework.ts index b82242e102..c281c7ad94 100644 --- a/src/main/services/knowledge/LangChainFramework.ts +++ b/src/main/services/knowledge/LangChainFramework.ts @@ -30,7 +30,7 @@ import { KnowledgeBaseParams, KnowledgeSearchResult } from '@types' -import { uuidv4 } from 'zod/v4' +import { uuidv4 } from 'zod' import { windowService } from '../WindowService' import { diff --git a/src/renderer/src/aiCore/tools/MemorySearchTool.ts b/src/renderer/src/aiCore/tools/MemorySearchTool.ts index 6430692d4c..d8e402db5d 100644 --- a/src/renderer/src/aiCore/tools/MemorySearchTool.ts +++ b/src/renderer/src/aiCore/tools/MemorySearchTool.ts @@ -82,7 +82,7 @@ export const memorySearchToolWithExtraction = (assistant: Assistant) => { role: z.enum(['user', 'assistant', 'system']).describe('Message role') }) .optional() - }) as z.ZodSchema, + }) satisfies z.ZodSchema, execute: async ({ userMessage }) => { // console.log('🧠 [memorySearchToolWithExtraction] Processing:', { userMessage, lastAnswer }) diff --git a/src/renderer/src/pages/home/Markdown/CitationTooltip.tsx b/src/renderer/src/pages/home/Markdown/CitationTooltip.tsx index 2e496dd7ee..1c66be71c0 100644 --- a/src/renderer/src/pages/home/Markdown/CitationTooltip.tsx +++ b/src/renderer/src/pages/home/Markdown/CitationTooltip.tsx @@ -5,7 +5,7 @@ import styled from 'styled-components' import { z } from 'zod' export const CitationSchema = z.object({ - url: z.string().url(), + url: z.url(), title: z.string().optional(), content: z.string().optional() }) diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 8fa0099c3f..428631e39d 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -2,7 +2,7 @@ import type { WebSearchResultBlock } from '@anthropic-ai/sdk/resources' import type { GenerateImagesConfig, GroundingMetadata, PersonGeneration } from '@google/genai' import type OpenAI from 'openai' import type { CSSProperties } from 'react' -import * as z from 'zod/v4' +import { z } from 'zod' export * from './file' export * from './note' diff --git a/src/renderer/src/types/mcp.ts b/src/renderer/src/types/mcp.ts index cfc7271714..23f04e882e 100644 --- a/src/renderer/src/types/mcp.ts +++ b/src/renderer/src/types/mcp.ts @@ -1,4 +1,4 @@ -import z from 'zod' +import { z } from 'zod' import { isBuiltinMCPServerName } from '.' @@ -17,6 +17,7 @@ export type MCPConfigSample = z.infer */ export const McpServerTypeSchema = z .string() + .default('stdio') .transform((type) => { if (type.includes('http')) { return 'streamableHttp' @@ -24,9 +25,7 @@ export const McpServerTypeSchema = z return type } }) - .pipe( - z.union([z.literal('stdio'), z.literal('sse'), z.literal('streamableHttp'), z.literal('inMemory')]).default('stdio') // 大多数情况下默认使用 stdio - ) + .pipe(z.union([z.literal('stdio'), z.literal('sse'), z.literal('streamableHttp'), z.literal('inMemory')])) // 大多数情况下默认使用 stdio /** * 定义单个 MCP 服务器的配置。 diff --git a/src/renderer/src/types/tool.ts b/src/renderer/src/types/tool.ts index b552c67685..d0567a8a61 100644 --- a/src/renderer/src/types/tool.ts +++ b/src/renderer/src/types/tool.ts @@ -1,4 +1,4 @@ -import * as z from 'zod/v4' +import { z } from 'zod' export type ToolType = 'builtin' | 'provider' | 'mcp' diff --git a/src/renderer/src/utils/error.ts b/src/renderer/src/utils/error.ts index a1640fd99d..1a2f13a855 100644 --- a/src/renderer/src/utils/error.ts +++ b/src/renderer/src/utils/error.ts @@ -8,7 +8,7 @@ import { } from '@renderer/types/error' import { InvalidToolInputError, NoSuchToolError } from 'ai' import { t } from 'i18next' -import z from 'zod' +import { z } from 'zod' import { safeSerialize } from './serialize' diff --git a/yarn.lock b/yarn.lock index f86b71a54e..de965b531a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2258,7 +2258,7 @@ __metadata: tsdown: "npm:^0.12.9" typescript: "npm:^5.0.0" vitest: "npm:^3.2.4" - zod: "npm:^3.25.0" + zod: "npm:^4.1.5" peerDependencies: ai: ^5.0.26 languageName: unknown @@ -10131,7 +10131,7 @@ __metadata: yjs: "npm:^13.6.27" youtubei.js: "npm:^15.0.1" zipread: "npm:^1.3.3" - zod: "npm:^3.25.74" + zod: "npm:^4.1.5" languageName: unknown linkType: soft @@ -25631,17 +25631,17 @@ __metadata: languageName: node linkType: hard -"zod@npm:^3.25.0, zod@npm:^3.25.32": +"zod@npm:^3.25.32": version: 3.25.76 resolution: "zod@npm:3.25.76" checksum: 10c0/5718ec35e3c40b600316c5b4c5e4976f7fee68151bc8f8d90ec18a469be9571f072e1bbaace10f1e85cf8892ea12d90821b200e980ab46916a6166a4260a983c languageName: node linkType: hard -"zod@npm:^3.25.74": - version: 3.25.74 - resolution: "zod@npm:3.25.74" - checksum: 10c0/59e38b046ac333b5bd1ba325a83b6798721227cbfb1e69dfc7159bd7824b904241ab923026edb714fafefec3624265ae374a70aee9a5a45b365bd31781ffa105 +"zod@npm:^4.1.5": + version: 4.1.5 + resolution: "zod@npm:4.1.5" + checksum: 10c0/7826fb931bc71d4d0fff2fbb72f1a1cf30a6672cf9dbe6933a216bbb60242ef1c3bdfbcd3c5b27e806235a35efaad7a4a9897ff4d3621452f9ea278bce6fd42a languageName: node linkType: hard From 4b65dfa6ea503acaf0f5a8e107d0b89a31807081 Mon Sep 17 00:00:00 2001 From: MyPrototypeWhat Date: Sun, 7 Sep 2025 16:49:30 +0800 Subject: [PATCH 11/66] feat: integrate HeroUI and Tailwind CSS for enhanced styling (#9973) --- .github/workflows/auto-i18n.yml | 4 +- .prettierrc | 4 +- components.json | 21 + electron.vite.config.ts | 1 + eslint.config.mjs | 4 +- package.json | 10 +- packages/aiCore/package.json | 2 +- resources/cherry-studio/license.html | 54 +- resources/cherry-studio/releases.html | 20 +- scripts/update-languages.ts | 2 +- src/renderer/src/App.tsx | 35 +- .../src/assets/styles/CommandListPopover.css | 60 + .../src/assets/styles/CommandListPopover.scss | 59 - .../styles/{animation.scss => animation.css} | 8 +- src/renderer/src/assets/styles/ant.css | 238 ++ src/renderer/src/assets/styles/ant.scss | 234 -- .../assets/styles/{color.scss => color.css} | 2 +- src/renderer/src/assets/styles/container.css | 9 + src/renderer/src/assets/styles/container.scss | 11 - .../src/assets/styles/{font.scss => font.css} | 2 +- .../assets/styles/{index.scss => index.css} | 130 +- src/renderer/src/assets/styles/markdown.css | 388 ++ src/renderer/src/assets/styles/markdown.scss | 379 -- .../{responsive.scss => responsive.css} | 4 +- src/renderer/src/assets/styles/richtext.css | 522 +++ src/renderer/src/assets/styles/richtext.scss | 508 --- .../styles/{scrollbar.scss => scrollbar.css} | 21 +- .../src/assets/styles/selection-toolbar.css | 72 + .../src/assets/styles/selection-toolbar.scss | 72 - src/renderer/src/assets/styles/tailwind.css | 156 + src/renderer/src/components/ErrorBoundary.tsx | 8 +- src/renderer/src/components/Icons/SVGIcon.tsx | 2 +- .../RichEditor/CommandListPopover.tsx | 2 +- src/renderer/src/components/ToastPortal.tsx | 27 + src/renderer/src/components/TopView/index.tsx | 8 + src/renderer/src/context/ThemeProvider.tsx | 7 + src/renderer/src/entryPoint.tsx | 3 +- src/renderer/src/env.d.ts | 11 + src/renderer/src/hero.ts | 2 + src/renderer/src/hooks/useMinappPopup.ts | 2 +- .../pages/home/Messages/Blocks/ErrorBlock.tsx | 17 +- src/renderer/src/utils/motionVariants.ts | 5 +- src/renderer/src/windows/mini/entryPoint.tsx | 2 +- .../action/components/WindowFooter.tsx | 2 +- .../windows/selection/action/entryPoint.tsx | 2 +- .../selection/toolbar/SelectionToolbar.tsx | 2 +- tsconfig.json | 6 +- tsconfig.node.json | 4 +- vitest.config.ts | 2 +- yarn.lock | 3519 ++++++++++++++++- 50 files changed, 5043 insertions(+), 1622 deletions(-) create mode 100644 components.json create mode 100644 src/renderer/src/assets/styles/CommandListPopover.css delete mode 100644 src/renderer/src/assets/styles/CommandListPopover.scss rename src/renderer/src/assets/styles/{animation.scss => animation.css} (94%) create mode 100644 src/renderer/src/assets/styles/ant.css delete mode 100644 src/renderer/src/assets/styles/ant.scss rename src/renderer/src/assets/styles/{color.scss => color.css} (98%) create mode 100644 src/renderer/src/assets/styles/container.css delete mode 100644 src/renderer/src/assets/styles/container.scss rename src/renderer/src/assets/styles/{font.scss => font.css} (96%) rename src/renderer/src/assets/styles/{index.scss => index.css} (54%) create mode 100644 src/renderer/src/assets/styles/markdown.css delete mode 100644 src/renderer/src/assets/styles/markdown.scss rename src/renderer/src/assets/styles/{responsive.scss => responsive.css} (92%) create mode 100644 src/renderer/src/assets/styles/richtext.css delete mode 100644 src/renderer/src/assets/styles/richtext.scss rename src/renderer/src/assets/styles/{scrollbar.scss => scrollbar.css} (87%) create mode 100644 src/renderer/src/assets/styles/selection-toolbar.css delete mode 100644 src/renderer/src/assets/styles/selection-toolbar.scss create mode 100644 src/renderer/src/assets/styles/tailwind.css create mode 100644 src/renderer/src/components/ToastPortal.tsx create mode 100644 src/renderer/src/hero.ts diff --git a/.github/workflows/auto-i18n.yml b/.github/workflows/auto-i18n.yml index 054dea40e6..29cfd1fda0 100644 --- a/.github/workflows/auto-i18n.yml +++ b/.github/workflows/auto-i18n.yml @@ -1,7 +1,7 @@ name: Auto I18N env: - API_KEY: ${{ secrets.TRANSLATE_API_KEY}} + API_KEY: ${{ secrets.TRANSLATE_API_KEY }} MODEL: ${{ vars.MODEL || 'deepseek/deepseek-v3.1'}} BASE_URL: ${{ vars.BASE_URL || 'https://api.ppinfra.com/openai'}} @@ -35,7 +35,7 @@ jobs: # 在临时目录安装依赖 mkdir -p /tmp/translation-deps cd /tmp/translation-deps - echo '{"dependencies": {"openai": "^5.12.2", "cli-progress": "^3.12.0", "tsx": "^4.20.3", "prettier": "^3.5.3", "prettier-plugin-sort-json": "^4.1.1"}}' > package.json + echo '{"dependencies": {"openai": "^5.12.2", "cli-progress": "^3.12.0", "tsx": "^4.20.3", "prettier": "^3.5.3", "prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-tailwindcss": "^0.6.14"}}' > package.json npm install --no-package-lock # 设置 NODE_PATH 让项目能找到这些依赖 diff --git a/.prettierrc b/.prettierrc index 85e2eb0ca6..7a06761104 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,9 +3,11 @@ "endOfLine": "lf", "jsonRecursiveSort": true, "jsonSortOrder": "{\"*\": \"lexical\"}", - "plugins": ["prettier-plugin-sort-json"], + "plugins": ["prettier-plugin-sort-json", "prettier-plugin-tailwindcss"], "printWidth": 120, "semi": false, "singleQuote": true, + "tailwindFunctions": ["clsx"], + "tailwindStylesheet": "./src/renderer/src/assets/styles/tailwind.css", "trailingComma": "none" } diff --git a/components.json b/components.json new file mode 100644 index 0000000000..c5aceeb3ce --- /dev/null +++ b/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "aliases": { + "components": "@renderer/ui/third-party", + "hooks": "@renderer/hooks", + "lib": "@renderer/lib", + "ui": "@renderer/ui", + "utils": "@renderer/utils" + }, + "iconLibrary": "lucide", + "rsc": false, + "style": "new-york", + "tailwind": { + "baseColor": "zinc", + "config": "", + "css": "src/renderer/src/assets/styles/tailwind.css", + "cssVariables": true, + "prefix": "" + }, + "tsx": true +} diff --git a/electron.vite.config.ts b/electron.vite.config.ts index dff0a94a37..83f82b5f4c 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -60,6 +60,7 @@ export default defineConfig({ }, renderer: { plugins: [ + (async () => (await import('@tailwindcss/vite')).default())(), react({ tsDecorators: true, plugins: [ diff --git a/eslint.config.mjs b/eslint.config.mjs index 133025b1fd..6863613c1b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -123,7 +123,9 @@ export default defineConfig([ '.gitignore', 'scripts/cloudflare-worker.js', 'src/main/integration/nutstore/sso/lib/**', - 'src/main/integration/cherryin/index.js' + 'src/main/integration/cherryin/index.js', + 'src/main/integration/nutstore/sso/lib/**', + 'src/renderer/src/ui/**' ] } ]) diff --git a/package.json b/package.json index f5c4e71032..9936eaca91 100644 --- a/package.json +++ b/package.json @@ -128,6 +128,7 @@ "@eslint/js": "^9.22.0", "@google/genai": "patch:@google/genai@npm%3A1.0.1#~/.yarn/patches/@google-genai-npm-1.0.1-e26f0f9af7.patch", "@hello-pangea/dnd": "^18.0.1", + "@heroui/react": "^2.8.3", "@kangfenmao/keyv-storage": "^0.1.0", "@langchain/community": "^0.3.50", "@langchain/core": "^0.3.68", @@ -148,6 +149,7 @@ "@reduxjs/toolkit": "^2.2.5", "@shikijs/markdown-it": "^3.12.0", "@swc/plugin-styled-components": "^8.0.4", + "@tailwindcss/vite": "^4.1.13", "@tanstack/react-query": "^5.85.5", "@tanstack/react-virtual": "^3.13.12", "@testing-library/dom": "^10.4.0", @@ -210,6 +212,7 @@ "cheerio": "^1.1.2", "chokidar": "^4.0.3", "cli-progress": "^3.12.0", + "clsx": "^2.1.1", "code-inspector-plugin": "^0.20.14", "color": "^5.0.0", "concurrently": "^9.2.1", @@ -238,6 +241,7 @@ "fast-diff": "^1.3.0", "fast-xml-parser": "^5.2.0", "fetch-socks": "1.3.2", + "framer-motion": "^12.23.12", "franc-min": "^6.2.0", "fs-extra": "^11.2.0", "google-auth-library": "^9.15.1", @@ -272,6 +276,7 @@ "playwright": "^1.52.0", "prettier": "^3.5.3", "prettier-plugin-sort-json": "^4.1.1", + "prettier-plugin-tailwindcss": "^0.6.14", "proxy-agent": "^6.5.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -301,17 +306,18 @@ "remark-math": "^6.0.0", "remove-markdown": "^0.6.2", "rollup-plugin-visualizer": "^5.12.0", - "sass": "^1.88.0", "shiki": "^3.12.0", "strict-url-sanitise": "^0.0.1", "string-width": "^7.2.0", "striptags": "^3.2.0", "styled-components": "^6.1.11", + "tailwindcss": "^4.1.13", "tar": "^7.4.3", "tiny-pinyin": "^1.3.2", "tokenx": "^1.1.0", "tsx": "^4.20.3", "turndown-plugin-gfm": "^1.0.2", + "tw-animate-css": "^1.3.8", "typescript": "^5.6.2", "undici": "6.21.2", "unified": "^11.0.5", @@ -356,7 +362,7 @@ "prettier --write", "eslint --fix" ], - "*.{json,yml,yaml,css,scss,html}": [ + "*.{json,yml,yaml,css,html}": [ "prettier --write" ] } diff --git a/packages/aiCore/package.json b/packages/aiCore/package.json index 020c95d7cb..16d9af76a5 100644 --- a/packages/aiCore/package.json +++ b/packages/aiCore/package.json @@ -1,6 +1,6 @@ { "name": "@cherrystudio/ai-core", - "version": "1.0.0-alpha.11", + "version": "1.0.0-alpha.12", "description": "Cherry Studio AI Core - Unified AI Provider Interface Based on Vercel AI SDK", "main": "dist/index.js", "module": "dist/index.mjs", diff --git a/resources/cherry-studio/license.html b/resources/cherry-studio/license.html index ce3079a322..1df611e47c 100644 --- a/resources/cherry-studio/license.html +++ b/resources/cherry-studio/license.html @@ -8,18 +8,18 @@ -
+
-

许可协议

+

许可协议

本项目采用区分用户的双重许可 (User-Segmented Dual Licensing) 模式。

-

核心原则

-
    +

    核心原则

    +
    • 个人用户 和 10人及以下企业/组织: 默认适用 GNU Affero 通用公共许可证 v3.0 (AGPLv3)。 @@ -32,7 +32,7 @@
-

定义:"10人及以下"

+

定义:"10人及以下"

指在您的组织(包括公司、非营利组织、政府机构、教育机构等任何实体)中,能够访问、使用或以任何方式直接或间接受益于本软件(Cherry Studio)功能的个人总数不超过10人。这包括但不限于开发者、测试人员、运营人员、最终用户、通过集成系统间接使用者等。 @@ -40,10 +40,10 @@

-

+

1. 开源许可证 (Open Source License): AGPLv3 - 适用于个人及10人及以下组织

-
    +
    • 如果您是个人用户,或者您的组织满足上述"10人及以下"的定义,您可以在 AGPLv3 的条款下自由使用、修改和分发 Cherry Studio。AGPLv3 的完整文本可以访问 @@ -62,10 +62,10 @@
-

+

2. 商业许可证 (Commercial License) - 适用于超过10人的组织,或希望规避 AGPLv3 义务的用户

-
    +
    • 强制要求: 如果您的组织满足上述"10人及以下"的定义(即有11人或更多人可以访问、使用或受益于本软件),您必须联系我们获取并签署一份商业许可证才能使用 @@ -80,7 +80,7 @@
    • 需要商业许可证的常见情况包括(但不限于): -
        +
        • 您的组织规模超过10人。
        • (无论组织规模)您希望分发修改过的 Cherry Studio 版本,但不希望根据 AGPLv3 @@ -104,8 +104,8 @@
-

3. 贡献 (Contributions)

-
    +

    3. 贡献 (Contributions)

    +
    • 我们欢迎社区对 Cherry Studio 的贡献。所有向本项目提交的贡献都将被视为在 AGPLv3 许可证下提供。 @@ -119,8 +119,8 @@
-

4. 其他条款 (Other Terms)

-