Merge remote-tracking branch 'origin/main' into feat/support-agent-sandbox

This commit is contained in:
yyh
2026-03-25 12:45:48 +08:00
55 changed files with 342 additions and 448 deletions

View File

@ -49,9 +49,12 @@ vi.mock('@/service/use-tools', () => ({
// Mock Toast - need to verify notification calls
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: {
success: (message: string) => mockToastNotify({ type: 'success', message }),
error: (message: string) => mockToastNotify({ type: 'error', message }),
warning: (message: string) => mockToastNotify({ type: 'warning', message }),
info: (message: string) => mockToastNotify({ type: 'info', message }),
},
}))

View File

@ -33,9 +33,12 @@ vi.mock('@/service/use-tools', () => ({
}))
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: {
success: (message: string) => mockToastNotify({ type: 'success', message }),
error: (message: string) => mockToastNotify({ type: 'error', message }),
warning: (message: string) => mockToastNotify({ type: 'warning', message }),
info: (message: string) => mockToastNotify({ type: 'info', message }),
},
}))

View File

@ -3,7 +3,7 @@ import type { InputVar, Variable } from '@/app/components/workflow/types'
import type { PublishWorkflowParams } from '@/types/workflow'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import { useAppContext } from '@/context/app-context'
import { useRouter } from '@/next/navigation'
import { createWorkflowToolProvider, saveWorkflowToolProvider } from '@/service/tools'
@ -188,14 +188,11 @@ export function useConfigureButton(options: UseConfigureButtonOptions) {
invalidateAllWorkflowTools()
onRefreshData?.()
invalidateDetail(workflowAppId)
Toast.notify({
type: 'success',
message: t('api.actionSuccess', { ns: 'common' }),
})
toast.success(t('api.actionSuccess', { ns: 'common' }))
setShowModal(false)
}
catch (e) {
Toast.notify({ type: 'error', message: (e as Error).message })
toast.error((e as Error).message)
}
}
@ -209,14 +206,11 @@ export function useConfigureButton(options: UseConfigureButtonOptions) {
onRefreshData?.()
invalidateAllWorkflowTools()
invalidateDetail(workflowAppId)
Toast.notify({
type: 'success',
message: t('api.actionSuccess', { ns: 'common' }),
})
toast.success(t('api.actionSuccess', { ns: 'common' }))
setShowModal(false)
}
catch (e) {
Toast.notify({ type: 'error', message: (e as Error).message })
toast.error((e as Error).message)
}
}

View File

@ -12,8 +12,8 @@ import Drawer from '@/app/components/base/drawer-plus'
import EmojiPicker from '@/app/components/base/emoji-picker'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import Toast from '@/app/components/base/toast'
import Tooltip from '@/app/components/base/tooltip'
import { toast } from '@/app/components/base/ui/toast'
import LabelSelector from '@/app/components/tools/labels/selector'
import ConfirmModal from '@/app/components/tools/workflow-tool/confirm-modal'
import MethodSelector from '@/app/components/tools/workflow-tool/method-selector'
@ -129,10 +129,7 @@ const WorkflowToolAsModal: FC<Props> = ({
errorMessage = t('createTool.nameForToolCall', { ns: 'tools' }) + t('createTool.nameForToolCallTip', { ns: 'tools' })
if (errorMessage) {
Toast.notify({
type: 'error',
message: errorMessage,
})
toast.error(errorMessage)
return
}

View File

@ -114,9 +114,12 @@ vi.mock('@/service/use-tools', () => ({
useInvalidateAllMCPTools: vi.fn(),
}))
vi.mock('@/app/components/base/toast', () => ({
default: {
notify: (payload: unknown) => mockNotify(payload),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: (message: string) => mockNotify({ type: 'success', message }),
error: (message: string) => mockNotify({ type: 'error', message }),
warning: (message: string) => mockNotify({ type: 'warning', message }),
info: (message: string) => mockNotify({ type: 'info', message }),
},
}))

View File

@ -16,7 +16,7 @@ import {
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import SearchBox from '@/app/components/plugins/marketplace/search-box'
import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal'
import AllTools from '@/app/components/workflow/block-selector/all-tools'
@ -161,10 +161,7 @@ const ToolPicker: FC<Props> = ({
const doCreateCustomToolCollection = async (data: CustomCollectionBackend) => {
await createCustomCollection(data)
Toast.notify({
type: 'success',
message: t('api.actionSuccess', { ns: 'common' }),
})
toast.success(t('api.actionSuccess', { ns: 'common' }))
hideEditCustomCollectionModal()
handleAddedCustomTool()
}

View File

@ -60,9 +60,12 @@ vi.mock('@/service/use-workflow', () => ({
}),
}))
vi.mock('../../../base/toast', () => ({
default: {
notify: (payload: unknown) => mockNotify(payload),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: (message: string) => mockNotify({ type: 'success', message }),
error: (message: string) => mockNotify({ type: 'error', message }),
warning: (message: string) => mockNotify({ type: 'warning', message }),
info: (message: string) => mockNotify({ type: 'info', message }),
},
}))

View File

@ -46,10 +46,13 @@ vi.mock('../../hooks/use-dynamic-test-run-options', () => ({
useDynamicTestRunOptions: () => mockDynamicOptions,
}))
vi.mock('@/app/components/base/toast/context', () => ({
useToastContext: () => ({
notify: mockNotify,
}),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: (message: string) => mockNotify({ type: 'success', message }),
error: (message: string) => mockNotify({ type: 'error', message }),
warning: (message: string) => mockNotify({ type: 'warning', message }),
info: (message: string) => mockNotify({ type: 'info', message }),
},
}))
vi.mock('@/app/components/base/amplitude', () => ({

View File

@ -5,11 +5,11 @@ import {
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import { useFeaturesStore } from '@/app/components/base/features/hooks'
import { toast } from '@/app/components/base/ui/toast'
import { useSelector as useAppContextSelector } from '@/context/app-context'
import useTheme from '@/hooks/use-theme'
import { useInvalidAllLastRun } from '@/service/use-workflow'
import { cn } from '@/utils/classnames'
import Toast from '../../base/toast'
import {
useLeaderRestore,
useWorkflowRun,
@ -83,23 +83,17 @@ const HeaderInRestoring = ({
conversationVariables,
}, {
onSuccess: () => {
Toast.notify({
type: 'success',
message: t('versionHistory.action.restoreSuccess', { ns: 'workflow' }),
})
toast.success(t('versionHistory.action.restoreSuccess', { ns: 'workflow' }))
deleteAllInspectVars()
invalidAllLastRun()
},
onError: () => {
Toast.notify({
type: 'error',
message: t('versionHistory.action.restoreFailure', { ns: 'workflow' }),
})
toast.error(t('versionHistory.action.restoreFailure', { ns: 'workflow' }))
},
onSettled: () => {
onRestoreSettled?.()
},
})
deleteAllInspectVars()
invalidAllLastRun()
}, [canRestore, currentVersion, featuresStore, setShowWorkflowVersionHistoryPanel, workflowStore, requestRestore, userProfile, deleteAllInspectVars, invalidAllLastRun, t, onRestoreSettled])
return (

View File

@ -5,7 +5,7 @@ import { useCallback, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { trackEvent } from '@/app/components/base/amplitude'
import { StopCircle } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import { useToastContext } from '@/app/components/base/toast/context'
import { toast } from '@/app/components/base/ui/toast'
import { useWorkflowRun, useWorkflowRunValidation, useWorkflowStartRun } from '@/app/components/workflow/hooks'
import ShortcutsName from '@/app/components/workflow/shortcuts-name'
import { useStore } from '@/app/components/workflow/store'
@ -41,7 +41,6 @@ const RunMode = ({
const dynamicOptions = useDynamicTestRunOptions()
const testRunMenuRef = useRef<TestRunMenuRef>(null)
const { notify } = useToastContext()
useEffect(() => {
// @ts-expect-error - Dynamic property for backward compatibility with keyboard shortcuts
@ -66,7 +65,7 @@ const RunMode = ({
isValid = false
})
if (!isValid) {
notify({ type: 'error', message: t('panel.checklistTip', { ns: 'workflow' }) })
toast.error(t('panel.checklistTip', { ns: 'workflow' }))
return
}
@ -98,7 +97,7 @@ const RunMode = ({
// Placeholder for trigger-specific execution logic for schedule, webhook, plugin types
console.log('TODO: Handle trigger execution for type:', option.type, 'nodeId:', option.nodeId)
}
}, [warningNodes, notify, t, handleWorkflowStartRunInWorkflow, handleWorkflowTriggerScheduleRunInWorkflow, handleWorkflowTriggerWebhookRunInWorkflow, handleWorkflowTriggerPluginRunInWorkflow, handleWorkflowRunAllTriggersInWorkflow])
}, [warningNodes, t, handleWorkflowStartRunInWorkflow, handleWorkflowTriggerScheduleRunInWorkflow, handleWorkflowTriggerWebhookRunInWorkflow, handleWorkflowTriggerPluginRunInWorkflow, handleWorkflowRunAllTriggersInWorkflow])
const { eventEmitter } = useEventEmitterContextContext()
eventEmitter?.useSubscription((v: any) => {

View File

@ -89,8 +89,13 @@ vi.mock('../index', () => ({
useNodesMetaData: () => ({ nodes: [], nodesMap: mockNodesMap }),
}))
vi.mock('@/app/components/base/toast/context', () => ({
useToastContext: () => ({ notify: vi.fn() }),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
info: vi.fn(),
},
}))
vi.mock('@/context/i18n', () => ({

View File

@ -27,7 +27,7 @@ import {
import { useTranslation } from 'react-i18next'
import { useEdges, useStoreApi } from 'reactflow'
import { useStore as useAppStore } from '@/app/components/app/store'
import { useToastContext } from '@/app/components/base/toast/context'
import { toast } from '@/app/components/base/ui/toast'
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
@ -337,7 +337,6 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => {
export const useChecklistBeforePublish = () => {
const { t } = useTranslation()
const language = useGetLanguage()
const { notify } = useToastContext()
const queryClient = useQueryClient()
const store = useStoreApi()
const { nodesMap: nodesExtraData } = useNodesMetaData()
@ -402,7 +401,7 @@ export const useChecklistBeforePublish = () => {
const { validNodes, maxDepth } = getValidTreeNodes(filteredNodes, edges)
if (maxDepth > MAX_TREE_DEPTH) {
notify({ type: 'error', message: t('common.maxTreeDepth', { ns: 'workflow', depth: MAX_TREE_DEPTH }) })
toast.error(t('common.maxTreeDepth', { ns: 'workflow', depth: MAX_TREE_DEPTH }))
return false
}
@ -500,7 +499,7 @@ export const useChecklistBeforePublish = () => {
isModelProviderInstalled: isLLMModelProviderInstalled(modelProvider, installedPluginIds),
})
if (modelIssue === LLMModelIssueCode.providerPluginUnavailable) {
notify({ type: 'error', message: `[${node.data.title}] ${t('errorMsg.configureModel', { ns: 'workflow' })}` })
toast.error(`[${node.data.title}] ${t('errorMsg.configureModel', { ns: 'workflow' })}`)
return false
}
}
@ -512,7 +511,7 @@ export const useChecklistBeforePublish = () => {
const { errorMessage } = nodeMetaData.checkValid(checkData, t, moreDataForCheckValid)
if (errorMessage) {
notify({ type: 'error', message: `[${node.data.title}] ${errorMessage}` })
toast.error(`[${node.data.title}] ${errorMessage}`)
return false
}
@ -520,7 +519,7 @@ export const useChecklistBeforePublish = () => {
for (const variable of usedVars) {
if (!isValueSelectorInNodeOutputVars(variable, availableVars)) {
notify({ type: 'error', message: `[${node.data.title}] ${t('errorMsg.invalidVariable', { ns: 'workflow' })}` })
toast.error(`[${node.data.title}] ${t('errorMsg.invalidVariable', { ns: 'workflow' })}`)
return false
}
}
@ -531,7 +530,7 @@ export const useChecklistBeforePublish = () => {
const isUnconnected = !validNodes.some(n => n.id === node.id)
if (isUnconnected && !canSkipConnectionCheck) {
notify({ type: 'error', message: `[${node.data.title}] ${t('common.needConnectTip', { ns: 'workflow' })}` })
toast.error(`[${node.data.title}] ${t('common.needConnectTip', { ns: 'workflow' })}`)
return false
}
}
@ -539,7 +538,7 @@ export const useChecklistBeforePublish = () => {
if (shouldCheckStartNode) {
const startNodesFiltered = nodes.filter(node => START_NODE_TYPES.includes(node.data.type as BlockEnum))
if (startNodesFiltered.length === 0) {
notify({ type: 'error', message: t('common.needStartNode', { ns: 'workflow' }) })
toast.error(t('common.needStartNode', { ns: 'workflow' }))
return false
}
}
@ -550,13 +549,13 @@ export const useChecklistBeforePublish = () => {
const type = isRequiredNodesType[i]
if (!filteredNodes.some(node => node.data.type === type)) {
notify({ type: 'error', message: t('common.needAdd', { ns: 'workflow', node: t(`blocks.${type}` as I18nKeysWithPrefix<'workflow', 'blocks.'>, { ns: 'workflow' }) }) })
toast.error(t('common.needAdd', { ns: 'workflow', node: t(`blocks.${type}` as I18nKeysWithPrefix<'workflow', 'blocks.'>, { ns: 'workflow' }) }))
return false
}
}
return true
}, [store, workflowStore, getNodesAvailableVarList, shouldCheckStartNode, nodesExtraData, notify, t, updateDatasetsDetail, buildInTools, customTools, workflowTools, language, getCheckData, queryClient, strategyProviders, modelProviders])
}, [store, workflowStore, getNodesAvailableVarList, shouldCheckStartNode, nodesExtraData, t, updateDatasetsDetail, buildInTools, customTools, workflowTools, language, getCheckData, queryClient, strategyProviders, modelProviders])
return {
handleCheckBeforePublish,
@ -568,15 +567,14 @@ export const useWorkflowRunValidation = () => {
const nodes = useNodes()
const edges = useEdges<CommonEdgeType>()
const needWarningNodes = useChecklist(nodes, edges)
const { notify } = useToastContext()
const validateBeforeRun = useCallback(() => {
if (needWarningNodes.length > 0) {
notify({ type: 'error', message: t('panel.checklistTip', { ns: 'workflow' }) })
toast.error(t('panel.checklistTip', { ns: 'workflow' }))
return false
}
return true
}, [needWarningNodes, notify, t])
}, [needWarningNodes, t])
return {
validateBeforeRun,

View File

@ -19,11 +19,13 @@ vi.mock('@/app/components/base/file-uploader/hooks', () => ({
useFileSizeLimit: vi.fn(),
}))
vi.mock('@/app/components/base/toast/context', () => ({
useToastContext: () => ({
notify: vi.fn(),
close: vi.fn(),
}),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
info: vi.fn(),
},
}))
const createPayload = (overrides: Partial<UploadFileSetting> = {}): UploadFileSetting => ({

View File

@ -10,7 +10,7 @@ import { useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import Split from '@/app/components/workflow/nodes/_base/components/split'
import SingleRunForm from '@/app/components/workflow/nodes/human-input/components/single-run-form'
import { BlockEnum, InputVarType } from '@/app/components/workflow/types'
@ -126,10 +126,7 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
})
})
if (errMsg) {
Toast.notify({
message: errMsg,
type: 'error',
})
toast.error(errMsg)
return
}
@ -147,10 +144,7 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
})
})
if (parseErrorJsonField) {
Toast.notify({
message: t('errorMsg.invalidJson', { ns: 'workflow', field: parseErrorJsonField }),
type: 'error',
})
toast.error(t('errorMsg.invalidJson', { ns: 'workflow', field: parseErrorJsonField }))
return
}

View File

@ -6,7 +6,7 @@ import type { CommonNodeType, ValueSelector } from '@/app/components/workflow/ty
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useStoreApi } from 'reactflow'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import {
useNodesSyncDraft,
} from '@/app/components/workflow/hooks'
@ -183,7 +183,7 @@ const useLastRun = <T>({
return false
const message = warningForNode.errorMessages[0] || 'This node has unresolved checklist issues'
Toast.notify({ type: 'error', message })
toast.error(message)
return true
}, [warningNodes, currentNodeId])
@ -304,13 +304,10 @@ const useLastRun = <T>({
if (!inspectVarValue) {
const nodeLabel = getContextNodeLabel(nodeId)
|| t('nodes.llm.contextUnknownNode', { ns: 'workflow' })
Toast.notify({
type: 'error',
message: t('nodes.llm.contextMissing', {
ns: 'workflow',
nodeName: nodeLabel,
}),
})
toast.error(t('nodes.llm.contextMissing', {
ns: 'workflow',
nodeName: nodeLabel,
}))
return false
}
}
@ -328,13 +325,10 @@ const useLastRun = <T>({
const dependentVars = singleRunParams?.getDependentVars?.()
const nullOutput = getNullDependentOutput(dependentVars)
if (nullOutput) {
Toast.notify({
type: 'error',
message: t('singleRun.subgraph.nullOutputError', {
ns: 'workflow',
output: formatSubgraphOutputLabel(nullOutput),
}),
})
toast.error(t('singleRun.subgraph.nullOutputError', {
ns: 'workflow',
output: formatSubgraphOutputLabel(nullOutput),
}))
return
}
setNodeRunning()
@ -442,13 +436,10 @@ const useLastRun = <T>({
const dependentVars = singleRunParams?.getDependentVars?.()
const nullOutput = getNullDependentOutput(dependentVars)
if (nullOutput) {
Toast.notify({
type: 'error',
message: t('singleRun.subgraph.nullOutputError', {
ns: 'workflow',
output: formatSubgraphOutputLabel(nullOutput),
}),
})
toast.error(t('singleRun.subgraph.nullOutputError', {
ns: 'workflow',
output: formatSubgraphOutputLabel(nullOutput),
}))
return
}
if (blockType === BlockEnum.TriggerWebhook || blockType === BlockEnum.TriggerPlugin || blockType === BlockEnum.TriggerSchedule)

View File

@ -13,7 +13,7 @@ import {
import { useShallow } from 'zustand/react/shallow'
import { trackEvent } from '@/app/components/base/amplitude'
import { getInputVars as doGetInputVars } from '@/app/components/base/prompt-editor/constants'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import {
useIsChatMode,
useNodeDataUpdate,
@ -429,14 +429,14 @@ const useOneStepRun = <T>({
})
if (!response) {
const message = 'Schedule trigger run failed'
Toast.notify({ type: 'error', message })
const message = t('common.scheduleTriggerRunFailed', { ns: 'workflow' })
toast.error(message)
throw new Error(message)
}
if (response?.status === 'error') {
const message = response?.message || 'Schedule trigger run failed'
Toast.notify({ type: 'error', message })
const message = response?.message || t('common.scheduleTriggerRunFailed', { ns: 'workflow' })
toast.error(message)
throw new Error(message)
}
@ -461,10 +461,10 @@ const useOneStepRun = <T>({
_singleRunningStatus: NodeRunningStatus.Failed,
},
})
Toast.notify({ type: 'error', message: 'Schedule trigger run failed' })
toast.error(t('common.scheduleTriggerRunFailed', { ns: 'workflow' }))
throw error
}
}, [flowId, id, handleNodeDataUpdate, data])
}, [flowId, id, handleNodeDataUpdate, data, t])
const runWebhookSingleRun = useCallback(async (): Promise<any | null> => {
const urlPath = `/apps/${flowId}/workflows/draft/nodes/${id}/trigger/run`
@ -486,8 +486,8 @@ const useOneStepRun = <T>({
return null
if (!response) {
const message = response?.message || 'Webhook debug failed'
Toast.notify({ type: 'error', message })
const message = response?.message || t('common.webhookDebugFailed', { ns: 'workflow' })
toast.error(message)
cancelWebhookSingleRun()
throw new Error(message)
}
@ -514,8 +514,8 @@ const useOneStepRun = <T>({
}
if (response?.status === 'error') {
const message = response.message || 'Webhook debug failed'
Toast.notify({ type: 'error', message })
const message = response.message || t('common.webhookDebugFailed', { ns: 'workflow' })
toast.error(message)
cancelWebhookSingleRun()
throw new Error(message)
}
@ -538,7 +538,7 @@ const useOneStepRun = <T>({
if (controller.signal.aborted)
return null
Toast.notify({ type: 'error', message: 'Webhook debug request failed' })
toast.error(t('common.webhookDebugRequestFailed', { ns: 'workflow' }))
cancelWebhookSingleRun()
if (error instanceof Error)
throw error
@ -550,7 +550,7 @@ const useOneStepRun = <T>({
}
return null
}, [flowId, id, data, handleNodeDataUpdate, cancelWebhookSingleRun])
}, [flowId, id, data, handleNodeDataUpdate, cancelWebhookSingleRun, t])
const runPluginSingleRun = useCallback(async (): Promise<any | null> => {
const urlPath = `/apps/${flowId}/workflows/draft/nodes/${id}/trigger/run`
@ -585,14 +585,14 @@ const useOneStepRun = <T>({
if (controller.signal.aborted)
return null
Toast.notify({ type: 'error', message: requestError.message })
toast.error(requestError.message)
cancelPluginSingleRun()
throw requestError
}
if (!response) {
const message = 'Plugin debug failed'
Toast.notify({ type: 'error', message })
toast.error(message)
cancelPluginSingleRun()
throw new Error(message)
}
@ -619,7 +619,7 @@ const useOneStepRun = <T>({
if (response?.status === 'error') {
const message = response.message || 'Plugin debug failed'
Toast.notify({ type: 'error', message })
toast.error(message)
cancelPluginSingleRun()
throw new Error(message)
}
@ -652,10 +652,8 @@ const useOneStepRun = <T>({
_isSingleRun: false,
},
})
Toast.notify({
type: 'error',
message: res.errorMessage || '',
})
if (res.errorMessage)
toast.error(res.errorMessage)
}
return res
}, [checkValid, data, handleNodeDataUpdate, id, t, moreDataForCheckValid])

View File

@ -1,15 +1,16 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { toast } from '@/app/components/base/ui/toast'
import { BodyPayloadValueType, BodyType } from '../../types'
import CurlPanel from '../curl-panel'
import * as curlParser from '../curl-parser'
const {
mockHandleNodeSelect,
mockNotify,
mockToastError,
} = vi.hoisted(() => ({
mockHandleNodeSelect: vi.fn(),
mockNotify: vi.fn(),
mockToastError: vi.fn(),
}))
vi.mock('@/app/components/workflow/hooks', () => ({
@ -18,9 +19,9 @@ vi.mock('@/app/components/workflow/hooks', () => ({
}),
}))
vi.mock('@/app/components/base/toast', () => ({
default: {
notify: mockNotify,
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
error: mockToastError,
},
}))
@ -131,9 +132,7 @@ describe('curl-panel', () => {
await user.type(screen.getByRole('textbox'), 'invalid')
await user.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({
type: 'error',
}))
expect(vi.mocked(toast.error)).toHaveBeenCalledWith(expect.stringContaining('Invalid cURL command'))
})
it('should keep the panel open when parsing returns no node and no error', async () => {
@ -159,7 +158,7 @@ describe('curl-panel', () => {
expect(onHide).not.toHaveBeenCalled()
expect(handleCurlImport).not.toHaveBeenCalled()
expect(mockHandleNodeSelect).not.toHaveBeenCalled()
expect(mockNotify).not.toHaveBeenCalled()
expect(vi.mocked(toast.error)).not.toHaveBeenCalled()
})
})
})

View File

@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import Modal from '@/app/components/base/modal'
import Textarea from '@/app/components/base/textarea'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import { useNodesInteractions } from '@/app/components/workflow/hooks'
import { parseCurl } from './curl-parser'
@ -26,10 +26,7 @@ const CurlPanel: FC<Props> = ({ nodeId, isShow, onHide, handleCurlImport }) => {
const handleSave = useCallback(() => {
const { node, error } = parseCurl(inputString)
if (error) {
Toast.notify({
type: 'error',
message: error,
})
toast.error(error)
return
}
if (!node)

View File

@ -12,7 +12,7 @@ import Divider from '@/app/components/base/divider'
import Input from '@/app/components/base/input'
import Modal from '@/app/components/base/modal'
import Switch from '@/app/components/base/switch'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
import MailBodyInput from './mail-body-input'
import Recipient from './recipient'
@ -45,31 +45,22 @@ const EmailConfigureModal = ({
const checkValidConfig = useCallback(() => {
if (!subject.trim()) {
Toast.notify({
type: 'error',
message: 'subject is required',
})
toast.error(t(`${i18nPrefix}.deliveryMethod.emailConfigure.subjectRequired`, { ns: 'workflow' }))
return false
}
if (!body.trim()) {
Toast.notify({
type: 'error',
message: 'body is required',
})
toast.error(t(`${i18nPrefix}.deliveryMethod.emailConfigure.bodyRequired`, { ns: 'workflow' }))
return false
}
if (!/\{\{#url#\}\}/.test(body.trim())) {
Toast.notify({
type: 'error',
message: `body must contain one ${t('promptEditor.requestURL.item.title', { ns: 'common' })}`,
})
toast.error(t(`${i18nPrefix}.deliveryMethod.emailConfigure.bodyMustContainRequestURL`, {
ns: 'workflow',
field: t('promptEditor.requestURL.item.title', { ns: 'common' }),
}))
return false
}
if (!recipients || (recipients.items.length === 0 && !recipients.whole_workspace)) {
Toast.notify({
type: 'error',
message: 'recipients is required',
})
toast.error(t(`${i18nPrefix}.deliveryMethod.emailConfigure.recipientsRequired`, { ns: 'workflow' }))
return false
}
return true

View File

@ -7,7 +7,7 @@ import * as React 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/toast'
import { toast } from '@/app/components/base/ui/toast'
import ButtonStyleDropdown from './button-style-dropdown'
const i18nPrefix = 'nodes.humanInput'
@ -47,14 +47,14 @@ const UserActionItem: FC<UserActionItemProps> = ({
.join('')
if (sanitized !== withUnderscores) {
Toast.notify({ type: 'error', message: t(`${i18nPrefix}.userActions.actionIdFormatTip`, { ns: 'workflow' }) })
toast.error(t(`${i18nPrefix}.userActions.actionIdFormatTip`, { ns: 'workflow' }))
return
}
// Limit to 20 characters
if (sanitized.length > ACTION_ID_MAX_LENGTH) {
sanitized = sanitized.slice(0, ACTION_ID_MAX_LENGTH)
Toast.notify({ type: 'error', message: t(`${i18nPrefix}.userActions.actionIdTooLong`, { ns: 'workflow', maxLength: ACTION_ID_MAX_LENGTH }) })
toast.error(t(`${i18nPrefix}.userActions.actionIdTooLong`, { ns: 'workflow', maxLength: ACTION_ID_MAX_LENGTH }))
}
if (sanitized)
@ -65,7 +65,7 @@ const UserActionItem: FC<UserActionItemProps> = ({
let value = e.target.value
if (value.length > BUTTON_TEXT_MAX_LENGTH) {
value = value.slice(0, BUTTON_TEXT_MAX_LENGTH)
Toast.notify({ type: 'error', message: t(`${i18nPrefix}.userActions.buttonTextTooLong`, { ns: 'workflow', maxLength: BUTTON_TEXT_MAX_LENGTH }) })
toast.error(t(`${i18nPrefix}.userActions.buttonTextTooLong`, { ns: 'workflow', maxLength: BUTTON_TEXT_MAX_LENGTH }))
}
onChange({ ...data, title: value })
}

View File

@ -16,8 +16,8 @@ import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Button from '@/app/components/base/button'
import Divider from '@/app/components/base/divider'
import Toast from '@/app/components/base/toast'
import Tooltip from '@/app/components/base/tooltip'
import { toast } from '@/app/components/base/ui/toast'
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
import Split from '@/app/components/workflow/nodes/_base/components/split'
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
@ -132,7 +132,7 @@ const Panel: FC<NodePanelProps<HumanInputNodeType>> = ({
className="flex size-6 cursor-pointer items-center justify-center rounded-md hover:bg-components-button-ghost-bg-hover"
onClick={() => {
copy(inputs.form_content)
Toast.notify({ type: 'success', message: t('actionMsg.copySuccessfully', { ns: 'common' }) })
toast.success(t('actionMsg.copySuccessfully', { ns: 'common' }))
}}
>
<RiClipboardLine className="h-4 w-4 text-text-secondary" />

View File

@ -3,7 +3,7 @@ import type { IterationNodeType } from '../types'
import type { PanelProps } from '@/types/workflow'
import { fireEvent, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import { ErrorHandleMode } from '@/app/components/workflow/types'
import { BlockEnum, VarType } from '../../../types'
import AddBlock from '../add-block'
@ -15,6 +15,15 @@ const mockHandleNodeAdd = vi.fn()
const mockHandleNodeIterationRerender = vi.fn()
let mockNodesReadOnly = false
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
info: vi.fn(),
},
}))
vi.mock('reactflow', async () => {
const actual = await vi.importActual<typeof import('reactflow')>('reactflow')
return {
@ -102,7 +111,7 @@ vi.mock('../use-config', () => ({
}))
const mockUseConfig = vi.mocked(useConfig)
const mockToastNotify = vi.spyOn(Toast, 'notify').mockImplementation(() => ({}))
const mockToastWarning = vi.mocked(toast.warning)
const createData = (overrides: Partial<IterationNodeType> = {}): IterationNodeType => ({
title: 'Iteration',
@ -191,11 +200,7 @@ describe('iteration path', () => {
expect(screen.getByRole('button', { name: 'select-block' })).toBeInTheDocument()
expect(screen.getByTestId('iteration-background-iteration-node')).toBeInTheDocument()
expect(mockHandleNodeIterationRerender).toHaveBeenCalledWith('iteration-node')
expect(mockToastNotify).toHaveBeenCalledWith({
type: 'warning',
message: 'workflow.nodes.iteration.answerNodeWarningDesc',
duration: 5000,
})
expect(mockToastWarning).toHaveBeenCalledWith('workflow.nodes.iteration.answerNodeWarningDesc')
})
it('should wire panel input, output, parallel, numeric, error mode, and flatten actions', async () => {

View File

@ -12,7 +12,7 @@ import {
useNodesInitialized,
useViewport,
} from 'reactflow'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import { cn } from '@/utils/classnames'
import { IterationStartNodeDumb } from '../iteration-start'
import AddBlock from './add-block'
@ -34,11 +34,7 @@ const Node: FC<NodeProps<IterationNodeType>> = ({
if (nodesInitialized)
handleNodeIterationRerender(id)
if (data.is_parallel && showTips) {
Toast.notify({
type: 'warning',
message: t(`${i18nPrefix}.answerNodeWarningDesc`, { ns: 'workflow' }),
duration: 5000,
})
toast.warning(t(`${i18nPrefix}.answerNodeWarningDesc`, { ns: 'workflow' }))
setShowTips(false)
}
}, [nodesInitialized, id, handleNodeIterationRerender, data.is_parallel, showTips, t])

View File

@ -4,7 +4,7 @@ import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import Divider from '@/app/components/base/divider'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import { JSON_SCHEMA_MAX_DEPTH } from '@/config'
import { cn } from '@/utils/classnames'
import { SegmentedControl } from '../../../../../base/segmented-control'
@ -196,10 +196,7 @@ const JsonSchemaConfig: FC<JsonSchemaConfigProps> = ({
}
else if (currentTab === SchemaView.VisualEditor) {
if (advancedEditing || isAddingNewField) {
Toast.notify({
type: 'warning',
message: t('nodes.llm.jsonSchema.warningTips.saveSchema', { ns: 'workflow' }),
})
toast.warning(t('nodes.llm.jsonSchema.warningTips.saveSchema', { ns: 'workflow' }))
return
}
}

View File

@ -9,7 +9,7 @@ import {
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { STORAGE_KEYS } from '@/config/storage-keys'
@ -110,10 +110,7 @@ const JsonSchemaGenerator: FC<JsonSchemaGeneratorProps> = ({
const generateSchema = useCallback(async () => {
const { output, error } = await generateStructuredOutputRules({ instruction, model_config: model! })
if (error) {
Toast.notify({
type: 'error',
message: error,
})
toast.error(error)
setSchema(null)
setView(GeneratorView.promptEditor)
return

View File

@ -3,7 +3,8 @@ import type { Field } from '../../../types'
import type { EditData } from './edit-card'
import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import Toast from '@/app/components/base/toast'
import { useTranslation } from 'react-i18next'
import { toast } from '@/app/components/base/ui/toast'
import { ArrayType, FILE_REF_FORMAT, Type } from '../../../types'
import { findPropertyWithPath } from '../../../utils'
import { useMittContext } from './context'
@ -22,6 +23,7 @@ type AddEventParams = {
export const useSchemaNodeOperations = (props: VisualEditorProps) => {
const { schema: jsonSchema, onChange: doOnChange } = props
const { t } = useTranslation()
const onChange = doOnChange || noop
const backupSchema = useVisualEditorStore(state => state.backupSchema)
const setBackupSchema = useVisualEditorStore(state => state.setBackupSchema)
@ -65,10 +67,7 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
if (schema.type === Type.object) {
const properties = schema.properties || {}
if (properties[newName]) {
Toast.notify({
type: 'error',
message: 'Property name already exists',
})
toast.error(t('nodes.llm.jsonSchema.fieldNameAlreadyExists', { ns: 'workflow' }))
emit('restorePropertyName')
return
}
@ -92,10 +91,7 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
if (schema.type === Type.array && schema.items && schema.items.type === Type.object) {
const properties = schema.items.properties || {}
if (properties[newName]) {
Toast.notify({
type: 'error',
message: 'Property name already exists',
})
toast.error(t('nodes.llm.jsonSchema.fieldNameAlreadyExists', { ns: 'workflow' }))
emit('restorePropertyName')
return
}
@ -279,10 +275,7 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
if (oldName !== newName) {
const properties = parentSchema.properties
if (properties[newName]) {
Toast.notify({
type: 'error',
message: 'Property name already exists',
})
toast.error(t('nodes.llm.jsonSchema.fieldNameAlreadyExists', { ns: 'workflow' }))
samePropertyNameError = true
}
@ -382,10 +375,7 @@ export const useSchemaNodeOperations = (props: VisualEditorProps) => {
if (oldName !== newName) {
const properties = parentSchema.items.properties || {}
if (properties[newName]) {
Toast.notify({
type: 'error',
message: 'Property name already exists',
})
toast.error(t('nodes.llm.jsonSchema.fieldNameAlreadyExists', { ns: 'workflow' }))
samePropertyNameError = true
}

View File

@ -8,8 +8,8 @@ import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import AddButton2 from '@/app/components/base/button/add-button'
import Switch from '@/app/components/base/switch'
import Toast from '@/app/components/base/toast'
import Tooltip from '@/app/components/base/tooltip'
import { toast } from '@/app/components/base/ui/toast'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
import Field from '@/app/components/workflow/nodes/_base/components/field'
@ -151,11 +151,11 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
)
const keys = Object.keys(removedDetails)
if (keys.length)
Toast.notify({ type: 'warning', message: `${t('modelProvider.parametersInvalidRemoved', { ns: 'common' })}: ${keys.map(k => `${k} (${removedDetails[k]})`).join(', ')}` })
toast.warning(`${t('modelProvider.parametersInvalidRemoved', { ns: 'common' })}: ${keys.map(k => `${k} (${removedDetails[k]})`).join(', ')}`)
handleCompletionParamsChange(filtered)
}
catch {
Toast.notify({ type: 'error', message: t('error', { ns: 'common' }) })
toast.error(t('error', { ns: 'common' }))
handleCompletionParamsChange({})
}
finally {

View File

@ -181,10 +181,12 @@ vi.mock('@/app/components/workflow/nodes/_base/components/editor/code-editor', (
),
}))
vi.mock('@/app/components/base/toast', () => ({
__esModule: true,
default: {
notify: (payload: unknown) => mockToastNotify(payload),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: (message: string) => mockToastNotify({ type: 'success', message }),
error: (message: string) => mockToastNotify({ type: 'error', message }),
warning: (message: string) => mockToastNotify({ type: 'warning', message }),
info: (message: string) => mockToastNotify({ type: 'info', message }),
},
}))

View File

@ -7,7 +7,7 @@ import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Input from '@/app/components/base/input'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import { ValueType, VarType } from '@/app/components/workflow/types'
import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var'
import FormItem from './form-item'
@ -28,10 +28,7 @@ const Item = ({
const checkVariableName = (value: string) => {
const { isValid, errorMessageKey } = checkKeys([value], false)
if (!isValid) {
Toast.notify({
type: 'error',
message: t(`varKeyError.${errorMessageKey}`, { ns: 'appDebug', key: t('env.modal.name', { ns: 'workflow' }) }),
})
toast.error(t(`varKeyError.${errorMessageKey}`, { ns: 'appDebug', key: t('env.modal.name', { ns: 'workflow' }) }))
return false
}
return true

View File

@ -6,7 +6,7 @@ import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/
import type { PanelProps } from '@/types/workflow'
import { fireEvent, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import {
useTextGenerationCurrentProviderAndModelAndModelList,
} from '@/app/components/header/account-setting/model-provider-page/hooks'
@ -36,6 +36,15 @@ let mockWorkflowTools: MockToolCollection[] = []
let mockSelectedToolInfo: ToolDefaultValue | undefined
let mockBlockSelectorOpen = false
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
info: vi.fn(),
},
}))
vi.mock('@/app/components/workflow/block-selector', () => ({
__esModule: true,
default: ({
@ -254,7 +263,7 @@ vi.mock('../use-config', () => ({
const mockUseTextGeneration = vi.mocked(useTextGenerationCurrentProviderAndModelAndModelList)
const mockUseConfig = vi.mocked(useConfig)
const mockToastNotify = vi.spyOn(Toast, 'notify').mockImplementation(() => ({}))
const mockToastError = vi.mocked(toast.error)
const createToolParameter = (overrides: Partial<ToolParameter> = {}): ToolParameter => ({
name: 'city',
@ -356,7 +365,7 @@ const panelProps: PanelProps = {
describe('parameter-extractor path', () => {
beforeEach(() => {
vi.clearAllMocks()
mockToastNotify.mockClear()
mockToastError.mockClear()
mockBuiltInTools = []
mockCustomTools = []
mockWorkflowTools = []
@ -582,7 +591,7 @@ describe('parameter-extractor path', () => {
await user.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(onSave).not.toHaveBeenCalled()
expect(mockToastNotify).toHaveBeenCalled()
expect(mockToastError).toHaveBeenCalled()
})
it('should render the add trigger for new parameters', () => {
@ -614,7 +623,7 @@ describe('parameter-extractor path', () => {
const descriptionInput = screen.getByPlaceholderText('workflow.nodes.parameterExtractor.addExtractParameterContent.descriptionPlaceholder')
fireEvent.change(nameInput, { target: { value: '1bad' } })
expect(mockToastNotify).toHaveBeenCalled()
expect(mockToastError).toHaveBeenCalled()
expect(nameInput).toHaveValue('')
fireEvent.change(nameInput, { target: { value: 'temporary_name' } })
@ -649,7 +658,7 @@ describe('parameter-extractor path', () => {
await user.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(onSave).not.toHaveBeenCalled()
expect(mockToastNotify).toHaveBeenCalled()
expect(mockToastError).toHaveBeenCalled()
})
it('should keep rename metadata and updated options when editing a select parameter', async () => {

View File

@ -15,7 +15,7 @@ import Modal from '@/app/components/base/modal'
import Select from '@/app/components/base/select'
import Switch from '@/app/components/base/switch'
import Textarea from '@/app/components/base/textarea'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import { ChangeType } from '@/app/components/workflow/types'
import { checkKeys } from '@/utils/var'
import { ParamType } from '../../types'
@ -54,10 +54,7 @@ const AddExtractParameter: FC<Props> = ({
if (key === 'name') {
const { isValid, errorKey, errorMessageKey } = checkKeys([value], true)
if (!isValid) {
Toast.notify({
type: 'error',
message: t(`varKeyError.${errorMessageKey}`, { ns: 'appDebug', key: errorKey }),
})
toast.error(t(`varKeyError.${errorMessageKey}`, { ns: 'appDebug', key: errorKey }))
return
}
}
@ -106,10 +103,7 @@ const AddExtractParameter: FC<Props> = ({
errMessage = t(`${errorI18nPrefix}.fieldRequired`, { ns: 'workflow', field: t(`${i18nPrefix}.addExtractParameterContent.description`, { ns: 'workflow' }) })
if (errMessage) {
Toast.notify({
type: 'error',
message: errMessage,
})
toast.error(errMessage)
return false
}
return true

View File

@ -7,7 +7,7 @@ import * as React from 'react'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { ReactSortable } from 'react-sortablejs'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import { ChangeType } from '@/app/components/workflow/types'
import { cn } from '@/utils/classnames'
import { hasDuplicateStr } from '@/utils/var'
@ -43,10 +43,7 @@ const VarList: FC<Props> = ({
}
if (errorMsgKey && typeName) {
Toast.notify({
type: 'error',
message: t(errorMsgKey, { ns: 'appDebug', key: t(typeName, { ns: 'appDebug' }) }),
})
toast.error(t(errorMsgKey, { ns: 'appDebug', key: t(typeName, { ns: 'appDebug' }) }))
return false
}
onChange(newList, moreInfo ? { index, payload: moreInfo } : undefined)

View File

@ -4,7 +4,7 @@ import { useBoolean } from 'ahooks'
import { produce } from 'immer'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import {
useIsChatMode,
useNodesReadOnly,
@ -97,10 +97,7 @@ const useConfig = (id: string, payload: StartNodeType) => {
}
if (errorMsgKey && typeName) {
Toast.notify({
type: 'error',
message: t(errorMsgKey, { ns: 'appDebug', key: t(typeName, { ns: 'appDebug' }) }),
})
toast.error(t(errorMsgKey, { ns: 'appDebug', key: t(typeName, { ns: 'appDebug' }) }))
return false
}
setInputs(newInputs)

View File

@ -5,7 +5,7 @@ import { capitalize } from 'es-toolkit/string'
import { produce } from 'immer'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { CollectionType } from '@/app/components/tools/types'
import {
@ -66,10 +66,7 @@ const useConfig = (id: string, payload: ToolNodeType) => {
async (value: any) => {
await updateBuiltInToolCredential(currCollection?.name as string, value)
Toast.notify({
type: 'success',
message: t('api.actionSuccess', { ns: 'common' }),
})
toast.success(t('api.actionSuccess', { ns: 'common' }))
invalidToolsByType()
hideSetAuthModal()
},

View File

@ -1,7 +1,7 @@
import type { WebhookTriggerNodeType } from '../types'
import { renderHook } from '@testing-library/react'
import { useStore as useAppStore } from '@/app/components/app/store'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import { BlockEnum, VarType } from '@/app/components/workflow/types'
import { fetchWebhookUrl } from '@/service/apps'
import { createNodeCrudModuleMock } from '../../__tests__/use-config-test-utils'
@ -18,10 +18,10 @@ vi.mock('react-i18next', () => ({
}),
}))
vi.mock('@/app/components/base/toast', () => ({
vi.mock('@/app/components/base/ui/toast', () => ({
__esModule: true,
default: {
notify: vi.fn(),
toast: {
error: vi.fn(),
},
}))
@ -42,7 +42,7 @@ vi.mock('@/service/apps', () => ({
}))
const mockedFetchWebhookUrl = vi.mocked(fetchWebhookUrl)
const mockedToastNotify = vi.mocked(Toast.notify)
const mockedToastError = vi.mocked(toast.error)
const createPayload = (overrides: Partial<WebhookTriggerNodeType> = {}): WebhookTriggerNodeType => ({
title: 'Webhook',
@ -148,7 +148,7 @@ describe('useConfig', () => {
}),
]),
}))
expect(mockedToastNotify).toHaveBeenCalledTimes(1)
expect(mockedToastError).toHaveBeenCalledTimes(1)
})
it('should generate webhook urls once and fall back to empty url on request failure', async () => {

View File

@ -8,7 +8,6 @@ import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import InputWithCopy from '@/app/components/base/input-with-copy'
import { SimpleSelect } from '@/app/components/base/select'
import Toast from '@/app/components/base/toast'
import Tooltip from '@/app/components/base/tooltip'
import {
NumberField,
@ -18,6 +17,7 @@ import {
NumberFieldIncrement,
NumberFieldInput,
} from '@/app/components/base/ui/number-field'
import { toast } from '@/app/components/base/ui/toast'
import Field from '@/app/components/workflow/nodes/_base/components/field'
import OutputVars from '@/app/components/workflow/nodes/_base/components/output-vars'
import Split from '@/app/components/workflow/nodes/_base/components/split'
@ -103,10 +103,7 @@ const Panel: FC<NodePanelProps<WebhookTriggerNodeType>> = ({
placeholder={t(`${i18nPrefix}.webhookUrlPlaceholder`, { ns: 'workflow' })}
readOnly
onCopy={() => {
Toast.notify({
type: 'success',
message: t(`${i18nPrefix}.urlCopied`, { ns: 'workflow' }),
})
toast.success(t(`${i18nPrefix}.urlCopied`, { ns: 'workflow' }))
}}
/>
</div>

View File

@ -2,7 +2,7 @@ import type { HttpMethod, WebhookHeader, WebhookParameter, WebhookTriggerNodeTyp
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useStore as useAppStore } from '@/app/components/app/store'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import { useNodesReadOnly, useWorkflow } from '@/app/components/workflow/hooks'
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
import { fetchWebhookUrl } from '@/service/apps'
@ -33,10 +33,7 @@ export const useConfig = (id: string, payload: WebhookTriggerNodeType) => {
? t(key as never, { ns: 'appDebug', key: fieldLabel })
: t('varKeyError.keyAlreadyExists', { ns: 'appDebug', key: fieldLabel })
Toast.notify({
type: 'error',
message,
})
toast.error(message)
}, [t])
const handleMethodChange = useCallback((method: HttpMethod) => {

View File

@ -3,7 +3,7 @@ import type { VariableAssignerNodeType } from '../types'
import type { PanelProps } from '@/types/workflow'
import { fireEvent, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import { renderWorkflowFlowComponent } from '@/app/components/workflow/__tests__/workflow-test-env'
import { BlockEnum, VarType } from '@/app/components/workflow/types'
import AddVariable from '../components/add-variable'
@ -19,6 +19,15 @@ const mockHandleGroupItemMouseEnter = vi.fn()
const mockHandleGroupItemMouseLeave = vi.fn()
const mockGetAvailableVars = vi.fn()
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
info: vi.fn(),
},
}))
vi.mock('@/app/components/workflow/nodes/_base/components/add-variable-popup', () => ({
default: ({ onSelect }: any) => (
<button
@ -102,6 +111,7 @@ vi.mock('../use-config', () => ({
}))
const mockUseConfig = vi.mocked(useConfig)
const mockToastError = vi.mocked(toast.error)
const createData = (overrides: Partial<VariableAssignerNodeType> = {}): VariableAssignerNodeType => ({
title: 'Variable Assigner',
@ -168,7 +178,6 @@ describe('variable-assigner path', () => {
},
])
mockUseConfig.mockReturnValue(createConfigResult())
vi.spyOn(Toast, 'notify').mockImplementation(() => ({}))
})
describe('Path Integration', () => {
@ -296,7 +305,7 @@ describe('variable-assigner path', () => {
await user.click(screen.getByText('Group1'))
fireEvent.change(screen.getByDisplayValue('Group1'), { target: { value: '1bad' } })
expect(Toast.notify).toHaveBeenCalled()
expect(mockToastError).toHaveBeenCalled()
fireEvent.change(screen.getByDisplayValue('Group1'), { target: { value: 'Renamed Group' } })
expect(onGroupNameChange).toHaveBeenCalledWith('Renamed_Group')

View File

@ -11,7 +11,7 @@ import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { Folder } from '@/app/components/base/icons/src/vender/line/files'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import Field from '@/app/components/workflow/nodes/_base/components/field'
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
import { VarType } from '@/app/components/workflow/types'
@ -94,10 +94,7 @@ const VarGroupItem: FC<Props> = ({
const value = e.target.value
const { isValid, errorKey, errorMessageKey } = checkKeys([value], false)
if (!isValid) {
Toast.notify({
type: 'error',
message: t(`varKeyError.${errorMessageKey}`, { ns: 'appDebug', key: errorKey }),
})
toast.error(t(`varKeyError.${errorMessageKey}`, { ns: 'appDebug', key: errorKey }))
return
}
onGroupNameChange?.(value)

View File

@ -17,11 +17,13 @@ vi.mock('next/navigation', () => ({
useParams: () => ({}),
}))
vi.mock('@/app/components/base/toast/context', () => ({
useToastContext: () => ({
notify: mockNotify,
close: vi.fn(),
}),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: (message: string) => mockNotify({ type: 'success', message }),
error: (message: string) => mockNotify({ type: 'error', message }),
warning: (message: string) => mockNotify({ type: 'warning', message }),
info: (message: string) => mockNotify({ type: 'info', message }),
},
}))
vi.mock('@/app/components/base/chat/chat/check-input-forms-hooks', () => ({

View File

@ -4,8 +4,7 @@ import { produce } from 'immer'
import * as React from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { ToastContext } from '@/app/components/base/toast/context'
import { toast } from '@/app/components/base/ui/toast'
import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button'
import VariableTypeSelector from '@/app/components/workflow/panel/chat-variable-panel/components/variable-type-select'
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
@ -33,19 +32,21 @@ const ObjectValueItem: FC<Props> = ({
onChange,
}) => {
const { t } = useTranslation()
const { notify } = useContext(ToastContext)
const [isFocus, setIsFocus] = useState(false)
const handleKeyChange = useCallback((index: number) => {
return (e: React.ChangeEvent<HTMLInputElement>) => {
if (!/^\w+$/.test(e.target.value)) {
toast.error(t('chatVariable.modal.objectKeyPatternError', { ns: 'workflow' }))
return
}
const newList = produce(list, (draft: any[]) => {
if (!/^\w+$/.test(e.target.value))
return notify({ type: 'error', message: 'key is can only contain letters, numbers and underscores' })
draft[index].key = e.target.value
})
onChange(newList)
}
}, [list, notify, onChange])
}, [list, onChange, t])
const handleTypeChange = useCallback((index: number) => {
return (type: ChatVarType) => {

View File

@ -3,11 +3,10 @@ import { RiCloseLine, RiDraftLine, RiInputField } from '@remixicon/react'
import * as React from 'react'
import { useCallback, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { v4 as uuid4 } from 'uuid'
import Button from '@/app/components/base/button'
import Input from '@/app/components/base/input'
import { ToastContext } from '@/app/components/base/toast/context'
import { toast } from '@/app/components/base/ui/toast'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import ArrayValueList from '@/app/components/workflow/panel/chat-variable-panel/components/array-value-list'
@ -57,7 +56,6 @@ const ChatVariableModal = ({
onSave,
}: ModalPropsType) => {
const { t } = useTranslation()
const { notify } = useContext(ToastContext)
const workflowStore = useWorkflowStore()
const [name, setName] = React.useState('')
const [type, setType] = React.useState<ChatVarType>(ChatVarType.String)
@ -129,10 +127,7 @@ const ChatVariableModal = ({
const checkVariableName = (value: string) => {
const { isValid, errorMessageKey } = checkKeys([value], false)
if (!isValid) {
notify({
type: 'error',
message: t(`varKeyError.${errorMessageKey}`, { ns: 'appDebug', key: t('env.modal.name', { ns: 'workflow' }) }),
})
toast.error(t(`varKeyError.${errorMessageKey}`, { ns: 'appDebug', key: t('env.modal.name', { ns: 'workflow' }) }))
return false
}
return true
@ -240,11 +235,9 @@ const ChatVariableModal = ({
return
const varList = workflowStore.getState().conversationVariables
if (!chatVar && varList.some(chatVar => chatVar.name === name))
return notify({ type: 'error', message: 'name is existed' })
// if (type !== ChatVarType.Object && !value)
// return notify({ type: 'error', message: 'value can not be empty' })
return toast.error(t('varKeyError.keyAlreadyExists', { ns: 'appDebug', key: t('chatVariable.modal.name', { ns: 'workflow' }) }))
if (type === ChatVarType.Object && objectValue.some(item => !item.key && !!item.value))
return notify({ type: 'error', message: 'object key can not be empty' })
return toast.error(t('chatVariable.modal.objectKeyRequired', { ns: 'workflow' }))
onSave({
id: chatVar ? chatVar.id : uuid4(),

View File

@ -27,8 +27,13 @@ vi.mock('@/service/workflow', () => ({
submitHumanInputForm: (...args: any[]) => mockSubmitHumanInputForm(...args),
}))
vi.mock('@/app/components/base/toast/context', () => ({
useToastContext: () => ({ notify: mockNotify }),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
info: (...args: any[]) => mockNotify(...args),
},
}))
vi.mock('reactflow', () => ({

View File

@ -26,8 +26,13 @@ vi.mock('@/service/workflow', () => ({
submitHumanInputForm: (...args: any[]) => mockSubmitHumanInputForm(...args),
}))
vi.mock('@/app/components/base/toast/context', () => ({
useToastContext: () => ({ notify: mockNotify }),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
info: (...args: any[]) => mockNotify(...args),
},
}))
vi.mock('reactflow', () => ({
@ -96,7 +101,7 @@ describe('useChat handleSend', () => {
expect(returned).toBe(false)
})
expect(mockNotify).toHaveBeenCalledWith(expect.objectContaining({ type: 'info' }))
expect(mockNotify).toHaveBeenCalledWith('appDebug.errorMessage.waitForResponse')
})
it('should set isResponding to true after sending', () => {

View File

@ -27,8 +27,13 @@ vi.mock('@/service/workflow', () => ({
submitHumanInputForm: (...args: any[]) => mockSubmitHumanInputForm(...args),
}))
vi.mock('@/app/components/base/toast/context', () => ({
useToastContext: () => ({ notify: mockNotify }),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
info: (...args: any[]) => mockNotify(...args),
},
}))
vi.mock('reactflow', () => ({

View File

@ -27,8 +27,13 @@ vi.mock('@/service/workflow', () => ({
submitHumanInputForm: (...args: any[]) => mockSubmitHumanInputForm(...args),
}))
vi.mock('@/app/components/base/toast/context', () => ({
useToastContext: () => ({ notify: mockNotify }),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
info: (...args: any[]) => mockNotify(...args),
},
}))
vi.mock('reactflow', () => ({

View File

@ -27,8 +27,13 @@ vi.mock('@/service/workflow', () => ({
submitHumanInputForm: (...args: any[]) => mockSubmitHumanInputForm(...args),
}))
vi.mock('@/app/components/base/toast/context', () => ({
useToastContext: () => ({ notify: mockNotify }),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
info: (...args: any[]) => mockNotify(...args),
},
}))
vi.mock('reactflow', () => ({

View File

@ -26,8 +26,13 @@ vi.mock('@/service/workflow', () => ({
submitHumanInputForm: (...args: any[]) => mockSubmitHumanInputForm(...args),
}))
vi.mock('@/app/components/base/toast/context', () => ({
useToastContext: () => ({ notify: mockNotify }),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
info: (...args: any[]) => mockNotify(...args),
},
}))
vi.mock('reactflow', () => ({

View File

@ -26,7 +26,7 @@ import {
getProcessedFiles,
getProcessedFilesFromResponse,
} from '@/app/components/base/file-uploader/utils'
import { useToastContext } from '@/app/components/base/toast/context'
import { toast } from '@/app/components/base/ui/toast'
import {
CUSTOM_NODE,
} from '@/app/components/workflow/constants'
@ -57,7 +57,6 @@ export const useChat = (
stopChat?: (taskId: string) => void,
) => {
const { t } = useTranslation()
const { notify } = useToastContext()
const { handleRun } = useWorkflowRun()
const hasStopResponded = useRef(false)
const workflowStore = useWorkflowStore()
@ -259,7 +258,7 @@ export const useChat = (
}: SendCallback,
) => {
if (isRespondingRef.current) {
notify({ type: 'info', message: t('errorMessage.waitForResponse', { ns: 'appDebug' }) })
toast.info(t('errorMessage.waitForResponse', { ns: 'appDebug' }))
return false
}
@ -659,7 +658,7 @@ export const useChat = (
},
},
)
}, [threadMessages, chatTree.length, updateCurrentQAOnTree, handleResponding, formSettings?.inputsForm, handleRun, notify, t, workflowStore, fetchInspectVars, invalidAllLastRun, config?.suggested_questions_after_answer?.enabled])
}, [threadMessages, chatTree.length, updateCurrentQAOnTree, handleResponding, formSettings?.inputsForm, handleRun, t, workflowStore, fetchInspectVars, invalidAllLastRun, config?.suggested_questions_after_answer?.enabled])
const handleSubmitHumanInputForm = async (formToken: string, formData: any) => {
await submitHumanInputForm(formToken, formData)

View File

@ -1,11 +1,10 @@
import type { ReactElement } from 'react'
import type { IToastProps } from '@/app/components/base/toast/context'
import type { Shape } from '@/app/components/workflow/store/workflow'
import type { EnvironmentVariable } from '@/app/components/workflow/types'
import { fireEvent, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import * as React from 'react'
import { ToastContext } from '@/app/components/base/toast/context'
import { toast } from '@/app/components/base/ui/toast'
import { WorkflowContext } from '@/app/components/workflow/context'
import { createWorkflowStore } from '@/app/components/workflow/store/workflow'
import EnvItem from '../env-item'
@ -16,6 +15,17 @@ vi.mock('uuid', () => ({
v4: () => 'env-created',
}))
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
info: vi.fn(),
},
}))
const mockToastError = vi.mocked(toast.error)
const createEnv = (overrides: Partial<EnvironmentVariable> = {}): EnvironmentVariable => ({
id: 'env-1',
name: 'api_key',
@ -29,27 +39,22 @@ const renderWithProviders = (
ui: ReactElement,
options: {
storeState?: Partial<Shape>
notify?: (props: IToastProps) => void
} = {},
) => {
const store = createWorkflowStore({})
const notify = options.notify ?? vi.fn<(props: IToastProps) => void>()
if (options.storeState)
store.setState(options.storeState)
const result = render(
<ToastContext.Provider value={{ notify, close: vi.fn() }}>
<WorkflowContext.Provider value={store}>
{ui}
</WorkflowContext.Provider>
</ToastContext.Provider>,
<WorkflowContext.Provider value={store}>
{ui}
</WorkflowContext.Provider>,
)
return {
...result,
store,
notify,
}
}
@ -153,33 +158,27 @@ describe('EnvPanel integration', () => {
it('should reject invalid and duplicate variable names', async () => {
const user = userEvent.setup()
const notify = vi.fn()
renderWithProviders(
<VariableModal onClose={vi.fn()} onSave={vi.fn()} />,
{
storeState: {
environmentVariables: [createEnv({ id: 'env-existing', name: 'duplicated', value_type: 'string', value: '1' })],
},
notify,
},
)
fireEvent.change(screen.getByPlaceholderText('workflow.env.modal.namePlaceholder'), {
target: { value: '1bad' },
})
expect(notify).toHaveBeenCalled()
expect(mockToastError).toHaveBeenCalled()
notify.mockClear()
mockToastError.mockClear()
await user.clear(screen.getByPlaceholderText('workflow.env.modal.namePlaceholder'))
await user.type(screen.getByPlaceholderText('workflow.env.modal.namePlaceholder'), 'duplicated')
await user.type(screen.getByPlaceholderText('workflow.env.modal.valuePlaceholder'), '42')
await user.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(notify).toHaveBeenCalledWith({
type: 'error',
message: 'name is existed',
})
expect(mockToastError).toHaveBeenCalledWith('appDebug.varKeyError.keyAlreadyExists:{"key":"workflow.env.modal.name"}')
})
it('should load existing secret values and convert them to numbers when editing', async () => {

View File

@ -3,12 +3,11 @@ import { RiCloseLine } from '@remixicon/react'
import * as React from 'react'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { v4 as uuid4 } from 'uuid'
import Button from '@/app/components/base/button'
import Input from '@/app/components/base/input'
import { ToastContext } from '@/app/components/base/toast/context'
import Tooltip from '@/app/components/base/tooltip'
import { toast } from '@/app/components/base/ui/toast'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { cn } from '@/utils/classnames'
import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } from '@/utils/var'
@ -24,7 +23,6 @@ const VariableModal = ({
onSave,
}: ModalPropsType) => {
const { t } = useTranslation()
const { notify } = useContext(ToastContext)
const workflowStore = useWorkflowStore()
const [type, setType] = React.useState<'string' | 'number' | 'secret'>('string')
const [name, setName] = React.useState('')
@ -34,10 +32,7 @@ const VariableModal = ({
const checkVariableName = (value: string) => {
const { isValid, errorMessageKey } = checkKeys([value], false)
if (!isValid) {
notify({
type: 'error',
message: t(`varKeyError.${errorMessageKey}`, { ns: 'appDebug', key: t('env.modal.name', { ns: 'workflow' }) }),
})
toast.error(t(`varKeyError.${errorMessageKey}`, { ns: 'appDebug', key: t('env.modal.name', { ns: 'workflow' }) }))
return false
}
return true
@ -54,15 +49,15 @@ const VariableModal = ({
if (!checkVariableName(name))
return
if (!value)
return notify({ type: 'error', message: 'value can not be empty' })
return toast.error(t('env.modal.valueRequired', { ns: 'workflow' }))
// Add check for duplicate name when editing
const envList = workflowStore.getState().environmentVariables
if (env && env.name !== name && envList.some(e => e.name === name))
return notify({ type: 'error', message: 'name is existed' })
return toast.error(t('varKeyError.keyAlreadyExists', { ns: 'appDebug', key: t('env.modal.name', { ns: 'workflow' }) }))
// Original check for create new variable
if (!env && envList.some(e => e.name === name))
return notify({ type: 'error', message: 'name is existed' })
return toast.error(t('varKeyError.keyAlreadyExists', { ns: 'appDebug', key: t('env.modal.name', { ns: 'workflow' }) }))
onSave({
id: env ? env.id : uuid4(),

View File

@ -4,9 +4,8 @@ import type { WorkflowRunDetailResponse } from '@/models/log'
import type { NodeTracing } from '@/types/workflow'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import Loading from '@/app/components/base/loading'
import { ToastContext } from '@/app/components/base/toast/context'
import { toast } from '@/app/components/base/ui/toast'
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
import { fetchRunDetail, fetchTracingList } from '@/service/log'
import { cn } from '@/utils/classnames'
@ -32,7 +31,6 @@ const RunPanel: FC<RunProps> = ({
tracingListUrl,
}) => {
const { t } = useTranslation()
const { notify } = useContext(ToastContext)
const [currentTab, setCurrentTab] = useState<string>(activeTab)
const [loading, setLoading] = useState<boolean>(true)
const [runDetail, setRunDetail] = useState<WorkflowRunDetailResponse>()
@ -55,12 +53,9 @@ const RunPanel: FC<RunProps> = ({
getResultCallback(res)
}
catch (err) {
notify({
type: 'error',
message: `${err}`,
})
toast.error(`${err}`)
}
}, [notify, getResultCallback, runDetailUrl])
}, [getResultCallback, runDetailUrl])
const getTracingList = useCallback(async () => {
try {
@ -70,12 +65,9 @@ const RunPanel: FC<RunProps> = ({
setList(nodeList)
}
catch (err) {
notify({
type: 'error',
message: `${err}`,
})
toast.error(`${err}`)
}
}, [notify, tracingListUrl])
}, [tracingListUrl])
const getData = useCallback(async () => {
setLoading(true)

View File

@ -18,12 +18,11 @@ import {
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import Uploader from '@/app/components/app/create-from-dsl-modal/uploader'
import { useStore as useAppStore } from '@/app/components/app/store'
import Button from '@/app/components/base/button'
import Modal from '@/app/components/base/modal'
import { ToastContext } from '@/app/components/base/toast/context'
import { toast } from '@/app/components/base/ui/toast'
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import {
@ -56,7 +55,6 @@ const UpdateDSLModal = ({
onImport,
}: UpdateDSLModalProps) => {
const { t } = useTranslation()
const { notify } = useContext(ToastContext)
const appDetail = useAppStore(s => s.appDetail)
const [currentFile, setDSLFile] = useState<File>()
const [fileContent, setFileContent] = useState<string>()
@ -128,13 +126,13 @@ const UpdateDSLModal = ({
return nodeType !== undefined && invalidNodes.includes(nodeType)
})
if (hasInvalidNode) {
notify({ type: 'error', message: t('common.importFailure', { ns: 'workflow' }) })
toast.error(t('common.importFailure', { ns: 'workflow' }))
return false
}
return true
}
catch {
notify({ type: 'error', message: t('common.importFailure', { ns: 'workflow' }) })
toast.error(t('common.importFailure', { ns: 'workflow' }))
return false
}
}
@ -154,17 +152,13 @@ const UpdateDSLModal = ({
if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) {
if (!app_id) {
notify({ type: 'error', message: t('common.importFailure', { ns: 'workflow' }) })
toast.error(t('common.importFailure', { ns: 'workflow' }))
return
}
handleWorkflowUpdate(app_id)
if (onImport)
onImport()
notify({
type: status === DSLImportStatus.COMPLETED ? 'success' : 'warning',
message: t(status === DSLImportStatus.COMPLETED ? 'common.importSuccess' : 'common.importWarning', { ns: 'workflow' }),
children: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('common.importWarningDetails', { ns: 'workflow' }),
})
toast(t(status === DSLImportStatus.COMPLETED ? 'common.importSuccess' : 'common.importWarning', { ns: 'workflow' }), { type: status === DSLImportStatus.COMPLETED ? 'success' : 'warning', description: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('common.importWarningDetails', { ns: 'workflow' }) })
await handleCheckPluginDependencies(app_id)
setLoading(false)
onCancel()
@ -182,17 +176,17 @@ const UpdateDSLModal = ({
}
else {
setLoading(false)
notify({ type: 'error', message: t('common.importFailure', { ns: 'workflow' }) })
toast.error(t('common.importFailure', { ns: 'workflow' }))
}
}
}
// eslint-disable-next-line unused-imports/no-unused-vars
catch (e) {
setLoading(false)
notify({ type: 'error', message: t('common.importFailure', { ns: 'workflow' }) })
toast.error(t('common.importFailure', { ns: 'workflow' }))
}
isCreatingRef.current = false
}, [currentFile, fileContent, onCancel, notify, t, appDetail, onImport, handleWorkflowUpdate, handleCheckPluginDependencies])
}, [currentFile, fileContent, onCancel, t, appDetail, onImport, handleWorkflowUpdate, handleCheckPluginDependencies])
const onUpdateDSLConfirm: MouseEventHandler = async () => {
try {
@ -206,7 +200,7 @@ const UpdateDSLModal = ({
if (status === DSLImportStatus.COMPLETED) {
if (!app_id) {
notify({ type: 'error', message: t('common.importFailure', { ns: 'workflow' }) })
toast.error(t('common.importFailure', { ns: 'workflow' }))
return
}
handleWorkflowUpdate(app_id)
@ -215,19 +209,19 @@ const UpdateDSLModal = ({
await handleCheckPluginDependencies(app_id)
if (onImport)
onImport()
notify({ type: 'success', message: t('common.importSuccess', { ns: 'workflow' }) })
toast.success(t('common.importSuccess', { ns: 'workflow' }))
setLoading(false)
onCancel()
}
else if (status === DSLImportStatus.FAILED) {
setLoading(false)
notify({ type: 'error', message: t('common.importFailure', { ns: 'workflow' }) })
toast.error(t('common.importFailure', { ns: 'workflow' }))
}
}
// eslint-disable-next-line unused-imports/no-unused-vars
catch (e) {
setLoading(false)
notify({ type: 'error', message: t('common.importFailure', { ns: 'workflow' }) })
toast.error(t('common.importFailure', { ns: 'workflow' }))
}
}