Spaces:
Running
Running
| import React, { useMemo, useState } from "react"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Badge } from "@/components/ui/badge"; | |
| import { Separator } from "@/components/ui/separator"; | |
| import { FileText, Download, Code, Type } from "lucide-react"; | |
| import { Trace } from "@/types"; | |
| import Editor from "@monaco-editor/react"; | |
| interface TraceSegmentModalProps { | |
| data: { | |
| trace: Trace; | |
| segment: { | |
| content: string; | |
| startChar: number; | |
| endChar: number; | |
| windowIndex: number; | |
| }; | |
| }; | |
| onClose: () => void; | |
| } | |
| export function TraceSegmentModal({ | |
| data, | |
| onClose: _onClose, | |
| }: TraceSegmentModalProps) { | |
| const { trace, segment } = data; | |
| const [viewMode, setViewMode] = useState<"editor" | "text" | "textarea">( | |
| "textarea" | |
| ); | |
| // Debug logging | |
| console.log("TraceSegmentModal data:", { trace, segment }); | |
| console.log("Segment content preview:", segment.content.substring(0, 200)); | |
| // Content analysis | |
| const lineCount = segment.content.split("\n").length; | |
| const wordCount = segment.content.trim() | |
| ? segment.content.trim().split(/\s+/).length | |
| : 0; | |
| // Language detection for syntax highlighting | |
| const detectedLanguage = useMemo(() => { | |
| // Check for JSON content | |
| if ( | |
| segment.content.trim().startsWith("{") && | |
| segment.content.trim().endsWith("}") | |
| ) { | |
| try { | |
| JSON.parse(segment.content); | |
| return "json"; | |
| } catch { | |
| // Not valid JSON, continue checking | |
| } | |
| } | |
| // Check for Python code patterns | |
| if (/^(import|from|def|class|if __name__|print\()/m.test(segment.content)) { | |
| return "python"; | |
| } | |
| // Check for JavaScript/TypeScript patterns | |
| if ( | |
| /^(import|export|function|const|let|var|console\.log)/m.test( | |
| segment.content | |
| ) | |
| ) { | |
| return "javascript"; | |
| } | |
| // Check for shell/bash patterns | |
| if ( | |
| /^(#!\/bin\/|cd |ls |mkdir |rm |pip install|npm install)/m.test( | |
| segment.content | |
| ) | |
| ) { | |
| return "shell"; | |
| } | |
| // Check for SQL patterns | |
| if ( | |
| /^(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER)/im.test(segment.content) | |
| ) { | |
| return "sql"; | |
| } | |
| // Default to plain text | |
| return "text"; | |
| }, [segment.content]); | |
| const handleDownload = () => { | |
| const blob = new Blob([segment.content], { type: "text/plain" }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement("a"); | |
| a.href = url; | |
| a.download = `${trace.filename}_window_${segment.windowIndex}_segment.txt`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| }; | |
| return ( | |
| <div className="flex flex-col h-[70vh]"> | |
| {/* Header */} | |
| <div className="px-6 py-4 border-b bg-muted/50"> | |
| <div className="flex items-center justify-between mb-2"> | |
| <div className="flex items-center gap-2 min-w-0 flex-1"> | |
| <FileText className="h-5 w-5 flex-shrink-0" /> | |
| <h2 | |
| className="text-lg font-semibold truncate" | |
| title={trace.filename} | |
| > | |
| {trace.filename} | |
| </h2> | |
| <Badge variant="outline" className="flex-shrink-0"> | |
| Window {segment.windowIndex} | |
| </Badge> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <Button | |
| variant={viewMode === "editor" ? "default" : "outline"} | |
| size="sm" | |
| onClick={() => setViewMode("editor")} | |
| > | |
| <Code className="h-4 w-4 mr-2" /> | |
| Editor | |
| </Button> | |
| <Button | |
| variant={viewMode === "text" ? "default" : "outline"} | |
| size="sm" | |
| onClick={() => setViewMode("text")} | |
| > | |
| <Type className="h-4 w-4 mr-2" /> | |
| Text | |
| </Button> | |
| <Button | |
| variant={viewMode === "textarea" ? "default" : "outline"} | |
| size="sm" | |
| onClick={() => setViewMode("textarea")} | |
| > | |
| <Type className="h-4 w-4 mr-2" /> | |
| Textarea | |
| </Button> | |
| <Button variant="outline" size="sm" onClick={handleDownload}> | |
| <Download className="h-4 w-4 mr-2" /> | |
| Download | |
| </Button> | |
| </div> | |
| </div> | |
| {/* Segment Info */} | |
| <div className="flex items-center gap-4 text-sm text-muted-foreground"> | |
| <span>{segment.content.length.toLocaleString()} characters</span> | |
| <span>{lineCount.toLocaleString()} lines</span> | |
| <span>{wordCount.toLocaleString()} words</span> | |
| <Separator orientation="vertical" className="h-4" /> | |
| <span> | |
| Characters {segment.startChar.toLocaleString()} -{" "} | |
| {segment.endChar.toLocaleString()} | |
| </span> | |
| </div> | |
| {/* Debug Info */} | |
| <div className="mt-2 p-2 bg-gray-100 rounded text-xs text-gray-600"> | |
| <strong>Debug:</strong> Content length: {segment.content.length}, | |
| Preview: "{segment.content.substring(0, 50)}..." | |
| </div> | |
| </div> | |
| {/* Content Area */} | |
| <div className="flex-1 overflow-hidden bg-white border border-gray-200 rounded m-2"> | |
| {segment.content ? ( | |
| viewMode === "editor" ? ( | |
| <div className="h-full"> | |
| <Editor | |
| height="100%" | |
| defaultLanguage={detectedLanguage} | |
| value={segment.content} | |
| theme="vs" | |
| loading={ | |
| <div className="p-4 text-center">Loading editor...</div> | |
| } | |
| onMount={(editor) => { | |
| console.log("Monaco Editor mounted"); | |
| console.log( | |
| "Editor content length:", | |
| editor.getValue().length | |
| ); | |
| console.log( | |
| "Editor content preview:", | |
| editor.getValue().substring(0, 200) | |
| ); | |
| // Force a layout update | |
| setTimeout(() => { | |
| editor.layout(); | |
| }, 100); | |
| }} | |
| options={{ | |
| readOnly: true, | |
| minimap: { enabled: false }, | |
| scrollBeyondLastLine: false, | |
| wordWrap: "on", | |
| lineNumbers: "on", | |
| folding: false, | |
| renderWhitespace: "selection", | |
| fontSize: 14, | |
| lineHeight: 20, | |
| fontFamily: | |
| "'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace", | |
| padding: { top: 10, bottom: 10 }, | |
| automaticLayout: true, | |
| contextmenu: false, | |
| selectOnLineNumbers: true, | |
| }} | |
| /> | |
| </div> | |
| ) : viewMode === "text" ? ( | |
| <div className="p-4 overflow-auto h-full"> | |
| <pre className="text-sm font-mono whitespace-pre-wrap break-words"> | |
| {segment.content} | |
| </pre> | |
| </div> | |
| ) : ( | |
| <div className="p-4 h-full"> | |
| <textarea | |
| value={segment.content} | |
| readOnly | |
| className="w-full h-full p-2 border border-gray-300 rounded font-mono text-sm resize-none focus:outline-none focus:ring-2 focus:ring-blue-500" | |
| placeholder="Content will appear here..." | |
| /> | |
| </div> | |
| ) | |
| ) : ( | |
| <div className="p-8 text-center text-gray-500"> | |
| <p>No content available for this segment</p> | |
| <p className="text-sm mt-2"> | |
| Characters: {segment.startChar} - {segment.endChar} | |
| </p> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |