refactor(toast): migrate message to toast (#10023)

* feat(toast): add toast utility functions to global interface

Expose error, success, warning, and info toast functions globally for consistent notification handling

* refactor(toast): use ToastPropsColored type to enforce color consistency

Create ToastPropsColored type to explicitly omit color property from ToastProps

* refactor(toast): simplify toast functions using factory pattern

Create a factory function to generate toast functions instead of repeating similar code. This improves maintainability and reduces code duplication.

* fix: replace window.message with window.toast for copy notifications

* refactor(toast): update type definition to use Parameters utility

Use Parameters utility type to derive ToastPropsColored from addToast parameters for better type safety

* feat(types): add RequireSome utility type for making specific properties required

* feat(toast): add loading toast functionality

Add loading toast type to support promises in toast notifications. This enables showing loading states for async operations.

* chore: add claude script to package.json

* build(eslint): add packages dist folder to ignore patterns

* refactor: migrate message to toast

* refactor: update toast import path from @heroui/react to @heroui/toast

* docs(toast): add JSDoc comments for toast functions

* fix(toast): set default timeout for loading toasts

Make loading toasts disappear immediately by default when timeout is not specified

* fix(translate): replace window.message with window.toast for consistency

Use window.toast consistently across the translation page for displaying notifications to maintain a uniform user experience and simplify the codebase.

* refactor: remove deprecated message interface from window

The MessageInstance interface from antd was marked as deprecated and is no longer needed in the window object.

* refactor(toast): consolidate toast utilities into single export

Move all toast-related functions into a single utility export to reduce code duplication and improve maintainability. Update all imports to use the new utility function.

* docs(useOcr): remove redundant comment in ocr function

* docs: update comments from Chinese to English

Update error log messages in CodeToolsPage to use English instead of Chinese for better consistency and maintainability

* feat(toast): add no-drag style and adjust toast placement

add custom CSS class to disable drag on toast elements
move toast placement to top-center and adjust timeout settings
This commit is contained in:
Phantom 2025-09-10 15:16:53 +08:00 committed by GitHub
parent e10042a433
commit cf7584bb63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
142 changed files with 774 additions and 907 deletions

View File

@ -125,7 +125,8 @@ export default defineConfig([
'src/main/integration/nutstore/sso/lib/**',
'src/main/integration/cherryin/index.js',
'src/main/integration/nutstore/sso/lib/**',
'src/renderer/src/ui/**'
'src/renderer/src/ui/**',
'packages/**/dist'
]
}
])

View File

@ -68,7 +68,8 @@
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix && yarn typecheck && yarn check:i18n",
"prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky"
"prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky",
"claude": "dotenv -e .env -- claude"
},
"dependencies": {
"@libsql/client": "0.14.0",

View File

@ -153,6 +153,11 @@
body {
@apply bg-background text-foreground;
}
/* To disable drag title bar on toast. tailwind css doesn't provide such class name. */
.hero-toast {
-webkit-app-region: no-drag;
}
}
:root {

View File

@ -43,8 +43,8 @@ vi.mock('@renderer/context/ThemeProvider', () => ({
// Mock navigator.clipboard
const mockWrite = vi.fn()
// Mock window.message
const mockMessage = {
// Mock window.toast
const mockedToast = {
success: vi.fn(),
error: vi.fn()
}
@ -68,8 +68,8 @@ describe('useImageTools', () => {
writable: true
})
Object.defineProperty(global.window, 'message', {
value: mockMessage,
Object.defineProperty(global.window, 'toast', {
value: mockedToast,
writable: true
})
@ -284,7 +284,7 @@ describe('useImageTools', () => {
expect(mocks.svgToPngBlob).toHaveBeenCalledWith(mockSvg)
expect(mockWrite).toHaveBeenCalled()
expect(mockMessage.success).toHaveBeenCalledWith('message.copy.success')
expect(mockedToast.success).toHaveBeenCalledWith('message.copy.success')
})
it('should download image as PNG and SVG', async () => {
@ -364,13 +364,13 @@ describe('useImageTools', () => {
await act(async () => {
await result.current.copy()
})
expect(mockMessage.error).toHaveBeenCalledWith('message.copy.failed')
expect(mockedToast.error).toHaveBeenCalledWith('message.copy.failed')
// 下载失败
await act(async () => {
await result.current.download('png')
})
expect(mockMessage.error).toHaveBeenCalledWith('message.download.failed')
expect(mockedToast.error).toHaveBeenCalledWith('message.download.failed')
})
})
@ -420,7 +420,7 @@ describe('useImageTools', () => {
await result.current.dialog()
})
expect(mockMessage.error).toHaveBeenCalledWith('message.dialog.failed')
expect(mockedToast.error).toHaveBeenCalledWith('message.dialog.failed')
})
it('should do nothing when no element is found', async () => {

View File

@ -210,10 +210,10 @@ export const useImageTools = (
const blob = await svgToPngBlob(imgElement)
await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })])
window.message.success(t('message.copy.success'))
window.toast.success(t('message.copy.success'))
} catch (error) {
logger.error('Copy failed:', error as Error)
window.message.error(t('message.copy.failed'))
window.toast.error(t('message.copy.failed'))
}
}, [getCleanImgElement, t])
@ -243,7 +243,7 @@ export const useImageTools = (
}
} catch (error) {
logger.error('Download failed:', error as Error)
window.message.error(t('message.download.failed'))
window.toast.error(t('message.download.failed'))
}
},
[getCleanImgElement, prefix, t]
@ -262,7 +262,7 @@ export const useImageTools = (
await ImagePreviewService.show(imgElement, { format: 'svg' })
} catch (error) {
logger.error('Dialog preview failed:', error as Error)
window.message.error(t('message.dialog.failed'))
window.toast.error(t('message.dialog.failed'))
}
}, [getCleanImgElement, t])

View File

@ -50,7 +50,7 @@ const HtmlArtifactsCard: FC<Props> = ({ html, onSave, isStreaming = false }) =>
const handleDownload = async () => {
const fileName = `${getFileNameFromHtmlTitle(title) || 'html-artifact'}.html`
await window.api.file.save(fileName, htmlContent)
window.message.success({ content: t('message.download.success'), key: 'download' })
window.toast.success(t('message.download.success'))
}
return (

View File

@ -62,7 +62,7 @@ const HtmlArtifactsPopup: React.FC<HtmlArtifactsPopupProps> = ({ open, title, ht
await captureScrollableIframeAsBlob(previewFrameRef, async (blob) => {
if (blob) {
await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })])
window.message.success(t('message.copy.success'))
window.toast.success(t('message.copy.success'))
}
})
}

View File

@ -128,7 +128,7 @@ export const CodeBlockView: React.FC<Props> = memo(({ children, language, onSave
const handleCopySource = useCallback(() => {
navigator.clipboard.writeText(children)
window.message.success({ content: t('code_block.copy.success'), key: 'copy-code' })
window.toast.success(t('code_block.copy.success'))
}, [children, t])
const handleDownloadSource = useCallback(() => {

View File

@ -22,10 +22,10 @@ const ContextMenu: React.FC<ContextMenuProps> = ({ children }) => {
navigator.clipboard
.writeText(selectedText)
.then(() => {
window.message.success({ content: t('message.copied'), key: 'copy-message' })
window.toast.success(t('message.copied'))
})
.catch(() => {
window.message.error({ content: t('message.copy.failed'), key: 'copy-message-failed' })
window.toast.error(t('message.copy.failed'))
})
}
}

View File

@ -32,10 +32,10 @@ const CopyButton: FC<CopyButtonProps> = ({
navigator.clipboard
.writeText(textToCopy)
.then(() => {
window.message?.success(t('message.copy.success'))
window.toast?.success(t('message.copy.success'))
})
.catch(() => {
window.message?.error(t('message.copy.failed'))
window.toast?.error(t('message.copy.failed'))
})
}

View File

@ -62,10 +62,10 @@ const ImageViewer: React.FC<ImageViewerProps> = ({ src, style, ...props }) => {
])
}
window.message.success(t('message.copy.success'))
window.toast.success(t('message.copy.success'))
} catch (error) {
logger.error('Failed to copy image:', error as Error)
window.message.error(t('message.copy.failed'))
window.toast.error(t('message.copy.failed'))
}
}
@ -77,7 +77,7 @@ const ImageViewer: React.FC<ImageViewerProps> = ({ src, style, ...props }) => {
icon: <CopyIcon size={size} />,
onClick: () => {
navigator.clipboard.writeText(src)
window.message.success(t('message.copy.success'))
window.toast.success(t('message.copy.success'))
}
},
{

View File

@ -35,13 +35,13 @@ const InputEmbeddingDimension = ({
const handleFetchDimension = useCallback(async () => {
if (!model) {
logger.warn('Failed to get embedding dimensions: no model')
window.message.error(t('knowledge.embedding_model_required'))
window.toast.error(t('knowledge.embedding_model_required'))
return
}
if (!provider) {
logger.warn('Failed to get embedding dimensions: no provider')
window.message.error(t('knowledge.provider_not_found'))
window.toast.error(t('knowledge.provider_not_found'))
return
}
@ -56,7 +56,7 @@ const InputEmbeddingDimension = ({
onChange?.(dimension)
} catch (error) {
logger.error(t('message.error.get_embedding_dimensions'), error as Error)
window.message.error(t('message.error.get_embedding_dimensions') + '\n' + getErrorMessage(error))
window.toast.error(t('message.error.get_embedding_dimensions') + '\n' + getErrorMessage(error))
} finally {
setLoading(false)
}

View File

@ -45,7 +45,7 @@ export function LocalBackupManager({ visible, onClose, localBackupDir, restoreMe
total: files.length
}))
} catch (error: any) {
window.message.error(`${t('settings.data.local.backup.manager.fetch.error')}: ${error.message}`)
window.toast.error(`${t('settings.data.local.backup.manager.fetch.error')}: ${error.message}`)
} finally {
setLoading(false)
}
@ -90,13 +90,13 @@ export function LocalBackupManager({ visible, onClose, localBackupDir, restoreMe
for (const key of selectedRowKeys) {
await window.api.backup.deleteLocalBackupFile(key.toString(), localBackupDir)
}
window.message.success(
window.toast.success(
t('settings.data.local.backup.manager.delete.success.multiple', { count: selectedRowKeys.length })
)
setSelectedRowKeys([])
await fetchBackupFiles()
} catch (error: any) {
window.message.error(`${t('settings.data.local.backup.manager.delete.error')}: ${error.message}`)
window.toast.error(`${t('settings.data.local.backup.manager.delete.error')}: ${error.message}`)
} finally {
setDeleting(false)
}
@ -123,7 +123,7 @@ export function LocalBackupManager({ visible, onClose, localBackupDir, restoreMe
message.success(t('settings.data.local.backup.manager.delete.success.single'))
await fetchBackupFiles()
} catch (error: any) {
window.message.error(`${t('settings.data.local.backup.manager.delete.error')}: ${error.message}`)
window.toast.error(`${t('settings.data.local.backup.manager.delete.error')}: ${error.message}`)
} finally {
setDeleting(false)
}
@ -150,7 +150,7 @@ export function LocalBackupManager({ visible, onClose, localBackupDir, restoreMe
message.success(t('settings.data.local.backup.manager.restore.success'))
onClose() // Close the modal
} catch (error: any) {
window.message.error(`${t('settings.data.local.backup.manager.restore.error')}: ${error.message}`)
window.toast.error(`${t('settings.data.local.backup.manager.restore.error')}: ${error.message}`)
} finally {
setRestoring(false)
}

View File

@ -91,14 +91,14 @@ const MinApp: FC<Props> = ({ app, onClick, size = 60, isLast }) => {
const customApps = JSON.parse(content)
const updatedApps = customApps.filter((customApp: MinAppType) => customApp.id !== app.id)
await window.api.file.writeWithId('custom-minapps.json', JSON.stringify(updatedApps, null, 2))
window.message.success(t('settings.miniapps.custom.remove_success'))
window.toast.success(t('settings.miniapps.custom.remove_success'))
const reloadedApps = [...ORIGIN_DEFAULT_MIN_APPS, ...(await loadCustomMiniApp())]
updateDefaultMinApps(reloadedApps)
updateMinapps(minapps.filter((item) => item.id !== app.id))
updatePinnedMinapps(pinned.filter((item) => item.id !== app.id))
updateDisabledMinapps(disabled.filter((item) => item.id !== app.id))
} catch (error) {
window.message.error(t('settings.miniapps.custom.remove_error'))
window.toast.error(t('settings.miniapps.custom.remove_error'))
logger.error('Failed to remove custom mini app:', error as Error)
}
}

View File

@ -394,10 +394,10 @@ const MinappPopupContainer: React.FC = () => {
navigator.clipboard
.writeText(url)
.then(() => {
window.toast.addToast({ title: 'URL ' + t('message.copy.success') })
window.toast.success('URL ' + t('message.copy.success'))
})
.catch(() => {
window.toast.addToast({ title: 'URL ' + t('message.copy.failed') })
window.toast.error('URL ' + t('message.copy.failed'))
})
}

View File

@ -23,7 +23,7 @@ const OAuthButton: FC<Props> = ({ provider, onSuccess, ...buttonProps }) => {
const handleSuccess = (key: string) => {
if (key.trim()) {
onSuccess?.(key)
window.message.success({ content: t('auth.get_key_success'), key: 'auth-success' })
window.toast.success(t('auth.get_key_success'))
}
}

View File

@ -245,7 +245,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({
content = `---\ntitle: ${state.title}\ncreated: ${state.createdAt}\nsource: ${state.source}\ntags: ${state.tags}\n---\n${markdown}`
}
if (content === '') {
window.message.error(i18n.t('chat.topics.export.obsidian_export_failed'))
window.toast.error(i18n.t('chat.topics.export.obsidian_export_failed'))
return
}
await navigator.clipboard.writeText(content)

View File

@ -302,11 +302,9 @@ async function getModelForCheck(provider: Provider, t: TFunction): Promise<Model
const modelsToCheck = provider.models.filter((model) => !isEmbeddingModel(model) && !isRerankModel(model))
if (isEmpty(modelsToCheck)) {
window.message.error({
key: 'no-models',
style: { marginTop: '3vh' },
duration: 5,
content: t('settings.provider.no_models_for_check')
window.toast.error({
title: t('settings.provider.no_models_for_check'),
timeout: 5000
})
return null
}

View File

@ -61,10 +61,7 @@ const ApiKeyItem: FC<ApiKeyItemProps> = ({
const handleSave = () => {
const result = onUpdate(editValue)
if (!result.isValid) {
window.message.warning({
key: 'api-key-error',
content: result.error
})
window.toast.warning(result.error)
return
}

View File

@ -3,10 +3,15 @@ import { PreprocessProvider, Provider, WebSearchProvider } from '@renderer/types
/**
* API key
*/
export type ApiKeyValidity = {
isValid: boolean
error?: string
}
export type ApiKeyValidity =
| {
isValid: true
error?: never
}
| {
isValid: false
error: string
}
export type ApiProvider = Provider | WebSearchProvider | PreprocessProvider

View File

@ -281,7 +281,7 @@ const PopupContainer: React.FC<Props> = ({ source, title, resolve }) => {
resolve({ success: true, savedCount })
} catch (error) {
logger.error('save failed:', error as Error)
window.message.error(
window.toast.error(
t(isTopicMode ? 'chat.save.topic.knowledge.error.save_failed' : 'chat.save.knowledge.error.save_failed')
)
setLoading(false)

View File

@ -112,10 +112,7 @@ const PopupContainer: React.FC<Props> = ({
}
} catch (error) {
logger.error('Translation failed:', error as Error)
window.message.error({
content: t('translate.error.failed'),
key: 'translate-message'
})
window.toast.error(t('translate.error.failed'))
} finally {
if (isMounted.current) {
setIsTranslating(false)

View File

@ -49,7 +49,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
dispatch(setAvatar(emoji))
setEmojiPickerOpen(false)
} catch (error: any) {
window.message.error(error.message)
window.toast.error(error.message)
}
}
const handleReset = async () => {
@ -58,7 +58,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
dispatch(setAvatar(DefaultAvatar))
setDropdownOpen(false)
} catch (error: any) {
window.message.error(error.message)
window.toast.error(error.message)
}
}
const items = [
@ -83,7 +83,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
dispatch(setAvatar(await ImageStorage.get('avatar')))
setDropdownOpen(false)
} catch (error: any) {
window.message.error(error.message)
window.toast.error(error.message)
}
}}>
{t('settings.general.image_upload')}

View File

@ -37,7 +37,7 @@ export function S3BackupManager({ visible, onClose, s3Config, restoreMethod }: S
const fetchBackupFiles = useCallback(async () => {
if (!endpoint || !region || !bucket || !accessKeyId || !secretAccessKey) {
window.message.error(t('settings.data.s3.manager.config.incomplete'))
window.toast.error(t('settings.data.s3.manager.config.incomplete'))
return
}
@ -61,7 +61,7 @@ export function S3BackupManager({ visible, onClose, s3Config, restoreMethod }: S
total: files.length
}))
} catch (error: any) {
window.message.error(t('settings.data.s3.manager.files.fetch.error', { message: error.message }))
window.toast.error(t('settings.data.s3.manager.files.fetch.error', { message: error.message }))
} finally {
setLoading(false)
}
@ -84,12 +84,12 @@ export function S3BackupManager({ visible, onClose, s3Config, restoreMethod }: S
const handleDeleteSelected = async () => {
if (selectedRowKeys.length === 0) {
window.message.warning(t('settings.data.s3.manager.select.warning'))
window.toast.warning(t('settings.data.s3.manager.select.warning'))
return
}
if (!endpoint || !region || !bucket || !accessKeyId || !secretAccessKey) {
window.message.error(t('settings.data.s3.manager.config.incomplete'))
window.toast.error(t('settings.data.s3.manager.config.incomplete'))
return
}
@ -118,13 +118,11 @@ export function S3BackupManager({ visible, onClose, s3Config, restoreMethod }: S
maxBackups: 0
})
}
window.message.success(
t('settings.data.s3.manager.delete.success.multiple', { count: selectedRowKeys.length })
)
window.toast.success(t('settings.data.s3.manager.delete.success.multiple', { count: selectedRowKeys.length }))
setSelectedRowKeys([])
await fetchBackupFiles()
} catch (error: any) {
window.message.error(t('settings.data.s3.manager.delete.error', { message: error.message }))
window.toast.error(t('settings.data.s3.manager.delete.error', { message: error.message }))
} finally {
setDeleting(false)
}
@ -134,7 +132,7 @@ export function S3BackupManager({ visible, onClose, s3Config, restoreMethod }: S
const handleDeleteSingle = async (fileName: string) => {
if (!endpoint || !region || !bucket || !accessKeyId || !secretAccessKey) {
window.message.error(t('settings.data.s3.manager.config.incomplete'))
window.toast.error(t('settings.data.s3.manager.config.incomplete'))
return
}
@ -160,10 +158,10 @@ export function S3BackupManager({ visible, onClose, s3Config, restoreMethod }: S
syncInterval: 0,
maxBackups: 0
})
window.message.success(t('settings.data.s3.manager.delete.success.single'))
window.toast.success(t('settings.data.s3.manager.delete.success.single'))
await fetchBackupFiles()
} catch (error: any) {
window.message.error(t('settings.data.s3.manager.delete.error', { message: error.message }))
window.toast.error(t('settings.data.s3.manager.delete.error', { message: error.message }))
} finally {
setDeleting(false)
}
@ -173,7 +171,7 @@ export function S3BackupManager({ visible, onClose, s3Config, restoreMethod }: S
const handleRestore = async (fileName: string) => {
if (!endpoint || !region || !bucket || !accessKeyId || !secretAccessKey) {
window.message.error(t('settings.data.s3.manager.config.incomplete'))
window.toast.error(t('settings.data.s3.manager.config.incomplete'))
return
}
@ -188,10 +186,10 @@ export function S3BackupManager({ visible, onClose, s3Config, restoreMethod }: S
setRestoring(true)
try {
await (restoreMethod || restoreFromS3)(fileName)
window.message.success(t('settings.data.s3.restore.success'))
window.toast.success(t('settings.data.s3.restore.success'))
onClose() // 关闭模态框
} catch (error: any) {
window.message.error(t('settings.data.s3.restore.error', { message: error.message }))
window.toast.error(t('settings.data.s3.restore.error', { message: error.message }))
} finally {
setRestoring(false)
}

View File

@ -114,7 +114,7 @@ export function useS3RestoreModal({
const showRestoreModal = useCallback(async () => {
if (!endpoint || !region || !bucket || !accessKeyId || !secretAccessKey) {
window.message.error({ content: t('settings.data.s3.manager.config.incomplete'), key: 's3-error' })
window.toast.error(t('settings.data.s3.manager.config.incomplete'))
return
}
@ -135,10 +135,7 @@ export function useS3RestoreModal({
})
setBackupFiles(files)
} catch (error: any) {
window.message.error({
content: t('settings.data.s3.manager.files.fetch.error', { message: error.message }),
key: 'list-files-error'
})
window.toast.error(t('settings.data.s3.manager.files.fetch.error', { message: error.message }))
} finally {
setLoadingFiles(false)
}
@ -146,12 +143,9 @@ export function useS3RestoreModal({
const handleRestore = useCallback(async () => {
if (!selectedFile || !endpoint || !region || !bucket || !accessKeyId || !secretAccessKey) {
window.message.error({
content: !selectedFile
? t('settings.data.s3.restore.file.required')
: t('settings.data.s3.restore.config.incomplete'),
key: 'restore-error'
})
window.toast.error(
!selectedFile ? t('settings.data.s3.restore.file.required') : t('settings.data.s3.restore.config.incomplete')
)
return
}
@ -177,13 +171,10 @@ export function useS3RestoreModal({
maxBackups: 0,
skipBackupFile: false
})
window.message.success({ content: t('message.restore.success'), key: 's3-restore' })
window.toast.success(t('message.restore.success'))
setIsRestoreModalVisible(false)
} catch (error: any) {
window.message.error({
content: t('settings.data.s3.restore.error', { message: error.message }),
key: 'restore-error'
})
window.toast.error(t('settings.data.s3.restore.error', { message: error.message }))
} finally {
setRestoring(false)
}

View File

@ -14,12 +14,17 @@ export const ToastPortal = () => {
return createPortal(
<ToastProvider
placement="bottom-center"
placement="top-center"
regionProps={{
className: 'z-[1001]'
}}
toastOffset={20}
toastProps={{
timeout: 3000
timeout: 3000,
classNames: {
// This setting causes the 'hero-toast' class to be applied twice to the toast element. This is weird and I don't know why, but it works.
base: 'hero-toast'
}
}}
/>,
document.body

View File

@ -1,12 +1,12 @@
// import { loggerService } from '@logger'
import { addToast, closeAll, closeToast, getToastQueue, isToastClosing } from '@heroui/toast'
import TopViewMinappContainer from '@renderer/components/MinApp/TopViewMinappContainer'
import { useAppInit } from '@renderer/hooks/useAppInit'
import { useShortcuts } from '@renderer/hooks/useShortcuts'
import { message, Modal } from 'antd'
import { Modal } from 'antd'
import React, { PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react'
import { Box } from '../Layout'
import { getToastUtilities } from './toast'
let onPop = () => {}
let onShow = ({ element, id }: { element: React.FC | React.ReactNode; id: string }) => {
@ -34,7 +34,6 @@ const TopViewContainer: React.FC<Props> = ({ children }) => {
const elementsRef = useRef<ElementItem[]>([])
elementsRef.current = elements
const [messageApi, messageContextHolder] = message.useMessage()
const [modal, modalContextHolder] = Modal.useModal()
const { shortcuts } = useShortcuts()
const enableQuitFullScreen = shortcuts.find((item) => item.key === 'exit_fullscreen')?.enabled
@ -42,16 +41,9 @@ const TopViewContainer: React.FC<Props> = ({ children }) => {
useAppInit()
useEffect(() => {
window.message = messageApi
window.modal = modal
window.toast = {
getToastQueue: getToastQueue,
addToast: addToast,
closeToast: closeToast,
closeAll: closeAll,
isToastClosing: isToastClosing
}
}, [messageApi, modal])
window.toast = getToastUtilities()
}, [modal])
onPop = () => {
const views = [...elementsRef.current]
@ -104,7 +96,6 @@ const TopViewContainer: React.FC<Props> = ({ children }) => {
return (
<>
{children}
{messageContextHolder}
{modalContextHolder}
<TopViewMinappContainer />
{elements.map(({ element: Element, id }) => (

View File

@ -0,0 +1,72 @@
import { addToast, closeAll, closeToast, getToastQueue, isToastClosing } from '@heroui/toast'
import { RequireSome } from '@renderer/types'
type AddToastProps = Parameters<typeof addToast>[0]
type ToastPropsColored = Omit<AddToastProps, 'color'>
const createToast = (color: 'danger' | 'success' | 'warning' | 'default') => {
return (arg: ToastPropsColored | string): string | null => {
if (typeof arg === 'string') {
return addToast({ color, title: arg })
} else {
return addToast({ color, ...arg })
}
}
}
// syntatic sugar, oh yeah
/**
* Display an error toast notification with red color
* @param arg - Toast content (string) or toast options object
* @returns Toast ID or null
*/
export 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')
/**
* 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')
/**
* 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')
/**
* 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<AddToastProps, 'promise'>) => {
// Disappear immediately by default
if (args.timeout === undefined) {
args.timeout = 1
}
return addToast(args)
}
export const getToastUtilities = () =>
({
getToastQueue,
addToast,
closeToast,
closeAll,
isToastClosing,
error,
success,
warning,
info,
loading
}) as const

View File

@ -52,10 +52,7 @@ const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style, isLoa
onTranslated(translatedText)
} catch (error) {
logger.error('Translation failed:', error as Error)
window.message.error({
content: t('translate.error.failed'),
key: 'translate-message'
})
window.toast.error(t('translate.error.failed'))
} finally {
setIsTranslating(false)
}

View File

@ -60,7 +60,7 @@ export function WebdavBackupManager({
const fetchBackupFiles = useCallback(async () => {
if (!webdavHost) {
window.message.error(t('message.error.invalid.webdav'))
window.toast.error(t('message.error.invalid.webdav'))
return
}
@ -78,7 +78,7 @@ export function WebdavBackupManager({
total: files.length
}))
} catch (error: any) {
window.message.error(`${t('settings.data.webdav.backup.manager.fetch.error')}: ${error.message}`)
window.toast.error(`${t('settings.data.webdav.backup.manager.fetch.error')}: ${error.message}`)
} finally {
setLoading(false)
}
@ -106,7 +106,7 @@ export function WebdavBackupManager({
}
if (!webdavHost) {
window.message.error(t('message.error.invalid.webdav'))
window.toast.error(t('message.error.invalid.webdav'))
return
}
@ -129,13 +129,13 @@ export function WebdavBackupManager({
webdavPath
} as WebdavConfig)
}
window.message.success(
window.toast.success(
t('settings.data.webdav.backup.manager.delete.success.multiple', { count: selectedRowKeys.length })
)
setSelectedRowKeys([])
await fetchBackupFiles()
} catch (error: any) {
window.message.error(`${t('settings.data.webdav.backup.manager.delete.error')}: ${error.message}`)
window.toast.error(`${t('settings.data.webdav.backup.manager.delete.error')}: ${error.message}`)
} finally {
setDeleting(false)
}
@ -145,7 +145,7 @@ export function WebdavBackupManager({
const handleDeleteSingle = async (fileName: string) => {
if (!webdavHost) {
window.message.error(t('message.error.invalid.webdav'))
window.toast.error(t('message.error.invalid.webdav'))
return
}
@ -165,10 +165,10 @@ export function WebdavBackupManager({
webdavPass,
webdavPath
} as WebdavConfig)
window.message.success(t('settings.data.webdav.backup.manager.delete.success.single'))
window.toast.success(t('settings.data.webdav.backup.manager.delete.success.single'))
await fetchBackupFiles()
} catch (error: any) {
window.message.error(`${t('settings.data.webdav.backup.manager.delete.error')}: ${error.message}`)
window.toast.error(`${t('settings.data.webdav.backup.manager.delete.error')}: ${error.message}`)
} finally {
setDeleting(false)
}
@ -178,7 +178,7 @@ export function WebdavBackupManager({
const handleRestore = async (fileName: string) => {
if (!webdavHost) {
window.message.error(customLabels?.invalidConfigMessage || t('message.error.invalid.webdav'))
window.toast.error(customLabels?.invalidConfigMessage || t('message.error.invalid.webdav'))
return
}
@ -193,10 +193,10 @@ export function WebdavBackupManager({
setRestoring(true)
try {
await (restoreMethod || restoreFromWebdav)(fileName)
window.message.success(t('settings.data.webdav.backup.manager.restore.success'))
window.toast.success(t('settings.data.webdav.backup.manager.restore.success'))
onClose() // 关闭模态框
} catch (error: any) {
window.message.error(`${t('settings.data.webdav.backup.manager.restore.error')}: ${error.message}`)
window.toast.error(`${t('settings.data.webdav.backup.manager.restore.error')}: ${error.message}`)
} finally {
setRestoring(false)
}

View File

@ -10,8 +10,8 @@ const mockClipboard = {
writeText: mockWriteText
}
// Mock window.message
const mockMessage = {
// Mock window.toast
const mockedToast = {
success: vi.fn(),
error: vi.fn()
}
@ -33,7 +33,7 @@ describe('CopyButton', () => {
beforeEach(() => {
// Setup mocks
Object.assign(navigator, { clipboard: mockClipboard })
Object.assign(window, { message: mockMessage })
Object.assign(window, { toast: mockedToast })
// Clear all mocks
vi.clearAllMocks()
@ -103,8 +103,8 @@ describe('CopyButton', () => {
const clickableElement = copyIcon?.parentElement
await userEvent.click(clickableElement!)
expect(mockMessage.success).toHaveBeenCalledWith('复制成功')
expect(mockMessage.error).not.toHaveBeenCalled()
expect(mockedToast.success).toHaveBeenCalledWith('复制成功')
expect(mockedToast.error).not.toHaveBeenCalled()
})
it('should show error message when copy fails', async () => {
@ -116,8 +116,8 @@ describe('CopyButton', () => {
const clickableElement = copyIcon?.parentElement
await userEvent.click(clickableElement!)
expect(mockMessage.error).toHaveBeenCalledWith('复制失败')
expect(mockMessage.success).not.toHaveBeenCalled()
expect(mockedToast.error).toHaveBeenCalledWith('复制失败')
expect(mockedToast.success).not.toHaveBeenCalled()
})
it('should apply custom size to icon and label', () => {

View File

@ -98,9 +98,9 @@ vi.mock('@renderer/components/Icons', () => ({
)
}))
// Mock window.message
// Mock window.toast
Object.assign(window, {
message: {
toast: {
error: vi.fn(),
success: vi.fn()
}
@ -195,7 +195,7 @@ describe('InputEmbeddingDimension', () => {
// We can skip this check to be explicit.
await userEvent.click(refreshButton, { pointerEventsCheck: 0 })
expect(window.message.error).not.toHaveBeenCalled()
expect(window.toast.error).not.toHaveBeenCalled()
})
it('should show error when API call fails', async () => {
@ -208,7 +208,7 @@ describe('InputEmbeddingDimension', () => {
await user.click(refreshButton)
await waitFor(() => {
expect(window.message.error).toHaveBeenCalledWith('获取嵌入维度失败\nAPI Error')
expect(window.toast.error).toHaveBeenCalledWith('获取嵌入维度失败\nAPI Error')
})
})

View File

@ -2,10 +2,11 @@
import { addToast, closeAll, closeToast, getToastQueue, isToastClosing } from '@heroui/toast'
import type KeyvStorage from '@kangfenmao/keyv-storage'
import { MessageInstance } from 'antd/es/message/interface'
import { HookAPI } from 'antd/es/modal/useModal'
import { NavigateFunction } from 'react-router-dom'
import { error, info, loading, success, warning } from './components/TopView/toast'
interface ImportMetaEnv {
VITE_RENDERER_INTEGRATED_MODEL: string
}
@ -17,10 +18,6 @@ interface ImportMeta {
declare global {
interface Window {
root: HTMLElement
/**
* @deprecated
*/
message: MessageInstance
modal: HookAPI
keyv: KeyvStorage
store: any
@ -31,6 +28,11 @@ declare global {
closeToast: typeof closeToast
closeAll: typeof closeAll
isToastClosing: typeof isToastClosing
error: typeof error
success: typeof success
warning: typeof warning
info: typeof info
loading: typeof loading
}
}
}

View File

@ -59,7 +59,7 @@ export function useAssistants() {
dispatch(insertAssistant({ index: index + 1, assistant: _assistant }))
} catch (e) {
logger.error('Failed to insert assistant', e as Error)
window.message.error(t('message.error.copy'))
window.toast.error(t('message.error.copy'))
}
}
return _assistant

View File

@ -97,7 +97,7 @@ export const useChatContext = (activeTopic: Topic) => {
const handleMultiSelectAction = useCallback(
async (actionType: string, messageIds: string[]) => {
if (messageIds.length === 0) {
window.message.warning(t('chat.multiple.select.empty'))
window.toast.warning(t('chat.multiple.select.empty'))
return
}
@ -115,11 +115,11 @@ export const useChatContext = (activeTopic: Topic) => {
onOk: async () => {
try {
await Promise.all(messageIds.map((messageId) => deleteMessage(messageId)))
window.message.success(t('message.delete.success'))
window.toast.success(t('message.delete.success'))
handleToggleMultiSelectMode(false)
} catch (error) {
logger.error('Failed to delete messages:', error as Error)
window.message.error(t('message.delete.failed'))
window.toast.error(t('message.delete.failed'))
}
}
})
@ -142,7 +142,7 @@ export const useChatContext = (activeTopic: Topic) => {
.join('\n\n---\n\n')
const fileName = `chat_export_${new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-')}.md`
await window.api.file.save(fileName, contentToSave)
window.message.success({ content: t('message.save.success.title'), key: 'save-messages' })
window.toast.success(t('message.save.success.title'))
handleToggleMultiSelectMode(false)
} else {
// 这个分支不会进入 因为 messageIds.length === 0 已提前返回,需要简化掉
@ -165,7 +165,7 @@ export const useChatContext = (activeTopic: Topic) => {
})
.join('\n\n---\n\n')
navigator.clipboard.writeText(contentToCopy)
window.message.success({ content: t('message.copied'), key: 'copy-messages' })
window.toast.success(t('message.copied'))
handleToggleMultiSelectMode(false)
} else {
// 和上面一样

View File

@ -68,12 +68,11 @@ export const useFiles = (props?: Props) => {
}
if (supportedFiles.length !== _files.length) {
window.message.info({
key: 'file_not_supported',
content: t('chat.input.file_not_supported_count', {
window.toast.info(
t('chat.input.file_not_supported_count', {
count: _files.length - supportedFiles.length
})
})
)
}
return supportedFiles
} else {

View File

@ -9,10 +9,9 @@ export function useFullScreenNotice() {
useEffect(() => {
const cleanup = window.electron.ipcRenderer.on(IpcChannel.FullscreenStatusChanged, (_, isFullscreen) => {
if (isWin && isFullscreen) {
window.message.info({
content: t('common.fullscreen'),
duration: 3,
key: 'fullscreen-notification'
window.toast.info({
title: t('common.fullscreen'),
timeout: 3000
})
}
})

View File

@ -126,7 +126,7 @@ export const useKnowledgeBaseForm = (base?: KnowledgeBase) => {
if (!value || (newBase.chunkSize && newBase.chunkSize > value)) {
setNewBase((prev) => ({ ...prev, chunkOverlap: value || undefined }))
} else {
window.message.error(t('message.error.chunk_overlap_too_large'))
window.toast.error(t('message.error.chunk_overlap_too_large'))
}
},
[newBase.chunkSize, t]

View File

@ -1,7 +1,6 @@
import { loggerService } from '@logger'
import * as OcrService from '@renderer/services/ocr/OcrService'
import { ImageFileMetadata, isImageFileMetadata, SupportedOcrFile } from '@renderer/types'
import { uuid } from '@renderer/utils'
import { formatErrorMessage } from '@renderer/utils/error'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
@ -35,24 +34,24 @@ export const useOcr = () => {
* @throws OCR失败时抛出错误
*/
const ocr = async (file: SupportedOcrFile) => {
const key = uuid()
window.message.loading({ content: t('ocr.processing'), key, duration: 0 })
// await to keep show loading message
const _ocr = async () => {
try {
if (isImageFileMetadata(file)) {
return await ocrImage(file)
return ocrImage(file)
} else {
// @ts-expect-error all types should be covered
throw new Error(t('ocr.file.not_supported', { type: file.type }))
}
} catch (e) {
logger.error('Failed to ocr.', e as Error)
window.message.error(t('ocr.error.unknown') + ': ' + formatErrorMessage(e))
window.toast.error(t('ocr.error.unknown') + ': ' + formatErrorMessage(e))
throw e
} finally {
window.message.destroy(key)
}
}
const promise = _ocr()
window.toast.loading({ title: t('ocr.processing'), promise })
return promise
}
return {
ocr

View File

@ -39,7 +39,7 @@ export const useOcrProviders = () => {
if (providers.some((p) => p.id === provider.id)) {
const msg = `Provider with id ${provider.id} already exists`
logger.error(msg)
window.message.error(t('ocr.error.provider.existing'))
window.toast.error(t('ocr.error.provider.existing'))
throw new Error(msg)
}
dispatch(addOcrProvider(provider))
@ -56,7 +56,7 @@ export const useOcrProviders = () => {
if (isBuiltinOcrProviderId(id)) {
const msg = `Cannot remove builtin provider ${id}`
logger.error(msg)
window.message.error(t('ocr.error.provider.cannot_remove_builtin'))
window.toast.error(t('ocr.error.provider.cannot_remove_builtin'))
throw new Error(msg)
}
@ -123,19 +123,19 @@ export const useOcrProvider = (id: string) => {
// safely fallback
if (!provider) {
logger.error(`Ocr Provider ${id} not found`)
window.message.error(t('ocr.error.provider.not_found'))
window.toast.error(t('ocr.error.provider.not_found'))
if (isBuiltinOcrProviderId(id)) {
try {
addProvider(BUILTIN_OCR_PROVIDERS_MAP[id])
} catch (e) {
logger.warn(`Add ${BUILTIN_OCR_PROVIDERS_MAP[id].name} failed. Just use temp provider from config.`)
window.message.warning(t('ocr.warning.provider.fallback', { name: BUILTIN_OCR_PROVIDERS_MAP[id].name }))
window.toast.warning(t('ocr.warning.provider.fallback', { name: BUILTIN_OCR_PROVIDERS_MAP[id].name }))
} finally {
provider = BUILTIN_OCR_PROVIDERS_MAP[id]
}
} else {
logger.warn(`Fallback to tesseract`)
window.message.warning(t('ocr.warning.provider.fallback', { name: 'Tesseract' }))
window.toast.warning(t('ocr.warning.provider.fallback', { name: 'Tesseract' }))
provider = BUILTIN_OCR_PROVIDERS_MAP.tesseract
}
}

View File

@ -9,7 +9,7 @@ export function modelGenerating() {
const generating = store.getState().runtime.generating
if (generating) {
window.message.warning({ content: i18n.t('message.switch.disabled'), key: 'model-generating' })
window.toast.warning(i18n.t('message.switch.disabled'))
return Promise.reject()
}

View File

@ -21,7 +21,7 @@ export default function useUpdateHandler() {
ipcRenderer.on(IpcChannel.UpdateNotAvailable, () => {
dispatch(setUpdateState({ checking: false }))
if (window.location.hash.includes('settings/about')) {
window.message.success(t('settings.about.updateNotAvailable'))
window.toast.success(t('settings.about.updateNotAvailable'))
}
}),
ipcRenderer.on(IpcChannel.UpdateAvailable, (_, releaseInfo: UpdateInfo) => {

View File

@ -170,10 +170,7 @@ const AgentsPage: FC = () => {
try {
await ImportAgentPopup.show()
} catch (error) {
window.message.error({
content: error instanceof Error ? error.message : t('message.agents.import.error'),
key: 'agents-import-error'
})
window.toast.error(error instanceof Error ? error.message : t('message.agents.import.error'))
}
}

View File

@ -74,19 +74,13 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
addAgent(newAgent)
}
window.message.success({
content: t('message.agents.imported'),
key: 'agents-imported'
})
window.toast.success(t('message.agents.imported'))
setTimeoutTimer('onFinish', () => EventEmitter.emit(EVENT_NAMES.SHOW_ASSISTANTS), 0)
setOpen(false)
resolve(agents)
} catch (error) {
window.message.error({
content: error instanceof Error ? error.message : t('message.agents.import.error'),
key: 'agents-import-error'
})
window.toast.error(error instanceof Error ? error.message : t('message.agents.import.error'))
} finally {
setLoading(false)
}

View File

@ -109,7 +109,7 @@ const CodeToolsPage: FC = () => {
const bunExists = await window.api.isBinaryExist('bun')
dispatch(setIsBunInstalled(bunExists))
} catch (error) {
logger.error('检查 bun 安装状态失败:', error as Error)
logger.error('Failed to check bun installation status:', error as Error)
dispatch(setIsBunInstalled(false))
}
}, [dispatch])
@ -120,16 +120,10 @@ const CodeToolsPage: FC = () => {
setIsInstallingBun(true)
await window.api.installBunBinary()
dispatch(setIsBunInstalled(true))
window.message.success({
content: t('settings.mcp.installSuccess'),
key: 'bun-install-message'
})
window.toast.success(t('settings.mcp.installSuccess'))
} catch (error: any) {
logger.error('安装 bun 失败:', error as Error)
window.message.error({
content: `${t('settings.mcp.installError')}: ${error.message}`,
key: 'bun-install-message'
})
logger.error('Failed to install bun:', error as Error)
window.toast.error(`${t('settings.mcp.installError')}: ${error.message}`)
} finally {
setIsInstallingBun(false)
// 重新检查安装状态
@ -180,7 +174,7 @@ const CodeToolsPage: FC = () => {
// 执行启动操作
const executeLaunch = async (env: Record<string, string>) => {
window.api.codeTools.run(selectedCliTool, selectedModel?.id!, currentDirectory, env, { autoUpdateToLatest })
window.message.success({ content: t('code.launch.success'), key: 'code-launch-message' })
window.toast.success(t('code.launch.success'))
}
// 处理启动
@ -188,7 +182,7 @@ const CodeToolsPage: FC = () => {
const validation = validateLaunch()
if (!validation.isValid) {
window.message.warning({ content: validation.message, key: 'code-launch-message' })
window.toast.warning(validation.message || t('code.launch.validation_error'))
return
}
@ -197,14 +191,14 @@ const CodeToolsPage: FC = () => {
try {
const env = await prepareLaunchEnvironment()
if (!env) {
window.message.error({ content: t('code.model_required'), key: 'code-launch-message' })
window.toast.error(t('code.model_required'))
return
}
await executeLaunch(env)
} catch (error) {
logger.error('启动失败:', error as Error)
window.message.error({ content: t('code.launch.error'), key: 'code-launch-message' })
window.toast.error(t('code.launch.error'))
} finally {
setIsLaunching(false)
}

View File

@ -55,7 +55,7 @@ const HistoryPage: FC = () => {
// topic 不包含 messages用到的时候才会获取
const onTopicClick = (topic: Topic | null | undefined) => {
if (!topic) {
window.message.error(t('history.error.topic_not_found'))
window.toast.error(t('history.error.topic_not_found'))
return
}
setStack((prev) => [...prev, 'topic'])

View File

@ -61,12 +61,11 @@ const AttachmentButton: FC<Props> = ({
}
if (supportedFiles.length !== _files.length) {
window.message.info({
key: 'file_not_supported',
content: t('chat.input.file_not_supported_count', {
window.toast.info(
t('chat.input.file_not_supported_count', {
count: _files.length - supportedFiles.length
})
})
)
}
}
}, [extensions, files, selecting, setFiles, t])

View File

@ -601,12 +601,11 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
const supportedFiles = await filterSupportedFiles(droppedFiles, supportedExts)
supportedFiles.length > 0 && setFiles((prevFiles) => [...prevFiles, ...supportedFiles])
if (droppedFiles.length > 0 && supportedFiles.length !== droppedFiles.length) {
window.message.info({
key: 'file_not_supported',
content: t('chat.input.file_not_supported_count', {
window.toast.info(
t('chat.input.file_not_supported_count', {
count: droppedFiles.length - supportedFiles.length
})
})
)
}
}
},

View File

@ -151,7 +151,7 @@ const MCPToolsButton: FC<Props> = ({ ref, setInputValue, resizeTextArea, Toolbar
if (update.mcpServers.length > 0 && isGeminiModel(model) && isToolUseModeFunction(assistant)) {
const provider = getProviderByModel(model)
if (isSupportUrlContextProvider(provider) && assistant.enableUrlContext) {
window.message.warning(t('chat.mcp.warning.url_context'))
window.toast.warning(t('chat.mcp.warning.url_context'))
update.enableUrlContext = false
}
if (
@ -160,7 +160,7 @@ const MCPToolsButton: FC<Props> = ({ ref, setInputValue, resizeTextArea, Toolbar
isGeminiWebSearchProvider(provider) &&
assistant.enableWebSearch
) {
window.message.warning(t('chat.mcp.warning.gemini_web_search'))
window.toast.warning(t('chat.mcp.warning.gemini_web_search'))
update.enableWebSearch = false
}
}

View File

@ -36,7 +36,7 @@ const UrlContextButton: FC<Props> = ({ assistant, ToolbarButton }) => {
isToolUseModeFunction(assistant)
) {
update.enableUrlContext = false
window.message.warning(t('chat.mcp.warning.url_context'))
window.toast.warning(t('chat.mcp.warning.url_context'))
} else {
update.enableUrlContext = urlContentNewState
}

View File

@ -101,7 +101,7 @@ const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
const provider = getProviderByModel(model)
if (!model) {
logger.error('Model does not exist.')
window.message.error(t('error.model.not_exists'))
window.toast.error(t('error.model.not_exists'))
return
}
if (
@ -113,7 +113,7 @@ const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
assistant.mcpServers.length > 0
) {
update.enableWebSearch = false
window.message.warning(t('chat.mcp.warning.gemini_web_search'))
window.toast.warning(t('chat.mcp.warning.gemini_web_search'))
}
setTimeoutTimer('updateSelectedWebSearchBuiltin', () => updateAssistant(update), 200)
}, [assistant, setTimeoutTimer, t, updateAssistant])

View File

@ -32,7 +32,7 @@ const Table: React.FC<Props> = ({ children, node, blockId }) => {
setCopied(true)
})
.catch((error) => {
window.message?.error({ content: `${t('message.copy.failed')}: ${error}`, key: 'copy-table-error' })
window.toast?.error(`${t('message.copy.failed')}: ${error}`)
})
}, [blockId, node?.position, setCopied, t])

View File

@ -193,21 +193,21 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
const assistantMessages = findAssistantMessages()
if (userMessages.length === 0 && assistantMessages.length === 0) {
// window.message.info({ content: t('chat.navigation.last'), key: 'navigation-info' })
// window.toast.info(t('chat.navigation.last'))
return scrollToBottom()
}
const visibleIndex = getCurrentVisibleIndex('down')
if (visibleIndex === -1) {
// window.message.info({ content: t('chat.navigation.last'), key: 'navigation-info' })
// window.toast.info(t('chat.navigation.last'))
return scrollToBottom()
}
const targetIndex = visibleIndex - 1
if (targetIndex < 0) {
// window.message.info({ content: t('chat.navigation.last'), key: 'navigation-info' })
// window.toast.info(t('chat.navigation.last'))
return scrollToBottom()
}
@ -219,21 +219,21 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
const userMessages = findUserMessages()
const assistantMessages = findAssistantMessages()
if (userMessages.length === 0 && assistantMessages.length === 0) {
// window.message.info({ content: t('chat.navigation.first'), key: 'navigation-info' })
// window.toast.info(t('chat.navigation.first'))
return scrollToTop()
}
const visibleIndex = getCurrentVisibleIndex('up')
if (visibleIndex === -1) {
// window.message.info({ content: t('chat.navigation.first'), key: 'navigation-info' })
// window.toast.info(t('chat.navigation.first'))
return scrollToTop()
}
const targetIndex = visibleIndex + 1
if (targetIndex >= userMessages.length) {
// window.message.info({ content: t('chat.navigation.first'), key: 'navigation-info' })
// window.toast.info(t('chat.navigation.first'))
return scrollToTop()
}

View File

@ -126,7 +126,7 @@ const CopyButton: React.FC<{ content: string }> = ({ content }) => {
.writeText(content)
.then(() => {
setCopied(true)
window.message.success(t('common.copied'))
window.toast.success(t('common.copied'))
})
.catch(() => {
message.error(t('message.copy.failed'))

View File

@ -183,10 +183,7 @@ const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onC
// 如果有文件,但都不支持
if (files.length > 0 && supportedFiles === 0) {
window.message.info({
key: 'file_not_supported',
content: t('chat.input.file_not_supported')
})
window.toast.info(t('chat.input.file_not_supported'))
}
}
}

View File

@ -32,10 +32,10 @@ const MessageImage: FC<Props> = ({ block }) => {
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.message.success(t('message.download.success'))
window.toast.success(t('message.download.success'))
} catch (error) {
logger.error('下载图片失败:', error as Error)
window.message.error(t('message.download.failed'))
window.toast.error(t('message.download.failed'))
}
}
@ -84,10 +84,10 @@ const MessageImage: FC<Props> = ({ block }) => {
break
}
window.message.success(t('message.copy.success'))
window.toast.success(t('message.copy.success'))
} catch (error) {
logger.error('复制图片失败:', error as Error)
window.message.error(t('message.copy.failed'))
window.toast.error(t('message.copy.failed'))
}
}

View File

@ -151,7 +151,7 @@ const MessageMenubar: FC<Props> = (props) => {
navigator.clipboard.writeText(removeTrailingDoubleSpaces(contentToCopy.trimStart()))
window.message.success({ content: t('message.copied'), key: 'copy-message' })
window.toast.success(t('message.copied'))
setCopied(true)
},
[message, setCopied, t] // message is needed for message.id and as a fallback. t is for translation.
@ -159,7 +159,7 @@ const MessageMenubar: FC<Props> = (props) => {
const onNewBranch = useCallback(async () => {
EventEmitter.emit(EVENT_NAMES.NEW_BRANCH, index)
window.message.success({ content: t('chat.message.new.branch.created'), key: 'new-branch' })
window.toast.success(t('chat.message.new.branch.created'))
}, [index, t])
const handleResendUserMessage = useCallback(
@ -186,7 +186,7 @@ const MessageMenubar: FC<Props> = (props) => {
try {
await translateText(mainTextContent, language, translationUpdater)
} catch (error) {
window.message.error({ content: t('translate.error.failed'), key: 'translate-message' })
window.toast.error(t('translate.error.failed'))
// 理应只有一个
const translationBlocks = findTranslationBlocksById(message.id)
logger.silly(`there are ${translationBlocks.length} translation blocks`)
@ -568,9 +568,9 @@ const MessageMenubar: FC<Props> = (props) => {
if (translationContent) {
navigator.clipboard.writeText(translationContent)
window.message.success({ content: t('translate.copied'), key: 'translate-copy' })
window.toast.success(t('translate.copied'))
} else {
window.message.warning({ content: t('translate.empty'), key: 'translate-copy' })
window.toast.warning(t('translate.empty'))
}
}
}
@ -588,7 +588,7 @@ const MessageMenubar: FC<Props> = (props) => {
translationBlocks.forEach((blockId) => {
if (blockId) removeMessageBlock(message.id, blockId)
})
window.message.success({ content: t('translate.closed'), key: 'translate-close' })
window.toast.success(t('translate.closed'))
}
}
}

View File

@ -194,7 +194,7 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic, o
// You might want to remove the added topic if cloning fails
// removeTopic(newTopic.id); // Assuming you have a removeTopic function
logger.error(`[NEW_BRANCH] Failed to create topic branch for topic ${newTopic.id}`)
window.message.error(t('message.branch.error')) // Example error message
window.toast.error(t('message.branch.error')) // Example error message
}
}),
EventEmitter.on(
@ -217,19 +217,19 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic, o
dispatch(updateOneBlock({ id: msgBlockId, changes: { content: updatedRaw } }))
await dispatch(updateMessageAndBlocksThunk(topic.id, null, [updatedBlock]))
window.message.success({ content: t('code_block.edit.save.success'), key: 'save-code' })
window.toast.success(t('code_block.edit.save.success'))
} catch (error) {
logger.error(
`Failed to save code block ${codeBlockId} content to message block ${msgBlockId}:`,
error as Error
)
window.message.error({ content: t('code_block.edit.save.failed.label'), key: 'save-code-failed' })
window.toast.error(t('code_block.edit.save.failed.label'))
}
} else {
logger.error(
`Failed to save code block ${codeBlockId} content to message block ${msgBlockId}: no such message block or the block doesn't have a content field`
)
window.message.error({ content: t('code_block.edit.save.failed.label'), key: 'save-code-failed' })
window.toast.error(t('code_block.edit.save.failed.label'))
}
}
)
@ -270,7 +270,7 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic, o
const lastMessage = last(messages)
if (lastMessage) {
navigator.clipboard.writeText(getMainTextContent(lastMessage))
window.message.success(t('message.copy.success'))
window.toast.success(t('message.copy.success'))
}
})

View File

@ -176,7 +176,7 @@ const MessageMcpTool: FC<Props> = ({ block }) => {
try {
const success = await window.api.mcp.abortTool(toolResponse.id)
if (success) {
window.message.success({ content: t('message.tools.aborted'), key: 'abort-tool' })
window.toast.success(t('message.tools.aborted'))
} else {
message.error({ content: t('message.tools.abort_failed'), key: 'abort-tool' })
}
@ -215,10 +215,7 @@ const MessageMcpTool: FC<Props> = ({ block }) => {
setIsConfirmed(true)
confirmToolAction(id)
window.message.success({
content: t('message.tools.autoApproveEnabled', 'Auto-approve enabled for this tool'),
key: 'auto-approve'
})
window.toast.success(t('message.tools.autoApproveEnabled', 'Auto-approve enabled for this tool'))
}
const renderStatusIndicator = (status: string, hasError: boolean) => {

View File

@ -85,7 +85,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
if (topic && name !== topic.name) {
const updatedTopic = { ...topic, name, isNameManuallyEdited: true }
updateTopic(updatedTopic)
window.message.success(t('common.saved'))
window.toast.success(t('common.saved'))
}
setEditingTopicId(null)
},
@ -213,7 +213,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
const updatedTopic = { ...topic, name: summaryText, isNameManuallyEdited: false }
updateTopic(updatedTopic)
} else {
window.message?.error(t('message.error.fetchTopicName'))
window.toast?.error(t('message.error.fetchTopicName'))
}
} finally {
finishTopicRenaming(topic.id)
@ -348,10 +348,10 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic,
try {
const result = await SaveToKnowledgePopup.showForTopic(topic)
if (result?.success) {
window.message.success(t('chat.save.topic.knowledge.success', { count: result.savedCount }))
window.toast.success(t('chat.save.topic.knowledge.success', { count: result.savedCount }))
}
} catch {
window.message.error(t('chat.save.topic.knowledge.error.save_failed'))
window.toast.error(t('chat.save.topic.knowledge.error.save_failed'))
}
}
}

View File

@ -301,10 +301,7 @@ function getMenuItems({
agent.id = uuid()
agent.type = 'agent'
addAgent(agent)
window.message.success({
content: t('assistants.save.success'),
key: 'save-to-agent'
})
window.toast.success(t('assistants.save.success'))
}
},
{

View File

@ -38,12 +38,12 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ title, resolve }) => {
const onOk = async () => {
if (!newBase.name?.trim()) {
window.message.error(t('knowledge.name_required'))
window.toast.error(t('knowledge.name_required'))
return
}
if (!newBase.model) {
window.message.error(t('knowledge.embedding_model_required'))
window.toast.error(t('knowledge.embedding_model_required'))
return
}
@ -62,7 +62,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ title, resolve }) => {
resolve(_newBase)
} catch (error) {
logger.error('KnowledgeBase creation failed:', error as Error)
window.message.error(t('knowledge.error.failed_to_create') + formatErrorMessage(error))
window.toast.error(t('knowledge.error.failed_to_create') + formatErrorMessage(error))
}
}

View File

@ -53,7 +53,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ base: _base, resolve })
resolve(migratedBase)
} catch (error) {
logger.error('KnowledgeBase migration failed:', error as Error)
window.message.error(t('knowledge.migrate.error.failed') + ': ' + formatErrorMessage(error))
window.toast.error(t('knowledge.migrate.error.failed') + ': ' + formatErrorMessage(error))
}
}, [newBase, migrateBase, resolve, t])
@ -94,7 +94,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ base: _base, resolve })
resolve(newBase)
} catch (error) {
logger.error('KnowledgeBase edit failed:', error as Error)
window.message.error(t('knowledge.error.failed_to_edit') + formatErrorMessage(error))
window.toast.error(t('knowledge.error.failed_to_edit') + formatErrorMessage(error))
}
}
}

View File

@ -37,7 +37,7 @@ export const useCopyText = () => {
message.success(t('message.copied'))
} catch (error) {
logger.error('Failed to copy text:', error as Error)
window.message.error(t('message.error.copy') || 'Failed to copy text')
window.toast.error(t('message.error.copy') || 'Failed to copy text')
}
}

View File

@ -22,7 +22,7 @@ const MigrationInfoTag: FC<{ base: KnowledgeBase }> = ({ base: _base }) => {
await migrateBase(migratedBase, MigrationModeEnum.MigrationToLangChain)
} catch (error) {
logger.error('KnowledgeBase migration failed:', error as Error)
window.message.error(t('knowledge.migrate.error.failed') + ': ' + formatErrorMessage(error))
window.toast.error(t('knowledge.migrate.error.failed') + ': ' + formatErrorMessage(error))
}
}, [newBase, migrateBase, t])

View File

@ -76,7 +76,7 @@ const KnowledgeUrls: FC<KnowledgeContentProps> = ({ selectedBase }) => {
if (!urlItems.find((item) => item.content === url.trim())) {
addUrl(url.trim())
} else {
window.message.success(t('knowledge.url_added'))
window.toast.success(t('knowledge.url_added'))
}
} catch (e) {
// Skip invalid URLs silently
@ -154,7 +154,7 @@ const KnowledgeUrls: FC<KnowledgeContentProps> = ({ selectedBase }) => {
label: t('common.copy'),
onClick: () => {
navigator.clipboard.writeText(item.content as string)
window.message.success(t('message.copied'))
window.toast.success(t('message.copied'))
}
}
]

View File

@ -36,11 +36,11 @@ const NewAppButton: FC<Props> = ({ size = 60 }) => {
// Check for duplicate ID
if (customApps.some((app: MinAppType) => app.id === values.id)) {
window.message.error(t('settings.miniapps.custom.duplicate_ids', { ids: values.id }))
window.toast.error(t('settings.miniapps.custom.duplicate_ids', { ids: values.id }))
return
}
if (ORIGIN_DEFAULT_MIN_APPS.some((app: MinAppType) => app.id === values.id)) {
window.message.error(t('settings.miniapps.custom.conflicting_ids', { ids: values.id }))
window.toast.error(t('settings.miniapps.custom.conflicting_ids', { ids: values.id }))
return
}
@ -54,7 +54,7 @@ const NewAppButton: FC<Props> = ({ size = 60 }) => {
}
customApps.push(newApp)
await window.api.file.writeWithId('custom-minapps.json', JSON.stringify(customApps, null, 2))
window.message.success(t('settings.miniapps.custom.save_success'))
window.toast.success(t('settings.miniapps.custom.save_success'))
setIsModalVisible(false)
form.resetFields()
setFileList([])
@ -62,7 +62,7 @@ const NewAppButton: FC<Props> = ({ size = 60 }) => {
updateDefaultMinApps(reloadedApps)
updateMinapps([...minapps, newApp])
} catch (error) {
window.message.error(t('settings.miniapps.custom.save_error'))
window.toast.error(t('settings.miniapps.custom.save_error'))
logger.error('Failed to save custom mini app:', error as Error)
}
}
@ -77,14 +77,14 @@ const NewAppButton: FC<Props> = ({ size = 60 }) => {
reader.onload = (event) => {
const base64Data = event.target?.result
if (typeof base64Data === 'string') {
window.message.success(t('settings.miniapps.custom.logo_upload_success'))
window.toast.success(t('settings.miniapps.custom.logo_upload_success'))
form.setFieldValue('logo', base64Data)
}
}
reader.readAsDataURL(file)
} catch (error) {
logger.error('Failed to read file:', error as Error)
window.message.error(t('settings.miniapps.custom.logo_upload_error'))
window.toast.error(t('settings.miniapps.custom.logo_upload_error'))
}
}
}

View File

@ -37,13 +37,13 @@ const HeaderNavbar = ({ notesTree, getCurrentNoteContent, onToggleStar }) => {
const content = getCurrentNoteContent?.()
if (content) {
await navigator.clipboard.writeText(content)
window.message.success(t('common.copied'))
window.toast.success(t('common.copied'))
} else {
window.message.warning(t('notes.no_content_to_copy'))
window.toast.warning(t('notes.no_content_to_copy'))
}
} catch (error) {
logger.error('Failed to copy content:', error as Error)
window.message.error(t('common.copy_failed'))
window.toast.error(t('common.copy_failed'))
}
}, [getCurrentNoteContent])

View File

@ -507,7 +507,7 @@ const NotesPage: FC = () => {
}
await sortAllLevels(sortType)
if (renamedNode.name !== newName) {
window.message.info(t('notes.rename_changed', { original: newName, final: renamedNode.name }))
window.toast.info(t('notes.rename_changed', { original: newName, final: renamedNode.name }))
}
}
} catch (error) {
@ -528,16 +528,16 @@ const NotesPage: FC = () => {
const fileToUpload = files[0]
if (!fileToUpload) {
window.message.warning(t('notes.no_file_selected'))
window.toast.warning(t('notes.no_file_selected'))
return
}
// 暂时这么处理
if (files.length > 1) {
window.message.warning(t('notes.only_one_file_allowed'))
window.toast.warning(t('notes.only_one_file_allowed'))
}
if (!fileToUpload.name.toLowerCase().endsWith('.md')) {
window.message.warning(t('notes.only_markdown'))
window.toast.warning(t('notes.only_markdown'))
return
}
@ -546,14 +546,14 @@ const NotesPage: FC = () => {
throw new Error('No folder path selected')
}
await uploadNote(fileToUpload, notesPath)
window.message.success(t('notes.upload_success', { count: 1 }))
window.toast.success(t('notes.upload_success', { count: 1 }))
} catch (error) {
logger.error(`Failed to upload note file ${fileToUpload.name}:`, error as Error)
window.message.error(t('notes.upload_failed', { name: fileToUpload.name }))
window.toast.error(t('notes.upload_failed', { name: fileToUpload.name }))
}
} catch (error) {
logger.error('Failed to handle file upload:', error as Error)
window.message.error(t('notes.upload_failed'))
window.toast.error(t('notes.upload_failed'))
}
},
[notesPath, t]

View File

@ -167,17 +167,17 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
async (note: NotesTreeNode) => {
try {
if (bases.length === 0) {
window.message.warning(t('chat.save.knowledge.empty.no_knowledge_base'))
window.toast.warning(t('chat.save.knowledge.empty.no_knowledge_base'))
return
}
const result = await SaveToKnowledgePopup.showForNote(note)
if (result?.success) {
window.message.success(t('notes.export_success', { count: result.savedCount }))
window.toast.success(t('notes.export_success', { count: result.savedCount }))
}
} catch (error) {
window.message.error(t('notes.export_failed'))
window.toast.error(t('notes.export_failed'))
logger.error(`Failed to export note to knowledge base: ${error}`)
}
},

View File

@ -135,10 +135,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
try {
if (!url?.trim()) {
logger.error('图像URL为空可能是提示词违禁')
window.message.warning({
content: t('message.empty_url'),
key: 'empty-url-warning'
})
window.toast.warning(t('message.empty_url'))
return null
}
return await window.api.file.download(url)
@ -148,10 +145,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
error instanceof Error &&
(error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL'))
) {
window.message.warning({
content: t('message.empty_url'),
key: 'empty-url-warning'
})
window.toast.warning(t('message.empty_url'))
}
return null
}

View File

@ -491,10 +491,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
urls.map(async (url) => {
try {
if (!url || url.trim() === '') {
window.message.warning({
content: t('message.empty_url'),
key: 'empty-url-warning'
})
window.toast.warning(t('message.empty_url'))
return null
}
return await window.api.file.download(url, true)
@ -503,10 +500,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
error instanceof Error &&
(error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL'))
) {
window.message.warning({
content: t('message.empty_url'),
key: 'empty-url-warning'
})
window.toast.warning(t('message.empty_url'))
}
return null
}
@ -591,10 +585,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
updatePaintingState({ files: validFiles, urls })
}
} else {
window.message.warning({
content: t('paintings.req_error_text'),
key: 'empty-url-warning'
})
window.toast.warning(t('paintings.req_error_text'))
}
}
} catch (error) {

View File

@ -198,10 +198,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
try {
if (!url?.trim()) {
logger.error('图像URL为空')
window.message.warning({
content: t('message.empty_url'),
key: 'empty-url-warning'
})
window.toast.warning(t('message.empty_url'))
return null
}
return await window.api.file.download(url)
@ -211,10 +208,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
error instanceof Error &&
(error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL'))
) {
window.message.warning({
content: t('message.empty_url'),
key: 'empty-url-warning'
})
window.toast.warning(t('message.empty_url'))
}
return null
}
@ -283,7 +277,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
} else if (mode === 'openai_image_edit') {
// -------- Edit Mode --------
if (editImages.length === 0) {
window.message.warning({ content: t('paintings.image_file_required') })
window.toast.warning(t('paintings.image_file_required'))
return
}

View File

@ -223,10 +223,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => {
try {
if (!url || url.trim() === '') {
logger.error('图像URL为空可能是提示词违禁')
window.message.warning({
content: t('message.empty_url'),
key: 'empty-url-warning'
})
window.toast.warning(t('message.empty_url'))
return null
}
return await window.api.file.download(url)
@ -236,10 +233,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => {
error instanceof Error &&
(error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL'))
) {
window.message.warning({
content: t('message.empty_url'),
key: 'empty-url-warning'
})
window.toast.warning(t('message.empty_url'))
}
return null
}

View File

@ -187,10 +187,7 @@ const ZhipuPage: FC<{ Options: string[] }> = ({ Options }) => {
imageUrls.map(async (url) => {
try {
if (!url || url.trim() === '') {
window.message.warning({
content: t('message.empty_url'),
key: 'empty-url-warning'
})
window.toast.warning(t('message.empty_url'))
return null
}
return await window.api.file.download(url)
@ -199,10 +196,7 @@ const ZhipuPage: FC<{ Options: string[] }> = ({ Options }) => {
error instanceof Error &&
(error.message.includes('Failed to parse URL') || error.message.includes('Invalid URL'))
) {
window.message.warning({
content: t('message.empty_url'),
key: 'empty-url-warning'
})
window.toast.warning(t('message.empty_url'))
}
return null
}

View File

@ -34,7 +34,7 @@ export const DynamicFormRender: React.FC<DynamicFormRenderProps> = ({
if (fileOrUrl.match(/^https?:\/\/.+\.(jpg|jpeg|png|gif|webp|svg)(\?.*)?$/i)) {
onChange(propertyName, fileOrUrl)
} else {
window.message?.error('Invalid image URL format')
window.toast?.error('Invalid image URL format')
}
} else {
// Handle File case - convert to base64

View File

@ -95,9 +95,7 @@ export const GetModelGroup = async (): Promise<DMXApiModelGroups> => {
} catch {
/* empty */
}
window.message.error({
content: t('paintings.req_error_model')
})
window.toast.error(t('paintings.req_error_model'))
return {}
}

View File

@ -219,10 +219,7 @@ export class TokenFluxService {
try {
if (!url?.trim()) {
logger.error('Image URL is empty')
window.message.warning({
content: 'Image URL is empty',
key: 'empty-url-warning'
})
window.toast.warning('Image URL is empty')
return null
}
return await window.api.file.download(url)

View File

@ -51,7 +51,7 @@ const AboutSettings: FC = () => {
try {
await window.api.checkForUpdate()
} catch (error) {
window.message.error(t('settings.about.updateError'))
window.toast.error(t('settings.about.updateError'))
}
dispatch(setUpdateState({ checking: false }))
@ -105,7 +105,7 @@ const AboutSettings: FC = () => {
const handleTestChannelChange = async (value: UpgradeChannel) => {
if (testPlan && currentChannelByVersion !== UpgradeChannel.LATEST && value !== currentChannelByVersion) {
window.message.warning(t('settings.general.test_plan.version_channel_not_match'))
window.toast.warning(t('settings.general.test_plan.version_channel_not_match'))
}
setTestChannel(value)
// Clear update info when switching upgrade channel

View File

@ -50,7 +50,7 @@ const AssistantPromptSettings: React.FC<Props> = ({ assistant, updateAssistant }
const onUpdate = () => {
const _assistant = { ...assistant, name: name.trim(), emoji, prompt }
updateAssistant(_assistant)
window.message.success(t('common.saved'))
window.toast.success(t('common.saved'))
}
const handleEmojiSelect = (selectedEmoji: string) => {

View File

@ -163,9 +163,9 @@ const DataSettings: FC = () => {
await window.api.clearCache()
await window.api.trace.cleanLocalData()
await window.api.getCacheSize().then(setCacheSize)
window.message.success(t('settings.data.clear_cache.success'))
window.toast.success(t('settings.data.clear_cache.success'))
} catch (error) {
window.message.error(t('settings.data.clear_cache.error'))
window.toast.error(t('settings.data.clear_cache.error'))
}
}
})
@ -178,7 +178,7 @@ const DataSettings: FC = () => {
content: t('settings.data.app_knowledge.remove_all_confirm'),
onOk: async () => {
await removeAllFiles()
window.message.success(t('settings.data.app_knowledge.remove_all_success'))
window.toast.success(t('settings.data.app_knowledge.remove_all_success'))
},
okText: t('common.delete'),
okButtonProps: {
@ -205,28 +205,28 @@ const DataSettings: FC = () => {
// if is root path, show error
const pathParts = newAppDataPath.split(/[/\\]/).filter((part: string) => part !== '')
if (pathParts.length <= 1) {
window.message.error(t('settings.data.app_data.select_error_root_path'))
window.toast.error(t('settings.data.app_data.select_error_root_path'))
return
}
// check new app data path is not in old app data path
const isInOldPath = await window.api.isPathInside(newAppDataPath, appInfo.appDataPath)
if (isInOldPath) {
window.message.error(t('settings.data.app_data.select_error_same_path'))
window.toast.error(t('settings.data.app_data.select_error_same_path'))
return
}
// check new app data path is not in app install path
const isInInstallPath = await window.api.isPathInside(newAppDataPath, appInfo.installPath)
if (isInInstallPath) {
window.message.error(t('settings.data.app_data.select_error_in_app_path'))
window.toast.error(t('settings.data.app_data.select_error_in_app_path'))
return
}
// check new app data path has write permission
const hasWritePermission = await window.api.hasWritePermission(newAppDataPath)
if (!hasWritePermission) {
window.message.error(t('settings.data.app_data.select_error_write_permission'))
window.toast.error(t('settings.data.app_data.select_error_write_permission'))
return
}
@ -245,9 +245,9 @@ const DataSettings: FC = () => {
okText: t('common.confirm'),
cancelText: t('common.cancel'),
onOk: () => {
window.message.info({
content: t('settings.data.app_data.restart_notice'),
duration: 2
window.toast.info({
title: t('settings.data.app_data.restart_notice'),
timeout: 2000
})
setTimeoutTimer(
'doubleConfirmModalBeforeCopyData',
@ -335,9 +335,9 @@ const DataSettings: FC = () => {
return
}
window.message.info({
content: t('settings.data.app_data.restart_notice'),
duration: 3
window.toast.info({
title: t('settings.data.app_data.restart_notice'),
timeout: 3000
})
setTimeoutTimer(
'showMigrationConfirmModal_1',
@ -352,7 +352,7 @@ const DataSettings: FC = () => {
}
// 如果不复制数据,直接设置新的应用数据路径
await window.api.setAppDataPath(newPath)
window.message.success(t('settings.data.app_data.path_changed_without_copy'))
window.toast.success(t('settings.data.app_data.path_changed_without_copy'))
// 更新应用数据路径
setAppInfo(await window.api.getAppInfo())
@ -361,7 +361,7 @@ const DataSettings: FC = () => {
setTimeoutTimer(
'showMigrationConfirmModal_2',
() => {
window.message.success(t('settings.data.app_data.select_success'))
window.toast.success(t('settings.data.app_data.select_success'))
window.api.setStopQuitApp(false, '')
window.api.relaunchApp()
},
@ -369,9 +369,9 @@ const DataSettings: FC = () => {
)
} catch (error) {
window.api.setStopQuitApp(false, '')
window.message.error({
content: t('settings.data.app_data.path_change_failed') + ': ' + error,
duration: 5
window.toast.error({
title: t('settings.data.app_data.path_change_failed') + ': ' + error,
timeout: 5000
})
}
}
@ -390,7 +390,6 @@ const DataSettings: FC = () => {
<div style={{ fontSize: '18px', fontWeight: 'bold' }}>{t('settings.data.app_data.migration_title')}</div>
)
const className = 'migration-modal'
const messageKey = 'data-migration'
// 显示进度模态框
const showProgressModal = (title: React.ReactNode, className: string, PathsContent: React.FC) => {
@ -463,8 +462,7 @@ const DataSettings: FC = () => {
newPath: string,
progressInterval: NodeJS.Timeout | null,
updateProgress: (progress: number, status?: 'active' | 'success') => void,
loadingModal: { destroy: () => void },
messageKey: string
loadingModal: { destroy: () => void }
): Promise<void> => {
// flush app data
await window.api.flushAppData()
@ -494,10 +492,9 @@ const DataSettings: FC = () => {
'startMigration_2',
() => {
loadingModal.destroy()
window.message.error({
content: t('settings.data.app_data.copy_failed') + ': ' + copyResult.error,
key: messageKey,
duration: 5
window.toast.error({
title: t('settings.data.app_data.copy_failed') + ': ' + copyResult.error,
timeout: 5000
})
resolve()
},
@ -517,10 +514,9 @@ const DataSettings: FC = () => {
// 关闭加载模态框
loadingModal.destroy()
window.message.success({
content: t('settings.data.app_data.copy_success'),
key: messageKey,
duration: 2
window.toast.success({
title: t('settings.data.app_data.copy_success'),
timeout: 2000
})
}
@ -541,7 +537,7 @@ const DataSettings: FC = () => {
const { loadingModal, progressInterval, updateProgress } = showProgressModal(title, className, PathsContent)
try {
window.api.setStopQuitApp(true, t('settings.data.app_data.stop_quit_app_reason'))
await startMigration(originalPath, newDataPath, progressInterval, updateProgress, loadingModal, messageKey)
await startMigration(originalPath, newDataPath, progressInterval, updateProgress, loadingModal)
// 更新应用数据路径
setAppInfo(await window.api.getAppInfo())
@ -550,7 +546,7 @@ const DataSettings: FC = () => {
setTimeoutTimer(
'handleDataMigration',
() => {
window.message.success(t('settings.data.app_data.select_success'))
window.toast.success(t('settings.data.app_data.select_success'))
window.api.setStopQuitApp(false, '')
window.api.relaunchApp({
args: ['--user-data-dir=' + newDataPath]
@ -560,10 +556,9 @@ const DataSettings: FC = () => {
)
} catch (error) {
window.api.setStopQuitApp(false, '')
window.message.error({
content: t('settings.data.app_data.copy_failed') + ': ' + error,
key: messageKey,
duration: 5
window.toast.error({
title: t('settings.data.app_data.copy_failed') + ': ' + error,
timeout: 5000
})
} finally {
if (progressInterval) {

View File

@ -42,11 +42,11 @@ const JoplinSettings: FC = () => {
const handleJoplinConnectionCheck = async () => {
try {
if (!joplinToken) {
window.message.error(t('settings.data.joplin.check.empty_token'))
window.toast.error(t('settings.data.joplin.check.empty_token'))
return
}
if (!joplinUrl) {
window.message.error(t('settings.data.joplin.check.empty_url'))
window.toast.error(t('settings.data.joplin.check.empty_url'))
return
}
@ -55,13 +55,13 @@ const JoplinSettings: FC = () => {
const data = await response.json()
if (!response.ok || data?.error) {
window.message.error(t('settings.data.joplin.check.fail'))
window.toast.error(t('settings.data.joplin.check.fail'))
return
}
window.message.success(t('settings.data.joplin.check.success'))
window.toast.success(t('settings.data.joplin.check.success'))
} catch (e) {
window.message.error(t('settings.data.joplin.check.fail'))
window.toast.error(t('settings.data.joplin.check.fail'))
}
}

View File

@ -83,21 +83,21 @@ const LocalBackupSettings: React.FC = () => {
// check new local backup dir is not in app data path
// if is in app data path, show error
if (await window.api.isPathInside(resolvedDir, appInfo!.appDataPath)) {
window.message.error(t('settings.data.local.directory.select_error_app_data_path'))
window.toast.error(t('settings.data.local.directory.select_error_app_data_path'))
return false
}
// check new local backup dir is not in app install path
// if is in app install path, show error
if (await window.api.isPathInside(resolvedDir, appInfo!.installPath)) {
window.message.error(t('settings.data.local.directory.select_error_in_app_install_path'))
window.toast.error(t('settings.data.local.directory.select_error_in_app_install_path'))
return false
}
// check new app data path has write permission
const hasWritePermission = await window.api.hasWritePermission(resolvedDir)
if (!hasWritePermission) {
window.message.error(t('settings.data.local.directory.select_error_write_permission'))
window.toast.error(t('settings.data.local.directory.select_error_write_permission'))
return false
}

View File

@ -42,11 +42,11 @@ const NotionSettings: FC = () => {
const handleNotionConnectionCheck = () => {
if (notionApiKey === null) {
window.message.error(t('settings.data.notion.check.empty_api_key'))
window.toast.error(t('settings.data.notion.check.empty_api_key'))
return
}
if (notionDatabaseID === null) {
window.message.error(t('settings.data.notion.check.empty_database_id'))
window.toast.error(t('settings.data.notion.check.empty_database_id'))
return
}
const notion = new Client({ auth: notionApiKey })
@ -56,13 +56,13 @@ const NotionSettings: FC = () => {
})
.then((result) => {
if (result) {
window.message.success(t('settings.data.notion.check.success'))
window.toast.success(t('settings.data.notion.check.success'))
} else {
window.message.error(t('settings.data.notion.check.fail'))
window.toast.error(t('settings.data.notion.check.fail'))
}
})
.catch(() => {
window.message.error(t('settings.data.notion.check.error'))
window.toast.error(t('settings.data.notion.check.error'))
})
}

View File

@ -106,11 +106,9 @@ const NutstoreSettings: FC = () => {
setCheckConnectionLoading(true)
const isConnectedToNutstore = await checkConnection()
window.message[isConnectedToNutstore ? 'success' : 'error']({
key: 'api-check',
style: { marginTop: '3vh' },
duration: 2,
content: isConnectedToNutstore
window.toast[isConnectedToNutstore ? 'success' : 'error']({
timeout: 2000,
title: isConnectedToNutstore
? t('settings.data.nutstore.checkConnection.success')
: t('settings.data.nutstore.checkConnection.fail')
})

View File

@ -53,7 +53,7 @@ const SiyuanSettings: FC = () => {
const handleCheckConnection = async () => {
try {
if (!siyuanApiUrl || !siyuanToken) {
window.message.error(t('settings.data.siyuan.check.empty_config'))
window.toast.error(t('settings.data.siyuan.check.empty_config'))
return
}
@ -66,20 +66,20 @@ const SiyuanSettings: FC = () => {
})
if (!response.ok) {
window.message.error(t('settings.data.siyuan.check.fail'))
window.toast.error(t('settings.data.siyuan.check.fail'))
return
}
const data = await response.json()
if (data.code !== 0) {
window.message.error(t('settings.data.siyuan.check.fail'))
window.toast.error(t('settings.data.siyuan.check.fail'))
return
}
window.message.success(t('settings.data.siyuan.check.success'))
window.toast.success(t('settings.data.siyuan.check.success'))
} catch (error) {
logger.error('Check Siyuan connection failed:', error as Error)
window.message.error(t('settings.data.siyuan.check.error'))
window.toast.error(t('settings.data.siyuan.check.error'))
}
}

View File

@ -31,11 +31,11 @@ const YuqueSettings: FC = () => {
const handleYuqueConnectionCheck = async () => {
if (!yuqueToken) {
window.message.error(t('settings.data.yuque.check.empty_token'))
window.toast.error(t('settings.data.yuque.check.empty_token'))
return
}
if (!yuqueUrl) {
window.message.error(t('settings.data.yuque.check.empty_repo_url'))
window.toast.error(t('settings.data.yuque.check.empty_repo_url'))
return
}
@ -46,7 +46,7 @@ const YuqueSettings: FC = () => {
})
if (!response.ok) {
window.message.error(t('settings.data.yuque.check.fail'))
window.toast.error(t('settings.data.yuque.check.fail'))
return
}
const yuqueSlug = yuqueUrl.replace('https://www.yuque.com/', '')
@ -56,12 +56,12 @@ const YuqueSettings: FC = () => {
}
})
if (!repoIDResponse.ok) {
window.message.error(t('settings.data.yuque.check.fail'))
window.toast.error(t('settings.data.yuque.check.fail'))
return
}
const data = await repoIDResponse.json()
dispatch(setYuqueRepoId(data.data.id))
window.message.success(t('settings.data.yuque.check.success'))
window.toast.success(t('settings.data.yuque.check.success'))
}
const handleYuqueHelpClick = () => {

View File

@ -30,7 +30,7 @@ const OcrImageSettings = ({ setProvider }: Props) => {
const provider = imageProviders.find((p) => p.id === id)
if (!provider) {
logger.error(`Failed to find image provider by id: ${id}`)
window.message.error(t('settings.tool.ocr.image.error.provider_not_found'))
window.toast.error(t('settings.tool.ocr.image.error.provider_not_found'))
return
}

View File

@ -20,6 +20,7 @@ import {
import { LanguageVarious } from '@renderer/types'
import { NotificationSource } from '@renderer/types/notification'
import { isValidProxyUrl } from '@renderer/utils'
import { formatErrorMessage } from '@renderer/utils/error'
import { defaultByPassRules, defaultLanguage } from '@shared/config/constant'
import { Flex, Input, Switch, Tooltip } from 'antd'
import { FC, useState } from 'react'
@ -96,7 +97,7 @@ const GeneralSettings: FC = () => {
const onSetProxyUrl = () => {
if (proxyUrl && !isValidProxyUrl(proxyUrl)) {
window.message.error({ content: t('message.error.invalid.proxy.url'), key: 'proxy-error' })
window.toast.error(t('message.error.invalid.proxy.url'))
return
}
@ -165,10 +166,7 @@ const GeneralSettings: FC = () => {
try {
setDisableHardwareAcceleration(checked)
} catch (error) {
window.message.error({
content: (error as Error).message,
key: 'disable-hardware-acceleration-error'
})
window.toast.error(formatErrorMessage(error))
return
}

View File

@ -131,10 +131,7 @@ const AddMcpServerModal: FC<AddMcpServerModalProps> = ({
if (importMethod === 'dxt') {
if (!dxtFile) {
window.message.error({
content: t('settings.mcp.addServer.importFrom.noDxtFile'),
key: 'mcp-no-dxt-file'
})
window.toast.error(t('settings.mcp.addServer.importFrom.noDxtFile'))
setLoading(false)
return
}
@ -144,10 +141,7 @@ const AddMcpServerModal: FC<AddMcpServerModalProps> = ({
const result = await window.api.mcp.uploadDxt(dxtFile)
if (!result.success) {
window.message.error({
content: result.error || t('settings.mcp.addServer.importFrom.dxtProcessFailed'),
key: 'mcp-dxt-process-failed'
})
window.toast.error(result.error || t('settings.mcp.addServer.importFrom.dxtProcessFailed'))
setLoading(false)
return
}
@ -156,10 +150,7 @@ const AddMcpServerModal: FC<AddMcpServerModalProps> = ({
// Check for duplicate names
if (existingServers && existingServers.some((server) => server.name === manifest.name)) {
window.message.error({
content: t('settings.mcp.addServer.importFrom.nameExists', { name: manifest.name }),
key: 'mcp-name-exists'
})
window.toast.error(t('settings.mcp.addServer.importFrom.nameExists', { name: manifest.name }))
setLoading(false)
return
}
@ -227,10 +218,7 @@ const AddMcpServerModal: FC<AddMcpServerModalProps> = ({
) // Delay to ensure server is properly added to store
} catch (error) {
logger.error('DXT processing error:', error as Error)
window.message.error({
content: t('settings.mcp.addServer.importFrom.dxtProcessFailed'),
key: 'mcp-dxt-error'
})
window.toast.error(t('settings.mcp.addServer.importFrom.dxtProcessFailed'))
setLoading(false)
return
}
@ -286,10 +274,7 @@ const AddMcpServerModal: FC<AddMcpServerModalProps> = ({
})
.catch((connError: any) => {
logger.error(`Connectivity check failed for ${newServer.name}:`, connError)
window.message.error({
content: newServer.name + t('settings.mcp.addServer.importFrom.connectionFailed'),
key: 'mcp-quick-add-failed'
})
window.toast.error(newServer.name + t('settings.mcp.addServer.importFrom.connectionFailed'))
})
}
} finally {

View File

@ -37,7 +37,7 @@ const BuiltinMCPServerList: FC = () => {
}
addMCPServer(server)
window.message.success({ content: t('settings.mcp.addSuccess'), key: 'mcp-add-builtin-server' })
window.toast.success(t('settings.mcp.addSuccess'))
}}
disabled={isInstalled}
/>

View File

@ -58,7 +58,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
try {
if (!jsonConfig.trim()) {
dispatch(setMCPServers([]))
window.message.success(t('settings.mcp.jsonSaveSuccess'))
window.toast.success(t('settings.mcp.jsonSaveSuccess'))
setJsonError('')
setJsonSaving(false)
return
@ -92,13 +92,13 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
dispatch(setMCPServers(serversArray))
window.message.success(t('settings.mcp.jsonSaveSuccess'))
window.toast.success(t('settings.mcp.jsonSaveSuccess'))
setJsonError('')
setOpen(false)
} catch (error: any) {
logger.error('Failed to save JSON config:', error)
setJsonError(error.message || t('settings.mcp.jsonSaveError'))
window.message.error(t('settings.mcp.jsonSaveError'))
window.toast.error(t('settings.mcp.jsonSaveError'))
} finally {
setJsonSaving(false)
}

View File

@ -54,7 +54,7 @@ const InstallNpxUv: FC<Props> = ({ mini = false }) => {
setIsInstallingUv(false)
dispatch(setIsUvInstalled(true))
} catch (error: any) {
window.message.error({ content: `${t('settings.mcp.installError')}: ${error.message}`, key: 'mcp-install-error' })
window.toast.error(`${t('settings.mcp.installError')}: ${error.message}`)
setIsInstallingUv(false)
}
clearTimeout(checkBinariesTimerRef.current)
@ -68,10 +68,7 @@ const InstallNpxUv: FC<Props> = ({ mini = false }) => {
setIsInstallingBun(false)
dispatch(setIsBunInstalled(true))
} catch (error: any) {
window.message.error({
content: `${t('settings.mcp.installError')}: ${error.message}`,
key: 'mcp-install-error'
})
window.toast.error(`${t('settings.mcp.installError')}: ${error.message}`)
setIsInstallingBun(false)
}
clearTimeout(checkBinariesTimerRef.current)

View File

@ -116,7 +116,7 @@ const McpServersList: FC = () => {
}
addMCPServer(newServer)
navigate(`/settings/mcp/settings/${encodeURIComponent(newServer.id)}`)
window.message.success({ content: t('settings.mcp.addSuccess'), key: 'mcp-list' })
window.toast.success(t('settings.mcp.addSuccess'))
}, [addMCPServer, navigate, t])
const onDeleteMcpServer = useCallback(
@ -129,14 +129,11 @@ const McpServersList: FC = () => {
onOk: async () => {
await window.api.mcp.removeServer(server)
deleteMCPServer(server.id)
window.message.success({ content: t('settings.mcp.deleteSuccess'), key: 'mcp-list' })
window.toast.success(t('settings.mcp.deleteSuccess'))
}
})
} catch (error: any) {
window.message.error({
content: `${t('settings.mcp.deleteError')}: ${error.message}`,
key: 'mcp-list'
})
window.toast.error(`${t('settings.mcp.deleteError')}: ${error.message}`)
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -151,7 +148,7 @@ const McpServersList: FC = () => {
async (server: MCPServer) => {
addMCPServer(server)
setIsAddModalVisible(false)
window.message.success({ content: t('settings.mcp.addSuccess'), key: 'mcp-quick-add' })
window.toast.success(t('settings.mcp.addSuccess'))
// Optionally navigate to the new server's settings page
// navigate(`/settings/mcp/settings/${encodeURIComponent(server.id)}`)
},

View File

@ -302,7 +302,7 @@ const McpSettings: React.FC = () => {
try {
await window.api.mcp.restartServer(mcpServer)
updateMCPServer({ ...mcpServer, isActive: true })
window.message.success({ content: t('settings.mcp.updateSuccess'), key: 'mcp-update-success' })
window.toast.success(t('settings.mcp.updateSuccess'))
setIsFormChanged(false)
} catch (error: any) {
updateMCPServer({ ...mcpServer, isActive: false })
@ -314,7 +314,7 @@ const McpSettings: React.FC = () => {
}
} else {
updateMCPServer({ ...mcpServer, isActive: false })
window.message.success({ content: t('settings.mcp.updateSuccess'), key: 'mcp-update-success' })
window.toast.success(t('settings.mcp.updateSuccess'))
setIsFormChanged(false)
}
setLoading(false)
@ -381,15 +381,12 @@ const McpSettings: React.FC = () => {
onOk: async () => {
await window.api.mcp.removeServer(server)
deleteMCPServer(server.id)
window.message.success({ content: t('settings.mcp.deleteSuccess'), key: 'mcp-list' })
window.toast.success(t('settings.mcp.deleteSuccess'))
navigate('/settings/mcp')
}
})
} catch (error: any) {
window.message.error({
content: `${t('settings.mcp.deleteError')}: ${error.message}`,
key: 'mcp-list'
})
window.toast.error(`${t('settings.mcp.deleteError')}: ${error.message}`)
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps

View File

@ -43,7 +43,7 @@ const NpxSearch: FC = () => {
const searchScope = scopeOverride || npmScope
if (!searchScope.trim()) {
window.message.warning({ content: t('settings.mcp.npx_list.scope_required'), key: 'mcp-npx-scope-required' })
window.toast.warning(t('settings.mcp.npx_list.scope_required'))
return
}
@ -79,18 +79,15 @@ const NpxSearch: FC = () => {
setSearchResults(formattedResults)
if (formattedResults.length === 0) {
window.message.info({ content: t('settings.mcp.npx_list.no_packages'), key: 'mcp-npx-no-packages' })
window.toast.info(t('settings.mcp.npx_list.no_packages'))
}
} catch (error: unknown) {
setSearchResults([])
_searchResults = []
if (error instanceof Error) {
window.message.error({
content: `${t('settings.mcp.npx_list.search_error')}: ${error.message}`,
key: 'mcp-npx-search-error'
})
window.toast.error(`${t('settings.mcp.npx_list.search_error')}: ${error.message}`)
} else {
window.message.error({ content: t('settings.mcp.npx_list.search_error'), key: 'mcp-npx-search-error' })
window.toast.error(t('settings.mcp.npx_list.search_error'))
}
} finally {
setSearchLoading(false)
@ -186,7 +183,7 @@ const NpxSearch: FC = () => {
}
addMCPServer(newServer)
window.message.success({ content: t('settings.mcp.addSuccess'), key: 'mcp-add-server' })
window.toast.success(t('settings.mcp.addSuccess'))
}}
/>
</Flex>

View File

@ -152,18 +152,18 @@ const PopupContainer: React.FC<Props> = ({ resolve, existingServers }) => {
updateMCPServer(server)
}
}
window.message.success(result.message)
window.toast.success(result.message)
setOpen(false)
} else {
// Show message but keep dialog open
if (result.success) {
window.message.info(result.message)
window.toast.info(result.message)
} else {
window.message.error(result.message)
window.toast.error(result.message)
}
}
} catch (error: any) {
window.message.error(`${t('settings.mcp.sync.error')}: ${error.message}`)
window.toast.error(`${t('settings.mcp.sync.error')}: ${error.message}`)
} finally {
setIsSyncing(false)
}

View File

@ -330,7 +330,7 @@ const MemorySettings = () => {
setAllMemories(result.results || [])
} catch (error) {
logger.error('Failed to load memories:', error as Error)
window.message.error(t('memory.load_failed'))
window.toast.error(t('memory.load_failed'))
} finally {
setLoading(false)
}
@ -390,25 +390,25 @@ const MemorySettings = () => {
try {
// The memory service will automatically use the current user from its state
await memoryService.add(memory, {})
window.message.success(t('memory.add_success'))
window.toast.success(t('memory.add_success'))
// Go to first page to see the newly added memory
setCurrentPage(1)
await loadMemories(currentUser)
} catch (error) {
logger.error('Failed to add memory:', error as Error)
window.message.error(t('memory.add_failed'))
window.toast.error(t('memory.add_failed'))
}
}
const handleDeleteMemory = async (id: string) => {
try {
await memoryService.delete(id)
window.message.success(t('memory.delete_success'))
window.toast.success(t('memory.delete_success'))
// Reload all memories
await loadMemories(currentUser)
} catch (error) {
logger.error('Failed to delete memory:', error as Error)
window.message.error(t('memory.delete_failed'))
window.toast.error(t('memory.delete_failed'))
}
}
@ -419,13 +419,13 @@ const MemorySettings = () => {
const handleUpdateMemory = async (id: string, memory: string, metadata?: Record<string, any>) => {
try {
await memoryService.update(id, memory, metadata)
window.message.success(t('memory.update_success'))
window.toast.success(t('memory.update_success'))
setEditingMemory(null)
// Reload all memories
await loadMemories(currentUser)
} catch (error) {
logger.error('Failed to update memory:', error as Error)
window.message.error(t('memory.update_failed'))
window.toast.error(t('memory.update_failed'))
}
}
@ -445,12 +445,12 @@ const MemorySettings = () => {
// Explicitly load memories for the new user
await loadMemories(userId)
window.message.success(
window.toast.success(
t('memory.user_switched', { user: userId === DEFAULT_USER_ID ? t('memory.default_user') : userId })
)
} catch (error) {
logger.error('Failed to switch user:', error as Error)
window.message.error(t('memory.user_switch_failed'))
window.toast.error(t('memory.user_switch_failed'))
}
}
@ -466,11 +466,11 @@ const MemorySettings = () => {
// Switch to the newly created user
await handleUserSwitch(userId)
window.message.success(t('memory.user_created', { user: userId }))
window.toast.success(t('memory.user_created', { user: userId }))
setAddUserModalVisible(false)
} catch (error) {
logger.error('Failed to add user:', error as Error)
window.message.error(t('memory.add_user_failed'))
window.toast.error(t('memory.add_user_failed'))
}
}
@ -501,13 +501,13 @@ const MemorySettings = () => {
onOk: async () => {
try {
await memoryService.deleteAllMemoriesForUser(userId)
window.message.success(t('memory.memories_reset_success', { user: getUserDisplayName(userId) }))
window.toast.success(t('memory.memories_reset_success', { user: getUserDisplayName(userId) }))
// Reload memories to show the empty state
await loadMemories(currentUser)
} catch (error) {
logger.error('Failed to reset memories:', error as Error)
window.message.error(t('memory.reset_memories_failed'))
window.toast.error(t('memory.reset_memories_failed'))
}
}
})
@ -515,7 +515,7 @@ const MemorySettings = () => {
const handleDeleteUser = async (userId: string) => {
if (userId === DEFAULT_USER_ID) {
window.message.error(t('memory.cannot_delete_default_user'))
window.toast.error(t('memory.cannot_delete_default_user'))
return
}
@ -528,7 +528,7 @@ const MemorySettings = () => {
onOk: async () => {
try {
await memoryService.deleteUser(userId)
window.message.success(t('memory.user_deleted', { user: userId }))
window.toast.success(t('memory.user_deleted', { user: userId }))
// Refresh the users list from database after deletion
await loadUniqueUsers()
@ -541,7 +541,7 @@ const MemorySettings = () => {
}
} catch (error) {
logger.error('Failed to delete user:', error as Error)
window.message.error(t('memory.delete_user_failed'))
window.toast.error(t('memory.delete_user_failed'))
}
}
})
@ -572,7 +572,7 @@ const MemorySettings = () => {
})
}
window.message.success(t('memory.global_memory_disabled_title'))
window.toast.success(t('memory.global_memory_disabled_title'))
}
const { theme } = useTheme()

View File

@ -71,10 +71,10 @@ const NotesSettings: FC = () => {
updateNotesPath(tempPath)
initWorkSpace(tempPath, 'sort_a2z')
window.message.success(t('notes.settings.data.path_updated'))
window.toast.success(t('notes.settings.data.path_updated'))
} catch (error) {
logger.error('Failed to apply notes path:', error as Error)
window.message.error(t('notes.settings.data.apply_path_failed'))
window.toast.error(t('notes.settings.data.apply_path_failed'))
}
}
@ -84,10 +84,10 @@ const NotesSettings: FC = () => {
setTempPath(info.notesPath)
updateNotesPath(info.notesPath)
initWorkSpace(info.notesPath, 'sort_a2z')
window.message.success(t('notes.settings.data.reset_to_default'))
window.toast.success(t('notes.settings.data.reset_to_default'))
} catch (error) {
logger.error('Failed to reset to default:', error as Error)
window.message.error(t('notes.settings.data.reset_failed'))
window.toast.error(t('notes.settings.data.reset_failed'))
}
}

View File

@ -84,7 +84,7 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
setLogoPickerOpen(false)
} catch (error: any) {
window.message.error(error.message)
window.toast.error(error.message)
}
}
@ -98,7 +98,7 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
setDropdownOpen(false)
} catch (error: any) {
window.message.error(error.message)
window.toast.error(error.message)
}
}
@ -146,7 +146,7 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
}
setDropdownOpen(false)
} catch (error: any) {
window.message.error(error.message)
window.toast.error(error.message)
}
}}>
<MenuItem ref={uploadRef}>{t('settings.general.image_upload')}</MenuItem>

Some files were not shown because too many files have changed in this diff Show More