From d594365a456e795762fbd0966fbaeaccb47f282e Mon Sep 17 00:00:00 2001 From: Novice Date: Tue, 24 Mar 2026 17:05:56 +0800 Subject: [PATCH] feat: enhance configuration and environment setup for SSH sandbox and Creators Platform; update local excludes and improve component logic in the web app --- api/pyrefly-local-excludes.txt | 3 ++ docker/.env.example | 13 ++++++ docker/docker-compose.yaml | 12 +++++ web/app/components/app-sidebar/index.tsx | 20 ++++---- .../base/chat/chat/answer/index.tsx | 2 +- .../components/base/upgrade-modal/index.tsx | 2 +- .../model-selector/popup.spec.tsx | 29 +++++++----- web/app/components/workflow-app/index.tsx | 8 ++-- .../__tests__/workflow-edge-events.spec.tsx | 26 +++++++++++ .../workflow/comment/comment-input.spec.tsx | 2 +- .../workflow/hooks/use-edges-interactions.ts | 3 +- .../nodes/llm/components/computer-use-tip.tsx | 2 +- .../run/loop-log/loop-result-panel.tsx | 2 +- web/app/components/workflow/run/node.tsx | 4 +- .../components/workflow/run/result-text.tsx | 2 +- .../file-reference-block/component.tsx | 2 +- .../plugins/tool-block/component.tsx | 6 +-- .../tool-block/tool-group-block-component.tsx | 8 ++-- .../file-tree/dnd/use-file-drop.spec.tsx | 38 ++++++--------- .../use-download-operation.spec.tsx | 22 +++++---- .../operations/use-modify-operations.spec.tsx | 30 ++++-------- .../operations/use-node-move.spec.tsx | 20 ++++---- .../operations/use-node-reorder.spec.tsx | 20 ++++---- .../operations/use-paste-operation.spec.tsx | 28 +++++------ .../hooks/use-skill-save-manager.spec.tsx | 24 +++++----- .../create-blank-skill-modal.spec.tsx | 22 ++++----- .../start-tab/import-skill-modal.spec.tsx | 46 ++++++++----------- .../viewer/unsupported-file-download.tsx | 2 +- web/hooks/use-pay.tsx | 4 -- 29 files changed, 208 insertions(+), 194 deletions(-) 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) } }