diff --git a/src/components/FlowEditor/node/style/baseOther.module.less b/src/components/FlowEditor/node/style/baseOther.module.less index c1d4a17..ced6423 100644 --- a/src/components/FlowEditor/node/style/baseOther.module.less +++ b/src/components/FlowEditor/node/style/baseOther.module.less @@ -123,6 +123,20 @@ max-height: 120px; object-fit: contain; } + + .node-image-loading, + .node-image-download { + display: block; + color: #86909c; + font-size: 12px; + line-height: 22px; + text-align: center; + word-break: break-all; + } + + .node-image-download { + color: #165dff; + } } .node-content-box { diff --git a/src/pages/flowEditor/components/nodeContentImage.tsx b/src/pages/flowEditor/components/nodeContentImage.tsx index 8680b66..33f27bc 100644 --- a/src/pages/flowEditor/components/nodeContentImage.tsx +++ b/src/pages/flowEditor/components/nodeContentImage.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import styles from '@/components/FlowEditor/node/style/baseOther.module.less'; import { Handle, Position } from '@xyflow/react'; import { Image } from '@arco-design/web-react'; @@ -103,7 +103,26 @@ const renderRegularNodeHandles = (dataIns: any[], dataOuts: any[], apiIns: any[] }; +const getImageBlobType = (url: string, blobType: string) => { + if (blobType && blobType !== 'application/octet-stream') { + return blobType; + } + + const cleanUrl = url.split('?')[0].toLowerCase(); + if (cleanUrl.endsWith('.png')) return 'image/png'; + if (cleanUrl.endsWith('.jpg') || cleanUrl.endsWith('.jpeg')) return 'image/jpeg'; + if (cleanUrl.endsWith('.gif')) return 'image/gif'; + if (cleanUrl.endsWith('.webp')) return 'image/webp'; + if (cleanUrl.endsWith('.bmp')) return 'image/bmp'; + if (cleanUrl.endsWith('.svg')) return 'image/svg+xml'; + if (cleanUrl.endsWith('.tif') || cleanUrl.endsWith('.tiff')) return 'image/tiff'; + + return blobType || 'application/octet-stream'; +}; + const NodeContent = ({ data, imageUrl = '' }: { data: NodeContentData; imageUrl?: string }) => { + const [previewUrl, setPreviewUrl] = useState(''); + const [imageLoadError, setImageLoadError] = useState(false); const apiIns = data.parameters?.apiIns || []; const apiOuts = data.parameters?.apiOuts || []; const dataIns = data.parameters?.dataIns || []; @@ -114,6 +133,52 @@ const NodeContent = ({ data, imageUrl = '' }: { data: NodeContentData; imageUrl? const isEndNode = data.type === 'end'; const isSpecialNode = isStartNode || isEndNode; + useEffect(() => { + let canceled = false; + let objectUrl = ''; + + setPreviewUrl(''); + setImageLoadError(false); + + if (!imageUrl) { + return undefined; + } + + const loadImageBlob = async () => { + try { + const response = await fetch(imageUrl); + if (!response.ok) { + throw new Error(`load image failed: ${response.status}`); + } + + const blob = await response.blob(); + const typedBlob = new Blob([blob], { + type: getImageBlobType(imageUrl, blob.type), + }); + objectUrl = URL.createObjectURL(typedBlob); + + if (!canceled) { + setPreviewUrl(objectUrl); + } + } catch (error) { + console.error('图片下载预览失败:', error); + if (!canceled) { + setPreviewUrl(imageUrl); + setImageLoadError(true); + } + } + }; + + loadImageBlob(); + + return () => { + canceled = true; + if (objectUrl) { + URL.revokeObjectURL(objectUrl); + } + }; + }, [imageUrl]); + return ( <> {/*content栏-api部分*/} @@ -180,12 +245,27 @@ const NodeContent = ({ data, imageUrl = '' }: { data: NodeContentData; imageUrl? {imageUrl && (