From 5ac09d5311794435947e8e2d475ad60ed895d445 Mon Sep 17 00:00:00 2001 From: Phantom <59059173+EurFelux@users.noreply.github.com> Date: Sat, 20 Sep 2025 16:58:41 +0800 Subject: [PATCH] refactor: migrate to toast from antd message (#10233) * style(eslint): reorganize eslint config comments and rules Move comments to consistent positions above their corresponding rules Update antd import restriction to include 'message' component * fix(eslint): reorganize eslint config to enable custom rules * fix(eslint): update antd import restriction to include message Prevent direct imports of both Flex and message from antd, enforcing usage of custom components * feat(migration): add toast utilities to migrate and test apps Initialize toast utilities on window object for both migration and test applications to enable toast notifications * build(ui): add path aliases for types and utils modules * refactor(toast): move toast utilities to ui package for better reusability Centralize toast utilities in the @cherrystudio/ui package to improve code organization and reuse across multiple components. This change includes: - Moving toast implementation to ui package - Updating all imports to use the new location - Adding proper type definitions * refactor: replace antd message with window.toast for consistency Replace all instances of antd's message component with window.toast throughout the application to maintain consistent notification behavior. Also add an ignore rule for dataRefactorTest files in eslint config. --- eslint.config.mjs | 34 +++++-------------- .../ui/src/components/base/Toast/index.ts | 27 +++++++++++---- packages/ui/src/components/index.ts | 1 + packages/ui/src/types/index.ts | 15 ++++++++ packages/ui/tsconfig.json | 4 +++ .../src/components/LocalBackupManager.tsx | 8 ++--- .../RichEditor/components/ImageUploader.tsx | 16 ++++----- src/renderer/src/components/TopView/index.tsx | 3 +- .../src/components/WebdavBackupManager.tsx | 4 +-- src/renderer/src/env.d.ts | 17 ++-------- .../home/Messages/Blocks/ThinkingBlock.tsx | 6 ++-- .../src/pages/home/Messages/CitationsList.tsx | 4 +-- .../home/Messages/Tools/MessageMcpTool.tsx | 21 +++--------- .../components/KnowledgeSearchItem/hooks.ts | 3 +- .../knowledge/items/KnowledgeSitemaps.tsx | 4 +-- .../MiniappSettings/MiniAppSettings.tsx | 12 +++---- .../DisplaySettings/SidebarIconsManager.tsx | 5 ++- .../src/pages/settings/NotesSettings.tsx | 8 ++--- .../EditModelPopup/ModelEditContent.tsx | 4 +-- .../dataRefactorMigrate/MigrateApp.tsx | 5 +++ .../src/windows/dataRefactorTest/TestApp.tsx | 7 +++- .../src/windows/mini/MiniWindowApp.tsx | 2 +- .../windows/selection/action/entryPoint.tsx | 2 +- 23 files changed, 104 insertions(+), 108 deletions(-) rename src/renderer/src/components/TopView/toast.ts => packages/ui/src/components/base/Toast/index.ts (71%) diff --git a/eslint.config.mjs b/eslint.config.mjs index 5bad77bb7a..668e35bfeb 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -69,29 +69,9 @@ export default defineConfig([ ...oxlint.configs['flat/eslint'], ...oxlint.configs['flat/typescript'], ...oxlint.configs['flat/unicorn'], + // Custom rules should be after oxlint to overwrite + // LoggerService Custom Rules - only apply to src directory { - ignores: [ - 'node_modules/**', - 'build/**', - 'dist/**', - 'out/**', - 'local/**', - '.yarn/**', - '.gitignore', - 'scripts/cloudflare-worker.js', - 'src/main/integration/nutstore/sso/lib/**', - 'src/main/integration/cherryin/index.js', - 'src/main/integration/nutstore/sso/lib/**', - 'src/renderer/src/ui/**', - 'packages/**/dist' - ] - }, - // turn off oxlint supported rules. - ...oxlint.configs['flat/eslint'], - ...oxlint.configs['flat/typescript'], - ...oxlint.configs['flat/unicorn'], - { - // LoggerService Custom Rules - only apply to src directory files: ['src/**/*.{ts,tsx,js,jsx}'], ignores: ['src/**/__tests__/**', 'src/**/__mocks__/**', 'src/**/*.test.*', 'src/preload/**'], rules: { @@ -105,6 +85,7 @@ export default defineConfig([ ] } }, + // i18n { files: ['**/*.{ts,tsx,js,jsx}'], languageOptions: { @@ -152,9 +133,11 @@ export default defineConfig([ 'i18n/no-template-in-t': 'warn' } }, + // ui migration { // Component Rules - prevent importing antd components when migration completed - files: ['src/**/*.{ts,tsx,js,jsx}'], + files: ['**/*.{ts,tsx,js,jsx}'], + ignores: ['src/renderer/src/windows/dataRefactorTest/**/*.{ts,tsx}'], rules: { 'no-restricted-imports': [ 'error', @@ -162,8 +145,7 @@ export default defineConfig([ paths: [ { name: 'antd', - // TODO: migrate message again - importNames: ['Flex', 'Switch'], + importNames: ['Flex', 'Switch', 'message',], message: '❌ Do not import this component from antd. Use our custom components instead: import { ... } from "@cherrystudio/ui"' }, @@ -176,5 +158,5 @@ export default defineConfig([ } ] } - } + }, ]) diff --git a/src/renderer/src/components/TopView/toast.ts b/packages/ui/src/components/base/Toast/index.ts similarity index 71% rename from src/renderer/src/components/TopView/toast.ts rename to packages/ui/src/components/base/Toast/index.ts index d1e5726310..13b70088f6 100644 --- a/src/renderer/src/components/TopView/toast.ts +++ b/packages/ui/src/components/base/Toast/index.ts @@ -1,5 +1,5 @@ import { addToast, closeAll, closeToast, getToastQueue, isToastClosing } from '@heroui/toast' -import type { RequireSome } from '@renderer/types' +import type { RequireSome } from '@types' type AddToastProps = Parameters[0] type ToastPropsColored = Omit @@ -21,35 +21,35 @@ const createToast = (color: 'danger' | 'success' | 'warning' | 'default') => { * @param arg - Toast content (string) or toast options object * @returns Toast ID or null */ -export const error = createToast('danger') +const error = createToast('danger') /** * Display a success toast notification with green color * @param arg - Toast content (string) or toast options object * @returns Toast ID or null */ -export const success = createToast('success') +const success = createToast('success') /** * Display a warning toast notification with yellow color * @param arg - Toast content (string) or toast options object * @returns Toast ID or null */ -export const warning = createToast('warning') +const warning = createToast('warning') /** * Display an info toast notification with default color * @param arg - Toast content (string) or toast options object * @returns Toast ID or null */ -export const info = createToast('default') +const info = createToast('default') /** * Display a loading toast notification that resolves with a promise * @param args - Toast options object containing a promise to resolve * @returns Toast ID or null */ -export const loading = (args: RequireSome) => { +const loading = (args: RequireSome) => { // Disappear immediately by default if (args.timeout === undefined) { args.timeout = 1 @@ -57,7 +57,20 @@ export const loading = (args: RequireSome) => { return addToast(args) } -export const getToastUtilities = () => +export type ToastUtilities = { + getToastQueue: typeof getToastQueue + addToast: typeof addToast + closeToast: typeof closeToast + closeAll: typeof closeAll + isToastClosing: typeof isToastClosing + error: typeof error + success: typeof success + warning: typeof warning + info: typeof info + loading: typeof loading +} + +export const getToastUtilities = (): ToastUtilities => ({ getToastQueue, addToast, diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 8e83e2ea82..4c6f97649f 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -12,6 +12,7 @@ export type { StatusTagProps, StatusType } from './base/StatusTag' export { ErrorTag, InfoTag, StatusTag, SuccessTag, WarnTag } from './base/StatusTag' export { Switch } from './base/Switch' export { default as TextBadge } from './base/TextBadge' +export { getToastUtilities, type ToastUtilities } from './base/Toast' // Display Components export { default as Ellipsis } from './display/Ellipsis' diff --git a/packages/ui/src/types/index.ts b/packages/ui/src/types/index.ts index e69de29bb2..6b44ed9856 100644 --- a/packages/ui/src/types/index.ts +++ b/packages/ui/src/types/index.ts @@ -0,0 +1,15 @@ +/** + * Makes specified properties required while keeping others as is + * @template T - The object type to modify + * @template K - Keys of T that should be required + * @example + * type User = { + * name?: string; + * age?: number; + * } + * + * type UserWithName = RequireSome + * // Result: { name: string; age?: number; } + */ +// The type is copied from src/renderer/src/types/index.ts. +export type RequireSome = Omit & Required> diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index b41decfb9e..7ab18cbccc 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -12,6 +12,10 @@ "moduleResolution": "bundler", "noFallthroughCasesInSwitch": true, "outDir": "./dist", + "paths": { + "@types": ["src/types"], + "@utils": ["src/utils"] + }, "resolveJsonModule": true, "rootDir": ".", "skipLibCheck": true, diff --git a/src/renderer/src/components/LocalBackupManager.tsx b/src/renderer/src/components/LocalBackupManager.tsx index fdc64222db..13dbcf329c 100644 --- a/src/renderer/src/components/LocalBackupManager.tsx +++ b/src/renderer/src/components/LocalBackupManager.tsx @@ -1,7 +1,7 @@ import { DeleteOutlined, ExclamationCircleOutlined, ReloadOutlined } from '@ant-design/icons' import { restoreFromLocal } from '@renderer/services/BackupService' import { formatFileSize } from '@renderer/utils' -import { Button, message, Modal, Table, Tooltip } from 'antd' +import { Button, Modal, Table, Tooltip } from 'antd' import dayjs from 'dayjs' import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -68,7 +68,7 @@ export function LocalBackupManager({ visible, onClose, localBackupDir, restoreMe const handleDeleteSelected = async () => { if (selectedRowKeys.length === 0) { - message.warning(t('settings.data.local.backup.manager.select.files.delete')) + window.toast.warning(t('settings.data.local.backup.manager.select.files.delete')) return } @@ -120,7 +120,7 @@ export function LocalBackupManager({ visible, onClose, localBackupDir, restoreMe setDeleting(true) try { await window.api.backup.deleteLocalBackupFile(fileName, localBackupDir) - message.success(t('settings.data.local.backup.manager.delete.success.single')) + window.toast.success(t('settings.data.local.backup.manager.delete.success.single')) await fetchBackupFiles() } catch (error: any) { window.toast.error(`${t('settings.data.local.backup.manager.delete.error')}: ${error.message}`) @@ -147,7 +147,7 @@ export function LocalBackupManager({ visible, onClose, localBackupDir, restoreMe setRestoring(true) try { await (restoreMethod || restoreFromLocal)(fileName) - message.success(t('settings.data.local.backup.manager.restore.success')) + window.toast.success(t('settings.data.local.backup.manager.restore.success')) onClose() // Close the modal } catch (error: any) { window.toast.error(`${t('settings.data.local.backup.manager.restore.error')}: ${error.message}`) diff --git a/src/renderer/src/components/RichEditor/components/ImageUploader.tsx b/src/renderer/src/components/RichEditor/components/ImageUploader.tsx index e74607d73c..631fc6bd1f 100644 --- a/src/renderer/src/components/RichEditor/components/ImageUploader.tsx +++ b/src/renderer/src/components/RichEditor/components/ImageUploader.tsx @@ -1,6 +1,6 @@ import { InboxOutlined, LinkOutlined, LoadingOutlined, UploadOutlined } from '@ant-design/icons' import { Flex } from '@cherrystudio/ui' -import { Button, Input, message, Modal, Spin, Tabs, Upload } from 'antd' +import { Button, Input, Modal, Spin, Tabs, Upload } from 'antd' const { Dragger } = Upload import type { RcFile } from 'antd/es/upload' @@ -71,24 +71,24 @@ export const ImageUploader: React.FC = ({ onImageSelect, vis // Validate file type const isImage = file.type.startsWith('image/') if (!isImage) { - message.error(t('richEditor.imageUploader.invalidType')) + window.toast.error(t('richEditor.imageUploader.invalidType')) return false } // Validate file size (max 10MB) const isLt10M = file.size / 1024 / 1024 < 10 if (!isLt10M) { - message.error(t('richEditor.imageUploader.tooLarge')) + window.toast.error(t('richEditor.imageUploader.tooLarge')) return false } // Convert to base64 and call callback const base64Url = await convertFileToBase64(file) onImageSelect(base64Url) - message.success(t('richEditor.imageUploader.uploadSuccess')) + window.toast.success(t('richEditor.imageUploader.uploadSuccess')) onClose() } catch (error) { - message.error(t('richEditor.imageUploader.uploadError')) + window.toast.error(t('richEditor.imageUploader.uploadError')) } finally { setLoading(false) } @@ -98,7 +98,7 @@ export const ImageUploader: React.FC = ({ onImageSelect, vis const handleUrlSubmit = () => { if (!urlInput.trim()) { - message.error(t('richEditor.imageUploader.urlRequired')) + window.toast.error(t('richEditor.imageUploader.urlRequired')) return } @@ -106,11 +106,11 @@ export const ImageUploader: React.FC = ({ onImageSelect, vis try { new URL(urlInput.trim()) onImageSelect(urlInput.trim()) - message.success(t('richEditor.imageUploader.embedSuccess')) + window.toast.success(t('richEditor.imageUploader.embedSuccess')) setUrlInput('') onClose() } catch { - message.error(t('richEditor.imageUploader.invalidUrl')) + window.toast.error(t('richEditor.imageUploader.invalidUrl')) } } diff --git a/src/renderer/src/components/TopView/index.tsx b/src/renderer/src/components/TopView/index.tsx index 6823b6b9a7..c2869a3917 100644 --- a/src/renderer/src/components/TopView/index.tsx +++ b/src/renderer/src/components/TopView/index.tsx @@ -1,5 +1,6 @@ // import { loggerService } from '@logger' import { Box } from '@cherrystudio/ui' +import { getToastUtilities } from '@cherrystudio/ui' import TopViewMinappContainer from '@renderer/components/MinApp/TopViewMinappContainer' import { useAppInit } from '@renderer/hooks/useAppInit' import { useShortcuts } from '@renderer/hooks/useShortcuts' @@ -7,8 +8,6 @@ import { Modal } from 'antd' import type { PropsWithChildren } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react' -import { getToastUtilities } from './toast' - let onPop = () => {} let onShow = ({ element, id }: { element: React.FC | React.ReactNode; id: string }) => { element diff --git a/src/renderer/src/components/WebdavBackupManager.tsx b/src/renderer/src/components/WebdavBackupManager.tsx index 6683cacfe0..a779871414 100644 --- a/src/renderer/src/components/WebdavBackupManager.tsx +++ b/src/renderer/src/components/WebdavBackupManager.tsx @@ -1,7 +1,7 @@ import { DeleteOutlined, ExclamationCircleOutlined, ReloadOutlined } from '@ant-design/icons' import { restoreFromWebdav } from '@renderer/services/BackupService' import { formatFileSize } from '@renderer/utils' -import { Button, message, Modal, Table, Tooltip } from 'antd' +import { Button, Modal, Table, Tooltip } from 'antd' import dayjs from 'dayjs' import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -101,7 +101,7 @@ export function WebdavBackupManager({ const handleDeleteSelected = async () => { if (selectedRowKeys.length === 0) { - message.warning(t('settings.data.webdav.backup.manager.select.files.delete')) + window.toast.warning(t('settings.data.webdav.backup.manager.select.files.delete')) return } diff --git a/src/renderer/src/env.d.ts b/src/renderer/src/env.d.ts index 653d2710e4..eefc880a57 100644 --- a/src/renderer/src/env.d.ts +++ b/src/renderer/src/env.d.ts @@ -1,12 +1,10 @@ /// -import type { addToast, closeAll, closeToast, getToastQueue, isToastClosing } from '@heroui/toast' +import type { ToastUtilities } from '@cherrystudio/ui' import type KeyvStorage from '@kangfenmao/keyv-storage' import type { HookAPI } from 'antd/es/modal/useModal' import type { NavigateFunction } from 'react-router-dom' -import type { error, info, loading, success, warning } from './components/TopView/toast' - interface ImportMetaEnv { VITE_RENDERER_INTEGRATED_MODEL: string } @@ -22,17 +20,6 @@ declare global { keyv: KeyvStorage store: any navigate: NavigateFunction - toast: { - getToastQueue: typeof getToastQueue - addToast: typeof addToast - closeToast: typeof closeToast - closeAll: typeof closeAll - isToastClosing: typeof isToastClosing - error: typeof error - success: typeof success - warning: typeof warning - info: typeof info - loading: typeof loading - } + toast: ToastUtilities } } diff --git a/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx b/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx index f5a19904e0..7124badd5d 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx @@ -4,7 +4,7 @@ import { loggerService } from '@logger' import ThinkingEffect from '@renderer/components/ThinkingEffect' import { useTemporaryValue } from '@renderer/hooks/useTemporaryValue' import { MessageBlockStatus, type ThinkingMessageBlock } from '@renderer/types/newMessage' -import { Collapse, message as antdMessage, Tooltip } from 'antd' +import { Collapse, Tooltip } from 'antd' import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -39,12 +39,12 @@ const ThinkingBlock: React.FC = ({ block }) => { navigator.clipboard .writeText(block.content) .then(() => { - antdMessage.success({ content: t('message.copied'), key: 'copy-message' }) + window.toast.success(t('message.copied')) setCopied(true) }) .catch((error) => { logger.error('Failed to copy text:', error) - antdMessage.error({ content: t('message.copy.failed'), key: 'copy-message-error' }) + window.toast.error(t('message.copy.failed')) }) } }, [block.content, setCopied, t]) diff --git a/src/renderer/src/pages/home/Messages/CitationsList.tsx b/src/renderer/src/pages/home/Messages/CitationsList.tsx index c9967d9f6b..0e9ca2265c 100644 --- a/src/renderer/src/pages/home/Messages/CitationsList.tsx +++ b/src/renderer/src/pages/home/Messages/CitationsList.tsx @@ -6,7 +6,7 @@ import type { Citation } from '@renderer/types' import { fetchWebContent } from '@renderer/utils/fetch' import { cleanMarkdownContent } from '@renderer/utils/formats' import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query' -import { Button, message, Popover, Skeleton } from 'antd' +import { Button, Popover, Skeleton } from 'antd' import { Check, Copy, FileSearch } from 'lucide-react' import React from 'react' import { useTranslation } from 'react-i18next' @@ -129,7 +129,7 @@ const CopyButton: React.FC<{ content: string }> = ({ content }) => { window.toast.success(t('common.copied')) }) .catch(() => { - message.error(t('message.copy.failed')) + window.toast.error(t('message.copy.failed')) }) } diff --git a/src/renderer/src/pages/home/Messages/Tools/MessageMcpTool.tsx b/src/renderer/src/pages/home/Messages/Tools/MessageMcpTool.tsx index 14822b93df..6893086c78 100644 --- a/src/renderer/src/pages/home/Messages/Tools/MessageMcpTool.tsx +++ b/src/renderer/src/pages/home/Messages/Tools/MessageMcpTool.tsx @@ -10,18 +10,7 @@ import { isToolAutoApproved } from '@renderer/utils/mcp-tools' import { cancelToolAction, confirmToolAction } from '@renderer/utils/userConfirmation' import type { MCPProgressEvent } from '@shared/config/types' import { IpcChannel } from '@shared/IpcChannel' -import { - Button, - Collapse, - ConfigProvider, - Dropdown, - message as antdMessage, - Modal, - Progress, - Tabs, - Tooltip -} from 'antd' -import { message } from 'antd' +import { Button, Collapse, ConfigProvider, Dropdown, Modal, Progress, Tabs, Tooltip } from 'antd' import { Check, ChevronDown, @@ -153,7 +142,7 @@ const MessageMcpTool: FC = ({ block }) => { const copyContent = (content: string, toolId: string) => { navigator.clipboard.writeText(content) - antdMessage.success({ content: t('message.copied'), key: 'copy-message' }) + window.toast.success(t('message.copied')) setCopiedMap((prev) => ({ ...prev, [toolId]: true })) setTimeoutTimer('copyContent', () => setCopiedMap((prev) => ({ ...prev, [toolId]: false })), 2000) } @@ -180,11 +169,11 @@ const MessageMcpTool: FC = ({ block }) => { if (success) { window.toast.success(t('message.tools.aborted')) } else { - message.error({ content: t('message.tools.abort_failed'), key: 'abort-tool' }) + window.toast.error(t('message.tools.abort_failed')) } } catch (error) { logger.error('Failed to abort tool:', error as Error) - message.error({ content: t('message.tools.abort_failed'), key: 'abort-tool' }) + window.toast.error(t('message.tools.abort_failed')) } } } @@ -490,7 +479,7 @@ const MessageMcpTool: FC = ({ block }) => { ? expandedResponse.content : JSON.stringify(expandedResponse.content, null, 2) ) - antdMessage.success({ content: t('message.copied'), key: 'copy-expanded' }) + window.toast.success(t('message.copied')) }} aria-label={t('common.copy')}> diff --git a/src/renderer/src/pages/knowledge/components/KnowledgeSearchItem/hooks.ts b/src/renderer/src/pages/knowledge/components/KnowledgeSearchItem/hooks.ts index d5159363fb..a6d2d9716a 100644 --- a/src/renderer/src/pages/knowledge/components/KnowledgeSearchItem/hooks.ts +++ b/src/renderer/src/pages/knowledge/components/KnowledgeSearchItem/hooks.ts @@ -1,6 +1,5 @@ import { loggerService } from '@logger' import { isValidUrl } from '@renderer/utils/fetch' -import { message } from 'antd' import type { ReactElement } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' @@ -35,7 +34,7 @@ export const useCopyText = () => { const handleCopy = async (text: string) => { try { await navigator.clipboard.writeText(text) - message.success(t('message.copied')) + window.toast.success(t('message.copied')) } catch (error) { logger.error('Failed to copy text:', error as Error) window.toast.error(t('message.error.copy') || 'Failed to copy text') diff --git a/src/renderer/src/pages/knowledge/items/KnowledgeSitemaps.tsx b/src/renderer/src/pages/knowledge/items/KnowledgeSitemaps.tsx index 2f8d32d222..e81a1beb9f 100644 --- a/src/renderer/src/pages/knowledge/items/KnowledgeSitemaps.tsx +++ b/src/renderer/src/pages/knowledge/items/KnowledgeSitemaps.tsx @@ -7,7 +7,7 @@ import { useKnowledge } from '@renderer/hooks/useKnowledge' import FileItem from '@renderer/pages/files/FileItem' import { getProviderName } from '@renderer/services/ProviderService' import type { KnowledgeBase, KnowledgeItem } from '@renderer/types' -import { Button, message, Tooltip } from 'antd' +import { Button, Tooltip } from 'antd' import dayjs from 'dayjs' import { PlusIcon } from 'lucide-react' import type { FC } from 'react' @@ -74,7 +74,7 @@ const KnowledgeSitemaps: FC = ({ selectedBase }) => { try { new URL(url) if (sitemapItems.find((item) => item.content === url)) { - message.success(t('knowledge.sitemap_added')) + window.toast.success(t('knowledge.sitemap_added')) return } addSitemap(url) diff --git a/src/renderer/src/pages/minapps/MiniappSettings/MiniAppSettings.tsx b/src/renderer/src/pages/minapps/MiniappSettings/MiniAppSettings.tsx index 7b3925d5b8..d3ed1181e8 100644 --- a/src/renderer/src/pages/minapps/MiniappSettings/MiniAppSettings.tsx +++ b/src/renderer/src/pages/minapps/MiniappSettings/MiniAppSettings.tsx @@ -4,7 +4,7 @@ import { usePreference } from '@data/hooks/usePreference' import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' import { useMinapps } from '@renderer/hooks/useMinapps' import { SettingDescription, SettingDivider, SettingRowTitle, SettingTitle } from '@renderer/pages/settings' -import { Button, message, Slider, Tooltip } from 'antd' +import { Button, Slider, Tooltip } from 'antd' import type { FC } from 'react' import { useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -28,7 +28,6 @@ const MiniAppSettings: FC = () => { const [visibleMiniApps, setVisibleMiniApps] = useState(minapps) const [disabledMiniApps, setDisabledMiniApps] = useState(disabled || []) - const [messageApi, contextHolder] = message.useMessage() const debounceTimerRef = useRef(null) const handleResetMinApps = useCallback(() => { @@ -47,8 +46,8 @@ const MiniAppSettings: FC = () => { // 恢复默认缓存数量 const handleResetCacheLimit = useCallback(() => { setMaxKeepAliveMinapps(DEFAULT_MAX_KEEPALIVE) - messageApi.info(t('settings.miniapps.cache_change_notice')) - }, [messageApi, t, setMaxKeepAliveMinapps]) + window.toast.info(t('settings.miniapps.cache_change_notice')) + }, [t, setMaxKeepAliveMinapps]) // 处理缓存数量变更 const handleCacheChange = useCallback( @@ -60,11 +59,11 @@ const MiniAppSettings: FC = () => { } debounceTimerRef.current = setTimeout(() => { - messageApi.info(t('settings.miniapps.cache_change_notice')) + window.toast.info(t('settings.miniapps.cache_change_notice')) debounceTimerRef.current = null }, 500) }, - [messageApi, t, setMaxKeepAliveMinapps] + [t, setMaxKeepAliveMinapps] ) // 组件卸载时清除定时器 @@ -78,7 +77,6 @@ const MiniAppSettings: FC = () => { return ( - {contextHolder} {/* 添加消息上下文 */} diff --git a/src/renderer/src/pages/settings/DisplaySettings/SidebarIconsManager.tsx b/src/renderer/src/pages/settings/DisplaySettings/SidebarIconsManager.tsx index f5fc045d3a..f1be291937 100644 --- a/src/renderer/src/pages/settings/DisplaySettings/SidebarIconsManager.tsx +++ b/src/renderer/src/pages/settings/DisplaySettings/SidebarIconsManager.tsx @@ -3,7 +3,6 @@ import type { DraggableProvided, DroppableProvided, DropResult } from '@hello-pa import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd' import { getSidebarIconLabel } from '@renderer/i18n/label' import type { SidebarIcon } from '@shared/data/preference/preferenceTypes' -import { message } from 'antd' import { Code, FileSearch, @@ -44,7 +43,7 @@ const SidebarIconsManager: FC = ({ // 如果是chat图标且目标是disabled区域,则不允许移动并提示 const draggedItem = source.droppableId === 'visible' ? visibleIcons[source.index] : invisibleIcons[source.index] if (draggedItem === 'assistants' && destination.droppableId === 'disabled') { - message.warning(t('settings.display.sidebar.chat.hiddenMessage')) + window.toast.warning(t('settings.display.sidebar.chat.hiddenMessage')) return } @@ -81,7 +80,7 @@ const SidebarIconsManager: FC = ({ (icon: SidebarIcon, fromList: 'visible' | 'disabled') => { // 如果是chat图标且要移动到disabled列表,则不允许并提示 if (icon === 'assistants' && fromList === 'visible') { - message.warning(t('settings.display.sidebar.chat.hiddenMessage')) + window.toast.warning(t('settings.display.sidebar.chat.hiddenMessage')) return } diff --git a/src/renderer/src/pages/settings/NotesSettings.tsx b/src/renderer/src/pages/settings/NotesSettings.tsx index 01b375f5da..09b1810087 100644 --- a/src/renderer/src/pages/settings/NotesSettings.tsx +++ b/src/renderer/src/pages/settings/NotesSettings.tsx @@ -5,7 +5,7 @@ import { useTheme } from '@renderer/context/ThemeProvider' import { useNotesSettings } from '@renderer/hooks/useNotesSettings' import { initWorkSpace } from '@renderer/services/NotesService' import type { EditorView } from '@renderer/types' -import { Button, Input, message, Slider } from 'antd' +import { Button, Input, Slider } from 'antd' import { FolderOpen } from 'lucide-react' import type { FC } from 'react' import { useEffect, useState } from 'react' @@ -50,7 +50,7 @@ const NotesSettings: FC = () => { } } catch (error) { logger.error('Failed to select directory:', error as Error) - message.error(t('notes.settings.data.select_directory_failed')) + window.toast.error(t('notes.settings.data.select_directory_failed')) } finally { setIsSelecting(false) } @@ -58,7 +58,7 @@ const NotesSettings: FC = () => { const handleApplyPath = async () => { if (!tempPath) { - message.error(t('notes.settings.data.path_required')) + window.toast.error(t('notes.settings.data.path_required')) return } @@ -67,7 +67,7 @@ const NotesSettings: FC = () => { const isValidDir = await window.api.file.validateNotesDirectory(tempPath) if (!isValidDir) { - message.error(t('notes.settings.data.invalid_directory')) + window.toast.error(t('notes.settings.data.invalid_directory')) return } diff --git a/src/renderer/src/pages/settings/ProviderSettings/EditModelPopup/ModelEditContent.tsx b/src/renderer/src/pages/settings/ProviderSettings/EditModelPopup/ModelEditContent.tsx index 0c40aa3e18..6eb632f972 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/EditModelPopup/ModelEditContent.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/EditModelPopup/ModelEditContent.tsx @@ -23,7 +23,7 @@ import { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth' import type { Model, ModelCapability, ModelType, Provider } from '@renderer/types' import { getDefaultGroupName, getDifference, getUnion, uniqueObjectArray } from '@renderer/utils' import type { ModalProps } from 'antd' -import { Button, Divider, Form, Input, InputNumber, message, Modal, Select, Tooltip } from 'antd' +import { Button, Divider, Form, Input, InputNumber, Modal, Select, Tooltip } from 'antd' import { cloneDeep } from 'lodash' import { ChevronDown, ChevronUp, RotateCcw, SaveIcon } from 'lucide-react' import type { FC } from 'react' @@ -281,7 +281,7 @@ const ModelEditContent: FC = ({ provider, mo onClick={() => { const val = form.getFieldValue('name') navigator.clipboard.writeText((val.id || model.id) as string) - message.success(t('message.copied')) + window.toast.success(t('message.copied')) }} /> } diff --git a/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx b/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx index 207d1fe104..0c91ef3822 100644 --- a/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx +++ b/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx @@ -1,3 +1,4 @@ +import { getToastUtilities } from '@cherrystudio/ui' import { AppLogo } from '@renderer/config/env' import { loggerService } from '@renderer/services/LoggerService' import { IpcChannel } from '@shared/IpcChannel' @@ -33,6 +34,10 @@ const MigrateApp: React.FC = () => { message: 'Ready to start data migration' }) + useEffect(() => { + window.toast = getToastUtilities() + }, []) + useEffect(() => { // Listen for progress updates const handleProgress = (_: any, progressData: MigrationProgress) => { diff --git a/src/renderer/src/windows/dataRefactorTest/TestApp.tsx b/src/renderer/src/windows/dataRefactorTest/TestApp.tsx index f083c7737d..47a84916cb 100644 --- a/src/renderer/src/windows/dataRefactorTest/TestApp.tsx +++ b/src/renderer/src/windows/dataRefactorTest/TestApp.tsx @@ -1,10 +1,11 @@ +import { getToastUtilities } from '@cherrystudio/ui' import { AppLogo } from '@renderer/config/env' import { usePreference } from '@renderer/data/hooks/usePreference' import { loggerService } from '@renderer/services/LoggerService' import { ThemeMode } from '@shared/data/preference/preferenceTypes' import { Button, Card, Col, Divider, Layout, Row, Space, Tabs, Typography } from 'antd' import { Activity, AlertTriangle, Database, FlaskConical, Settings, TestTube, TrendingUp, Zap } from 'lucide-react' -import React from 'react' +import React, { useEffect } from 'react' import styled from 'styled-components' import CacheAdvancedTests from './components/CacheAdvancedTests' @@ -54,6 +55,10 @@ const TestApp: React.FC = () => { return Math.floor(Date.now() / 1000) % 100 } + useEffect(() => { + window.toast = getToastUtilities() + }, []) + const windowNumber = getWindowNumber() // Add theme preference monitoring for UI changes diff --git a/src/renderer/src/windows/mini/MiniWindowApp.tsx b/src/renderer/src/windows/mini/MiniWindowApp.tsx index da9bad816c..5a2f44ec5f 100644 --- a/src/renderer/src/windows/mini/MiniWindowApp.tsx +++ b/src/renderer/src/windows/mini/MiniWindowApp.tsx @@ -1,9 +1,9 @@ import '@renderer/databases' +import { getToastUtilities } from '@cherrystudio/ui' import { usePreference } from '@data/hooks/usePreference' import { ErrorBoundary } from '@renderer/components/ErrorBoundary' import { ToastPortal } from '@renderer/components/ToastPortal' -import { getToastUtilities } from '@renderer/components/TopView/toast' import { HeroUIProvider } from '@renderer/context/HeroUIProvider' import store, { persistor } from '@renderer/store' import { useEffect } from 'react' diff --git a/src/renderer/src/windows/selection/action/entryPoint.tsx b/src/renderer/src/windows/selection/action/entryPoint.tsx index 39e15bfe89..82cac5a5b2 100644 --- a/src/renderer/src/windows/selection/action/entryPoint.tsx +++ b/src/renderer/src/windows/selection/action/entryPoint.tsx @@ -2,11 +2,11 @@ import '@renderer/assets/styles/index.css' import '@renderer/assets/styles/tailwind.css' import '@ant-design/v5-patch-for-react-19' +import { getToastUtilities } from '@cherrystudio/ui' import { preferenceService } from '@data/PreferenceService' import KeyvStorage from '@kangfenmao/keyv-storage' import { loggerService } from '@logger' import { ToastPortal } from '@renderer/components/ToastPortal' -import { getToastUtilities } from '@renderer/components/TopView/toast' import AntdProvider from '@renderer/context/AntdProvider' import { CodeStyleProvider } from '@renderer/context/CodeStyleProvider' import { HeroUIProvider } from '@renderer/context/HeroUIProvider'