Spaces:
Sleeping
Sleeping
Download frontend/src/components/admin/OpenAILogsPanel.jsx from chowdary1/ai-interviewer-version-2: direct link, hf CLI and curl.
- Browser
- Download file 20.8 kB
-
https://huggingface.co/spaces/chowdary1/ai-interviewer-version-2/resolve/main/frontend/src/components/admin/OpenAILogsPanel.jsx
- Command line
-
hf download hf://spaces/chowdary1/ai-interviewer-version-2/frontend/src/components/admin/OpenAILogsPanel.jsx
-
curl -L -o OpenAILogsPanel.jsx https://huggingface.co/spaces/chowdary1/ai-interviewer-version-2/resolve/main/frontend/src/components/admin/OpenAILogsPanel.jsx
20.8 kB
| /** | |
| * OpenAI API Logs Panel Component | |
| * | |
| * Simple, clean panel to monitor OpenAI API calls: | |
| * - Real-time log viewer (last 100 calls from memory) | |
| * - Token usage statistics | |
| * - Response time tracking | |
| * - Operation breakdown | |
| */ | |
| import React, { useState, useEffect, useCallback } from 'react'; | |
| import { | |
| Zap, RefreshCw, Clock, Brain, Trash2, | |
| Check, XCircle, AlertTriangle, BarChart3, | |
| Activity, Hash, Cpu, TrendingUp | |
| } from 'lucide-react'; | |
| import { getToken } from '../../services/api'; | |
| const API_BASE = import.meta.env.VITE_API_URL || ''; | |
| // Status badge | |
| const StatusBadge = ({ status }) => { | |
| const isSuccess = status === 'success'; | |
| return ( | |
| <span style={{ | |
| backgroundColor: isSuccess ? 'rgba(16, 185, 129, 0.15)' : 'rgba(239, 68, 68, 0.15)', | |
| color: isSuccess ? '#10b981' : '#ef4444', | |
| padding: '4px 10px', | |
| borderRadius: '12px', | |
| fontSize: '0.75rem', | |
| fontWeight: '600', | |
| display: 'inline-flex', | |
| alignItems: 'center', | |
| gap: '4px' | |
| }}> | |
| {isSuccess ? <Check size={12} /> : <XCircle size={12} />} | |
| {status} | |
| </span> | |
| ); | |
| }; | |
| // Response time indicator | |
| const ResponseTimeIndicator = ({ ms }) => { | |
| if (!ms) return <span style={{ color: '#6b7280' }}>-</span>; | |
| let color; | |
| if (ms < 1000) color = '#10b981'; | |
| else if (ms < 3000) color = '#f59e0b'; | |
| else color = '#ef4444'; | |
| return ( | |
| <span style={{ color, fontWeight: '600', fontFamily: 'monospace' }}> | |
| {ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`} | |
| </span> | |
| ); | |
| }; | |
| function OpenAILogsPanel() { | |
| const [logs, setLogs] = useState([]); | |
| const [stats, setStats] = useState(null); | |
| const [loading, setLoading] = useState(true); | |
| const [error, setError] = useState(null); | |
| const [autoRefresh, setAutoRefresh] = useState(true); | |
| // Fetch logs | |
| const fetchLogs = useCallback(async () => { | |
| setLoading(true); | |
| setError(null); | |
| try { | |
| const token = getToken(); | |
| const response = await fetch(`${API_BASE}/api/admin/openai-logs?limit=50`, { | |
| headers: { | |
| 'Authorization': `Bearer ${token}`, | |
| 'Content-Type': 'application/json' | |
| } | |
| }); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| if (data.success) { | |
| setLogs(data.logs || []); | |
| setStats(data.stats || null); | |
| } | |
| } else { | |
| throw new Error('Failed to fetch OpenAI logs'); | |
| } | |
| } catch (err) { | |
| setError(err.message); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }, []); | |
| // Clear logs | |
| const clearLogs = async () => { | |
| if (!window.confirm('Clear all OpenAI API logs from memory?')) return; | |
| try { | |
| const token = getToken(); | |
| const response = await fetch(`${API_BASE}/api/admin/openai-logs`, { | |
| method: 'DELETE', | |
| headers: { | |
| 'Authorization': `Bearer ${token}`, | |
| 'Content-Type': 'application/json' | |
| } | |
| }); | |
| if (response.ok) { | |
| setLogs([]); | |
| setStats(null); | |
| } | |
| } catch (err) { | |
| setError(err.message); | |
| } | |
| }; | |
| useEffect(() => { | |
| fetchLogs(); | |
| }, [fetchLogs]); | |
| // Auto-refresh every 5 seconds | |
| useEffect(() => { | |
| if (!autoRefresh) return; | |
| const interval = setInterval(fetchLogs, 5000); | |
| return () => clearInterval(interval); | |
| }, [autoRefresh, fetchLogs]); | |
| // Format timestamp | |
| const formatTime = (isoString) => { | |
| if (!isoString) return '-'; | |
| const date = new Date(isoString); | |
| return date.toLocaleTimeString('en-US', { | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| second: '2-digit' | |
| }); | |
| }; | |
| return ( | |
| <div className="openai-logs-panel"> | |
| {/* Header */} | |
| <div style={{ | |
| display: 'flex', | |
| justifyContent: 'space-between', | |
| alignItems: 'center', | |
| marginBottom: '1.5rem', | |
| padding: '1rem 1.5rem', | |
| background: 'linear-gradient(135deg, rgba(16, 185, 129, 0.1), rgba(59, 130, 246, 0.1))', | |
| borderRadius: '12px', | |
| border: '1px solid rgba(16, 185, 129, 0.2)' | |
| }}> | |
| <div> | |
| <h2 style={{ | |
| margin: 0, | |
| display: 'flex', | |
| alignItems: 'center', | |
| gap: '0.5rem', | |
| color: '#f3f4f6' | |
| }}> | |
| <Brain size={24} style={{ color: '#10b981' }} /> | |
| OpenAI API Logs | |
| </h2> | |
| <p style={{ margin: '0.5rem 0 0', color: '#9ca3af', fontSize: '0.9rem' }}> | |
| Real-time monitoring • Last 100 calls (in-memory) | |
| </p> | |
| </div> | |
| <div style={{ display: 'flex', gap: '0.75rem', alignItems: 'center' }}> | |
| <label style={{ | |
| display: 'flex', | |
| alignItems: 'center', | |
| gap: '0.5rem', | |
| cursor: 'pointer', | |
| fontSize: '0.85rem', | |
| color: '#9ca3af' | |
| }}> | |
| <input | |
| type="checkbox" | |
| checked={autoRefresh} | |
| onChange={(e) => setAutoRefresh(e.target.checked)} | |
| style={{ accentColor: '#10b981' }} | |
| /> | |
| Auto-refresh | |
| </label> | |
| <button onClick={fetchLogs} style={{ | |
| display: 'flex', | |
| alignItems: 'center', | |
| gap: '0.5rem', | |
| padding: '0.5rem 1rem', | |
| background: 'rgba(59, 130, 246, 0.2)', | |
| border: '1px solid rgba(59, 130, 246, 0.3)', | |
| borderRadius: '8px', | |
| color: '#60a5fa', | |
| cursor: 'pointer', | |
| fontSize: '0.85rem', | |
| fontWeight: '500' | |
| }}> | |
| <RefreshCw size={14} className={loading ? 'spinning' : ''} /> | |
| Refresh | |
| </button> | |
| <button onClick={clearLogs} style={{ | |
| display: 'flex', | |
| alignItems: 'center', | |
| gap: '0.5rem', | |
| padding: '0.5rem 1rem', | |
| background: 'rgba(239, 68, 68, 0.1)', | |
| border: '1px solid rgba(239, 68, 68, 0.3)', | |
| borderRadius: '8px', | |
| color: '#ef4444', | |
| cursor: 'pointer', | |
| fontSize: '0.85rem', | |
| fontWeight: '500' | |
| }}> | |
| <Trash2 size={14} /> | |
| Clear | |
| </button> | |
| </div> | |
| </div> | |
| {/* Stats Cards */} | |
| {stats && ( | |
| <div style={{ | |
| display: 'grid', | |
| gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', | |
| gap: '1rem', | |
| marginBottom: '1.5rem' | |
| }}> | |
| <div style={{ | |
| padding: '1rem', | |
| background: 'rgba(59, 130, 246, 0.1)', | |
| borderRadius: '12px', | |
| border: '1px solid rgba(59, 130, 246, 0.2)' | |
| }}> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.5rem' }}> | |
| <Activity size={16} style={{ color: '#60a5fa' }} /> | |
| <span style={{ color: '#9ca3af', fontSize: '0.8rem' }}>Total Calls</span> | |
| </div> | |
| <span style={{ fontSize: '1.5rem', fontWeight: '700', color: '#f3f4f6' }}> | |
| {stats.total_calls || 0} | |
| </span> | |
| </div> | |
| <div style={{ | |
| padding: '1rem', | |
| background: 'rgba(16, 185, 129, 0.1)', | |
| borderRadius: '12px', | |
| border: '1px solid rgba(16, 185, 129, 0.2)' | |
| }}> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.5rem' }}> | |
| <TrendingUp size={16} style={{ color: '#10b981' }} /> | |
| <span style={{ color: '#9ca3af', fontSize: '0.8rem' }}>Success Rate</span> | |
| </div> | |
| <span style={{ fontSize: '1.5rem', fontWeight: '700', color: '#10b981' }}> | |
| {stats.success_rate || 100}% | |
| </span> | |
| </div> | |
| <div style={{ | |
| padding: '1rem', | |
| background: 'rgba(139, 92, 246, 0.1)', | |
| borderRadius: '12px', | |
| border: '1px solid rgba(139, 92, 246, 0.2)' | |
| }}> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.5rem' }}> | |
| <Hash size={16} style={{ color: '#a78bfa' }} /> | |
| <span style={{ color: '#9ca3af', fontSize: '0.8rem' }}>Total Tokens</span> | |
| </div> | |
| <span style={{ fontSize: '1.5rem', fontWeight: '700', color: '#f3f4f6' }}> | |
| {(stats.total_tokens || 0).toLocaleString()} | |
| </span> | |
| </div> | |
| <div style={{ | |
| padding: '1rem', | |
| background: 'rgba(245, 158, 11, 0.1)', | |
| borderRadius: '12px', | |
| border: '1px solid rgba(245, 158, 11, 0.2)' | |
| }}> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.5rem' }}> | |
| <Clock size={16} style={{ color: '#f59e0b' }} /> | |
| <span style={{ color: '#9ca3af', fontSize: '0.8rem' }}>Avg Response</span> | |
| </div> | |
| <span style={{ fontSize: '1.5rem', fontWeight: '700', color: '#f3f4f6' }}> | |
| {stats.avg_response_time_ms || 0}ms | |
| </span> | |
| </div> | |
| </div> | |
| )} | |
| {/* Operations Breakdown */} | |
| {stats?.operations && Object.keys(stats.operations).length > 0 && ( | |
| <div style={{ | |
| padding: '1rem', | |
| background: 'rgba(0, 0, 0, 0.2)', | |
| borderRadius: '12px', | |
| border: '1px solid rgba(255, 255, 255, 0.1)', | |
| marginBottom: '1.5rem' | |
| }}> | |
| <h4 style={{ margin: '0 0 0.75rem', color: '#9ca3af', fontSize: '0.85rem' }}> | |
| <Cpu size={14} style={{ marginRight: '0.5rem', verticalAlign: 'middle' }} /> | |
| Operations Breakdown | |
| </h4> | |
| <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}> | |
| {Object.entries(stats.operations).map(([op, count]) => ( | |
| <span key={op} style={{ | |
| padding: '0.25rem 0.75rem', | |
| background: 'rgba(59, 130, 246, 0.2)', | |
| borderRadius: '16px', | |
| fontSize: '0.8rem', | |
| color: '#93c5fd' | |
| }}> | |
| {op}: <strong>{count}</strong> | |
| </span> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| {/* Error display */} | |
| {error && ( | |
| <div style={{ | |
| padding: '1rem', | |
| backgroundColor: 'rgba(239, 68, 68, 0.1)', | |
| borderRadius: '8px', | |
| border: '1px solid rgba(239, 68, 68, 0.3)', | |
| color: '#ef4444', | |
| marginBottom: '1rem', | |
| display: 'flex', | |
| alignItems: 'center', | |
| gap: '0.5rem' | |
| }}> | |
| <AlertTriangle size={16} /> | |
| {error} | |
| </div> | |
| )} | |
| {/* Logs Table */} | |
| <div style={{ | |
| background: 'rgba(0, 0, 0, 0.3)', | |
| borderRadius: '12px', | |
| border: '1px solid rgba(255, 255, 255, 0.1)', | |
| overflow: 'hidden' | |
| }}> | |
| <table style={{ | |
| width: '100%', | |
| borderCollapse: 'collapse', | |
| fontSize: '0.85rem' | |
| }}> | |
| <thead> | |
| <tr style={{ | |
| background: 'rgba(0, 0, 0, 0.4)', | |
| borderBottom: '1px solid rgba(255, 255, 255, 0.1)' | |
| }}> | |
| <th style={{ padding: '0.75rem 1rem', textAlign: 'left', color: '#9ca3af', fontWeight: '600' }}>Time</th> | |
| <th style={{ padding: '0.75rem 1rem', textAlign: 'left', color: '#9ca3af', fontWeight: '600' }}>Operation</th> | |
| <th style={{ padding: '0.75rem 1rem', textAlign: 'left', color: '#9ca3af', fontWeight: '600' }}>Model</th> | |
| <th style={{ padding: '0.75rem 1rem', textAlign: 'center', color: '#9ca3af', fontWeight: '600' }}>Tokens</th> | |
| <th style={{ padding: '0.75rem 1rem', textAlign: 'center', color: '#9ca3af', fontWeight: '600' }}>Time</th> | |
| <th style={{ padding: '0.75rem 1rem', textAlign: 'center', color: '#9ca3af', fontWeight: '600' }}>Status</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {logs.length === 0 ? ( | |
| <tr> | |
| <td colSpan="6" style={{ | |
| padding: '3rem', | |
| textAlign: 'center', | |
| color: '#6b7280' | |
| }}> | |
| <Brain size={32} style={{ opacity: 0.5, marginBottom: '0.5rem' }} /> | |
| <p style={{ margin: 0 }}>No API calls logged yet</p> | |
| <p style={{ margin: '0.25rem 0 0', fontSize: '0.8rem' }}> | |
| Logs appear here when the app makes OpenAI API calls | |
| </p> | |
| </td> | |
| </tr> | |
| ) : ( | |
| logs.map((log, idx) => ( | |
| <tr key={idx} style={{ | |
| borderBottom: '1px solid rgba(255, 255, 255, 0.05)', | |
| background: log.status === 'error' ? 'rgba(239, 68, 68, 0.05)' : 'transparent' | |
| }}> | |
| <td style={{ padding: '0.75rem 1rem', color: '#9ca3af', fontFamily: 'monospace' }}> | |
| {formatTime(log.timestamp)} | |
| </td> | |
| <td style={{ padding: '0.75rem 1rem', color: '#f3f4f6', fontWeight: '500' }}> | |
| {log.operation} | |
| </td> | |
| <td style={{ padding: '0.75rem 1rem' }}> | |
| <span style={{ | |
| padding: '2px 8px', | |
| background: 'rgba(16, 185, 129, 0.15)', | |
| borderRadius: '6px', | |
| color: '#10b981', | |
| fontSize: '0.8rem', | |
| fontFamily: 'monospace' | |
| }}> | |
| {log.model} | |
| </span> | |
| </td> | |
| <td style={{ padding: '0.75rem 1rem', textAlign: 'center', color: '#a78bfa', fontFamily: 'monospace' }}> | |
| {log.total_tokens ? ( | |
| <span title={`Prompt: ${log.prompt_tokens || '?'} | Completion: ${log.completion_tokens || '?'}`}> | |
| {log.total_tokens.toLocaleString()} | |
| </span> | |
| ) : '-'} | |
| </td> | |
| <td style={{ padding: '0.75rem 1rem', textAlign: 'center' }}> | |
| <ResponseTimeIndicator ms={log.response_time_ms} /> | |
| </td> | |
| <td style={{ padding: '0.75rem 1rem', textAlign: 'center' }}> | |
| <StatusBadge status={log.status} /> | |
| {log.error_message && ( | |
| <div style={{ | |
| fontSize: '0.75rem', | |
| color: '#ef4444', | |
| marginTop: '0.25rem', | |
| maxWidth: '200px', | |
| overflow: 'hidden', | |
| textOverflow: 'ellipsis', | |
| whiteSpace: 'nowrap' | |
| }} title={log.error_message}> | |
| {log.error_message} | |
| </div> | |
| )} | |
| </td> | |
| </tr> | |
| )) | |
| )} | |
| </tbody> | |
| </table> | |
| </div> | |
| {/* Footer Info */} | |
| <div style={{ | |
| marginTop: '1rem', | |
| padding: '0.75rem 1rem', | |
| background: 'rgba(0, 0, 0, 0.2)', | |
| borderRadius: '8px', | |
| fontSize: '0.8rem', | |
| color: '#6b7280', | |
| display: 'flex', | |
| justifyContent: 'space-between', | |
| alignItems: 'center' | |
| }}> | |
| <span> | |
| 📊 Showing {logs.length} of last 100 calls (stored in-memory) | |
| </span> | |
| <span> | |
| {autoRefresh && '🔄 Auto-refreshing every 5s'} | |
| </span> | |
| </div> | |
| {/* Spinner styles */} | |
| <style>{` | |
| @keyframes spin { | |
| from { transform: rotate(0deg); } | |
| to { transform: rotate(360deg); } | |
| } | |
| .spinning { | |
| animation: spin 1s linear infinite; | |
| } | |
| `}</style> | |
| </div> | |
| ); | |
| } | |
| export default OpenAILogsPanel; | |