import { CodeOutlined, DownloadOutlined, EyeOutlined, FileMarkdownOutlined, FilePdfOutlined, FileTextOutlined, LinkOutlined, PictureOutlined, VideoCameraOutlined, } from '@ant-design/icons'; import { Alert, Button, Empty, Space, Spin, Typography } from 'antd'; import { InlineImage } from '../../../components/common/InlineImage'; import { MarkdownPreviewContent } from '../../../components/markdownPreview/MarkdownPreviewContent'; import { CodexDiffBlock } from '../../../components/previewer'; import { inferCodeLanguage, renderEditorBlock } from '../../../components/previewer/renderers'; import { triggerResourceDownload } from './downloadUtils'; import '../../../components/previewer/PreviewerUI.css'; const { Paragraph, Text } = Typography; export type ChatPreviewKind = 'image' | 'video' | 'markdown' | 'code' | 'diff' | 'document' | 'pdf' | 'file'; export type ChatPreviewTarget = { label: string; url: string; kind: ChatPreviewKind; }; export function resolveChatPreviewGlyph(kind: ChatPreviewKind) { switch (kind) { case 'image': return ; case 'video': return ; case 'markdown': return ; case 'code': case 'diff': return ; case 'document': return ; case 'pdf': return ; default: return ; } } export function resolveChatPreviewKindLabel(kind: ChatPreviewKind) { switch (kind) { case 'image': return 'image preview'; case 'video': return 'video preview'; case 'markdown': return 'markdown preview'; case 'code': return 'code preview'; case 'diff': return 'diff preview'; case 'document': return 'document preview'; case 'pdf': return 'pdf preview'; default: return 'resource preview'; } } function resolvePreviewErrorMessage(previewError: string) { const normalized = previewError.trim(); if (!normalized) { return ''; } if (/^\s*403\b/.test(normalized) || normalized.includes('권한으로 열 수 없습니다')) { return '권한이 없거나 허용되지 않은 경로입니다. 세션 리소스 경로와 접근 권한을 확인해 주세요.'; } if (/^\s*404\b/.test(normalized) || normalized.includes('찾을 수 없습니다')) { return '파일이 이동되었거나 아직 세션 리소스 경로에 생성되지 않았습니다. 경로를 다시 확인해 주세요.'; } if (/^\s*401\b/.test(normalized) || normalized.includes('인증이 필요합니다')) { return '인증 정보가 없어서 문서를 열 수 없습니다.'; } return normalized; } function resolvePreviewExtension(target: ChatPreviewTarget) { const raw = target.label || target.url; const normalized = raw.toLowerCase().split('?')[0] ?? ''; const match = normalized.match(/\.([a-z0-9]+)$/i); return match?.[1] ?? ''; } function resolveCodeLanguage(target: ChatPreviewTarget, previewText: string) { const extension = resolvePreviewExtension(target); if (extension === 'tsx' || extension === 'ts') { return 'typescript'; } if (extension === 'jsx' || extension === 'js' || extension === 'mjs' || extension === 'cjs') { return 'javascript'; } if (extension === 'json') { return 'json'; } if (extension === 'css') { return 'css'; } if (extension === 'scss') { return 'scss'; } if (extension === 'html' || extension === 'htm') { return 'html'; } if (extension === 'md' || extension === 'markdown') { return 'markdown'; } if (extension === 'java') { return 'java'; } if (extension === 'kt') { return 'kotlin'; } if (extension === 'py') { return 'python'; } if (extension === 'go') { return 'go'; } if (extension === 'rs') { return 'rust'; } if (extension === 'sql') { return 'sql'; } if (extension === 'sh' || extension === 'bash' || extension === 'zsh') { return 'bash'; } if (extension === 'yml' || extension === 'yaml') { return 'yaml'; } if (extension === 'xml') { return 'xml'; } if (extension === 'diff' || extension === 'patch') { return 'diff'; } if (/^(diff --git|@@\s|--- a\/|\+\+\+ b\/)/m.test(previewText)) { return 'diff'; } return inferCodeLanguage(extension || 'text'); } function isAppRouteUrl(url: string) { if (typeof window === 'undefined') { return false; } try { const parsed = new URL(url, window.location.href); const pathname = parsed.pathname.toLowerCase(); const hasKnownFileExtension = /\.[a-z0-9]+$/i.test(pathname); return parsed.origin === window.location.origin && !hasKnownFileExtension; } catch { return false; } } function canRenderFramePreview(url: string) { if (typeof window === 'undefined') { return false; } try { const parsed = new URL(url, window.location.href); return parsed.origin === window.location.origin; } catch { return false; } } type ChatPreviewBodyProps = { target: ChatPreviewTarget | null; previewText: string; isPreviewLoading: boolean; previewError: string; previewContentType?: string; maxMarkdownBlocks?: number; }; function isHtmlFallbackPreview(target: ChatPreviewTarget, previewText: string, previewContentType: string | undefined) { const extension = resolvePreviewExtension(target); const normalizedContentType = previewContentType?.toLowerCase() ?? ''; const normalizedPreview = previewText.trimStart().toLowerCase(); if (extension === 'html' || extension === 'htm' || target.kind === 'markdown') { return false; } const looksLikeHtml = normalizedContentType.includes('text/html') || normalizedPreview.startsWith('; } if (isPreviewLoading) { return (
preview를 불러오는 중입니다.
); } if (previewError) { return ( ); } if (isHtmlFallbackPreview(target, previewText, previewContentType)) { return ( ); } if (target.kind === 'image') { return ( ); } if (target.kind === 'video') { return