import { useState } from 'react'; import { Badge } from './Badge'; import { Button } from './Button'; import { Tabs, TabsContent, TabsList, TabsTrigger } from './Tabs'; import { HeadersTab } from '../tabs/HeadersTab'; import { RequestTab } from '../tabs/RequestTab'; import { ResponseTab } from '../tabs/ResponseTab'; import { CookiesTab } from '../tabs/CookiesTab'; import { TimingTab } from '../tabs/TimingTab'; import { InitiatorTab } from '../tabs/InitiatorTab'; import { X } from 'lucide-react'; import { useNetworkActivityActions, useNetworkActivityStore, useOverrides, useSelectedRequest, } from '../state/hooks'; import { NetworkEntry as OldNetworkEntry } from '../types'; import { getStatusColor } from '../utils/getStatusColor'; import { MessagesTab } from '../tabs/MessagesTab'; import { SSEMessagesTab } from '../tabs/SSEMessagesTab'; import type { ResponseView } from '../response-renderers'; const getTypeColor = (type: string) => { const colors: Record = { document: 'bg-blue-600', script: 'bg-yellow-600', stylesheet: 'bg-purple-600', xhr: 'bg-green-600', img: 'bg-pink-600', font: 'bg-orange-600', http: 'bg-green-600', websocket: 'bg-blue-600', sse: 'bg-purple-600', }; return colors[type] || 'bg-gray-600'; }; // Adapter to convert new model to old format for tab components const createLegacyNetworkEntry = ( selectedRequest: any, httpDetails: any, wsDetails: any, ): OldNetworkEntry | null => { if (selectedRequest.type === 'http' && httpDetails) { return { requestId: httpDetails.id, url: httpDetails.request?.url || '', method: httpDetails.request?.method || 'GET', headers: httpDetails.request?.headers || {}, body: httpDetails.request?.body, status: httpDetails.status, startTime: httpDetails.timestamp, endTime: httpDetails.duration ? httpDetails.timestamp + httpDetails.duration : undefined, duration: httpDetails.duration, ttfb: httpDetails.ttfb, type: httpDetails.resourceType, initiator: httpDetails.initiator, request: httpDetails.request, response: httpDetails.response, responseBody: httpDetails.response?.body ? { body: httpDetails.response.body } : undefined, error: httpDetails.error, canceled: httpDetails.canceled, size: httpDetails.size, }; } else if (selectedRequest.type === 'websocket' && wsDetails) { // For WebSocket, create a minimal entry since tabs are designed for HTTP return { requestId: wsDetails.id, url: wsDetails.connection?.url || '', method: 'WS', headers: {}, status: wsDetails.status === 'open' ? 'finished' : 'pending', startTime: wsDetails.timestamp, endTime: wsDetails.duration ? wsDetails.timestamp + wsDetails.duration : undefined, duration: wsDetails.duration, }; } return null; }; export const SidePanel = () => { const actions = useNetworkActivityActions(); const selectedRequest = useSelectedRequest(); const client = useNetworkActivityStore((state) => state._client); const overrides = useOverrides(); // Sticky Preview/Raw preference. Lives here, not in ResponseTab, // because the `` below intentionally // remounts the Tabs subtree on every request switch (so the active // inner tab resets). SidePanel itself stays mounted across request // switches, so the preference survives — flipping to Raw on one // response keeps Raw selected for every subsequent response whose // renderer supports it. Resets when the panel is closed. const [preferredView, setPreferredView] = useState('preview'); const onClose = (): void => { actions.setSelectedRequest(null); }; // Early return if no request is selected if (!selectedRequest) { return null; } // Get detailed information based on request type const httpDetails = selectedRequest.type === 'http' ? selectedRequest : null; const wsDetails = selectedRequest.type === 'websocket' ? selectedRequest : null; const sseDetails = selectedRequest.type === 'sse' ? selectedRequest : null; // Extract name from the request const requestName = selectedRequest.type === 'http' ? httpDetails?.request?.url || 'Unknown' : selectedRequest.type === 'websocket' ? wsDetails?.connection?.url || 'Unknown' : sseDetails?.request?.url || 'Unknown'; // Extract status from the request const requestStatus = selectedRequest.type === 'http' ? httpDetails?.response?.status || httpDetails?.status || 'pending' : selectedRequest.type === 'websocket' ? wsDetails?.status || 'unknown' : sseDetails?.status || 'unknown'; // Create legacy network entry for tab components const legacyEntry = createLegacyNetworkEntry( selectedRequest, httpDetails, wsDetails, ); const legacyNetworkEntries = new Map(); if (legacyEntry) { legacyNetworkEntries.set(legacyEntry.requestId, legacyEntry); } const override = legacyEntry !== null ? overrides.get(legacyEntry.url) : null; const supportsOverrides = httpDetails?.source !== 'nitro'; const hasResponseOverride = supportsOverrides && override && override.body ? true : false; const getTabsListTriggers = () => { if (httpDetails) { return ( <> Headers Request Response {hasResponseOverride && ( )} Cookies Initiator Timing ); } if (sseDetails) { return ( <> Headers Request Messages Initiator ); } return ( <> Messages ); }; const getTabsContent = () => { if (httpDetails) { return ( <> { if (client) { client.send('get-response-body', { requestId, }); } }} /> ); } if (wsDetails) { return ( <> ); } if (sseDetails) { return ( <> ); } throw new Error('Invalid request type'); }; return (
{/* Side Panel Header */}
{requestName} {requestStatus}
{/* Side Panel Content */}
{getTabsListTriggers()} {getTabsContent()}
); };