import type { MetaFunction } from "@remix-run/node"; import { useState, useEffect, useTransition } from "react"; import { Activity, RefreshCw, Trash2, List, FileText, X, ChevronRight, ChevronDown, Inbox, Wrench, Bot, User, Settings, Zap, Users, Target, Cpu, MessageCircle, Brain, CheckCircle, ClipboardCheck, BarChart3, MessageSquare, Sparkles, Copy, Check, Lightbulb, Loader2 } from "lucide-react"; import RequestDetailContent from "../components/RequestDetailContent"; import { ConversationThread } from "../components/ConversationThread"; export const meta: MetaFunction = () => { return [ { title: "Claude Code Monitor" }, { name: "description", content: "Claude Code Monitor - Real-time API request visualization" }, ]; }; interface Request { id: number; conversationId?: string; turnNumber?: number; isRoot?: boolean; timestamp: string; method: string; endpoint: string; headers: Record; body?: { model?: string; messages?: Array<{ role: string; content: any; }>; system?: Array<{ text: string; type: string; cache_control?: { type: string }; }>; max_tokens?: number; temperature?: number; stream?: boolean; }; response?: { statusCode: number; headers: Record; body?: any; bodyText?: string; responseTime: number; streamingChunks?: string[]; isStreaming: boolean; completedAt: string; }; promptGrade?: { score: number; criteria: Record; feedback: string; improvedPrompt: string; gradingTimestamp: string; }; } interface ConversationSummary { id: string; requestCount: number; startTime: string; lastActivity: string; duration: number; firstMessage: string; lastMessage: string; projectName: string; } interface Conversation { sessionId: string; projectPath: string; projectName: string; messages: Array<{ parentUuid: string | null; isSidechain: boolean; userType: string; cwd: string; sessionId: string; version: string; type: 'user' | 'assistant'; message: any; uuid: string; timestamp: string; }>; startTime: string; endTime: string; messageCount: number; } export default function Index() { const [requests, setRequests] = useState([]); const [conversations, setConversations] = useState([]); const [selectedRequest, setSelectedRequest] = useState(null); const [selectedConversation, setSelectedConversation] = useState(null); const [filter, setFilter] = useState("all"); const [viewMode, setViewMode] = useState<"requests" | "conversations">("requests"); const [isModalOpen, setIsModalOpen] = useState(false); const [isConversationModalOpen, setIsConversationModalOpen] = useState(false); const [modelFilter, setModelFilter] = useState("all"); const [isFetching, setIsFetching] = useState(false); const [isPending, startTransition] = useTransition(); const [requestsCurrentPage, setRequestsCurrentPage] = useState(1); const [hasMoreRequests, setHasMoreRequests] = useState(true); const [conversationsCurrentPage, setConversationsCurrentPage] = useState(1); const [hasMoreConversations, setHasMoreConversations] = useState(true); const itemsPerPage = 50; const loadRequests = async (filter?: string, loadMore = false) => { setIsFetching(true); const pageToFetch = loadMore ? requestsCurrentPage + 1 : 1; try { const currentModelFilter = filter || modelFilter; const url = new URL('/api/requests', window.location.origin); url.searchParams.append("page", pageToFetch.toString()); url.searchParams.append("limit", itemsPerPage.toString()); if (currentModelFilter !== "all") { url.searchParams.append("model", currentModelFilter); } const response = await fetch(url.toString()); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); const requests = data.requests || []; const mappedRequests = requests.map((req: any, index: number) => ({ ...req, id: req.requestId ? `${req.requestId}_${index}` : `request_${index}` })); startTransition(() => { if (loadMore) { setRequests(prev => [...prev, ...mappedRequests]); } else { setRequests(mappedRequests); } setRequestsCurrentPage(pageToFetch); setHasMoreRequests(mappedRequests.length === itemsPerPage); }); } catch (error) { console.error('Failed to load requests:', error); // Fallback to example data for demo const exampleRequest = { timestamp: "2025-06-04T23:47:37-04:00", method: "POST", endpoint: "/v1/messages", headers: { "User-Agent": ["claude-cli/1.0.11 (external, cli)"], "Content-Type": ["application/json"], "Anthropic-Version": ["2023-06-01"] }, body: { model: "claude-sonnet-4-20250514", messages: [ { role: "user", content: [{ type: "text", text: "I need to extract the complete list of tools available to Claude Code from the request file..." }] } ], max_tokens: 32000, temperature: 1, stream: true } }; startTransition(() => { // setRequests([ // { ...exampleRequest, id: 1 }, // { // ...exampleRequest, // id: 2, // timestamp: "2025-06-04T23:45:12-04:00", // endpoint: "/v1/chat/completions", // body: { ...exampleRequest.body, model: "gpt-4-turbo" } // }, // { // ...exampleRequest, // id: 3, // timestamp: "2025-06-04T23:42:33-04:00", // method: "GET", // endpoint: "/v1/models", // body: undefined // } // ]); }); } finally { setIsFetching(false); } }; const loadConversations = async (filter?: string, loadMore = false) => { setIsFetching(true); const pageToFetch = loadMore ? conversationsCurrentPage + 1 : 1; try { const currentModelFilter = filter || modelFilter; const url = new URL('/api/conversations', window.location.origin); url.searchParams.append("page", pageToFetch.toString()); url.searchParams.append("limit", itemsPerPage.toString()); if (currentModelFilter !== "all") { url.searchParams.append("model", currentModelFilter); } const response = await fetch(url.toString()); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); startTransition(() => { if (loadMore) { setConversations(prev => [...prev, ...data.conversations]); } else { setConversations(data.conversations); } setConversationsCurrentPage(pageToFetch); setHasMoreConversations(data.conversations.length === itemsPerPage); }); } catch (error) { console.error('Failed to load conversations:', error); startTransition(() => { setConversations([]); }); } finally { setIsFetching(false); } }; const loadConversationDetails = async (conversationId: string, projectName: string) => { try { const response = await fetch(`/api/conversations/${conversationId}?project=${encodeURIComponent(projectName)}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const conversation = await response.json(); setSelectedConversation(conversation); setIsConversationModalOpen(true); } catch (error) { console.error('Failed to load conversation details:', error); } }; const clearRequests = async () => { try { const response = await fetch('/api/requests', { method: 'DELETE' }); if (response.ok) { setRequests([]); setConversations([]); setRequestsCurrentPage(1); setHasMoreRequests(true); setConversationsCurrentPage(1); setHasMoreConversations(true); } } catch (error) { console.error('Failed to clear requests:', error); setRequests([]); } }; const filterRequests = (filter: string) => { if (filter === 'all') return requests; return requests.filter(req => { switch (filter) { case 'messages': return req.endpoint.includes('/messages'); case 'completions': return req.endpoint.includes('/completions'); case 'models': return req.endpoint.includes('/models'); default: return true; } }); }; const getMethodColor = (method: string) => { const colors = { 'GET': 'bg-green-50 text-green-700 border border-green-200', 'POST': 'bg-blue-50 text-blue-700 border border-blue-200', 'PUT': 'bg-yellow-50 text-yellow-700 border border-yellow-200', 'DELETE': 'bg-red-50 text-red-700 border border-red-200' }; return colors[method as keyof typeof colors] || 'bg-gray-50 text-gray-700 border border-gray-200'; }; const getRequestSummary = (request: Request) => { if (request.body?.messages) { const messageCount = request.body.messages.length; // Count tool calls const toolCalls = request.body.messages.reduce((count, msg) => { if (msg.content && Array.isArray(msg.content)) { return count + msg.content.filter((c: any) => c.type === 'tool_use').length; } return count; }, 0); // Count tool definitions in system prompt let toolDefinitions = 0; if (request.body.system) { request.body.system.forEach(sys => { if (sys.text && sys.text.includes('')) { const functionMatches = [...sys.text.matchAll(/([\s\S]*?)<\/function>/g)]; toolDefinitions += functionMatches.length; } }); } let summary = `💬 ${messageCount} messages`; if (toolDefinitions > 0) { summary += ` • 🛠️ ${toolDefinitions} tools available`; } if (toolCalls > 0) { summary += ` • ⚡ ${toolCalls} tool calls executed`; } return summary; } return '📡 API request'; }; const showRequestDetails = (requestId: number) => { const request = requests.find(r => r.id === requestId); if (request) { setSelectedRequest(request); setIsModalOpen(true); } }; const closeModal = () => { setIsModalOpen(false); setSelectedRequest(null); }; const getToolStats = () => { let toolDefinitions = 0; let toolCalls = 0; requests.forEach(req => { if (req.body) { // Count tool definitions in system prompts if (req.body.system) { req.body.system.forEach(sys => { if (sys.text && sys.text.includes('')) { const functionMatches = [...sys.text.matchAll(/([\s\S]*?)<\/function>/g)]; toolDefinitions += functionMatches.length; } }); } // Count actual tool calls in messages if (req.body.messages) { req.body.messages.forEach(msg => { if (msg.content && Array.isArray(msg.content)) { msg.content.forEach((contentPart: any) => { if (contentPart.type === 'tool_use') { toolCalls++; } if (contentPart.type === 'text' && contentPart.text && contentPart.text.includes('')) { const functionMatches = [...contentPart.text.matchAll(/([\s\S]*?)<\/function>/g)]; toolDefinitions += functionMatches.length; } }); } }); } } }); return `${toolCalls} calls / ${toolDefinitions} tools`; }; const getPromptGradeStats = () => { let totalGrades = 0; let gradeCount = 0; requests.forEach(req => { if (req.promptGrade && req.promptGrade.score) { totalGrades += req.promptGrade.score; gradeCount++; } }); if (gradeCount > 0) { const avgGrade = (totalGrades / gradeCount).toFixed(1); return `${avgGrade}/5`; } return '-/5'; }; const formatDuration = (milliseconds: number) => { if (milliseconds < 60000) { return `${Math.round(milliseconds / 1000)}s`; } else if (milliseconds < 3600000) { return `${Math.round(milliseconds / 60000)}m`; } else { return `${Math.round(milliseconds / 3600000)}h`; } }; const formatConversationSummary = (conversation: ConversationSummary) => { const duration = formatDuration(conversation.duration); return `${conversation.requestCount} requests • ${duration} duration`; }; const canGradeRequest = (request: Request) => { return request.body && request.body.messages && request.body.messages.some(msg => msg.role === 'user') && request.endpoint.includes('/messages'); }; const gradeRequest = async (requestId: number) => { const request = requests.find(r => r.id === requestId); if (!request || !canGradeRequest(request)) return; try { const response = await fetch('/api/grade-prompt', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: request.body!.messages, systemMessages: request.body!.system || [], requestId: request.timestamp }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const promptGrade = await response.json(); // Update the request with the new grading const updatedRequests = requests.map(r => r.id === requestId ? { ...r, promptGrade } : r ); setRequests(updatedRequests); } catch (error) { console.error('Failed to grade prompt:', error); } }; const handleModelFilterChange = (newFilter: string) => { setModelFilter(newFilter); if (viewMode === 'requests') { loadRequests(newFilter); } else { loadConversations(newFilter); } }; useEffect(() => { if (viewMode === 'requests') { loadRequests(modelFilter); } else { loadConversations(modelFilter); } }, [viewMode, modelFilter]); const filteredRequests = filterRequests(filter); return (
{/* Header */}

Claude Code Monitor

{/* Filter buttons */}
{/* View mode toggle */}
{/* Main Content */}
{/* Stats Grid */}

{viewMode === "requests" ? "Total Requests" : "Total Conversations"}

{viewMode === "requests" ? requests.length : conversations.length}

{/*

All time

*/}
{viewMode === "requests" ? ( ) : ( )}
{/* Main Content */} {viewMode === "requests" ? ( /* Request History */

Request History

{/*
*/}
{(isFetching && requestsCurrentPage === 1) || isPending ? (

Loading requests...

) : filteredRequests.length === 0 ? (

No requests found

Make sure you have set the ANTHROPIC_BASE_URL environment variable to the proxy server URL

) : ( <> {filteredRequests.map(request => (
showRequestDetails(request.id)}>
{request.method}
{request.endpoint} {request.conversationId && ( Turn {request.turnNumber} )}
{new Date(request.timestamp).toLocaleString()}
{request.body?.model && ( {request.body.model} )} {/* {request.promptGrade ? ( = 4 ? 'bg-green-50 border-green-200 text-green-700' : request.promptGrade.score >= 3 ? 'bg-yellow-50 border-yellow-200 text-yellow-700' : 'bg-red-50 border-red-200 text-red-700' }`}> {request.promptGrade.score >= 4 ? '🎉' : request.promptGrade.score >= 3 ? '👍' : '⚠️'} {request.promptGrade.score}/5 ) : ( canGradeRequest(request) && ( ) )} */}
{getRequestSummary(request)}
))} {hasMoreRequests && (
)} )}
) : ( /* Conversations View */

Conversations

{(isFetching && conversationsCurrentPage === 1) || isPending ? (

Loading conversations...

) : conversations.length === 0 ? (

No conversations found

Start a conversation to see it appear here

) : ( <> {conversations.map(conversation => (
loadConversationDetails(conversation.id, conversation.projectName)}>
Conversation {conversation.id.slice(-8)} {new Date(conversation.startTime).toLocaleString()} {conversation.projectName && ( {conversation.projectName} )}
{conversation.requestCount} turns
First: {conversation.firstMessage.substring(0, 200) || "No content"}{conversation.firstMessage.length > 200 && "..."}
{conversation.lastMessage && conversation.lastMessage !== conversation.firstMessage && (
Latest: {conversation.lastMessage.substring(0, 200)}{conversation.lastMessage.length > 200 && "..."}
)}
))} {hasMoreConversations && (
)} )}
)}
{/* Request Detail Modal */} {isModalOpen && selectedRequest && (

Request Details

gradeRequest(selectedRequest.id)} />
)} {/* Conversation Detail Modal */} {isConversationModalOpen && selectedConversation && (

Conversation {selectedConversation.sessionId.slice(-8)}

{selectedConversation.messageCount} messages
{/* Conversation Overview */}
{selectedConversation.messageCount}
Messages
{new Date(selectedConversation.startTime).toLocaleDateString()}
Started
{new Date(selectedConversation.endTime).toLocaleDateString()}
Last Activity
{/* Conversation Thread */}
)}
); }