mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 14:41:24 +08:00
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:
parent
e10042a433
commit
cf7584bb63
@ -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'
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 () => {
|
||||
|
||||
@ -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])
|
||||
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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'))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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'))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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'))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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')}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 }) => (
|
||||
|
||||
72
src/renderer/src/components/TopView/toast.ts
Normal file
72
src/renderer/src/components/TopView/toast.ts
Normal 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
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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', () => {
|
||||
|
||||
@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
12
src/renderer/src/env.d.ts
vendored
12
src/renderer/src/env.d.ts
vendored
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
// 和上面一样
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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'])
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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])
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -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'))
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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])
|
||||
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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])
|
||||
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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}`)
|
||||
}
|
||||
},
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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'))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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')
|
||||
})
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 = () => {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)}`)
|
||||
},
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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'))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
Loading…
Reference in New Issue
Block a user