/** * output-viewer.tsx — Rich file viewer for task outputs * * Renders different file types appropriately: * - Markdown: rendered with react-markdown * - Code: syntax highlighted with prism-react-renderer * - JSON: collapsible tree view * - HTML: sandboxed iframe * - Plain text: preformatted block * - Binary: download link */ "use client" import * as React from "react" import { Download, FileText, Code, FileJson, Image as ImageIcon, File } from "lucide-react" import { Button } from "@/components/ui/button" import { Card, CardContent } from "@/components/ui/card" import { cn } from "@/lib/utils" interface OutputViewerProps { filename: string fileType: string content: string filePath?: string size?: number } function getFileCategory(filename: string, fileType: string): string { const ext = filename.split(".").pop()?.toLowerCase() || "" const type = fileType.toLowerCase() if (type.includes("markdown") || ext === "md") return "markdown" if (type.includes("html") || ext === "html" || ext === "htm") return "html" if (type.includes("json") || ext === "json") return "json" if (["js", "ts", "jsx", "tsx", "py", "sh", "bash", "go", "rs", "java"].includes(ext)) return "code" if (type.includes("image")) return "image" if (type.includes("pdf")) return "pdf" if (type.includes("text") || type === "application/octet-stream") return "text" return "unknown" } function formatSize(bytes: number): string { if (bytes < 1024) return `${bytes} B` if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB` return `${(bytes / (1024 * 1024)).toFixed(1)} MB` } // Simple JSON tree component function JsonTree({ data, depth = 0 }: { data: unknown; depth?: number }) { const [collapsed, setCollapsed] = React.useState(false) if (depth > 5) return {JSON.stringify(data)} if (data === null) return null if (data === undefined) return undefined if (typeof data === "boolean") { return {String(data)} } if (typeof data === "number") { return {data} } if (typeof data === "string") { return "{data}" } if (Array.isArray(data)) { if (data.length === 0) return [] return ( {!collapsed && ( {data.map((item, i) => (
))}
)}
) } if (typeof data === "object") { const entries = Object.entries(data as Record) if (entries.length === 0) return {"{}"} return ( {!collapsed && ( {entries.map(([key, value]) => (
{key} :
))}
)}
) } return {String(data)} } export function OutputViewer({ filename, fileType, content, filePath, size }: OutputViewerProps) { const category = getFileCategory(filename, fileType) return ( {/* File header */}
{category === "markdown" && } {category === "code" && } {category === "json" && } {category === "html" && } {category === "image" && } {category === "text" && } {category === "unknown" && } {filename} {size && ({formatSize(size)})}
{filePath && ( )}
{/* Content based on category */}
{category === "json" && (
               { try { return JSON.parse(content) } catch { return content } })()} />
            
)} {category === "code" && (
              {content}
            
)} {category === "text" && (
              {content}
            
)} {category === "markdown" && (
{content}
)} {category === "html" && (