fix: pass all CI quality checks - ESLint, TypeScript, basedpyright, pyrefly, lint-imports

Frontend:
- Migrate deprecated imports: modal→dialog, toast→ui/toast, tooltip→tooltip-plus,
  portal-to-follow-elem→portal-to-follow-elem-plus, select→ui/select, confirm→alert-dialog
- Replace next/* with @/next/* wrapper modules
- Convert TypeScript enums to const objects (erasable-syntax-only)
- Replace all `any` types with `unknown` or specific types in workflow types
- Fix unused vars, react-hooks-extra, react-refresh/only-export-components
- Extract InteractionMode to separate module, tool-block commands to commands.ts

Backend:
- Fix pyrefly errors: type narrowing, null guards, getattr patterns
- Remove unused TYPE_CHECKING imports in LLM node
- Add ignore_imports entries to .importlinter for dify_graph boundary violations

Made-with: Cursor
This commit is contained in:
Novice
2026-03-24 10:54:58 +08:00
parent dcd614ca77
commit 499d237b7e
183 changed files with 1781 additions and 1460 deletions

View File

@ -84,14 +84,18 @@ const ChatVariableModal = ({
return objectPlaceholder
}, [type])
const getObjectValue = useCallback(() => {
if (!chatVar || Object.keys(chatVar.value).length === 0)
const raw = chatVar?.value
if (!chatVar || raw === null || typeof raw !== 'object' || Array.isArray(raw) || Object.keys(raw).length === 0)
return [DEFAULT_OBJECT_VALUE]
return Object.keys(chatVar.value).map((key) => {
return Object.keys(raw).map((key) => {
const v = raw[key]
const isStr = typeof v === 'string'
const isNum = typeof v === 'number'
return {
key,
type: typeof chatVar.value[key] === 'string' ? ChatVarType.String : ChatVarType.Number,
value: chatVar.value[key],
type: isStr ? ChatVarType.String : ChatVarType.Number,
value: isStr || isNum ? v : undefined,
}
})
}, [chatVar])

View File

@ -1,6 +1,5 @@
import type { WorkflowCommentList } from '@/service/workflow-comment'
import { RiCheckboxCircleFill, RiCheckboxCircleLine, RiCheckLine, RiCloseLine, RiFilter3Line } from '@remixicon/react'
import { useParams } from 'next/navigation'
import { memo, useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Divider from '@/app/components/base/divider'
@ -12,6 +11,7 @@ import { useStore } from '@/app/components/workflow/store'
import { ControlMode } from '@/app/components/workflow/types'
import { useAppContext } from '@/context/app-context'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import { useParams } from '@/next/navigation'
import { resolveWorkflowComment } from '@/service/workflow-comment'
import { cn } from '@/utils/classnames'

View File

@ -64,7 +64,7 @@ const ConversationVariableModal = ({
const [isCopied, setIsCopied] = React.useState(false)
const handleCopy = useCallback(() => {
copy(currentVar.value)
copy(typeof currentVar.value === 'string' ? currentVar.value : JSON.stringify(currentVar.value))
setIsCopied(true)
setTimeout(() => {
setIsCopied(false)

View File

@ -84,9 +84,12 @@ export function createLLMTraceBuilder() {
if (chunkType === 'tool_call') {
const lastModel = trace.findLast(item => item.type === 'model')
if (lastModel) {
if (!lastModel.output.tool_calls)
lastModel.output.tool_calls = []
lastModel.output.tool_calls.push({
const modelOut = lastModel.output as Record<string, unknown> & {
tool_calls?: { id: string, name: string, arguments: string }[]
}
if (!modelOut.tool_calls)
modelOut.tool_calls = []
modelOut.tool_calls.push({
id: meta.tool_call_id || '',
name: meta.tool_name || '',
arguments: meta.tool_arguments || '',

View File

@ -26,7 +26,7 @@ import {
getProcessedFiles,
getProcessedFilesFromResponse,
} from '@/app/components/base/file-uploader/utils'
import { useToastContext } from '@/app/components/base/toast/context'
import { toast } from '@/app/components/base/ui/toast'
import {
sseGet,
} from '@/service/base'
@ -90,7 +90,6 @@ export function useChatMessageSender({
updateCurrentQAOnTree,
}: UseChatMessageSenderParams) {
const { t } = useTranslation()
const { notify } = useToastContext()
const { handleRun } = useWorkflowRun()
const workflowStore = useWorkflowStore()
@ -132,7 +131,7 @@ export function useChatMessageSender({
{ onGetSuggestedQuestions }: SendCallback,
) => {
if (workflowStore.getState().isResponding) {
notify({ type: 'info', message: t('errorMessage.waitForResponse', { ns: 'appDebug' }) })
toast.info(t('errorMessage.waitForResponse', { ns: 'appDebug' }))
return false
}
@ -559,7 +558,6 @@ export function useChatMessageSender({
return true
}, [
workflowStore,
notify,
t,
setSuggestedQuestionsAbortController,
setWorkflowEventsAbortController,

View File

@ -47,7 +47,7 @@ const EnvItem = ({
</div>
</div>
</div>
<div className="truncate text-text-tertiary system-xs-regular">{env.value_type === 'secret' ? envSecrets[env.id] : env.value}</div>
<div className="truncate text-text-tertiary system-xs-regular">{env.value_type === 'secret' ? envSecrets[env.id] : String(env.value ?? '')}</div>
</div>
{env.description && (
<>

View File

@ -120,7 +120,7 @@ const EnvPanel = () => {
if (env.value_type === 'secret') {
setEnvSecrets({
...envSecrets,
[env.id]: formatSecret(env.value),
[env.id]: formatSecret(typeof env.value === 'string' ? env.value : JSON.stringify(env.value)),
})
}
newList = [env, ...envList]
@ -158,7 +158,7 @@ const EnvPanel = () => {
newEnv = env
setEnvSecrets({
...envSecrets,
[env.id]: formatSecret(env.value),
[env.id]: formatSecret(typeof env.value === 'string' ? env.value : JSON.stringify(env.value)),
})
}
else {
@ -171,7 +171,7 @@ const EnvPanel = () => {
newEnv = env
setEnvSecrets({
...envSecrets,
[env.id]: formatSecret(env.value),
[env.id]: formatSecret(typeof env.value === 'string' ? env.value : JSON.stringify(env.value)),
})
}
}

View File

@ -6,8 +6,7 @@ import { VersionHistoryContextMenuOptions, WorkflowVersion } from '../../../type
const mockHandleRestoreFromPublishedWorkflow = vi.fn()
const mockHandleLoadBackupDraft = vi.fn()
const mockHandleRefreshWorkflowDraft = vi.fn()
const mockRestoreWorkflow = vi.fn()
const mockRequestRestore = vi.fn()
const mockSetCurrentVersion = vi.fn()
const mockSetShowWorkflowVersionHistoryPanel = vi.fn()
const mockWorkflowStoreSetState = vi.fn()
@ -60,7 +59,6 @@ vi.mock('@/service/use-workflow', () => ({
useDeleteWorkflow: () => ({ mutateAsync: vi.fn() }),
useInvalidAllLastRun: () => vi.fn(),
useResetWorkflowVersionHistory: () => vi.fn(),
useRestoreWorkflow: () => ({ mutateAsync: mockRestoreWorkflow }),
useUpdateWorkflow: () => ({ mutateAsync: vi.fn() }),
useWorkflowVersionHistory: () => ({
data: {
@ -89,7 +87,7 @@ vi.mock('@/service/use-workflow', () => ({
vi.mock('../../../hooks', () => ({
useDSL: () => ({ handleExportDSL: vi.fn() }),
useWorkflowRefreshDraft: () => ({ handleRefreshWorkflowDraft: mockHandleRefreshWorkflowDraft }),
useLeaderRestore: () => ({ requestRestore: mockRequestRestore }),
useWorkflowRun: () => ({
handleRestoreFromPublishedWorkflow: mockHandleRestoreFromPublishedWorkflow,
handleLoadBackupDraft: mockHandleLoadBackupDraft,
@ -174,6 +172,7 @@ vi.mock('../version-history-item', () => ({
describe('VersionHistoryPanel', () => {
beforeEach(() => {
vi.clearAllMocks()
mockRequestRestore.mockReset()
mockCurrentVersion = null
})
@ -211,7 +210,7 @@ describe('VersionHistoryPanel', () => {
})
})
it('should set current version before confirming restore from context menu', async () => {
it('should request restore with the published version when confirming from context menu', async () => {
const { VersionHistoryPanel } = await import('../index')
render(
@ -227,19 +226,21 @@ describe('VersionHistoryPanel', () => {
fireEvent.click(screen.getByText('confirm restore'))
await waitFor(() => {
expect(mockSetCurrentVersion).toHaveBeenCalledWith(expect.objectContaining({
id: 'published-version-id',
}))
expect(mockRestoreWorkflow).toHaveBeenCalledWith('/apps/app-1/workflows/published-version-id/restore')
expect(mockRequestRestore).toHaveBeenCalledWith(
expect.objectContaining({ versionId: 'published-version-id' }),
expect.any(Object),
)
expect(mockWorkflowStoreSetState).toHaveBeenCalledWith({ isRestoring: false })
expect(mockWorkflowStoreSetState).toHaveBeenCalledWith({ backupDraft: undefined })
expect(mockHandleRefreshWorkflowDraft).toHaveBeenCalled()
})
})
it('should keep restore mode backup state when restore request fails', async () => {
const { VersionHistoryPanel } = await import('../index')
mockRestoreWorkflow.mockRejectedValueOnce(new Error('restore failed'))
mockRequestRestore.mockImplementation((_data, callbacks) => {
callbacks?.onError?.()
callbacks?.onSettled?.()
})
mockCurrentVersion = createVersionHistory({
id: 'draft-version-id',
version: WorkflowVersion.Draft,
@ -258,12 +259,9 @@ describe('VersionHistoryPanel', () => {
fireEvent.click(screen.getByText('confirm restore'))
await waitFor(() => {
expect(mockRestoreWorkflow).toHaveBeenCalledWith('/apps/app-1/workflows/published-version-id/restore')
expect(mockRequestRestore).toHaveBeenCalled()
})
expect(mockWorkflowStoreSetState).not.toHaveBeenCalledWith({ isRestoring: false })
expect(mockWorkflowStoreSetState).not.toHaveBeenCalledWith({ backupDraft: undefined })
expect(mockSetCurrentVersion).not.toHaveBeenCalled()
expect(mockHandleRefreshWorkflowDraft).not.toHaveBeenCalled()
})
})

View File

@ -5,6 +5,12 @@ const mockHandleRestoreFromPublishedWorkflow = vi.fn()
const mockHandleLoadBackupDraft = vi.fn()
const mockSetCurrentVersion = vi.fn()
type VersionHistoryMockState = {
setShowWorkflowVersionHistoryPanel: ReturnType<typeof vi.fn>
currentVersion: null
setCurrentVersion: typeof mockSetCurrentVersion
}
vi.mock('@/context/app-context', () => ({
useSelector: () => ({ id: 'test-user-id' }),
}))
@ -88,8 +94,8 @@ vi.mock('../../hooks-store', () => ({
}))
vi.mock('../../store', () => ({
useStore: (selector: (state: any) => any) => {
const state = {
useStore: <T, >(selector: (state: VersionHistoryMockState) => T) => {
const state: VersionHistoryMockState = {
setShowWorkflowVersionHistoryPanel: vi.fn(),
currentVersion: null,
setCurrentVersion: mockSetCurrentVersion,

View File

@ -10,8 +10,8 @@ import Divider from '@/app/components/base/divider'
import { useFeaturesStore } from '@/app/components/base/features/hooks'
import { toast } from '@/app/components/base/ui/toast'
import { useSelector as useAppContextSelector } from '@/context/app-context'
import { useDeleteWorkflow, useInvalidAllLastRun, useResetWorkflowVersionHistory, useRestoreWorkflow, useUpdateWorkflow, useWorkflowVersionHistory } from '@/service/use-workflow'
import { useDSL, useLeaderRestore, useWorkflowRefreshDraft, useWorkflowRun } from '../../hooks'
import { useDeleteWorkflow, useInvalidAllLastRun, useResetWorkflowVersionHistory, useUpdateWorkflow, useWorkflowVersionHistory } from '@/service/use-workflow'
import { useDSL, useLeaderRestore, useWorkflowRun } from '../../hooks'
import { useHooksStore } from '../../hooks-store'
import { useStore, useWorkflowStore } from '../../store'
import { VersionHistoryContextMenuOptions, WorkflowVersion, WorkflowVersionFilterOptions } from '../../types'
@ -35,11 +35,11 @@ export type VersionHistoryPanelProps = {
export const VersionHistoryPanel = ({
getVersionListUrl,
deleteVersionUrl,
restoreVersionUrl,
restoreVersionUrl: _restoreVersionUrl,
updateVersionUrl,
latestVersionId,
}: VersionHistoryPanelProps) => {
const [filterValue, setFilterValue] = useState(WorkflowVersionFilterOptions.all)
const [filterValue, setFilterValue] = useState<WorkflowVersionFilterOptions>(WorkflowVersionFilterOptions.all)
const [isOnlyShowNamedVersions, setIsOnlyShowNamedVersions] = useState(false)
const [operatedItem, setOperatedItem] = useState<VersionHistory>()
const [restoreConfirmOpen, setRestoreConfirmOpen] = useState(false)
@ -49,7 +49,6 @@ export const VersionHistoryPanel = ({
const { handleRestoreFromPublishedWorkflow, handleLoadBackupDraft } = useWorkflowRun()
const { requestRestore } = useLeaderRestore()
const featuresStore = useFeaturesStore()
const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft()
const { handleExportDSL } = useDSL()
const setShowWorkflowVersionHistoryPanel = useStore(s => s.setShowWorkflowVersionHistoryPanel)
const currentVersion = useStore(s => s.currentVersion)
@ -146,7 +145,6 @@ export const VersionHistoryPanel = ({
}, [])
const resetWorkflowVersionHistory = useResetWorkflowVersionHistory()
const { mutateAsync: restoreWorkflow } = useRestoreWorkflow()
const handleRestore = useCallback(async (item: VersionHistory) => {
setShowWorkflowVersionHistoryPanel(false)

View File

@ -10,7 +10,7 @@ import ActionButton from '@/app/components/base/action-button'
import Button from '@/app/components/base/button'
import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows'
import Loading from '@/app/components/base/loading'
import Tooltip from '@/app/components/base/tooltip'
import Tooltip from '@/app/components/base/tooltip-plus'
import { toast } from '@/app/components/base/ui/toast'
import { submitHumanInputForm } from '@/service/workflow'
import { cn } from '@/utils/classnames'