diff --git a/api/pyrefly-local-excludes.txt b/api/pyrefly-local-excludes.txt
index ad3c1e8389..7edb64869a 100644
--- a/api/pyrefly-local-excludes.txt
+++ b/api/pyrefly-local-excludes.txt
@@ -1,3 +1,5 @@
+activate/
+app_factory.py
controllers/console/app/annotation.py
controllers/console/app/app.py
controllers/console/app/app_import.py
@@ -10,6 +12,7 @@ controllers/console/ping.py
controllers/console/setup.py
controllers/console/version.py
controllers/console/workspace/trigger_providers.py
+controllers/console/socketio/workflow.py
controllers/service_api/app/annotation.py
controllers/web/workflow_events.py
core/agent/fc_agent_runner.py
diff --git a/docker/.env.example b/docker/.env.example
index 862c4ffcdc..2fbdf396ce 100644
--- a/docker/.env.example
+++ b/docker/.env.example
@@ -1233,6 +1233,14 @@ SANDBOX_HTTPS_PROXY=http://ssrf_proxy:3128
# The port on which the sandbox service runs
SANDBOX_PORT=8194
+# Optional defaults for SSH sandbox provider setup (for manual config/CLI usage).
+# Middleware/local dev usually uses 127.0.0.1:2222; full docker deployment usually uses agentbox:22.
+SSH_SANDBOX_HOST=agentbox
+SSH_SANDBOX_PORT=22
+SSH_SANDBOX_USERNAME=agentbox
+SSH_SANDBOX_PASSWORD=agentbox
+SSH_SANDBOX_BASE_WORKING_PATH=/workspace/sandboxes
+
# ------------------------------
# Environment Variables for agentbox Service
# ------------------------------
@@ -1467,6 +1475,11 @@ ENDPOINT_URL_TEMPLATE=http://localhost/e/{hook_id}
MARKETPLACE_ENABLED=true
MARKETPLACE_API_URL=https://marketplace.dify.ai
+# Creators Platform configuration
+CREATORS_PLATFORM_FEATURES_ENABLED=true
+CREATORS_PLATFORM_API_URL=https://creators.dify.ai
+CREATORS_PLATFORM_OAUTH_CLIENT_ID=
+
FORCE_VERIFYING_SIGNATURE=true
ENFORCE_LANGGENIUS_PLUGIN_SIGNATURES=true
diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml
index 6e11cac678..2eddb4ca49 100644
--- a/docker/docker-compose.yaml
+++ b/docker/docker-compose.yaml
@@ -30,6 +30,7 @@ x-shared-env: &shared-api-worker-env
SECRET_KEY: ${SECRET_KEY:-sk-9f73s3ljTXVcMT3Blb3ljTqtsKiGHXVcMT3BlbkFJLK7U}
INIT_PASSWORD: ${INIT_PASSWORD:-}
DEPLOY_ENV: ${DEPLOY_ENV:-PRODUCTION}
+ ENABLE_COLLABORATION_MODE: ${ENABLE_COLLABORATION_MODE:-false}
CHECK_UPDATE_URL: ${CHECK_UPDATE_URL:-https://updates.dify.ai}
OPENAI_API_BASE: ${OPENAI_API_BASE:-https://api.openai.com/v1}
MIGRATION_ENABLED: ${MIGRATION_ENABLED:-true}
@@ -450,6 +451,14 @@ x-shared-env: &shared-api-worker-env
EMAIL_REGISTER_TOKEN_EXPIRY_MINUTES: ${EMAIL_REGISTER_TOKEN_EXPIRY_MINUTES:-5}
CHANGE_EMAIL_TOKEN_EXPIRY_MINUTES: ${CHANGE_EMAIL_TOKEN_EXPIRY_MINUTES:-5}
OWNER_TRANSFER_TOKEN_EXPIRY_MINUTES: ${OWNER_TRANSFER_TOKEN_EXPIRY_MINUTES:-5}
+ SANDBOX_DIFY_CLI_ROOT: ${SANDBOX_DIFY_CLI_ROOT:-}
+ CLI_API_URL: ${CLI_API_URL:-http://api:5001}
+ FILES_API_URL: ${FILES_API_URL:-http://localhost}
+ SSH_SANDBOX_HOST: ${SSH_SANDBOX_HOST:-agentbox}
+ SSH_SANDBOX_PORT: ${SSH_SANDBOX_PORT:-22}
+ SSH_SANDBOX_USERNAME: ${SSH_SANDBOX_USERNAME:-agentbox}
+ SSH_SANDBOX_PASSWORD: ${SSH_SANDBOX_PASSWORD:-agentbox}
+ SSH_SANDBOX_BASE_WORKING_PATH: ${SSH_SANDBOX_BASE_WORKING_PATH:-/workspace/sandboxes}
CODE_EXECUTION_ENDPOINT: ${CODE_EXECUTION_ENDPOINT:-http://sandbox:8194}
CODE_EXECUTION_API_KEY: ${CODE_EXECUTION_API_KEY:-dify-sandbox}
CODE_EXECUTION_SSL_VERIFY: ${CODE_EXECUTION_SSL_VERIFY:-True}
@@ -616,6 +625,9 @@ x-shared-env: &shared-api-worker-env
PLUGIN_DIFY_INNER_API_KEY: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1}
PLUGIN_DIFY_INNER_API_URL: ${PLUGIN_DIFY_INNER_API_URL:-http://api:5001}
ENDPOINT_URL_TEMPLATE: ${ENDPOINT_URL_TEMPLATE:-http://localhost/e/{hook_id}}
+ CREATORS_PLATFORM_FEATURES_ENABLED: ${CREATORS_PLATFORM_FEATURES_ENABLED:-true}
+ CREATORS_PLATFORM_API_URL: ${CREATORS_PLATFORM_API_URL:-https://creators.dify.ai}
+ CREATORS_PLATFORM_OAUTH_CLIENT_ID: ${CREATORS_PLATFORM_OAUTH_CLIENT_ID:-}
MARKETPLACE_ENABLED: ${MARKETPLACE_ENABLED:-true}
MARKETPLACE_API_URL: ${MARKETPLACE_API_URL:-https://marketplace.dify.ai}
FORCE_VERIFYING_SIGNATURE: ${FORCE_VERIFYING_SIGNATURE:-true}
diff --git a/web/app/components/app-sidebar/index.tsx b/web/app/components/app-sidebar/index.tsx
index 31d3209c59..37c8a107e3 100644
--- a/web/app/components/app-sidebar/index.tsx
+++ b/web/app/components/app-sidebar/index.tsx
@@ -5,12 +5,12 @@ import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useShallow } from 'zustand/react/shallow'
import { useStore as useAppStore } from '@/app/components/app/store'
+import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { usePathname } from '@/next/navigation'
import { cn } from '@/utils/classnames'
import Divider from '../base/divider'
-import Tooltip from '../base/tooltip'
import { getKeyboardKeyCodeBySystem } from '../workflow/utils'
import AppInfo from './app-info'
import AppSidebarDropdown from './app-sidebar-dropdown'
@@ -63,9 +63,9 @@ const AppDetailNav = ({
const [hideHeader, setHideHeader] = useState(workflowCanvasMaximize)
const { eventEmitter } = useEventEmitterContextContext()
- eventEmitter?.useSubscription((v: { type: string; payload?: boolean }) => {
- if (v?.type === 'workflow-canvas-maximize')
- setHideHeader(v.payload ?? false)
+ eventEmitter?.useSubscription((v) => {
+ if (typeof v === 'object' && v?.type === 'workflow-canvas-maximize')
+ setHideHeader((v.payload as boolean) ?? false)
})
useEffect(() => {
@@ -158,9 +158,8 @@ const AppDetailNav = ({
{iconType !== 'app' && extraInfo && extraInfo(appSidebarExpand)}
{iconType === 'app' && showUpgradeButton && (
-
-
+
+ {!expand && (
+
+ {t('sandboxMigrationModal.title', { ns: 'workflow' })}
+
+ )}
)}
diff --git a/web/app/components/base/chat/chat/answer/index.tsx b/web/app/components/base/chat/chat/answer/index.tsx
index bff42dc5c4..337f462c2e 100644
--- a/web/app/components/base/chat/chat/answer/index.tsx
+++ b/web/app/components/base/chat/chat/answer/index.tsx
@@ -141,7 +141,7 @@ const Answer: FC = ({
}, [switchSibling, item.prevSibling, item.nextSibling])
const contentIsEmpty = typeof content === 'string' && content.trim() === ''
- const generationContentRenderIsUsed = llmGenerationItems?.length && llmGenerationItems.some((item) => {
+ const generationContentRenderIsUsed = !!llmGenerationItems?.length && llmGenerationItems.some((item) => {
return item.type === 'tool' || item.type === 'thought'
})
diff --git a/web/app/components/base/upgrade-modal/index.tsx b/web/app/components/base/upgrade-modal/index.tsx
index 4350bd9be4..6c741b1128 100644
--- a/web/app/components/base/upgrade-modal/index.tsx
+++ b/web/app/components/base/upgrade-modal/index.tsx
@@ -48,7 +48,7 @@ const UpgradeModalBase: FC = ({
- {footer && (
+ {!!footer && (
{footer}
diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/popup.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/popup.spec.tsx
index 8293033d0f..882d53dd60 100644
--- a/web/app/components/header/account-setting/model-provider-page/model-selector/popup.spec.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/model-selector/popup.spec.tsx
@@ -1,4 +1,5 @@
import type { Model, ModelItem } from '../declarations'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { fireEvent, render, screen } from '@testing-library/react'
import { tooltipManager } from '@/app/components/base/tooltip/TooltipManager'
import {
@@ -9,6 +10,11 @@ import {
} from '../declarations'
import Popup from './popup'
+const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
+function renderWithProviders(ui: React.ReactElement) {
+ return render({ui})
+}
+
let mockLanguage = 'en_US'
const mockSetShowAccountSettingModal = vi.hoisted(() => vi.fn())
@@ -28,6 +34,7 @@ vi.mock('../hooks', async () => {
return {
...actual,
useLanguage: () => mockLanguage,
+ useMarketplaceAllPlugins: () => [],
}
})
@@ -66,7 +73,7 @@ describe('Popup', () => {
})
it('should filter models by search and allow clearing search', () => {
- render(
+ renderWithProviders(
{
// When tool-call support is missing, it should be filtered out.
mockSupportFunctionCall.mockReturnValue(false)
- const { unmount } = render(
+ const { unmount } = renderWithProviders(
{
// When tool-call support exists, the non-toolCall feature check should also pass.
unmount()
mockSupportFunctionCall.mockReturnValue(true)
- const { unmount: unmount2 } = render(
+ const { unmount: unmount2 } = renderWithProviders(
{
expect(screen.getByText('openai')).toBeInTheDocument()
unmount2()
- const { unmount: unmount3 } = render(
+ const { unmount: unmount3 } = renderWithProviders(
{
// When features are missing, non-toolCall feature checks should fail.
unmount3()
- render(
+ renderWithProviders(
{
it('should match labels from other languages when current language key is missing', () => {
mockLanguage = 'fr_FR'
- render(
+ renderWithProviders(
{
models: [makeModelItem({ features: [ModelFeatureEnum.toolCall] })],
})
- render(
+ renderWithProviders(
{
})
it('should close tooltip on scroll', () => {
- const { container } = render(
+ const { container } = renderWithProviders(
{
})
it('should open provider settings when clicking footer link', () => {
- render(
+ renderWithProviders(
{
it('should call onHide when footer settings link is clicked', () => {
const mockOnHide = vi.fn()
- render(
+ renderWithProviders(
{
})
it('should match model label when searchText is non-empty and label key exists for current language', () => {
- render(
+ renderWithProviders(
{
}, [appId])
const { eventEmitter } = useEventEmitterContextContext()
- eventEmitter?.useSubscription((v: { type: string }) => {
- if (v?.type === 'upgrade-runtime-click')
+ eventEmitter?.useSubscription((v) => {
+ if (typeof v === 'object' && v?.type === 'upgrade-runtime-click')
setShowMigrationModal(true)
})
@@ -204,7 +204,7 @@ const WorkflowAppWithAdditionalContext = () => {
const setShowUpgradeRuntimeModal = useStore(s => s.setShowUpgradeRuntimeModal)
useEffect(() => {
if (showUpgradeRuntimeModal) {
- // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
+ // eslint-disable-next-line react/set-state-in-effect
setShowMigrationModal(true)
setShowUpgradeRuntimeModal(false)
}
@@ -375,7 +375,7 @@ const WorkflowAppWithAdditionalContext = () => {
if (lastCheckedAppIdRef.current !== appId) {
lastCheckedAppIdRef.current = appId
const dismissed = getSandboxMigrationDismissed(appId)
- // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
+ // eslint-disable-next-line react/set-state-in-effect
setShowMigrationModal(!sandboxEnabled && !dismissed)
}
}, [appId, isDataReady, sandboxEnabled, setNeedsRuntimeUpgrade])
diff --git a/web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx b/web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx
index 3c86e97d0a..d6f6e55c70 100644
--- a/web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx
+++ b/web/app/components/workflow/__tests__/workflow-edge-events.spec.tsx
@@ -261,6 +261,32 @@ vi.mock('../hooks/use-workflow-search', () => ({
useWorkflowSearch: workflowHookMocks.useWorkflowSearch,
}))
+vi.mock('../hooks/use-workflow-comment', () => ({
+ useWorkflowComment: () => ({
+ comments: [],
+ loading: false,
+ pendingComment: null,
+ activeComment: null,
+ activeCommentLoading: false,
+ replySubmitting: false,
+ replyUpdating: false,
+ handleCommentSubmit: vi.fn(),
+ handleCommentCancel: vi.fn(),
+ handleCommentIconClick: vi.fn(),
+ handleActiveCommentClose: vi.fn(),
+ handleCommentResolve: vi.fn(),
+ handleCommentDelete: vi.fn(),
+ handleCommentNavigate: vi.fn(),
+ handleCommentReply: vi.fn(),
+ handleCommentReplyUpdate: vi.fn(),
+ handleCommentReplyDelete: vi.fn(),
+ handleCommentPositionUpdate: vi.fn(),
+ refreshActiveComment: vi.fn(),
+ handleCreateComment: vi.fn(),
+ loadComments: vi.fn(),
+ }),
+}))
+
vi.mock('../nodes/_base/components/variable/use-match-schema-type', () => ({
default: () => ({
schemaTypeDefinitions: undefined,
diff --git a/web/app/components/workflow/comment/comment-input.spec.tsx b/web/app/components/workflow/comment/comment-input.spec.tsx
index de416a86e3..9c2c425297 100644
--- a/web/app/components/workflow/comment/comment-input.spec.tsx
+++ b/web/app/components/workflow/comment/comment-input.spec.tsx
@@ -36,7 +36,7 @@ vi.mock('@/context/app-context', () => ({
}))
vi.mock('@/app/components/base/avatar', () => ({
- default: ({ name }: { name: string }) => {name}
,
+ Avatar: ({ name }: { name: string }) => {name}
,
}))
vi.mock('./mention-input', () => ({
diff --git a/web/app/components/workflow/hooks/use-edges-interactions.ts b/web/app/components/workflow/hooks/use-edges-interactions.ts
index bc86d05a7f..0b3a58866f 100644
--- a/web/app/components/workflow/hooks/use-edges-interactions.ts
+++ b/web/app/components/workflow/hooks/use-edges-interactions.ts
@@ -172,9 +172,10 @@ export const useEdgesInteractions = () => {
}
})
setEdges(newEdges)
+ workflowStore.setState({ edgeMenu: undefined })
handleSyncWorkflowDraft()
saveStateToHistory(WorkflowHistoryEvent.EdgeDelete)
- }, [getNodesReadOnly, collaborativeWorkflow, handleSyncWorkflowDraft, saveStateToHistory])
+ }, [getNodesReadOnly, collaborativeWorkflow, workflowStore, handleSyncWorkflowDraft, saveStateToHistory])
const handleEdgeDeleteById = useCallback((edgeId: string) => {
if (getNodesReadOnly())
diff --git a/web/app/components/workflow/nodes/llm/components/computer-use-tip.tsx b/web/app/components/workflow/nodes/llm/components/computer-use-tip.tsx
index 0e6af3a548..cff544038e 100644
--- a/web/app/components/workflow/nodes/llm/components/computer-use-tip.tsx
+++ b/web/app/components/workflow/nodes/llm/components/computer-use-tip.tsx
@@ -17,7 +17,7 @@ const ComputerUseTip: FC = ({
React.useEffect(() => {
if (!visible)
- // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
+ // eslint-disable-next-line react/set-state-in-effect
setDismissed(false)
}, [visible])
diff --git a/web/app/components/workflow/run/loop-log/loop-result-panel.tsx b/web/app/components/workflow/run/loop-log/loop-result-panel.tsx
index d37b166474..9e922181e6 100644
--- a/web/app/components/workflow/run/loop-log/loop-result-panel.tsx
+++ b/web/app/components/workflow/run/loop-log/loop-result-panel.tsx
@@ -129,7 +129,7 @@ const LoopResultPanel: FC = ({
)}
>
{
- loopVariableMap?.[index] && (
+ !!loopVariableMap?.[index] && (
= ({
)}
- {nodeInfo.inputs && (
+ {!!nodeInfo.inputs && (
= ({
/>
)}
- {nodeInfo.process_data && (
+ {!!nodeInfo.process_data && (
= ({
allFiles,
}) => {
const { t } = useTranslation()
- const generationContentRenderIsUsed = llmGenerationItems?.length && llmGenerationItems.some((item) => {
+ const generationContentRenderIsUsed = !!llmGenerationItems?.length && llmGenerationItems.some((item) => {
return item.type === 'tool' || item.type === 'thought'
})
diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/component.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/component.tsx
index 89e4b66d10..82bac137f6 100644
--- a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/component.tsx
+++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/component.tsx
@@ -134,7 +134,7 @@ const FileReferenceBlock = ({ nodeKey, resourceId }: FileReferenceBlockProps) =>
const width = 400
const gap = 4
const left = Math.max(8, rect.left - gap - width)
- // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
+ // eslint-disable-next-line react/set-state-in-effect
setPreviewStyle(_prev => ({
position: 'fixed',
top: rect.top,
diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx
index 76655e0e0c..e8d8a54594 100644
--- a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx
+++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/component.tsx
@@ -293,14 +293,14 @@ const ToolBlockComponent = ({
if (!configuredToolValue)
return
if (!toolValue || toolValue.tool_name !== configuredToolValue.tool_name || toolValue.provider_name !== configuredToolValue.provider_name)
- // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
+ // eslint-disable-next-line react/set-state-in-effect
setToolValue(configuredToolValue)
}, [configuredToolValue, toolValue])
useEffect(() => {
if (!isSettingOpen || !configuredToolValue)
return
- // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
+ // eslint-disable-next-line react/set-state-in-effect
setToolValue(configuredToolValue)
}, [configuredToolValue, isSettingOpen])
@@ -311,7 +311,7 @@ const ToolBlockComponent = ({
const fallbackContainer = document.querySelector('[data-skill-editor-root="true"]') as HTMLElement | null
const container = containerFromRef || fallbackContainer
if (container)
- // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
+ // eslint-disable-next-line react/set-state-in-effect
setPortalContainer(container)
}, [ref, useModalValue])
diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx
index bab3275f3d..8582613785 100644
--- a/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx
+++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/tool-block/tool-group-block-component.tsx
@@ -368,7 +368,7 @@ const ToolGroupBlockComponent = ({
if (!configuredToolValue)
return
if (!toolValue || toolValue.tool_name !== configuredToolValue.tool_name || toolValue.provider_name !== configuredToolValue.provider_name)
- // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
+ // eslint-disable-next-line react/set-state-in-effect
setToolValue(configuredToolValue)
}, [configuredToolValue, toolValue])
@@ -376,14 +376,14 @@ const ToolGroupBlockComponent = ({
if (expandedToolId)
return
if (toolValue)
- // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
+ // eslint-disable-next-line react/set-state-in-effect
setToolValue(null)
}, [expandedToolId, toolValue])
useEffect(() => {
if (!isSettingOpen || !configuredToolValue)
return
- // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
+ // eslint-disable-next-line react/set-state-in-effect
setToolValue(configuredToolValue)
}, [configuredToolValue, isSettingOpen])
@@ -394,7 +394,7 @@ const ToolGroupBlockComponent = ({
const fallbackContainer = document.querySelector('[data-skill-editor-root="true"]') as HTMLElement | null
const container = containerFromRef || fallbackContainer
if (container)
- // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
+ // eslint-disable-next-line react/set-state-in-effect
setPortalContainer(container)
}, [ref, useModalValue])
diff --git a/web/app/components/workflow/skill/hooks/file-tree/dnd/use-file-drop.spec.tsx b/web/app/components/workflow/skill/hooks/file-tree/dnd/use-file-drop.spec.tsx
index a85c05a113..65f88a0735 100644
--- a/web/app/components/workflow/skill/hooks/file-tree/dnd/use-file-drop.spec.tsx
+++ b/web/app/components/workflow/skill/hooks/file-tree/dnd/use-file-drop.spec.tsx
@@ -11,12 +11,14 @@ const {
mockUploadMutateAsync,
mockPrepareSkillUploadFile,
mockEmitTreeUpdate,
- mockToastNotify,
+ mockToastSuccess,
+ mockToastError,
} = vi.hoisted(() => ({
mockUploadMutateAsync: vi.fn(),
mockPrepareSkillUploadFile: vi.fn(),
mockEmitTreeUpdate: vi.fn(),
- mockToastNotify: vi.fn(),
+ mockToastSuccess: vi.fn(),
+ mockToastError: vi.fn(),
}))
vi.mock('@/service/use-app-asset', () => ({
@@ -34,9 +36,10 @@ vi.mock('../data/use-skill-tree-collaboration', () => ({
useSkillTreeUpdateEmitter: () => mockEmitTreeUpdate,
}))
-vi.mock('@/app/components/base/toast', () => ({
- default: {
- notify: mockToastNotify,
+vi.mock('@/app/components/base/ui/toast', () => ({
+ toast: {
+ success: mockToastSuccess,
+ error: mockToastError,
},
}))
@@ -169,10 +172,7 @@ describe('useFileDrop', () => {
expect(mockPrepareSkillUploadFile).not.toHaveBeenCalled()
expect(mockUploadMutateAsync).not.toHaveBeenCalled()
- expect(mockToastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'workflow.skillSidebar.menu.folderDropNotSupported',
- })
+ expect(mockToastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.folderDropNotSupported')
expect(store.getState().currentDragType).toBeNull()
expect(store.getState().dragOverFolderId).toBeNull()
})
@@ -201,14 +201,8 @@ describe('useFileDrop', () => {
parentId: 'folder-mixed',
})
expect(mockEmitTreeUpdate).toHaveBeenCalledTimes(1)
- expect(mockToastNotify).toHaveBeenNthCalledWith(1, {
- type: 'error',
- message: 'workflow.skillSidebar.menu.folderDropNotSupported',
- })
- expect(mockToastNotify).toHaveBeenNthCalledWith(2, {
- type: 'success',
- message: 'workflow.skillSidebar.menu.filesUploaded:{"count":1}',
- })
+ expect(mockToastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.folderDropNotSupported')
+ expect(mockToastSuccess).toHaveBeenCalledWith('workflow.skillSidebar.menu.filesUploaded:{"count":1}')
})
})
@@ -245,10 +239,7 @@ describe('useFileDrop', () => {
parentId: 'folder-9',
})
expect(mockEmitTreeUpdate).toHaveBeenCalledTimes(1)
- expect(mockToastNotify).toHaveBeenCalledWith({
- type: 'success',
- message: 'workflow.skillSidebar.menu.filesUploaded:{"count":2}',
- })
+ expect(mockToastSuccess).toHaveBeenCalledWith('workflow.skillSidebar.menu.filesUploaded:{"count":2}')
})
})
@@ -269,10 +260,7 @@ describe('useFileDrop', () => {
expect(mockUploadMutateAsync).toHaveBeenCalledTimes(1)
expect(mockEmitTreeUpdate).not.toHaveBeenCalled()
- expect(mockToastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'workflow.skillSidebar.menu.uploadError',
- })
+ expect(mockToastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.uploadError')
})
})
})
diff --git a/web/app/components/workflow/skill/hooks/file-tree/operations/use-download-operation.spec.tsx b/web/app/components/workflow/skill/hooks/file-tree/operations/use-download-operation.spec.tsx
index c30fc222d5..726185d934 100644
--- a/web/app/components/workflow/skill/hooks/file-tree/operations/use-download-operation.spec.tsx
+++ b/web/app/components/workflow/skill/hooks/file-tree/operations/use-download-operation.spec.tsx
@@ -31,11 +31,13 @@ const createDeferred = (): Deferred => {
const {
mockGetFileDownloadUrl,
mockDownloadUrl,
- mockToastNotify,
+ mockToastSuccess,
+ mockToastError,
} = vi.hoisted(() => ({
mockGetFileDownloadUrl: vi.fn<(request: DownloadRequest) => Promise>(),
mockDownloadUrl: vi.fn<(payload: { url: string, fileName?: string }) => void>(),
- mockToastNotify: vi.fn<(payload: { type: string, message: string }) => void>(),
+ mockToastSuccess: vi.fn(),
+ mockToastError: vi.fn(),
}))
vi.mock('@/service/client', () => ({
@@ -50,9 +52,10 @@ vi.mock('@/utils/download', () => ({
downloadUrl: mockDownloadUrl,
}))
-vi.mock('@/app/components/base/toast', () => ({
- default: {
- notify: mockToastNotify,
+vi.mock('@/app/components/base/ui/toast', () => ({
+ toast: {
+ success: mockToastSuccess,
+ error: mockToastError,
},
}))
@@ -109,7 +112,8 @@ describe('useDownloadOperation', () => {
url: 'https://example.com/file.txt',
fileName: 'notes.md',
})
- expect(mockToastNotify).not.toHaveBeenCalled()
+ expect(mockToastSuccess).not.toHaveBeenCalled()
+ expect(mockToastError).not.toHaveBeenCalled()
expect(result.current.isDownloading).toBe(false)
})
@@ -163,10 +167,8 @@ describe('useDownloadOperation', () => {
expect(onClose).toHaveBeenCalledTimes(1)
expect(mockDownloadUrl).not.toHaveBeenCalled()
- expect(mockToastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'workflow.skillSidebar.menu.downloadError',
- })
+ expect(mockToastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.downloadError')
+ expect(mockToastSuccess).not.toHaveBeenCalled()
expect(result.current.isDownloading).toBe(false)
})
})
diff --git a/web/app/components/workflow/skill/hooks/file-tree/operations/use-modify-operations.spec.tsx b/web/app/components/workflow/skill/hooks/file-tree/operations/use-modify-operations.spec.tsx
index 612dcd2cb5..6cdfe18097 100644
--- a/web/app/components/workflow/skill/hooks/file-tree/operations/use-modify-operations.spec.tsx
+++ b/web/app/components/workflow/skill/hooks/file-tree/operations/use-modify-operations.spec.tsx
@@ -16,7 +16,8 @@ const mocks = vi.hoisted(() => ({
deletePending: false,
deleteMutateAsync: vi.fn<(payload: DeleteMutationPayload) => Promise>(),
emitTreeUpdate: vi.fn<() => void>(),
- toastNotify: vi.fn<(payload: { type: string, message: string }) => void>(),
+ toastSuccess: vi.fn<(message: string) => void>(),
+ toastError: vi.fn<(message: string) => void>(),
getAllDescendantFileIds: vi.fn<(nodeId: string, nodes: TreeNodeData[]) => string[]>(),
}))
@@ -35,9 +36,10 @@ vi.mock('../../../utils/tree-utils', () => ({
getAllDescendantFileIds: mocks.getAllDescendantFileIds,
}))
-vi.mock('@/app/components/base/toast', () => ({
- default: {
- notify: mocks.toastNotify,
+vi.mock('@/app/components/base/ui/toast', () => ({
+ toast: {
+ success: mocks.toastSuccess,
+ error: mocks.toastError,
},
}))
@@ -234,10 +236,7 @@ describe('useModifyOperations', () => {
expect(clearDraftContent).toHaveBeenNthCalledWith(2, 'desc-2')
expect(clearDraftContent).toHaveBeenNthCalledWith(3, 'file-7')
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'success',
- message: 'workflow.skillSidebar.menu.fileDeleted',
- })
+ expect(mocks.toastSuccess).toHaveBeenCalledWith('workflow.skillSidebar.menu.fileDeleted')
expect(result.current.showDeleteConfirm).toBe(false)
expect(onClose).toHaveBeenCalledTimes(1)
})
@@ -269,10 +268,7 @@ describe('useModifyOperations', () => {
expect(clearDraftContent).toHaveBeenCalledWith('file-in-folder')
expect(closeTab).not.toHaveBeenCalledWith('folder-9')
expect(clearDraftContent).not.toHaveBeenCalledWith('folder-9')
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'success',
- message: 'workflow.skillSidebar.menu.deleted',
- })
+ expect(mocks.toastSuccess).toHaveBeenCalledWith('workflow.skillSidebar.menu.deleted')
})
})
@@ -303,10 +299,7 @@ describe('useModifyOperations', () => {
expect(mocks.emitTreeUpdate).not.toHaveBeenCalled()
expect(closeTab).not.toHaveBeenCalled()
expect(clearDraftContent).not.toHaveBeenCalled()
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'workflow.skillSidebar.menu.deleteError',
- })
+ expect(mocks.toastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.deleteError')
expect(result.current.showDeleteConfirm).toBe(false)
expect(onClose).toHaveBeenCalledTimes(1)
})
@@ -329,10 +322,7 @@ describe('useModifyOperations', () => {
})
expect(mocks.getAllDescendantFileIds).not.toHaveBeenCalled()
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'workflow.skillSidebar.menu.fileDeleteError',
- })
+ expect(mocks.toastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.fileDeleteError')
})
})
})
diff --git a/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-move.spec.tsx b/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-move.spec.tsx
index 40c6c4ee64..a3f806c24b 100644
--- a/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-move.spec.tsx
+++ b/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-move.spec.tsx
@@ -22,7 +22,8 @@ const mocks = vi.hoisted(() => ({
movePending: false,
moveMutateAsync: vi.fn<(payload: MoveMutationPayload) => Promise>(),
emitTreeUpdate: vi.fn<() => void>(),
- toastNotify: vi.fn<(payload: { type: string, message: string }) => void>(),
+ toastSuccess: vi.fn<(message: string) => void>(),
+ toastError: vi.fn<(message: string) => void>(),
toApiParentId: vi.fn<(folderId: string | null | undefined) => string | null>(),
}))
@@ -45,9 +46,10 @@ vi.mock('../../../utils/tree-utils', () => ({
toApiParentId: mocks.toApiParentId,
}))
-vi.mock('@/app/components/base/toast', () => ({
- default: {
- notify: mocks.toastNotify,
+vi.mock('@/app/components/base/ui/toast', () => ({
+ toast: {
+ success: mocks.toastSuccess,
+ error: mocks.toastError,
},
}))
@@ -90,10 +92,7 @@ describe('useNodeMove', () => {
},
})
expect(mocks.emitTreeUpdate).toHaveBeenCalledTimes(1)
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'success',
- message: 'workflow.skillSidebar.menu.moved',
- })
+ expect(mocks.toastSuccess).toHaveBeenCalledWith('workflow.skillSidebar.menu.moved')
})
it('should use empty appId when app detail is unavailable', async () => {
@@ -126,10 +125,7 @@ describe('useNodeMove', () => {
})
expect(mocks.emitTreeUpdate).not.toHaveBeenCalled()
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'workflow.skillSidebar.menu.moveError',
- })
+ expect(mocks.toastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.moveError')
})
})
})
diff --git a/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-reorder.spec.tsx b/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-reorder.spec.tsx
index e42a7069f1..378be4bb71 100644
--- a/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-reorder.spec.tsx
+++ b/web/app/components/workflow/skill/hooks/file-tree/operations/use-node-reorder.spec.tsx
@@ -22,7 +22,8 @@ const mocks = vi.hoisted(() => ({
reorderPending: false,
reorderMutateAsync: vi.fn<(payload: ReorderMutationPayload) => Promise>(),
emitTreeUpdate: vi.fn<() => void>(),
- toastNotify: vi.fn<(payload: { type: string, message: string }) => void>(),
+ toastSuccess: vi.fn<(message: string) => void>(),
+ toastError: vi.fn<(message: string) => void>(),
}))
vi.mock('@/app/components/app/store', () => ({
@@ -40,9 +41,10 @@ vi.mock('../data/use-skill-tree-collaboration', () => ({
useSkillTreeUpdateEmitter: () => mocks.emitTreeUpdate,
}))
-vi.mock('@/app/components/base/toast', () => ({
- default: {
- notify: mocks.toastNotify,
+vi.mock('@/app/components/base/ui/toast', () => ({
+ toast: {
+ success: mocks.toastSuccess,
+ error: mocks.toastError,
},
}))
@@ -82,10 +84,7 @@ describe('useNodeReorder', () => {
},
})
expect(mocks.emitTreeUpdate).toHaveBeenCalledTimes(1)
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'success',
- message: 'workflow.skillSidebar.menu.moved',
- })
+ expect(mocks.toastSuccess).toHaveBeenCalledWith('workflow.skillSidebar.menu.moved')
})
it('should use empty appId when app detail is missing', async () => {
@@ -117,10 +116,7 @@ describe('useNodeReorder', () => {
})
expect(mocks.emitTreeUpdate).not.toHaveBeenCalled()
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'workflow.skillSidebar.menu.moveError',
- })
+ expect(mocks.toastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.moveError')
})
})
})
diff --git a/web/app/components/workflow/skill/hooks/file-tree/operations/use-paste-operation.spec.tsx b/web/app/components/workflow/skill/hooks/file-tree/operations/use-paste-operation.spec.tsx
index 7b1a4f4d60..15fa155430 100644
--- a/web/app/components/workflow/skill/hooks/file-tree/operations/use-paste-operation.spec.tsx
+++ b/web/app/components/workflow/skill/hooks/file-tree/operations/use-paste-operation.spec.tsx
@@ -56,7 +56,8 @@ const mocks = vi.hoisted(() => ({
movePending: false,
moveMutateAsync: vi.fn<(payload: MoveMutationPayload) => Promise>(),
emitTreeUpdate: vi.fn<() => void>(),
- toastNotify: vi.fn<(payload: { type: string, message: string }) => void>(),
+ toastSuccess: vi.fn<(message: string) => void>(),
+ toastError: vi.fn<(message: string) => void>(),
getTargetFolderIdFromSelection: vi.fn<(selectedId: string | null, nodes: TreeNodeData[]) => string>(),
toApiParentId: vi.fn<(folderId: string | null | undefined) => string | null>(),
findNodeById: vi.fn<(nodes: TreeNodeData[], nodeId: string) => TreeNodeData | null>(),
@@ -89,9 +90,10 @@ vi.mock('../../../utils/tree-utils', () => ({
findNodeById: mocks.findNodeById,
}))
-vi.mock('@/app/components/base/toast', () => ({
- default: {
- notify: mocks.toastNotify,
+vi.mock('@/app/components/base/ui/toast', () => ({
+ toast: {
+ success: mocks.toastSuccess,
+ error: mocks.toastError,
},
}))
@@ -151,7 +153,8 @@ describe('usePasteOperation', () => {
expect(mocks.getTargetFolderIdFromSelection).not.toHaveBeenCalled()
expect(mocks.moveMutateAsync).not.toHaveBeenCalled()
- expect(mocks.toastNotify).not.toHaveBeenCalled()
+ expect(mocks.toastSuccess).not.toHaveBeenCalled()
+ expect(mocks.toastError).not.toHaveBeenCalled()
})
it('should no-op when clipboard has no node ids', async () => {
@@ -188,10 +191,7 @@ describe('usePasteOperation', () => {
})
expect(mocks.moveMutateAsync).not.toHaveBeenCalled()
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'workflow.skillSidebar.menu.cannotMoveToSelf',
- })
+ expect(mocks.toastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.cannotMoveToSelf')
})
})
@@ -232,10 +232,7 @@ describe('usePasteOperation', () => {
})
expect(mocks.workflowState.clearClipboard).toHaveBeenCalledTimes(1)
expect(mocks.emitTreeUpdate).toHaveBeenCalledTimes(1)
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'success',
- message: 'workflow.skillSidebar.menu.moved',
- })
+ expect(mocks.toastSuccess).toHaveBeenCalledWith('workflow.skillSidebar.menu.moved')
})
it('should fallback to selectedTreeNodeId when tree has no selected node', async () => {
@@ -280,10 +277,7 @@ describe('usePasteOperation', () => {
expect(mocks.workflowState.clearClipboard).not.toHaveBeenCalled()
expect(mocks.emitTreeUpdate).not.toHaveBeenCalled()
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'workflow.skillSidebar.menu.moveError',
- })
+ expect(mocks.toastError).toHaveBeenCalledWith('workflow.skillSidebar.menu.moveError')
})
it('should prevent re-entrant paste while a paste is in progress', async () => {
diff --git a/web/app/components/workflow/skill/hooks/use-skill-save-manager.spec.tsx b/web/app/components/workflow/skill/hooks/use-skill-save-manager.spec.tsx
index f84c64250b..9a81b4917a 100644
--- a/web/app/components/workflow/skill/hooks/use-skill-save-manager.spec.tsx
+++ b/web/app/components/workflow/skill/hooks/use-skill-save-manager.spec.tsx
@@ -8,9 +8,10 @@ import { START_TAB_ID } from '../constants'
import { useSkillSaveManager } from './skill-save-context'
import { SkillSaveProvider } from './use-skill-save-manager'
-const { mockMutateAsync, mockToastNotify } = vi.hoisted(() => ({
+const { mockMutateAsync, mockToastSuccess, mockToastError } = vi.hoisted(() => ({
mockMutateAsync: vi.fn(),
- mockToastNotify: vi.fn(),
+ mockToastSuccess: vi.fn(),
+ mockToastError: vi.fn(),
}))
vi.mock('@/service/use-app-asset', () => ({
@@ -19,9 +20,10 @@ vi.mock('@/service/use-app-asset', () => ({
}),
}))
-vi.mock('@/app/components/base/toast', () => ({
- default: {
- notify: mockToastNotify,
+vi.mock('@/app/components/base/ui/toast', () => ({
+ toast: {
+ success: mockToastSuccess,
+ error: mockToastError,
},
}))
@@ -507,11 +509,9 @@ describe('useSkillSaveManager', () => {
// Assert
await waitFor(() => {
- expect(mockToastNotify).toHaveBeenCalledWith({
- type: 'success',
- message: 'common.api.saved',
- })
+ expect(mockToastSuccess).toHaveBeenCalledWith('common.api.saved')
})
+ expect(mockToastError).not.toHaveBeenCalled()
})
it('should show error toast when save fails', async () => {
@@ -531,11 +531,9 @@ describe('useSkillSaveManager', () => {
// Assert
await waitFor(() => {
- expect(mockToastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'Network error',
- })
+ expect(mockToastError).toHaveBeenCalledWith('Network error')
})
+ expect(mockToastSuccess).not.toHaveBeenCalled()
})
it('should use registered fallback content for keyboard save', async () => {
diff --git a/web/app/components/workflow/skill/start-tab/create-blank-skill-modal.spec.tsx b/web/app/components/workflow/skill/start-tab/create-blank-skill-modal.spec.tsx
index db9409d8a7..f0c314d202 100644
--- a/web/app/components/workflow/skill/start-tab/create-blank-skill-modal.spec.tsx
+++ b/web/app/components/workflow/skill/start-tab/create-blank-skill-modal.spec.tsx
@@ -13,7 +13,8 @@ const mocks = vi.hoisted(() => ({
mutateAsync: vi.fn(),
emitTreeUpdate: vi.fn(),
prepareSkillUploadFile: vi.fn(),
- toastNotify: vi.fn(),
+ toastSuccess: vi.fn(),
+ toastError: vi.fn(),
existingNames: new Set(),
workflowState: {
setUploadStatus: vi.fn(),
@@ -48,9 +49,10 @@ vi.mock('@/app/components/workflow/store', () => ({
}),
}))
-vi.mock('@/app/components/base/toast', () => ({
- default: {
- notify: (...args: unknown[]) => mocks.toastNotify(...args),
+vi.mock('@/app/components/base/ui/toast', () => ({
+ toast: {
+ success: (...args: unknown[]) => mocks.toastSuccess(...args),
+ error: (...args: unknown[]) => mocks.toastError(...args),
},
}))
@@ -121,10 +123,8 @@ describe('CreateBlankSkillModal', () => {
expect(mocks.workflowState.setUploadProgress).toHaveBeenCalledWith({ uploaded: 1, total: 1, failed: 0 })
expect(mocks.emitTreeUpdate).toHaveBeenCalledTimes(1)
expect(mocks.workflowState.openTab).toHaveBeenCalledWith('skill-md-id', { pinned: true })
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'success',
- message: 'workflow.skill.startTab.createSuccess:{"name":"new-skill"}',
- })
+ expect(mocks.toastSuccess).toHaveBeenCalledWith('workflow.skill.startTab.createSuccess:{"name":"new-skill"}')
+ expect(mocks.toastError).not.toHaveBeenCalled()
expect(onClose).toHaveBeenCalledTimes(1)
expect(screen.getByRole('textbox')).toHaveValue('')
})
@@ -141,10 +141,8 @@ describe('CreateBlankSkillModal', () => {
expect(mocks.workflowState.setUploadStatus).toHaveBeenCalledWith('partial_error')
})
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'workflow.skill.startTab.createError',
- })
+ expect(mocks.toastError).toHaveBeenCalledWith('workflow.skill.startTab.createError')
+ expect(mocks.toastSuccess).not.toHaveBeenCalled()
expect(onClose).not.toHaveBeenCalled()
expect(screen.getByRole('textbox')).toHaveValue('')
})
diff --git a/web/app/components/workflow/skill/start-tab/import-skill-modal.spec.tsx b/web/app/components/workflow/skill/start-tab/import-skill-modal.spec.tsx
index ab0532bb50..8aaf920d59 100644
--- a/web/app/components/workflow/skill/start-tab/import-skill-modal.spec.tsx
+++ b/web/app/components/workflow/skill/start-tab/import-skill-modal.spec.tsx
@@ -15,7 +15,8 @@ const mocks = vi.hoisted(() => ({
buildUploadDataFromZip: vi.fn(),
mutateAsync: vi.fn(),
emitTreeUpdate: vi.fn(),
- toastNotify: vi.fn(),
+ toastSuccess: vi.fn(),
+ toastError: vi.fn(),
existingNames: new Set(),
workflowState: {
setUploadStatus: vi.fn(),
@@ -67,9 +68,10 @@ vi.mock('@/app/components/workflow/store', () => ({
}),
}))
-vi.mock('@/app/components/base/toast', () => ({
- default: {
- notify: (...args: unknown[]) => mocks.toastNotify(...args),
+vi.mock('@/app/components/base/ui/toast', () => ({
+ toast: {
+ success: (...args: unknown[]) => mocks.toastSuccess(...args),
+ error: (...args: unknown[]) => mocks.toastError(...args),
},
}))
@@ -116,10 +118,8 @@ describe('ImportSkillModal', () => {
selectFile(new File(['readme'], 'README.md', { type: 'text/markdown' }))
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'workflow.skill.startTab.importModal.invalidFileType',
- })
+ expect(mocks.toastError).toHaveBeenCalledWith('workflow.skill.startTab.importModal.invalidFileType')
+ expect(mocks.toastSuccess).not.toHaveBeenCalled()
expect(screen.getByRole('button', { name: /workflow\.skill\.startTab\.importModal\.importButton/i })).toBeDisabled()
})
@@ -210,10 +210,8 @@ describe('ImportSkillModal', () => {
expect(mocks.workflowState.setUploadProgress).toHaveBeenCalledWith({ uploaded: 1, total: 1, failed: 0 })
expect(mocks.emitTreeUpdate).toHaveBeenCalledTimes(1)
expect(mocks.workflowState.openTab).toHaveBeenCalledWith('skill-md-id', { pinned: true })
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'success',
- message: 'workflow.skill.startTab.importModal.importSuccess:{"name":"new-skill"}',
- })
+ expect(mocks.toastSuccess).toHaveBeenCalledWith('workflow.skill.startTab.importModal.importSuccess:{"name":"new-skill"}')
+ expect(mocks.toastError).not.toHaveBeenCalled()
expect(onClose).toHaveBeenCalledTimes(1)
})
@@ -232,10 +230,8 @@ describe('ImportSkillModal', () => {
expect(mocks.workflowState.setUploadStatus).toHaveBeenCalledWith('partial_error')
})
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'workflow.skill.startTab.importModal.nameDuplicate',
- })
+ expect(mocks.toastError).toHaveBeenCalledWith('workflow.skill.startTab.importModal.nameDuplicate')
+ expect(mocks.toastSuccess).not.toHaveBeenCalled()
expect(mocks.buildUploadDataFromZip).not.toHaveBeenCalled()
expect(mocks.mutateAsync).not.toHaveBeenCalled()
})
@@ -264,10 +260,8 @@ describe('ImportSkillModal', () => {
expect(mocks.workflowState.setUploadStatus).toHaveBeenCalledWith('partial_error')
})
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'workflow.skill.startTab.importModal.errorEmptyZip',
- })
+ expect(mocks.toastError).toHaveBeenCalledWith('workflow.skill.startTab.importModal.errorEmptyZip')
+ expect(mocks.toastSuccess).not.toHaveBeenCalled()
})
it('should fallback to raw error message when zip validation code is unknown', async () => {
@@ -283,10 +277,8 @@ describe('ImportSkillModal', () => {
expect(mocks.workflowState.setUploadStatus).toHaveBeenCalledWith('partial_error')
})
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'custom zip error',
- })
+ expect(mocks.toastError).toHaveBeenCalledWith('custom zip error')
+ expect(mocks.toastSuccess).not.toHaveBeenCalled()
})
it('should fallback to invalid zip error when import fails with non-validation error', async () => {
@@ -300,10 +292,8 @@ describe('ImportSkillModal', () => {
expect(mocks.workflowState.setUploadStatus).toHaveBeenCalledWith('partial_error')
})
- expect(mocks.toastNotify).toHaveBeenCalledWith({
- type: 'error',
- message: 'workflow.skill.startTab.importModal.errorInvalidZip',
- })
+ expect(mocks.toastError).toHaveBeenCalledWith('workflow.skill.startTab.importModal.errorInvalidZip')
+ expect(mocks.toastSuccess).not.toHaveBeenCalled()
})
})
})
diff --git a/web/app/components/workflow/skill/viewer/unsupported-file-download.tsx b/web/app/components/workflow/skill/viewer/unsupported-file-download.tsx
index 1f2caf2765..e367f7bf2e 100644
--- a/web/app/components/workflow/skill/viewer/unsupported-file-download.tsx
+++ b/web/app/components/workflow/skill/viewer/unsupported-file-download.tsx
@@ -30,7 +30,7 @@ const UnsupportedFileDownload = ({ name, size, downloadUrl }: UnsupportedFileDow
{name}
- {fileSize && (
+ {!!fileSize && (
{fileSize}
)}
diff --git a/web/hooks/use-pay.tsx b/web/hooks/use-pay.tsx
index e07eaf56c7..5ce50fdb0f 100644
--- a/web/hooks/use-pay.tsx
+++ b/web/hooks/use-pay.tsx
@@ -18,7 +18,6 @@ export const useAnthropicCheckPay = () => {
useEffect(() => {
if (providerName === 'anthropic' && (paymentResult === 'succeeded' || paymentResult === 'cancelled')) {
- // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
setConfirm({
type: paymentResult === 'succeeded' ? 'info' : 'warning',
title: paymentResult === 'succeeded' ? t('actionMsg.paySucceeded', { ns: 'common' }) : t('actionMsg.payCancelled', { ns: 'common' }),
@@ -38,7 +37,6 @@ export const useBillingPay = () => {
useEffect(() => {
if (paymentType === 'billing' && (paymentResult === 'succeeded' || paymentResult === 'cancelled')) {
- // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
setConfirm({
type: paymentResult === 'succeeded' ? 'info' : 'warning',
title: paymentResult === 'succeeded' ? t('actionMsg.paySucceeded', { ns: 'common' }) : t('actionMsg.payCancelled', { ns: 'common' }),
@@ -66,14 +64,12 @@ export const useCheckNotion = () => {
useEffect(() => {
if (type === 'notion') {
if (notionError) {
- // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
setConfirm({
type: 'warning',
title: notionError,
})
}
else if (notionCode) {
- // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect
setCanBinding(true)
}
}