mirror of https://github.com/langgenius/dify.git
feat: enhance configuration and environment setup for SSH sandbox and Creators Platform; update local excludes and improve component logic in the web app
This commit is contained in:
parent
a844936759
commit
d594365a45
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<div className={cn('shrink-0 border-t border-divider-subtle', expand ? 'p-3' : 'p-2')}>
|
||||
<Tooltip popupContent={!expand ? t('sandboxMigrationModal.title', { ns: 'workflow' }) : undefined}>
|
||||
<button
|
||||
type="button"
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
className={cn(
|
||||
'flex w-full cursor-pointer items-center gap-2 rounded-lg text-components-menu-item-text',
|
||||
'hover:bg-components-menu-item-bg-hover hover:text-components-menu-item-text-hover',
|
||||
|
|
@ -174,7 +173,12 @@ const AppDetailNav = ({
|
|||
{expand && (
|
||||
<span className="system-xs-medium">{t('sandboxMigrationModal.title', { ns: 'workflow' })}</span>
|
||||
)}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
{!expand && (
|
||||
<TooltipContent>
|
||||
{t('sandboxMigrationModal.title', { ns: 'workflow' })}
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ const Answer: FC<AnswerProps> = ({
|
|||
}, [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'
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ const UpgradeModalBase: FC<Props> = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{footer && (
|
||||
{!!footer && (
|
||||
<div className="mb-8 mt-10 flex justify-end space-x-2 px-8">
|
||||
{footer}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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(<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>)
|
||||
}
|
||||
|
||||
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(
|
||||
<Popup
|
||||
modelList={[makeModel()]}
|
||||
onSelect={vi.fn()}
|
||||
|
|
@ -92,7 +99,7 @@ describe('Popup', () => {
|
|||
|
||||
// When tool-call support is missing, it should be filtered out.
|
||||
mockSupportFunctionCall.mockReturnValue(false)
|
||||
const { unmount } = render(
|
||||
const { unmount } = renderWithProviders(
|
||||
<Popup
|
||||
modelList={modelList}
|
||||
onSelect={vi.fn()}
|
||||
|
|
@ -105,7 +112,7 @@ describe('Popup', () => {
|
|||
// 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(
|
||||
<Popup
|
||||
modelList={modelList}
|
||||
onSelect={vi.fn()}
|
||||
|
|
@ -116,7 +123,7 @@ describe('Popup', () => {
|
|||
expect(screen.getByText('openai')).toBeInTheDocument()
|
||||
|
||||
unmount2()
|
||||
const { unmount: unmount3 } = render(
|
||||
const { unmount: unmount3 } = renderWithProviders(
|
||||
<Popup
|
||||
modelList={modelList}
|
||||
onSelect={vi.fn()}
|
||||
|
|
@ -128,7 +135,7 @@ describe('Popup', () => {
|
|||
|
||||
// When features are missing, non-toolCall feature checks should fail.
|
||||
unmount3()
|
||||
render(
|
||||
renderWithProviders(
|
||||
<Popup
|
||||
modelList={[makeModel({ models: [makeModelItem({ features: undefined })] })]}
|
||||
onSelect={vi.fn()}
|
||||
|
|
@ -142,7 +149,7 @@ describe('Popup', () => {
|
|||
it('should match labels from other languages when current language key is missing', () => {
|
||||
mockLanguage = 'fr_FR'
|
||||
|
||||
render(
|
||||
renderWithProviders(
|
||||
<Popup
|
||||
modelList={[makeModel()]}
|
||||
onSelect={vi.fn()}
|
||||
|
|
@ -163,7 +170,7 @@ describe('Popup', () => {
|
|||
models: [makeModelItem({ features: [ModelFeatureEnum.toolCall] })],
|
||||
})
|
||||
|
||||
render(
|
||||
renderWithProviders(
|
||||
<Popup
|
||||
modelList={[modelWithToolCallOnly]}
|
||||
onSelect={vi.fn()}
|
||||
|
|
@ -177,7 +184,7 @@ describe('Popup', () => {
|
|||
})
|
||||
|
||||
it('should close tooltip on scroll', () => {
|
||||
const { container } = render(
|
||||
const { container } = renderWithProviders(
|
||||
<Popup
|
||||
modelList={[makeModel()]}
|
||||
onSelect={vi.fn()}
|
||||
|
|
@ -190,7 +197,7 @@ describe('Popup', () => {
|
|||
})
|
||||
|
||||
it('should open provider settings when clicking footer link', () => {
|
||||
render(
|
||||
renderWithProviders(
|
||||
<Popup
|
||||
modelList={[makeModel()]}
|
||||
onSelect={vi.fn()}
|
||||
|
|
@ -207,7 +214,7 @@ describe('Popup', () => {
|
|||
|
||||
it('should call onHide when footer settings link is clicked', () => {
|
||||
const mockOnHide = vi.fn()
|
||||
render(
|
||||
renderWithProviders(
|
||||
<Popup
|
||||
modelList={[makeModel()]}
|
||||
onSelect={vi.fn()}
|
||||
|
|
@ -221,7 +228,7 @@ describe('Popup', () => {
|
|||
})
|
||||
|
||||
it('should match model label when searchText is non-empty and label key exists for current language', () => {
|
||||
render(
|
||||
renderWithProviders(
|
||||
<Popup
|
||||
modelList={[makeModel()]}
|
||||
onSelect={vi.fn()}
|
||||
|
|
|
|||
|
|
@ -195,8 +195,8 @@ const WorkflowAppWithAdditionalContext = () => {
|
|||
}, [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])
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ vi.mock('@/context/app-context', () => ({
|
|||
}))
|
||||
|
||||
vi.mock('@/app/components/base/avatar', () => ({
|
||||
default: ({ name }: { name: string }) => <div data-testid="avatar">{name}</div>,
|
||||
Avatar: ({ name }: { name: string }) => <div data-testid="avatar">{name}</div>,
|
||||
}))
|
||||
|
||||
vi.mock('./mention-input', () => ({
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const ComputerUseTip: FC<Props> = ({
|
|||
|
||||
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])
|
||||
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ const LoopResultPanel: FC<Props> = ({
|
|||
)}
|
||||
>
|
||||
{
|
||||
loopVariableMap?.[index] && (
|
||||
!!loopVariableMap?.[index] && (
|
||||
<div className="p-2 pb-0">
|
||||
<CodeEditor
|
||||
readOnly
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ const NodePanel: FC<Props> = ({
|
|||
</StatusContainer>
|
||||
)}
|
||||
</div>
|
||||
{nodeInfo.inputs && (
|
||||
{!!nodeInfo.inputs && (
|
||||
<div className={cn('mb-1')}>
|
||||
<CodeEditor
|
||||
readOnly
|
||||
|
|
@ -267,7 +267,7 @@ const NodePanel: FC<Props> = ({
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
{nodeInfo.process_data && (
|
||||
{!!nodeInfo.process_data && (
|
||||
<div className={cn('mb-1')}>
|
||||
<CodeEditor
|
||||
readOnly
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ const ResultText: FC<ResultTextProps> = ({
|
|||
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'
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -31,11 +31,13 @@ const createDeferred = <T,>(): Deferred<T> => {
|
|||
const {
|
||||
mockGetFileDownloadUrl,
|
||||
mockDownloadUrl,
|
||||
mockToastNotify,
|
||||
mockToastSuccess,
|
||||
mockToastError,
|
||||
} = vi.hoisted(() => ({
|
||||
mockGetFileDownloadUrl: vi.fn<(request: DownloadRequest) => Promise<DownloadResponse>>(),
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ const mocks = vi.hoisted(() => ({
|
|||
deletePending: false,
|
||||
deleteMutateAsync: vi.fn<(payload: DeleteMutationPayload) => Promise<void>>(),
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ const mocks = vi.hoisted(() => ({
|
|||
movePending: false,
|
||||
moveMutateAsync: vi.fn<(payload: MoveMutationPayload) => Promise<void>>(),
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ const mocks = vi.hoisted(() => ({
|
|||
reorderPending: false,
|
||||
reorderMutateAsync: vi.fn<(payload: ReorderMutationPayload) => Promise<void>>(),
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -56,7 +56,8 @@ const mocks = vi.hoisted(() => ({
|
|||
movePending: false,
|
||||
moveMutateAsync: vi.fn<(payload: MoveMutationPayload) => Promise<void>>(),
|
||||
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 () => {
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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<string>(),
|
||||
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('')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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<string>(),
|
||||
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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const UnsupportedFileDownload = ({ name, size, downloadUrl }: UnsupportedFileDow
|
|||
<FileTypeIcon type={FileAppearanceTypeEnum.custom} size="xl" className="size-16 text-text-tertiary" />
|
||||
<div className="flex flex-col items-center gap-1 text-center">
|
||||
<p className="text-text-secondary system-md-medium">{name}</p>
|
||||
{fileSize && (
|
||||
{!!fileSize && (
|
||||
<p className="text-text-tertiary system-xs-regular">{fileSize}</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue