diff --git a/src/components/InferenceMessageMonitor.tsx b/src/components/InferenceMessageMonitor.tsx new file mode 100644 index 0000000..2bec1be --- /dev/null +++ b/src/components/InferenceMessageMonitor.tsx @@ -0,0 +1,186 @@ +// Component to monitor and display inference messages + +import { useState, useRef } from 'react'; +import { useInference } from '@/contexts/InferenceContext'; +import type { InferenceMessage } from '@/types/inference'; + +export function InferenceMessageMonitor() { + const { messages, clearMessages } = useInference(); + const [maxMessages, setMaxMessages] = useState(50); + const messagesEndRef = useRef(null); + + // Slice messages to show only the latest maxMessages + const displayMessages = messages.slice(-maxMessages); + + const getMessageTypeColor = (type: InferenceMessage['type']) => { + switch (type) { + case 'request': + return 'bg-blue-50 dark:bg-blue-900/30 border-blue-200 dark:border-blue-800'; + case 'response': + return 'bg-green-50 dark:bg-green-900/30 border-green-200 dark:border-green-800'; + case 'error': + return 'bg-red-50 dark:bg-red-900/30 border-red-200 dark:border-red-800'; + case 'stream_chunk': + return 'bg-yellow-50 dark:bg-yellow-900/30 border-yellow-200 dark:border-yellow-800'; + default: + return 'bg-gray-50 dark:bg-gray-900/30 border-gray-200 dark:border-gray-800'; + } + }; + + const formatDuration = (duration?: number) => { + if (!duration) return ''; + if (duration < 1000) return `${duration}ms`; + return `${(duration / 1000).toFixed(2)}s`; + }; + + const formatMessage = (message: InferenceMessage) => { + switch (message.type) { + case 'request': + return `Request to ${message.model || 'default model'}`; + case 'response': + return `Response from ${message.model || 'default model'}`; + case 'error': + return `Error from ${message.model || 'default model'}`; + case 'stream_chunk': + return `Stream chunk from ${message.model || 'default model'}`; + default: + return 'Unknown message type'; + } + }; + + return ( +
+
+

+ Inference Monitor ({displayMessages.length}) +

+ +
+ +
+ {displayMessages.length === 0 ? ( +
+ No messages yet. Messages will appear here as inference requests are made. +
+ ) : ( +
+ {displayMessages.map((message) => ( +
+
+
+ + {message.type === 'request' ? '→' : + message.type === 'response' ? '←' : + message.type === 'error' ? '✗' : '⋯'} + + + {formatMessage(message)} + + {message.duration && ( + + ({formatDuration(message.duration)}) + + )} +
+ + {message.timestamp.toLocaleTimeString()} + +
+ + {/* Message metadata */} +
+ + Message Metadata + +
+                    {JSON.stringify({
+                      id: message.id,
+                      type: message.type,
+                      providerId: message.providerId,
+                      providerName: message.providerName,
+                      model: message.model,
+                      timestamp: message.timestamp.toISOString(),
+                      duration: message.duration
+                    }, null, 2)}
+                  
+
+ + {/* Request details */} + {message.request && ( +
+ + Request Details + +
+                      {JSON.stringify(message.request, null, 2)}
+                    
+
+ )} + + {/* Response details */} + {message.response && ( +
+ + Response Details + +
+                      {JSON.stringify(message.response, null, 2)}
+                    
+
+ )} + + {/* Error details */} + {message.error && ( +
+ + Error Details + +
+                      {message.error instanceof Error ? message.error.message : JSON.stringify(message.error, null, 2)}
+                    
+
+ )} +
+ ))} +
+
+ )} +
+ + {/* Controls */} +
+
+ + +
+
+ Live monitoring active +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/InferenceTab.tsx b/src/components/InferenceTab.tsx index df8f9ae..c94bffd 100644 --- a/src/components/InferenceTab.tsx +++ b/src/components/InferenceTab.tsx @@ -3,10 +3,13 @@ import { useState } from 'react'; import { useInference } from '@/contexts/InferenceContext'; import { OpenRouterOAuthProvider } from '@/providers/openrouter/oauth-provider'; +import { InferenceMessageMonitor } from './InferenceMessageMonitor'; export function InferenceTab() { const { provider, models, clearProvider, setProvider } = useInference(); const [isConnecting, setIsConnecting] = useState(false); + const [showConnectionStatus, setShowConnectionStatus] = useState(true); + const [showModels, setShowModels] = useState(false); const handleConnect = async () => { setIsConnecting(true); @@ -28,88 +31,112 @@ export function InferenceTab() { }; return ( -
-
- {/* Connection Status */} -
-

- Inference Provider -

- - {provider ? ( -
-
-
-

- 🟢 Connected to OpenRouter -

-

- {models.length} models available -

-
- -
-
- ) : ( -
-

- ⚪ Not connected -

- -
- )} -
- - {/* Available Models */} - {provider && models.length > 0 && ( -
-

- Available Models ({models.length}) +
+ {/* Connection Status */} +
+ + {showConnectionStatus && ( +
+
+ {provider ? ( +
+
+
+

+ 🟢 Connected to {provider.name} +

+

+ {models.length} models available +

- )} -
- Context: {model.contextLength?.toLocaleString() || 'Unknown'} +
- ))} + ) : ( +
+

+ ⚪ Not connected +

+ +
+

Connect to OpenRouter to access AI models for conversations.

+

You'll need an OpenRouter account and API key to authenticate.

+
+
+ )}
)} - - {/* Help Text */} -
-

- Connect to OpenRouter to access AI models for conversations. -

-

- You'll need an OpenRouter account and API key to authenticate. -

-
+ + {/* Available Models */} + {provider && models.length > 0 && ( +
+ + {showModels && ( +
+
+ {models.map((model) => ( +
+
+ {model.id} +
+ {model.name && model.name !== model.id && ( +
+ {model.name} +
+ )} +
+ Context: {model.contextLength?.toLocaleString() || 'Unknown'} +
+
+ ))} +
+
+ )} +
+ )} + + {/* Inference Message Monitor */} +
); } \ No newline at end of file diff --git a/src/components/MCPMessageMonitor.tsx b/src/components/MCPMessageMonitor.tsx index 8aefdc3..b1ab550 100644 --- a/src/components/MCPMessageMonitor.tsx +++ b/src/components/MCPMessageMonitor.tsx @@ -1,53 +1,16 @@ // Component to monitor and display MCP messages -import { useState, useEffect, useRef } from 'react'; +import { useState, useRef } from 'react'; import { useMCP } from '@/contexts/MCPContext'; import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'; -interface MCPMessageEntry { - id: string; - timestamp: Date; - connectionId: string; - connectionName: string; - direction: 'sent' | 'received'; - message: JSONRPCMessage; - extra?: any; -} - export function MCPMessageMonitor() { - const { addMessageCallback, removeMessageCallback, connections } = useMCP(); - const [messages, setMessages] = useState([]); + const { messages, clearMessages } = useMCP(); const [maxMessages, setMaxMessages] = useState(50); const messagesEndRef = useRef(null); - // Remove auto-scroll - let user control scroll position - - // Register message callback - useEffect(() => { - const callbackId = addMessageCallback((connectionId, _client, message, direction, extra) => { - const connection = connections.find(c => c.id === connectionId); - - setMessages(prev => { - const newMessage: MCPMessageEntry = { - id: `${Date.now()}-${Math.random()}`, - timestamp: new Date(), - connectionId, - connectionName: connection?.name || 'Unknown', - direction, - message, - extra, - }; - - // Keep only the latest maxMessages - const newMessages = [...prev, newMessage]; - return newMessages.slice(-maxMessages); - }); - }); - - return () => { - removeMessageCallback(callbackId); - }; - }, [addMessageCallback, removeMessageCallback, connections, maxMessages]); + // Slice messages to show only the latest maxMessages + const displayMessages = messages.slice(-maxMessages); const formatMessage = (msg: JSONRPCMessage) => { if ('method' in msg) { @@ -74,15 +37,12 @@ export function MCPMessageMonitor() { return baseClass; }; - const clearMessages = () => { - setMessages([]); - }; return (

- Message Monitor ({messages.length}) + Message Monitor ({displayMessages.length})