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 12c17724a9..048a7d4027 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 @@ -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(null) const closeTimerRef = useRef | 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 + ? ( +
+
{t('skillEditor.fileNotFound', { ns: 'workflow' })}
+ {pathForTooltip && ( +
{pathForTooltip}
+ )} +
+ ) + : (
{pathForTooltip ?? displayName}
) const handleSelect = useCallback((node: TreeNodeData) => { editor.update(() => { @@ -124,35 +148,42 @@ const FileReferenceBlock = ({ nodeKey, resourceId }: FileReferenceBlockProps) => placement="bottom-start" offset={4} > - - { - if (!isInteractive) - return - setOpen(prev => !prev) - }} - > - - {isFolder - ? +