234 lines
6.7 KiB
TypeScript
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
|