feat: support file and fold not find

This commit is contained in:
Joel
2026-02-06 11:12:37 +08:00
parent 6ac9bbfd5f
commit a0984a779f
3 changed files with 67 additions and 34 deletions

View File

@ -2,11 +2,12 @@ import type { LexicalNode } from 'lexical'
import type { FileAppearanceType } from '@/app/components/base/file-uploader/types'
import type { TreeNodeData } from '@/app/components/workflow/skill/type'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { RiFolderLine } from '@remixicon/react'
import { RiAlertFill, RiFolderLine } from '@remixicon/react'
import { $getNodeByKey } from 'lexical'
import * as React from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import { useTranslation } from 'react-i18next'
import FileTypeIcon from '@/app/components/base/file-uploader/file-type-icon'
import {
PortalToFollowElem,
@ -14,6 +15,7 @@ import {
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { useSelectOrDelete } from '@/app/components/base/prompt-editor/hooks'
import Tooltip from '@/app/components/base/tooltip'
import { useSkillAssetNodeMap } from '@/app/components/workflow/skill/hooks/use-skill-asset-tree'
import { getFileIconType } from '@/app/components/workflow/skill/utils/file-utils'
import { cn } from '@/utils/classnames'
@ -29,20 +31,42 @@ type FileReferenceBlockProps = {
const FileReferenceBlock = ({ nodeKey, resourceId }: FileReferenceBlockProps) => {
const [editor] = useLexicalComposerContext()
const [ref, isSelected] = useSelectOrDelete(nodeKey)
const { data: nodeMap } = useSkillAssetNodeMap()
const { data: nodeMap, isLoading: isNodeMapLoading } = useSkillAssetNodeMap()
const [open, setOpen] = useState(false)
const [previewOpen, setPreviewOpen] = useState(false)
const [previewStyle, setPreviewStyle] = useState<React.CSSProperties | null>(null)
const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const { enabled: isPreviewEnabled } = useFilePreviewContext()
const { t } = useTranslation()
const isInteractive = editor.isEditable()
const currentNode = useMemo(() => nodeMap?.get(resourceId), [nodeMap, resourceId])
const fallbackName = useMemo(() => {
if (resourceId.includes('/')) {
const segments = resourceId.split('/').filter(Boolean)
return segments[segments.length - 1] ?? resourceId.slice(0, 8)
}
return resourceId.slice(0, 8)
}, [resourceId])
const isFolder = currentNode?.node_type === 'folder'
const shouldPreview = isPreviewEnabled && !isFolder
const displayName = currentNode?.name ?? resourceId
const iconType = !isFolder && currentNode ? getFileIconType(currentNode.name, currentNode.extension) : null
const title = currentNode?.path ?? displayName
const isMissing = !isNodeMapLoading && !currentNode
const shouldPreview = isPreviewEnabled && !isFolder && !isMissing
const displayName = currentNode?.name ?? fallbackName
const iconType = !isFolder && currentNode
? getFileIconType(currentNode?.name || '', currentNode?.extension)
: null
const pathForTooltip = (currentNode?.path ?? (resourceId.includes('/') ? resourceId : undefined))?.slice(1) // remove leading slash for better display
const tooltipContent = isMissing
? (
<div className="space-y-1">
<div className="system-xs-medium text-text-secondary">{t('skillEditor.fileNotFound', { ns: 'workflow' })}</div>
{pathForTooltip && (
<div>{pathForTooltip}</div>
)}
</div>
)
: (<div className="system-xs-medium text-text-secondary">{pathForTooltip ?? displayName}</div>)
const handleSelect = useCallback((node: TreeNodeData) => {
editor.update(() => {
@ -124,35 +148,42 @@ const FileReferenceBlock = ({ nodeKey, resourceId }: FileReferenceBlockProps) =>
placement="bottom-start"
offset={4}
>
<PortalToFollowElemTrigger asChild ref={ref}>
<span
className={cn(
'inline-flex min-w-[18px] select-none items-center gap-[2px] overflow-hidden rounded-[5px] border border-state-accent-hover-alt bg-state-accent-hover py-[1px] pl-[1px] pr-[4px] shadow-xs',
isInteractive ? 'cursor-pointer' : 'cursor-default',
isSelected && 'border-text-accent',
)}
title={title}
onMouseDown={() => {
if (!isInteractive)
return
setOpen(prev => !prev)
}}
>
<span className="flex items-center justify-center p-px">
{isFolder
? <RiFolderLine className="size-[14px] text-text-accent" aria-hidden="true" />
: (
<FileTypeIcon
type={(iconType || 'document') as FileAppearanceType}
size="sm"
className="!size-[14px]"
/>
)}
<PortalToFollowElemTrigger ref={ref} className="inline-flex">
<Tooltip popupContent={tooltipContent} disabled={!tooltipContent}>
<span
className={cn(
'inline-flex min-w-[18px] select-none items-center gap-[2px] overflow-hidden rounded-[5px] border py-[1px] pl-[1px] pr-[4px] shadow-xs',
isInteractive ? 'cursor-pointer' : 'cursor-default',
isMissing ? 'border-state-warning-active bg-state-warning-hover' : 'border-state-accent-hover-alt bg-state-accent-hover',
isSelected && 'border-text-accent',
)}
onMouseDown={() => {
if (!isInteractive)
return
setOpen(prev => !prev)
}}
>
<span className="flex items-center justify-center p-px">
{isFolder
? <RiFolderLine className={cn('size-[14px]', isMissing ? 'text-text-warning' : 'text-text-accent')} aria-hidden="true" />
: (
<FileTypeIcon
type={(iconType || 'document') as FileAppearanceType}
size="sm"
className={cn('!size-[14px]', isMissing && '!text-text-warning')}
/>
)}
</span>
<span className={cn('max-w-[180px] truncate text-[12px] font-medium leading-4', isMissing ? 'text-text-warning' : 'text-text-accent')}>
{displayName}
</span>
{
isMissing && (
<RiAlertFill className="size-3 text-text-warning" />
)
}
</span>
<span className="max-w-[180px] truncate text-[12px] font-medium leading-4 text-text-accent">
{displayName}
</span>
</span>
</Tooltip>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className="z-[1000]">
<FilePickerPanel

View File

@ -1111,6 +1111,7 @@
"skill.startTab.useThisSkill": "Use this Skill",
"skillEditor.authorizationBadge": "Auth",
"skillEditor.authorizationRequired": "Authorization required before use.",
"skillEditor.fileNotFound": "The file does not exist",
"skillEditor.openInSkillEditor": "Open in Skill Editor",
"skillEditor.previewUnavailable": "Preview unavailable",
"skillEditor.referenceFiles": "Reference files",

View File

@ -1103,6 +1103,7 @@
"skill.startTab.useThisSkill": "使用此 Skill",
"skillEditor.authorizationBadge": "Auth",
"skillEditor.authorizationRequired": "使用前需要授权。",
"skillEditor.fileNotFound": "文件不存在",
"skillEditor.openInSkillEditor": "在技能编辑器中打开",
"skillEditor.previewUnavailable": "无法预览",
"skillEditor.referenceFiles": "引用文件",