Compare commits

...

4 Commits

Author SHA1 Message Date
yyh
2ad7f53c5b Merge branch 'main' into chore/workflow-toast-overlay-migration
# Conflicts:
#	web/app/components/workflow/panel/debug-and-preview/__tests__/hooks/opening-statement.spec.ts
2026-03-24 14:40:55 +08:00
yyh
e4fc606e44 fix(workflow): localize workflow toast messages 2026-03-24 14:02:20 +08:00
yyh
43d22289d1 Update web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-24 13:57:48 +08:00
yyh
2603ef8eca refactor(workflow): migrate legacy toast usage to ui toast 2026-03-24 13:42:52 +08:00
53 changed files with 330 additions and 435 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'
@@ -137,10 +137,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

@@ -4,11 +4,11 @@ import {
} from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import { toast } from '@/app/components/base/ui/toast'
import useTheme from '@/hooks/use-theme'
import { useInvalidAllLastRun, useRestoreWorkflow } from '@/service/use-workflow'
import { getFlowPrefix } from '@/service/utils'
import { cn } from '@/utils/classnames'
import Toast from '../../base/toast'
import {
useWorkflowRefreshDraft,
useWorkflowRun,
@@ -65,18 +65,12 @@ const HeaderInRestoring = ({
workflowStore.setState({ isRestoring: false })
workflowStore.setState({ backupDraft: undefined })
handleRefreshWorkflowDraft()
Toast.notify({
type: 'success',
message: t('versionHistory.action.restoreSuccess', { ns: 'workflow' }),
})
toast.success(t('versionHistory.action.restoreSuccess', { ns: 'workflow' }))
deleteAllInspectVars()
invalidAllLastRun()
}
catch {
Toast.notify({
type: 'error',
message: t('versionHistory.action.restoreFailure', { ns: 'workflow' }),
})
toast.error(t('versionHistory.action.restoreFailure', { ns: 'workflow' }))
}
finally {
onRestoreSettled?.()

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'
@@ -325,7 +325,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()
@@ -390,7 +389,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
}
@@ -488,7 +487,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
}
}
@@ -497,7 +496,7 @@ export const useChecklistBeforePublish = () => {
const { errorMessage } = nodesExtraData![node.data.type as BlockEnum].checkValid(checkData, t, moreDataForCheckValid)
if (errorMessage) {
notify({ type: 'error', message: `[${node.data.title}] ${errorMessage}` })
toast.error(`[${node.data.title}] ${errorMessage}`)
return false
}
@@ -510,12 +509,12 @@ export const useChecklistBeforePublish = () => {
if (usedNode) {
const usedVar = usedNode.vars.find(v => v.variable === variable?.[1])
if (!usedVar) {
notify({ type: 'error', message: `[${node.data.title}] ${t('errorMsg.invalidVariable', { ns: 'workflow' })}` })
toast.error(`[${node.data.title}] ${t('errorMsg.invalidVariable', { ns: 'workflow' })}`)
return false
}
}
else {
notify({ type: 'error', message: `[${node.data.title}] ${t('errorMsg.invalidVariable', { ns: 'workflow' })}` })
toast.error(`[${node.data.title}] ${t('errorMsg.invalidVariable', { ns: 'workflow' })}`)
return false
}
}
@@ -526,7 +525,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
}
}
@@ -534,7 +533,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
}
}
@@ -545,13 +544,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,
@@ -563,15 +562,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

@@ -3,7 +3,7 @@ import type { Params as OneStepRunParams } from '@/app/components/workflow/nodes
// import
import type { CommonNodeType, ValueSelector } from '@/app/components/workflow/types'
import { useCallback, useEffect, useState } from 'react'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import {
useNodesSyncDraft,
} from '@/app/components/workflow/hooks'
@@ -163,7 +163,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, id])

View File

@@ -12,7 +12,7 @@ import {
} from 'reactflow'
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,
@@ -410,14 +410,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)
}
@@ -442,10 +442,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`
@@ -467,8 +467,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)
}
@@ -495,8 +495,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)
}
@@ -519,7 +519,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
@@ -531,7 +531,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`
@@ -566,14 +566,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)
}
@@ -600,7 +600,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)
}
@@ -633,10 +633,8 @@ const useOneStepRun = <T>({
_isSingleRun: false,
},
})
Toast.notify({
type: 'error',
message: res.errorMessage || '',
})
if (res.errorMessage)
toast.error(res.errorMessage)
}
return res
}

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 { BodyPayloadValueType, BodyType, Method } from '../types'
@@ -124,10 +124,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 useTheme from '@/hooks/use-theme'
@@ -112,10 +112,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, 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
}
@@ -267,10 +263,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
}
@@ -358,10 +351,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

@@ -7,8 +7,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 Field from '@/app/components/workflow/nodes/_base/components/field'
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
@@ -98,11 +98,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

@@ -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'
@@ -102,10 +102,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

@@ -5,7 +5,7 @@ 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 { VarType } from '@/app/components/workflow/types'
@@ -67,13 +67,10 @@ export const useConfig = (id: string, payload: WebhookTriggerNodeType) => {
const hasReservedConflict = sanitizedEntries.some(entry => entry.sanitizedName === WEBHOOK_RAW_VARIABLE_NAME)
if (hasReservedConflict) {
Toast.notify({
type: 'error',
message: t('varKeyError.keyAlreadyExists', {
ns: 'appDebug',
key: t('variableConfig.varName', { ns: 'appDebug' }),
}),
})
toast.error(t('varKeyError.keyAlreadyExists', {
ns: 'appDebug',
key: t('variableConfig.varName', { ns: 'appDebug' }),
}))
return false
}
const existingOtherVarNames = new Set(
@@ -84,37 +81,28 @@ export const useConfig = (id: string, payload: WebhookTriggerNodeType) => {
const crossScopeConflict = sanitizedEntries.find(entry => existingOtherVarNames.has(entry.sanitizedName))
if (crossScopeConflict) {
Toast.notify({
type: 'error',
message: t('varKeyError.keyAlreadyExists', {
ns: 'appDebug',
key: crossScopeConflict.sanitizedName,
}),
})
toast.error(t('varKeyError.keyAlreadyExists', {
ns: 'appDebug',
key: crossScopeConflict.sanitizedName,
}))
return false
}
if (hasDuplicateStr(sanitizedEntries.map(entry => entry.sanitizedName))) {
Toast.notify({
type: 'error',
message: t('varKeyError.keyAlreadyExists', {
ns: 'appDebug',
key: t('variableConfig.varName', { ns: 'appDebug' }),
}),
})
toast.error(t('varKeyError.keyAlreadyExists', {
ns: 'appDebug',
key: t('variableConfig.varName', { ns: 'appDebug' }),
}))
return false
}
for (const { sanitizedName } of sanitizedEntries) {
const { isValid, errorMessageKey } = checkKeys([sanitizedName], false)
if (!isValid) {
Toast.notify({
type: 'error',
message: t(`varKeyError.${errorMessageKey}`, {
ns: 'appDebug',
key: t('variableConfig.varName', { ns: 'appDebug' }),
}),
})
toast.error(t(`varKeyError.${errorMessageKey}`, {
ns: 'appDebug',
key: t('variableConfig.varName', { ns: 'appDebug' }),
}))
return false
}
}

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)
@@ -125,10 +123,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
@@ -236,11 +231,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,13 +18,12 @@ 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 { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
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 {
@@ -59,7 +58,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>()
@@ -153,13 +151,13 @@ const UpdateDSLModal = ({
return invalidNodes.includes(node?.data?.type)
})
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
}
}
@@ -179,17 +177,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()
@@ -207,17 +201,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 {
@@ -231,26 +225,26 @@ 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)
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' }))
}
}

View File

@@ -6386,14 +6386,9 @@
"count": 1
}
},
"app/components/tools/workflow-tool/hooks/use-configure-button.ts": {
"no-restricted-imports": {
"count": 1
}
},
"app/components/tools/workflow-tool/index.tsx": {
"no-restricted-imports": {
"count": 2
"count": 1
},
"tailwindcss/enforce-consistent-class-order": {
"count": 7
@@ -6599,7 +6594,7 @@
},
"app/components/workflow/block-selector/tool-picker.tsx": {
"no-restricted-imports": {
"count": 2
"count": 1
}
},
"app/components/workflow/block-selector/tool/action-item.tsx": {
@@ -6694,9 +6689,6 @@
}
},
"app/components/workflow/header/header-in-restoring.tsx": {
"no-restricted-imports": {
"count": 1
},
"tailwindcss/no-unnecessary-whitespace": {
"count": 1
}
@@ -6710,9 +6702,6 @@
"no-console": {
"count": 1
},
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 1
}
@@ -6773,9 +6762,6 @@
}
},
"app/components/workflow/hooks/use-checklist.ts": {
"no-restricted-imports": {
"count": 1
},
"ts/no-empty-object-type": {
"count": 1
},
@@ -6891,9 +6877,6 @@
}
},
"app/components/workflow/nodes/_base/components/before-run-form/index.tsx": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 11
}
@@ -7331,9 +7314,6 @@
}
},
"app/components/workflow/nodes/_base/components/workflow-panel/last-run/use-last-run.ts": {
"no-restricted-imports": {
"count": 1
},
"react/set-state-in-effect": {
"count": 1
},
@@ -7353,9 +7333,6 @@
}
},
"app/components/workflow/nodes/_base/hooks/use-one-step-run.ts": {
"no-restricted-imports": {
"count": 1
},
"react/set-state-in-effect": {
"count": 2
},
@@ -7623,7 +7600,7 @@
},
"app/components/workflow/nodes/http/components/curl-panel.tsx": {
"no-restricted-imports": {
"count": 2
"count": 1
}
},
"app/components/workflow/nodes/http/components/key-value/key-value-edit/index.tsx": {
@@ -7680,7 +7657,7 @@
},
"app/components/workflow/nodes/human-input/components/delivery-method/email-configure-modal.tsx": {
"no-restricted-imports": {
"count": 2
"count": 1
},
"tailwindcss/enforce-consistent-class-order": {
"count": 8
@@ -7792,11 +7769,6 @@
"count": 2
}
},
"app/components/workflow/nodes/human-input/components/user-action.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"app/components/workflow/nodes/human-input/components/variable-in-markdown.tsx": {
"react-refresh/only-export-components": {
"count": 2
@@ -7812,7 +7784,7 @@
},
"app/components/workflow/nodes/human-input/panel.tsx": {
"no-restricted-imports": {
"count": 2
"count": 1
},
"tailwindcss/enforce-consistent-class-order": {
"count": 4
@@ -7912,9 +7884,6 @@
}
},
"app/components/workflow/nodes/iteration/node.tsx": {
"no-restricted-imports": {
"count": 1
},
"react/set-state-in-effect": {
"count": 1
}
@@ -8222,9 +8191,6 @@
"app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-config.tsx": {
"erasable-syntax-only/enums": {
"count": 1
},
"no-restricted-imports": {
"count": 1
}
},
"app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/generated-result.tsx": {
@@ -8240,7 +8206,7 @@
"count": 1
},
"no-restricted-imports": {
"count": 2
"count": 1
},
"react/set-state-in-effect": {
"count": 2
@@ -8301,9 +8267,6 @@
}
},
"app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 1
}
@@ -8323,7 +8286,7 @@
},
"app/components/workflow/nodes/llm/panel.tsx": {
"no-restricted-imports": {
"count": 2
"count": 1
}
},
"app/components/workflow/nodes/llm/types.ts": {
@@ -8433,9 +8396,6 @@
}
},
"app/components/workflow/nodes/loop/components/loop-variables/item.tsx": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 4
}
@@ -8470,7 +8430,7 @@
},
"app/components/workflow/nodes/parameter-extractor/components/extract-parameter/update.tsx": {
"no-restricted-imports": {
"count": 3
"count": 2
},
"ts/no-explicit-any": {
"count": 1
@@ -8548,11 +8508,6 @@
"count": 8
}
},
"app/components/workflow/nodes/start/components/var-list.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"app/components/workflow/nodes/start/node.tsx": {
"tailwindcss/enforce-consistent-class-order": {
"count": 2
@@ -8567,9 +8522,6 @@
}
},
"app/components/workflow/nodes/start/use-config.ts": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 1
}
@@ -8648,9 +8600,6 @@
}
},
"app/components/workflow/nodes/tool/hooks/use-config.ts": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 6
}
@@ -8754,12 +8703,7 @@
},
"app/components/workflow/nodes/trigger-webhook/panel.tsx": {
"no-restricted-imports": {
"count": 3
}
},
"app/components/workflow/nodes/trigger-webhook/use-config.ts": {
"no-restricted-imports": {
"count": 1
"count": 2
}
},
"app/components/workflow/nodes/trigger-webhook/utils/render-output-vars.tsx": {
@@ -8783,9 +8727,6 @@
}
},
"app/components/workflow/nodes/variable-assigner/components/var-group-item.tsx": {
"no-restricted-imports": {
"count": 1
},
"tailwindcss/enforce-consistent-class-order": {
"count": 2
},
@@ -8910,9 +8851,6 @@
}
},
"app/components/workflow/panel/chat-variable-panel/components/object-value-item.tsx": {
"no-restricted-imports": {
"count": 1
},
"react-refresh/only-export-components": {
"count": 1
},
@@ -8942,9 +8880,6 @@
}
},
"app/components/workflow/panel/chat-variable-panel/components/variable-modal.tsx": {
"no-restricted-imports": {
"count": 1
},
"react/set-state-in-effect": {
"count": 8
},
@@ -8990,9 +8925,6 @@
}
},
"app/components/workflow/panel/debug-and-preview/hooks.ts": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 12
}
@@ -9017,7 +8949,7 @@
},
"app/components/workflow/panel/env-panel/variable-modal.tsx": {
"no-restricted-imports": {
"count": 2
"count": 1
},
"react/set-state-in-effect": {
"count": 4
@@ -9171,9 +9103,6 @@
}
},
"app/components/workflow/run/index.tsx": {
"no-restricted-imports": {
"count": 1
},
"react/set-state-in-effect": {
"count": 2
}
@@ -9349,7 +9278,7 @@
},
"app/components/workflow/update-dsl-modal.tsx": {
"no-restricted-imports": {
"count": 2
"count": 1
},
"ts/no-explicit-any": {
"count": 2

View File

@@ -96,6 +96,8 @@
"chatVariable.modal.name": "Name",
"chatVariable.modal.namePlaceholder": "Variable name",
"chatVariable.modal.objectKey": "Key",
"chatVariable.modal.objectKeyPatternError": "Key can only contain letters, numbers, and underscores",
"chatVariable.modal.objectKeyRequired": "Object key cannot be empty",
"chatVariable.modal.objectType": "Type",
"chatVariable.modal.objectValue": "Default Value",
"chatVariable.modal.oneByOne": "Add one by one",
@@ -207,6 +209,7 @@
"common.runApp": "Run App",
"common.runHistory": "Run History",
"common.running": "Running",
"common.scheduleTriggerRunFailed": "Schedule trigger run failed",
"common.searchVar": "Search variable",
"common.setVarValuePlaceholder": "Set variable",
"common.showRunHistory": "Show Run History",
@@ -220,6 +223,8 @@
"common.viewDetailInTracingPanel": "View details",
"common.viewOnly": "View Only",
"common.viewRunHistory": "View run history",
"common.webhookDebugFailed": "Webhook debug failed",
"common.webhookDebugRequestFailed": "Webhook debug request failed",
"common.workflowAsTool": "Workflow as Tool",
"common.workflowAsToolDisabledHint": "Publish the latest workflow and ensure a connected User Input node before configuring it as a tool.",
"common.workflowAsToolTip": "Tool reconfiguration is required after the workflow update.",
@@ -293,6 +298,7 @@
"env.modal.type": "Type",
"env.modal.value": "Value",
"env.modal.valuePlaceholder": "env value",
"env.modal.valueRequired": "Value cannot be empty",
"error.operations.addingNodes": "adding nodes",
"error.operations.connectingNodes": "connecting nodes",
"error.operations.modifyingWorkflow": "modifying workflow",
@@ -513,7 +519,9 @@
"nodes.humanInput.deliveryMethod.contactTip2": "Tell us at <email>support@dify.ai</email>.",
"nodes.humanInput.deliveryMethod.emailConfigure.allMembers": "All members ({{workspaceName}})",
"nodes.humanInput.deliveryMethod.emailConfigure.body": "Body",
"nodes.humanInput.deliveryMethod.emailConfigure.bodyMustContainRequestURL": "Body must contain {{field}}",
"nodes.humanInput.deliveryMethod.emailConfigure.bodyPlaceholder": "Enter email body",
"nodes.humanInput.deliveryMethod.emailConfigure.bodyRequired": "Body is required",
"nodes.humanInput.deliveryMethod.emailConfigure.debugMode": "Debug Mode",
"nodes.humanInput.deliveryMethod.emailConfigure.debugModeTip1": "In debug mode, the email will only be sent to your account email <email>{{email}}</email>.",
"nodes.humanInput.deliveryMethod.emailConfigure.debugModeTip2": "The production environment is not affected.",
@@ -524,9 +532,11 @@
"nodes.humanInput.deliveryMethod.emailConfigure.memberSelector.title": "Add workspace members or external recipients",
"nodes.humanInput.deliveryMethod.emailConfigure.memberSelector.trigger": "Select",
"nodes.humanInput.deliveryMethod.emailConfigure.recipient": "Recipient",
"nodes.humanInput.deliveryMethod.emailConfigure.recipientsRequired": "At least one recipient is required",
"nodes.humanInput.deliveryMethod.emailConfigure.requestURLTip": "The request URL variable is the trigger entry for human input.",
"nodes.humanInput.deliveryMethod.emailConfigure.subject": "Subject",
"nodes.humanInput.deliveryMethod.emailConfigure.subjectPlaceholder": "Enter email subject",
"nodes.humanInput.deliveryMethod.emailConfigure.subjectRequired": "Subject is required",
"nodes.humanInput.deliveryMethod.emailConfigure.title": "Email Configuration",
"nodes.humanInput.deliveryMethod.emailSender.debugDone": "A test email has been sent to <email>{{email}}</email>. Please check your inbox.",
"nodes.humanInput.deliveryMethod.emailSender.debugModeTip": "Debug mode is enabled.",
@@ -741,6 +751,7 @@
"nodes.llm.jsonSchema.back": "Back",
"nodes.llm.jsonSchema.descriptionPlaceholder": "Add description",
"nodes.llm.jsonSchema.doc": "Learn more about structured output",
"nodes.llm.jsonSchema.fieldNameAlreadyExists": "Property name already exists",
"nodes.llm.jsonSchema.fieldNamePlaceholder": "Field Name",
"nodes.llm.jsonSchema.generate": "Generate",
"nodes.llm.jsonSchema.generateJsonSchema": "Generate JSON Schema",