import React, { useState } from 'react'; import { Eye, Globe, Code, Image as ImageIcon, FileText, Clock, ExternalLink, ChevronDown, ChevronUp, Maximize2, } from 'lucide-react'; import { Card, CardHeader, CardContent } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; import { useCurrentEpisode, useEpisodeState } from '@/hooks/useEpisode'; import { formatTimestamp, truncateText } from '@/utils/helpers'; import type { DOMElement } from '@/types'; interface ObservationViewProps { className?: string; } const DOMTree: React.FC<{ elements: DOMElement[]; depth?: number }> = ({ elements, depth = 0, }) => { const [expanded, setExpanded] = useState>(new Set([0, 1, 2])); const toggleExpand = (index: number) => { setExpanded((prev) => { const next = new Set(prev); if (next.has(index)) { next.delete(index); } else { next.add(index); } return next; }); }; if (depth > 3) return null; return (
{elements.slice(0, 10).map((el, i) => { const hasChildren = el.children && el.children.length > 0; const isExpanded = expanded.has(i); return (
{hasChildren && ( )} {!hasChildren && } <{el.tag} {el.id && ( #{el.id} )} {el.classes.length > 0 && ( .{el.classes.slice(0, 2).join('.')} )} > {el.text && ( {truncateText(el.text, 20)} )}
{hasChildren && isExpanded && ( )}
); })} {elements.length > 10 && (
+{elements.length - 10} more elements
)}
); }; export const ObservationView: React.FC = ({ className, }) => { const { data: episode } = useCurrentEpisode(); const { data: state, isLoading } = useEpisodeState(episode?.id); const [activeTab, setActiveTab] = useState<'page' | 'dom' | 'data' | 'screenshot'>('page'); const [showFullScreen, setShowFullScreen] = useState(false); const observation = state?.observation; const tabs = [ { key: 'page' as const, label: 'Page', icon: }, { key: 'dom' as const, label: 'DOM', icon: }, { key: 'data' as const, label: 'Data', icon: }, { key: 'screenshot' as const, label: 'Screenshot', icon: }, ]; return ( } action={ observation && (
{observation.interactableElements?.length ?? 0} elements
) } /> {/* Tabs */}
{tabs.map((tab) => ( ))}
{isLoading ? (
) : !observation ? (

No observation data

) : (
{/* Page Info */} {activeTab === 'page' && (
{observation.page.title || 'Untitled'}
{observation.page.domain} {observation.page.statusCode} {observation.page.loadTime}ms
Visible Text
{truncateText(observation.visibleText, 500) || 'No text content'}
{formatTimestamp(observation.timestamp)}
)} {/* DOM Tree */} {activeTab === 'dom' && (
{observation.dom?.length ?? 0} root elements
{observation.dom && observation.dom.length > 0 ? ( ) : (
No DOM data
)}
)} {/* Extracted Data */} {activeTab === 'data' && (
Extracted Data
{Object.keys(observation.extractedData || {}).length > 0 ? JSON.stringify(observation.extractedData, null, 2) : '// No extracted data yet'}
Metadata
{JSON.stringify(observation.metadata, null, 2)}
)} {/* Screenshot */} {activeTab === 'screenshot' && (
{observation.screenshot ? ( Page screenshot ) : (

No screenshot available

)}
)}
)}
{/* Full Screen Modal */} {showFullScreen && observation && (
setShowFullScreen(false)} >
e.stopPropagation()} >

Observation - Step {observation.step}

{JSON.stringify(observation, null, 2)}
)}
); }; export default ObservationView;