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

@ -11,7 +11,7 @@ import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
} from '@/app/components/base/portal-to-follow-elem-plus'
import { useBasicTypeaheadTriggerMatch } from '@/app/components/base/prompt-editor/hooks'
import { $splitNodeContainingQuery } from '@/app/components/base/prompt-editor/utils'
import { FilePickerPanel } from './file-picker-panel'

View File

@ -8,13 +8,13 @@ import { useTranslation } from 'react-i18next'
import { useStore as useAppStore } from '@/app/components/app/store'
import Button from '@/app/components/base/button'
import Input from '@/app/components/base/input'
import Modal from '@/app/components/base/modal'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Toast from '@/app/components/base/toast'
} from '@/app/components/base/portal-to-follow-elem-plus'
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@/app/components/base/ui/dialog'
import { toast } from '@/app/components/base/ui/toast'
import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card'
import { ROOT_ID } from '@/app/components/workflow/skill/constants'
import TreeGuideLines from '@/app/components/workflow/skill/file-tree/tree/tree-guide-lines'
@ -261,187 +261,190 @@ const FilePickerUploadModal = ({
onClose()
}
catch {
Toast.notify({
type: 'error',
message: t('skillSidebar.menu.createError'),
})
toast.error(t('skillSidebar.menu.createError'))
}
}, [appId, canCreate, effectiveUploadFolderId, emitTreeUpdate, onClose, t, trimmedFileName, uploadFile])
const modeLabel = t('skillEditor.uploadIn')
return (
<Modal
isShow={isOpen}
onClose={handleClose}
title={t('skillEditor.addFiles')}
className="max-w-[360px]"
closable={!isBusy}
clickOutsideNotClose={isBusy}
<Dialog
open={isOpen}
onOpenChange={(open) => {
if (!open)
handleClose()
}}
disablePointerDismissal={isBusy}
>
<div className="-mx-6 mt-4 h-px bg-divider-subtle" />
<div className="mt-4 space-y-4">
<div className="flex items-center gap-2">
<OptionCard
className="flex-1"
title={`${t('operation.create', { ns: 'common' })} ${t('skillSidebar.menu.newFile')}`}
onSelect={() => setMode('create')}
selected={mode === 'create'}
disabled={isBusy}
/>
<OptionCard
className="flex-1"
title={t('skillEditor.uploadFiles')}
onSelect={() => setMode('upload')}
selected={mode === 'upload'}
disabled={isBusy}
/>
</div>
<div className="space-y-1">
<div className="text-text-secondary system-sm-medium">{modeLabel}</div>
<PortalToFollowElem
open={isFolderPickerOpen}
onOpenChange={handleFolderPickerOpenChange}
placement="bottom-start"
offset={{ mainAxis: 4 }}
triggerPopupSameWidth
>
<PortalToFollowElemTrigger asChild>
<button
type="button"
disabled={isBusy}
onClick={handleToggleFolderPicker}
className={cn(
'relative flex h-8 w-full items-center rounded-lg bg-components-input-bg-normal pl-3 pr-10 hover:bg-state-base-hover-alt',
isBusy && 'cursor-not-allowed opacity-60',
)}
>
<span className="i-ri-folder-line mr-2 size-4 shrink-0 text-text-secondary" aria-hidden="true" />
<span className="min-w-0 truncate text-left text-components-input-text-filled system-sm-regular">{selectedFolderPath}</span>
<span className={cn(
'i-ri-arrow-down-s-line absolute right-3 top-1/2 size-4 -translate-y-1/2 text-text-quaternary transition-transform',
isFolderPickerOpen && 'rotate-180',
)}
/>
</button>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className="z-[1200]">
<div
className="max-h-[260px] overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-lg backdrop-blur-sm"
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
}}
>
<Tree<TreeNodeData>
key={folderPickerVersion}
data={uploadInTreeData}
idAccessor="id"
childrenAccessor="children"
width="100%"
className="pb-1"
height={240}
rowHeight={24}
indent={20}
overscanCount={5}
openByDefault={false}
initialOpenState={folderPickerOpenState}
disableDrag
disableDrop
>
{(props: NodeRendererProps<TreeNodeData>) => (
<FolderPickerTreeNode
{...props}
onSelectNode={handleSelectUploadFolder}
/>
)}
</Tree>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
</div>
{mode === 'create' && (
<div className="space-y-1">
<div className="text-text-secondary system-sm-medium">{t('skillSidebar.fileNamePlaceholder')}</div>
<Input
value={fileName}
onChange={e => setFileName(e.target.value)}
placeholder={t('skillSidebar.fileNamePlaceholder') || ''}
<DialogContent className="max-w-[360px]">
{!isBusy && <DialogCloseButton />}
<DialogTitle className="text-text-primary title-2xl-semi-bold">
{t('skillEditor.addFiles')}
</DialogTitle>
<div className="mt-4 h-px bg-divider-subtle" />
<div className="mt-4 space-y-4">
<div className="flex items-center gap-2">
<OptionCard
className="flex-1"
title={`${t('operation.create', { ns: 'common' })} ${t('skillSidebar.menu.newFile')}`}
onSelect={() => setMode('create')}
selected={mode === 'create'}
disabled={isBusy}
/>
<OptionCard
className="flex-1"
title={t('skillEditor.uploadFiles')}
onSelect={() => setMode('upload')}
selected={mode === 'upload'}
disabled={isBusy}
onKeyDown={(e) => {
if (e.key === 'Enter')
void handleCreateFile()
}}
/>
</div>
)}
<input
ref={fileInputRef}
type="file"
multiple
className="hidden"
onChange={handleUploadFilesChange}
/>
{mode === 'upload' && (
<div
role="button"
tabIndex={0}
className={cn(
'flex h-12 cursor-pointer items-center justify-center gap-1 rounded-xl border border-dashed text-text-secondary',
isDragOver ? 'border-components-button-primary-border bg-state-accent-hover' : 'border-divider-subtle bg-components-panel-bg',
isBusy && 'cursor-not-allowed opacity-60',
)}
onClick={() => {
if (!isBusy && appId)
fileInputRef.current?.click()
}}
onDragOver={(e) => {
e.preventDefault()
if (!isBusy)
setIsDragOver(true)
}}
onDragLeave={(e) => {
e.preventDefault()
setIsDragOver(false)
}}
onDrop={handleDrop}
onKeyDown={(e) => {
if ((e.key === 'Enter' || e.key === ' ') && !isBusy && appId)
fileInputRef.current?.click()
}}
>
<span className="i-ri-upload-cloud-line size-4 text-text-tertiary" aria-hidden="true" />
<span className="system-sm-regular">{t('skillSidebar.dropTip')}</span>
<span className="text-text-accent system-sm-medium">{t('skill.startTab.importModal.browseFiles')}</span>
<div className="space-y-1">
<div className="text-text-secondary system-sm-medium">{modeLabel}</div>
<PortalToFollowElem
open={isFolderPickerOpen}
onOpenChange={handleFolderPickerOpenChange}
placement="bottom-start"
offset={{ mainAxis: 4 }}
triggerPopupSameWidth
>
<PortalToFollowElemTrigger asChild>
<button
type="button"
disabled={isBusy}
onClick={handleToggleFolderPicker}
className={cn(
'relative flex h-8 w-full items-center rounded-lg bg-components-input-bg-normal pl-3 pr-10 hover:bg-state-base-hover-alt',
isBusy && 'cursor-not-allowed opacity-60',
)}
>
<span className="i-ri-folder-line mr-2 size-4 shrink-0 text-text-secondary" aria-hidden="true" />
<span className="min-w-0 truncate text-left text-components-input-text-filled system-sm-regular">{selectedFolderPath}</span>
<span className={cn(
'i-ri-arrow-down-s-line absolute right-3 top-1/2 size-4 -translate-y-1/2 text-text-quaternary transition-transform',
isFolderPickerOpen && 'rotate-180',
)}
/>
</button>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className="z-[1200]">
<div
className="max-h-[260px] overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-lg backdrop-blur-sm"
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
}}
>
<Tree<TreeNodeData>
key={folderPickerVersion}
data={uploadInTreeData}
idAccessor="id"
childrenAccessor="children"
width="100%"
className="pb-1"
height={240}
rowHeight={24}
indent={20}
overscanCount={5}
openByDefault={false}
initialOpenState={folderPickerOpenState}
disableDrag
disableDrop
>
{(props: NodeRendererProps<TreeNodeData>) => (
<FolderPickerTreeNode
{...props}
onSelectNode={handleSelectUploadFolder}
/>
)}
</Tree>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
</div>
)}
<div className="flex justify-end gap-2">
<Button onClick={handleClose} disabled={isBusy}>
{t('operation.cancel', { ns: 'common' })}
</Button>
{mode === 'create'
? (
<Button
variant="primary"
onClick={handleCreateFile}
disabled={!canCreate}
loading={isCreatingFile}
>
{t('operation.create', { ns: 'common' })}
</Button>
)
: (
<Button
variant="primary"
onClick={() => fileInputRef.current?.click()}
disabled={!appId || isBusy}
loading={isCreating}
>
{t('skillEditor.uploadFiles')}
</Button>
{mode === 'create' && (
<div className="space-y-1">
<div className="text-text-secondary system-sm-medium">{t('skillSidebar.fileNamePlaceholder')}</div>
<Input
value={fileName}
onChange={e => setFileName(e.target.value)}
placeholder={t('skillSidebar.fileNamePlaceholder') || ''}
disabled={isBusy}
onKeyDown={(e) => {
if (e.key === 'Enter')
void handleCreateFile()
}}
/>
</div>
)}
<input
ref={fileInputRef}
type="file"
multiple
className="hidden"
onChange={handleUploadFilesChange}
/>
{mode === 'upload' && (
<div
role="button"
tabIndex={0}
className={cn(
'flex h-12 cursor-pointer items-center justify-center gap-1 rounded-xl border border-dashed text-text-secondary',
isDragOver ? 'border-components-button-primary-border bg-state-accent-hover' : 'border-divider-subtle bg-components-panel-bg',
isBusy && 'cursor-not-allowed opacity-60',
)}
onClick={() => {
if (!isBusy && appId)
fileInputRef.current?.click()
}}
onDragOver={(e) => {
e.preventDefault()
if (!isBusy)
setIsDragOver(true)
}}
onDragLeave={(e) => {
e.preventDefault()
setIsDragOver(false)
}}
onDrop={handleDrop}
onKeyDown={(e) => {
if ((e.key === 'Enter' || e.key === ' ') && !isBusy && appId)
fileInputRef.current?.click()
}}
>
<span className="i-ri-upload-cloud-line size-4 text-text-tertiary" aria-hidden="true" />
<span className="system-sm-regular">{t('skillSidebar.dropTip')}</span>
<span className="text-text-accent system-sm-medium">{t('skill.startTab.importModal.browseFiles')}</span>
</div>
)}
<div className="flex justify-end gap-2">
<Button onClick={handleClose} disabled={isBusy}>
{t('operation.cancel', { ns: 'common' })}
</Button>
{mode === 'create'
? (
<Button
variant="primary"
onClick={handleCreateFile}
disabled={!canCreate}
loading={isCreatingFile}
>
{t('operation.create', { ns: 'common' })}
</Button>
)
: (
<Button
variant="primary"
onClick={() => fileInputRef.current?.click()}
disabled={!appId || isBusy}
loading={isCreating}
>
{t('skillEditor.uploadFiles')}
</Button>
)}
</div>
</div>
</div>
</Modal>
</DialogContent>
</Dialog>
)
}

View File

@ -13,9 +13,9 @@ import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
} from '@/app/components/base/portal-to-follow-elem-plus'
import { useSelectOrDelete } from '@/app/components/base/prompt-editor/hooks'
import Tooltip from '@/app/components/base/tooltip'
import Tooltip from '@/app/components/base/tooltip-plus'
import { START_TAB_ID } from '@/app/components/workflow/skill/constants'
import { useSkillAssetNodeMap } from '@/app/components/workflow/skill/hooks/file-tree/data/use-skill-asset-tree'
import { getFileIconType } from '@/app/components/workflow/skill/utils/file-utils'

View File

@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'
import { useStore as useAppStore } from '@/app/components/app/store'
import FileTypeIcon from '@/app/components/base/file-uploader/file-type-icon'
import Loading from '@/app/components/base/loading'
import Tooltip from '@/app/components/base/tooltip'
import Tooltip from '@/app/components/base/tooltip-plus'
import SkillEditor from '@/app/components/workflow/skill/editor/skill-editor'
import { useFileTypeInfo } from '@/app/components/workflow/skill/hooks/use-file-type-info'
import { getFileIconType } from '@/app/components/workflow/skill/utils/file-utils'

View File

@ -0,0 +1,5 @@
import type { ToolBlockPayload } from './node'
import { createCommand } from 'lexical'
export const INSERT_TOOL_BLOCK_COMMAND = createCommand<ToolBlockPayload>('INSERT_TOOL_BLOCK_COMMAND')
export const DELETE_TOOL_BLOCK_COMMAND = createCommand('DELETE_TOOL_BLOCK_COMMAND')

View File

@ -4,7 +4,6 @@ import type { ToolValue } from '@/app/components/workflow/block-selector/types'
import type { ToolWithProvider } from '@/app/components/workflow/types'
import type { AppAssetTreeView } from '@/types/app-asset'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import Link from 'next/link'
import * as React from 'react'
import { useEffect, useMemo, useState } from 'react'
import { createPortal } from 'react-dom'
@ -12,9 +11,11 @@ import { Trans, useTranslation } from 'react-i18next'
import { useShallow } from 'zustand/react/shallow'
import AppIcon from '@/app/components/base/app-icon'
import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general'
// eslint-disable-next-line no-restricted-imports
import Modal from '@/app/components/base/modal'
import { useSelectOrDelete } from '@/app/components/base/prompt-editor/hooks'
import Tooltip from '@/app/components/base/tooltip'
import Tooltip from '@/app/components/base/tooltip-plus'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import ToolAuthorizationSection from '@/app/components/plugins/plugin-detail-panel/tool-selector/sections/tool-authorization-section'
import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance'
@ -27,6 +28,7 @@ import ToolSettingsSection from '@/app/components/workflow/skill/editor/skill-ed
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
import { useGetLanguage } from '@/context/i18n'
import useTheme from '@/hooks/use-theme'
import Link from '@/next/link'
import {
useAllBuiltInTools,
useAllCustomTools,
@ -37,7 +39,7 @@ import { Theme } from '@/types/app'
import { canFindTool } from '@/utils'
import { cn } from '@/utils/classnames'
import { basePath } from '@/utils/var'
import { DELETE_TOOL_BLOCK_COMMAND } from './index'
import { DELETE_TOOL_BLOCK_COMMAND } from './commands'
import { useToolBlockContext } from './tool-block-context'
import ToolHeader from './tool-header'

View File

@ -4,14 +4,11 @@ import { mergeRegister } from '@lexical/utils'
import {
$insertNodes,
COMMAND_PRIORITY_EDITOR,
createCommand,
} from 'lexical'
import { memo, useEffect } from 'react'
import { DELETE_TOOL_BLOCK_COMMAND, INSERT_TOOL_BLOCK_COMMAND } from './commands'
import { $createToolBlockNode, ToolBlockNode } from './node'
export const INSERT_TOOL_BLOCK_COMMAND = createCommand<ToolBlockPayload>('INSERT_TOOL_BLOCK_COMMAND')
export const DELETE_TOOL_BLOCK_COMMAND = createCommand('DELETE_TOOL_BLOCK_COMMAND')
const ToolBlock = memo(() => {
const [editor] = useLexicalComposerContext()

View File

@ -4,17 +4,18 @@ import type { ToolParameter } from '@/app/components/tools/types'
import type { ToolValue } from '@/app/components/workflow/block-selector/types'
import type { ToolWithProvider } from '@/app/components/workflow/types'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import Link from 'next/link'
import * as React from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { createPortal } from 'react-dom'
import { Trans, useTranslation } from 'react-i18next'
import { useShallow } from 'zustand/react/shallow'
import AppIcon from '@/app/components/base/app-icon'
// eslint-disable-next-line no-restricted-imports
import Modal from '@/app/components/base/modal'
import { useSelectOrDelete } from '@/app/components/base/prompt-editor/hooks'
import Switch from '@/app/components/base/switch'
import Tooltip from '@/app/components/base/tooltip'
import Tooltip from '@/app/components/base/tooltip-plus'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import ToolAuthorizationSection from '@/app/components/plugins/plugin-detail-panel/tool-selector/sections/tool-authorization-section'
import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance'
@ -27,6 +28,7 @@ import ToolSettingsSection from '@/app/components/workflow/skill/editor/skill-ed
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
import { useGetLanguage } from '@/context/i18n'
import useTheme from '@/hooks/use-theme'
import Link from '@/next/link'
import {
useAllBuiltInTools,
useAllCustomTools,
@ -37,7 +39,7 @@ import { Theme } from '@/types/app'
import { canFindTool } from '@/utils'
import { cn } from '@/utils/classnames'
import { basePath } from '@/utils/var'
import { DELETE_TOOL_BLOCK_COMMAND } from './index'
import { DELETE_TOOL_BLOCK_COMMAND } from './commands'
import { useToolBlockContext } from './tool-block-context'
import ToolHeader from './tool-header'