Files
test1/components/nova-sdk/task-panel/Preview/ToolOutputArtifactPreview.tsx
2026-03-20 07:33:46 +00:00

234 lines
6.7 KiB
TypeScript

import React from 'react'
import { cn } from '@/utils/cn'
import type { ImageAttachment, TaskArtifact } from '../../types'
import { useNovaKit } from '../../context/useNovaKit'
import { isImageFile } from '../utils'
import { TaskArtifactHtml } from '@/components/html-editor'
import { Html } from '@/components/html-editor/components/html-render/task-html'
import PptPreview from '@/components/ppt-editor'
import { ImageAttachmentItem } from '../../message-list/message-item/ImageAttachmentItem'
import { UrlScriptPreview } from './UrlScriptPreview'
import { ShellExecutePreview } from './ShellExecutePreview'
import { MarkdownContent, MarkdownPreview } from './MarkdownPreview'
import { CsvPreview } from './CsvPreview'
import { VirtualPdfPreview } from './VirtualPdfPreview'
import { isScriptLikeFile, normalizeArtifactFileType } from './previewUtils'
export interface ToolOutputArtifactPreviewProps {
artifact: TaskArtifact
className?: string
}
const PREVIEW_FILE_TYPES = ['xlsx', 'xls', 'doc', 'docx']
const TEXT_LIKE_FILE_TYPES = ['txt', 'text', 'json', 'log', 'xml', 'yaml', 'yml']
export function ToolOutputArtifactPreview({
artifact,
className,
}: ToolOutputArtifactPreviewProps) {
const { api, conversationId, mode } = useNovaKit()
const [url, setUrl] = React.useState('')
const [isUrlLoading, setIsUrlLoading] = React.useState(false)
const editable = mode === 'chat'
const normalizedFileType = normalizeArtifactFileType(
artifact.file_type,
artifact.file_name,
artifact.path,
)
React.useEffect(() => {
const directUrl = artifact.url || (/^https?:\/\//.test(artifact.path) ? artifact.path : '')
if (directUrl) {
setUrl(directUrl)
setIsUrlLoading(false)
return
}
if (artifact.path) {
setIsUrlLoading(true)
setUrl('')
api
.getArtifactUrl?.(
artifact,
PREVIEW_FILE_TYPES.includes(normalizedFileType)
? {
'x-oss-process': 'doc/preview,print_1,copy_1,export_1',
}
: undefined,
)
.then(res => {
const originUrl = typeof res?.data === 'string' ? res.data : ''
if (PREVIEW_FILE_TYPES.includes(normalizedFileType)) {
const shortUrl = originUrl.replace(
'oss-cn-hangzhou.aliyuncs.com',
'betteryeah.com',
)
setUrl(
shortUrl
? `${shortUrl}&x-oss-process=doc%2Fpreview%2Cprint_1%2Ccopy_1%2Cexport_1`
: '',
)
} else {
setUrl(originUrl)
}
setIsUrlLoading(false)
})
.catch(() => {
setIsUrlLoading(false)
})
return
}
setIsUrlLoading(false)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [artifact.path, artifact.url, normalizedFileType])
const isImage =
isImageFile(artifact.path) ||
isImageFile(artifact.file_name) ||
['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp'].includes(normalizedFileType)
if (isImage) {
const imageAttachment: ImageAttachment = {
url: artifact.url || '',
path: artifact.path,
file_name: artifact.file_name,
file_url: artifact.url,
}
return (
<div className={cn('flex h-full items-center justify-center p-6 bg-muted/10', className)}>
<ImageAttachmentItem image={imageAttachment} />
</div>
)
}
const isHtml =
normalizedFileType === 'html' ||
artifact.file_name?.toLowerCase().endsWith('.html')
if (isHtml && artifact.content && !artifact.path) {
return <Html className={cn('h-full', className)} content={artifact.content} />
}
if (isHtml) {
return (
<div className={cn('h-full', className)}>
<TaskArtifactHtml
taskId={conversationId || ''}
taskArtifact={artifact}
editable={editable}
type="web"
/>
</div>
)
}
const isMarkdown =
normalizedFileType === 'md' ||
normalizedFileType === 'markdown' ||
artifact.file_name?.toLowerCase().endsWith('.md')
if (isMarkdown && url) {
return <div className={cn('h-full', className)}><MarkdownPreview url={url} /></div>
}
if (isMarkdown && artifact.content) {
return (
<div className={cn('h-full overflow-y-auto p-6', className)}>
<MarkdownContent content={artifact.content} />
</div>
)
}
const isPpt =
normalizedFileType === 'ppt' ||
normalizedFileType === 'pptx' ||
artifact.file_name?.toLowerCase().endsWith('.ppt') ||
artifact.file_name?.toLowerCase().endsWith('.pptx')
if (isPpt && url) {
return (
<div className={cn('h-full', className)}>
<PptPreview
url={url}
artifact={artifact}
taskId={conversationId || ''}
editable={editable}
/>
</div>
)
}
const isCsv =
normalizedFileType === 'csv' ||
artifact.file_name?.toLowerCase().endsWith('.csv')
if (isCsv && artifact.content) {
return <div className={cn('h-full', className)}><CsvPreview content={artifact.content} /></div>
}
if (isCsv && url) {
return <div className={cn('h-full', className)}><CsvPreview url={url} /></div>
}
const isPdf =
normalizedFileType === 'pdf' ||
artifact.file_name?.toLowerCase().endsWith('.pdf')
if (isPdf && url) {
return <div className={cn('h-full', className)}><VirtualPdfPreview url={url} /></div>
}
const isScript = isScriptLikeFile(artifact)
if (isScript && artifact.content) {
return (
<div className={cn('h-full', className)}>
<ShellExecutePreview output={artifact.content} toolLabel={artifact.file_name} />
</div>
)
}
const isTextLike = TEXT_LIKE_FILE_TYPES.includes(normalizedFileType)
if (isTextLike && artifact.content) {
return (
<div className={cn('h-full', className)}>
<ShellExecutePreview output={artifact.content} toolLabel={artifact.file_name} />
</div>
)
}
if ((isScript || isTextLike) && url) {
return <div className={cn('h-full', className)}><UrlScriptPreview url={url} title={artifact.file_name} /></div>
}
if (url) {
return (
<iframe
src={url}
className={cn('w-full h-full border-0', className)}
title={artifact.file_name}
/>
)
}
if (isUrlLoading) {
return (
<div className={cn('flex h-full items-center justify-center p-8 text-muted-foreground', className)}>
<div className="text-sm">...</div>
</div>
)
}
return (
<div className={cn('flex h-full items-center justify-center p-8 text-muted-foreground', className)}>
<div className="text-sm"></div>
</div>
)
}
export default ToolOutputArtifactPreview