diff --git a/web/app/components/datasets/external-knowledge-base/connector/__tests__/index.spec.tsx b/web/app/components/datasets/external-knowledge-base/connector/__tests__/index.spec.tsx index c948450f1b..46235256ce 100644 --- a/web/app/components/datasets/external-knowledge-base/connector/__tests__/index.spec.tsx +++ b/web/app/components/datasets/external-knowledge-base/connector/__tests__/index.spec.tsx @@ -164,7 +164,7 @@ describe('ExternalKnowledgeBaseConnector', () => { // Verify success notification expect(mockNotify).toHaveBeenCalledWith({ type: 'success', - title: 'External Knowledge Base Connected Successfully', + title: 'dataset.externalKnowledgeForm.connectedSuccess', }) // Verify navigation back @@ -206,7 +206,7 @@ describe('ExternalKnowledgeBaseConnector', () => { await waitFor(() => { expect(mockNotify).toHaveBeenCalledWith({ type: 'error', - title: 'Failed to connect External Knowledge Base', + title: 'dataset.externalKnowledgeForm.connectedFailed', }) }) @@ -228,7 +228,7 @@ describe('ExternalKnowledgeBaseConnector', () => { await waitFor(() => { expect(mockNotify).toHaveBeenCalledWith({ type: 'error', - title: 'Failed to connect External Knowledge Base', + title: 'dataset.externalKnowledgeForm.connectedFailed', }) }) @@ -274,7 +274,7 @@ describe('ExternalKnowledgeBaseConnector', () => { await waitFor(() => { expect(mockNotify).toHaveBeenCalledWith({ type: 'success', - title: 'External Knowledge Base Connected Successfully', + title: 'dataset.externalKnowledgeForm.connectedSuccess', }) }) }) diff --git a/web/app/components/datasets/external-knowledge-base/connector/index.tsx b/web/app/components/datasets/external-knowledge-base/connector/index.tsx index 6ff7014f47..adf9be0104 100644 --- a/web/app/components/datasets/external-knowledge-base/connector/index.tsx +++ b/web/app/components/datasets/external-knowledge-base/connector/index.tsx @@ -3,6 +3,7 @@ import type { CreateKnowledgeBaseReq } from '@/app/components/datasets/external-knowledge-base/create/declarations' import * as React from 'react' import { useState } from 'react' +import { useTranslation } from 'react-i18next' import { trackEvent } from '@/app/components/base/amplitude' import { toast } from '@/app/components/base/ui/toast' import ExternalKnowledgeBaseCreate from '@/app/components/datasets/external-knowledge-base/create' @@ -12,13 +13,14 @@ import { createExternalKnowledgeBase } from '@/service/datasets' const ExternalKnowledgeBaseConnector = () => { const [loading, setLoading] = useState(false) const router = useRouter() + const { t } = useTranslation() const handleConnect = async (formValue: CreateKnowledgeBaseReq) => { try { setLoading(true) const result = await createExternalKnowledgeBase({ body: formValue }) if (result && result.id) { - toast.add({ type: 'success', title: 'External Knowledge Base Connected Successfully' }) + toast.add({ type: 'success', title: t('externalKnowledgeForm.connectedSuccess', { ns: 'dataset' }) }) trackEvent('create_external_knowledge_base', { provider: formValue.provider, name: formValue.name, @@ -29,7 +31,7 @@ const ExternalKnowledgeBaseConnector = () => { } catch (error) { console.error('Error creating external knowledge base:', error) - toast.add({ type: 'error', title: 'Failed to connect External Knowledge Base' }) + toast.add({ type: 'error', title: t('externalKnowledgeForm.connectedFailed', { ns: 'dataset' }) }) } setLoading(false) } diff --git a/web/app/components/tools/edit-custom-collection-modal/__tests__/get-schema.spec.tsx b/web/app/components/tools/edit-custom-collection-modal/__tests__/get-schema.spec.tsx index edd2d3dc43..b19a234dc6 100644 --- a/web/app/components/tools/edit-custom-collection-modal/__tests__/get-schema.spec.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/__tests__/get-schema.spec.tsx @@ -1,21 +1,24 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { importSchemaFromURL } from '@/service/tools' -import Toast from '../../../base/toast' import examples from '../examples' import GetSchema from '../get-schema' vi.mock('@/service/tools', () => ({ importSchemaFromURL: vi.fn(), })) +const mockToastAdd = vi.hoisted(() => vi.fn()) +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: { + add: mockToastAdd, + }, +})) const importSchemaFromURLMock = vi.mocked(importSchemaFromURL) describe('GetSchema', () => { - const notifySpy = vi.spyOn(Toast, 'notify') const mockOnChange = vi.fn() beforeEach(() => { vi.clearAllMocks() - notifySpy.mockClear() importSchemaFromURLMock.mockReset() render() }) @@ -27,9 +30,9 @@ describe('GetSchema', () => { fireEvent.change(input, { target: { value: 'ftp://invalid' } }) fireEvent.click(screen.getByText('common.operation.ok')) - expect(notifySpy).toHaveBeenCalledWith({ + expect(mockToastAdd).toHaveBeenCalledWith({ type: 'error', - message: 'tools.createTool.urlError', + title: 'tools.createTool.urlError', }) }) diff --git a/web/app/components/tools/edit-custom-collection-modal/get-schema.tsx b/web/app/components/tools/edit-custom-collection-modal/get-schema.tsx index 7ad8050a2d..7d34658dec 100644 --- a/web/app/components/tools/edit-custom-collection-modal/get-schema.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/get-schema.tsx @@ -10,8 +10,8 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' +import { toast } from '@/app/components/base/ui/toast' import { importSchemaFromURL } from '@/service/tools' -import Toast from '../../base/toast' import examples from './examples' type Props = { @@ -27,9 +27,9 @@ const GetSchema: FC = ({ const [isParsing, setIsParsing] = useState(false) const handleImportFromUrl = async () => { if (!importUrl.startsWith('http://') && !importUrl.startsWith('https://')) { - Toast.notify({ + toast.add({ type: 'error', - message: t('createTool.urlError', { ns: 'tools' }), + title: t('createTool.urlError', { ns: 'tools' }), }) return } diff --git a/web/app/components/tools/mcp/__tests__/modal.spec.tsx b/web/app/components/tools/mcp/__tests__/modal.spec.tsx index af24ba6061..6b396cae7c 100644 --- a/web/app/components/tools/mcp/__tests__/modal.spec.tsx +++ b/web/app/components/tools/mcp/__tests__/modal.spec.tsx @@ -3,7 +3,7 @@ import type { ToolWithProvider } from '@/app/components/workflow/types' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import * as React from 'react' -import { describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' import MCPModal from '../modal' // Mock the service API @@ -48,7 +48,18 @@ vi.mock('@/service/use-plugins', () => ({ }), })) +const mockToastAdd = vi.hoisted(() => vi.fn()) +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: { + add: mockToastAdd, + }, +})) + describe('MCPModal', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + const createWrapper = () => { const queryClient = new QueryClient({ defaultOptions: { @@ -299,6 +310,10 @@ describe('MCPModal', () => { // Wait a bit and verify onConfirm was not called await new Promise(resolve => setTimeout(resolve, 100)) expect(onConfirm).not.toHaveBeenCalled() + expect(mockToastAdd).toHaveBeenCalledWith({ + type: 'error', + title: 'tools.mcp.modal.invalidServerUrl', + }) }) it('should not call onConfirm with invalid server identifier', async () => { @@ -320,6 +335,10 @@ describe('MCPModal', () => { // Wait a bit and verify onConfirm was not called await new Promise(resolve => setTimeout(resolve, 100)) expect(onConfirm).not.toHaveBeenCalled() + expect(mockToastAdd).toHaveBeenCalledWith({ + type: 'error', + title: 'tools.mcp.modal.invalidServerIdentifier', + }) }) }) diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx index 76ba42f2bf..0f21214d34 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -14,7 +14,7 @@ import { Mcp } from '@/app/components/base/icons/src/vender/other' import Input from '@/app/components/base/input' import Modal from '@/app/components/base/modal' import TabSlider from '@/app/components/base/tab-slider' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { MCPAuthMethod } from '@/app/components/tools/types' import { cn } from '@/utils/classnames' import { shouldUseMcpIconForAppIcon } from '@/utils/mcp' @@ -82,11 +82,11 @@ const MCPModalContent: FC = ({ const submit = async () => { if (!isValidUrl(state.url)) { - Toast.notify({ type: 'error', message: 'invalid server url' }) + toast.add({ type: 'error', title: t('mcp.modal.invalidServerUrl', { ns: 'tools' }) }) return } if (!isValidServerID(state.serverIdentifier.trim())) { - Toast.notify({ type: 'error', message: 'invalid server identifier' }) + toast.add({ type: 'error', title: t('mcp.modal.invalidServerIdentifier', { ns: 'tools' }) }) return } const formattedHeaders = state.headers.reduce((acc, item) => { diff --git a/web/app/components/tools/provider/__tests__/custom-create-card.spec.tsx b/web/app/components/tools/provider/__tests__/custom-create-card.spec.tsx index 3643b769f7..63e2531a7f 100644 --- a/web/app/components/tools/provider/__tests__/custom-create-card.spec.tsx +++ b/web/app/components/tools/provider/__tests__/custom-create-card.spec.tsx @@ -70,11 +70,11 @@ vi.mock('@/app/components/tools/edit-custom-collection-modal', () => ({ }, })) -// Mock Toast +// Mock toast const mockToastNotify = vi.fn() -vi.mock('@/app/components/base/toast', () => ({ - default: { - notify: (options: { type: string, message: string }) => mockToastNotify(options), +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: { + add: (options: { type: string, title: string }) => mockToastNotify(options), }, })) @@ -200,7 +200,7 @@ describe('CustomCreateCard', () => { await waitFor(() => { expect(mockToastNotify).toHaveBeenCalledWith({ type: 'success', - message: expect.any(String), + title: expect.any(String), }) }) }) diff --git a/web/app/components/tools/provider/__tests__/detail.spec.tsx b/web/app/components/tools/provider/__tests__/detail.spec.tsx index f2d47f8e43..7f8c415c16 100644 --- a/web/app/components/tools/provider/__tests__/detail.spec.tsx +++ b/web/app/components/tools/provider/__tests__/detail.spec.tsx @@ -92,8 +92,9 @@ vi.mock('@/app/components/base/confirm', () => ({ : null, })) -vi.mock('@/app/components/base/toast', () => ({ - default: { notify: vi.fn() }, +const mockToastAdd = vi.hoisted(() => vi.fn()) +vi.mock('@/app/components/base/ui/toast', () => ({ + toast: { add: mockToastAdd }, })) vi.mock('@/app/components/header/indicator', () => ({ diff --git a/web/app/components/tools/provider/custom-create-card.tsx b/web/app/components/tools/provider/custom-create-card.tsx index bf86a1f833..f09d8e45d9 100644 --- a/web/app/components/tools/provider/custom-create-card.tsx +++ b/web/app/components/tools/provider/custom-create-card.tsx @@ -5,7 +5,7 @@ import { } from '@remixicon/react' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal' import { useAppContext } from '@/context/app-context' import { createCustomCollection } from '@/service/tools' @@ -21,9 +21,9 @@ const Contribute = ({ onRefreshData }: Props) => { const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false) const doCreateCustomToolCollection = async (data: CustomCollectionBackend) => { await createCustomCollection(data) - Toast.notify({ + toast.add({ type: 'success', - message: t('api.actionSuccess', { ns: 'common' }), + title: t('api.actionSuccess', { ns: 'common' }), }) setIsShowEditCustomCollectionModal(false) onRefreshData() diff --git a/web/app/components/tools/provider/detail.tsx b/web/app/components/tools/provider/detail.tsx index e25bcacb9b..626a80a57b 100644 --- a/web/app/components/tools/provider/detail.tsx +++ b/web/app/components/tools/provider/detail.tsx @@ -13,7 +13,7 @@ import Confirm from '@/app/components/base/confirm' import Drawer from '@/app/components/base/drawer' import { LinkExternal02, Settings01 } from '@/app/components/base/icons/src/vender/line/general' import Loading from '@/app/components/base/loading' -import Toast from '@/app/components/base/toast' +import { toast } from '@/app/components/base/ui/toast' import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import Indicator from '@/app/components/header/indicator' import Icon from '@/app/components/plugins/card/base/card-icon' @@ -122,18 +122,18 @@ const ProviderDetail = ({ await getCustomProvider() // Use fresh data from form submission to avoid race condition with collection.labels setCustomCollection(prev => prev ? { ...prev, labels: data.labels } : null) - Toast.notify({ + toast.add({ type: 'success', - message: t('api.actionSuccess', { ns: 'common' }), + title: t('api.actionSuccess', { ns: 'common' }), }) setIsShowEditCustomCollectionModal(false) } const doRemoveCustomToolCollection = async () => { await removeCustomCollection(collection?.name as string) onRefreshData() - Toast.notify({ + toast.add({ type: 'success', - message: t('api.actionSuccess', { ns: 'common' }), + title: t('api.actionSuccess', { ns: 'common' }), }) setIsShowEditCustomCollectionModal(false) } @@ -161,9 +161,9 @@ const ProviderDetail = ({ const removeWorkflowToolProvider = async () => { await deleteWorkflowTool(collection.id) onRefreshData() - Toast.notify({ + toast.add({ type: 'success', - message: t('api.actionSuccess', { ns: 'common' }), + title: t('api.actionSuccess', { ns: 'common' }), }) setIsShowEditWorkflowToolModal(false) } @@ -175,9 +175,9 @@ const ProviderDetail = ({ invalidateAllWorkflowTools() onRefreshData() getWorkflowToolProvider() - Toast.notify({ + toast.add({ type: 'success', - message: t('api.actionSuccess', { ns: 'common' }), + title: t('api.actionSuccess', { ns: 'common' }), }) setIsShowEditWorkflowToolModal(false) } @@ -385,18 +385,18 @@ const ProviderDetail = ({ onCancel={() => setShowSettingAuth(false)} onSaved={async (value) => { await updateBuiltInToolCredential(collection.name, value) - Toast.notify({ + toast.add({ type: 'success', - message: t('api.actionSuccess', { ns: 'common' }), + title: t('api.actionSuccess', { ns: 'common' }), }) await onRefreshData() setShowSettingAuth(false) }} onRemove={async () => { await removeBuiltInToolCredential(collection.name) - Toast.notify({ + toast.add({ type: 'success', - message: t('api.actionSuccess', { ns: 'common' }), + title: t('api.actionSuccess', { ns: 'common' }), }) await onRefreshData() setShowSettingAuth(false) diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index 1b4b9c2ff8..fb0da9b649 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -5928,9 +5928,6 @@ } }, "app/components/tools/edit-custom-collection-modal/get-schema.tsx": { - "no-restricted-imports": { - "count": 1 - }, "tailwindcss/enforce-consistent-class-order": { "count": 3 }, @@ -6056,7 +6053,7 @@ }, "app/components/tools/mcp/modal.tsx": { "no-restricted-imports": { - "count": 2 + "count": 1 }, "tailwindcss/enforce-consistent-class-order": { "count": 7 @@ -6097,16 +6094,13 @@ } }, "app/components/tools/provider/custom-create-card.tsx": { - "no-restricted-imports": { - "count": 1 - }, "tailwindcss/enforce-consistent-class-order": { "count": 1 } }, "app/components/tools/provider/detail.tsx": { "no-restricted-imports": { - "count": 2 + "count": 1 }, "tailwindcss/enforce-consistent-class-order": { "count": 10 diff --git a/web/i18n/en-US/dataset.json b/web/i18n/en-US/dataset.json index 538517dccd..72d0a7b909 100644 --- a/web/i18n/en-US/dataset.json +++ b/web/i18n/en-US/dataset.json @@ -77,6 +77,8 @@ "externalKnowledgeDescriptionPlaceholder": "Describe what's in this Knowledge Base (optional)", "externalKnowledgeForm.cancel": "Cancel", "externalKnowledgeForm.connect": "Connect", + "externalKnowledgeForm.connectedFailed": "Failed to connect External Knowledge Base", + "externalKnowledgeForm.connectedSuccess": "External Knowledge Base Connected Successfully", "externalKnowledgeId": "External Knowledge ID", "externalKnowledgeIdPlaceholder": "Please enter the Knowledge ID", "externalKnowledgeName": "External Knowledge Name", diff --git a/web/i18n/en-US/tools.json b/web/i18n/en-US/tools.json index 30ee4f58df..391e109317 100644 --- a/web/i18n/en-US/tools.json +++ b/web/i18n/en-US/tools.json @@ -126,6 +126,8 @@ "mcp.modal.headerValuePlaceholder": "e.g., Bearer token123", "mcp.modal.headers": "Headers", "mcp.modal.headersTip": "Additional HTTP headers to send with MCP server requests", + "mcp.modal.invalidServerIdentifier": "Please enter a valid server identifier", + "mcp.modal.invalidServerUrl": "Please enter a valid server URL", "mcp.modal.maskedHeadersTip": "Header values are masked for security. Changes will update the actual values.", "mcp.modal.name": "Name & Icon", "mcp.modal.namePlaceholder": "Name your MCP server",