Spaces:
Sleeping
Sleeping
Download frontend/src/components/ConfidenceGraph/ConfidenceGraph.jsx from chowdary1/ai-interviewer-version-2: direct link, hf CLI and curl.
- Browser
- Download file 8.87 kB
-
https://huggingface.co/spaces/chowdary1/ai-interviewer-version-2/resolve/main/frontend/src/components/ConfidenceGraph/ConfidenceGraph.jsx
- Command line
-
hf download hf://spaces/chowdary1/ai-interviewer-version-2/frontend/src/components/ConfidenceGraph/ConfidenceGraph.jsx
-
curl -L -o ConfidenceGraph.jsx https://huggingface.co/spaces/chowdary1/ai-interviewer-version-2/resolve/main/frontend/src/components/ConfidenceGraph/ConfidenceGraph.jsx
8.87 kB
| /** | |
| * ConfidenceGraph Component | |
| * | |
| * Displays interview confidence over time as a line chart. | |
| * Data is from computer vision analysis - NOT LLM sentiment. | |
| * | |
| * Features: | |
| * - Smooth line chart with gradient fill | |
| * - Tooltips with timestamp and score | |
| * - Confidence level indicator | |
| * - Works in light/dark mode | |
| */ | |
| import React, { useMemo } from 'react'; | |
| import { | |
| LineChart, | |
| Line, | |
| XAxis, | |
| YAxis, | |
| CartesianGrid, | |
| Tooltip, | |
| ResponsiveContainer, | |
| Area, | |
| ReferenceLine, | |
| } from 'recharts'; | |
| import './ConfidenceGraph.css'; | |
| // Confidence level thresholds | |
| const CONFIDENCE_LEVELS = { | |
| very_low: { min: 0, max: 20, color: '#EF4444', label: 'Very Low' }, | |
| low: { min: 21, max: 40, color: '#F97316', label: 'Low' }, | |
| moderate: { min: 41, max: 60, color: '#EAB308', label: 'Moderate' }, | |
| high: { min: 61, max: 80, color: '#22C55E', label: 'High' }, | |
| very_high: { min: 81, max: 100, color: '#10B981', label: 'Very High' }, | |
| }; | |
| /** | |
| * Custom tooltip for the graph | |
| */ | |
| const CustomTooltip = ({ active, payload, label }) => { | |
| if (!active || !payload || !payload.length) return null; | |
| const value = payload[0].value; | |
| const level = getConfidenceLevel(value); | |
| return ( | |
| <div className="confidence-tooltip"> | |
| <div className="tooltip-time"> | |
| {formatTime(label)} | |
| </div> | |
| <div className="tooltip-value" style={{ color: level.color }}> | |
| {value.toFixed(1)}% | |
| </div> | |
| <div className="tooltip-level" style={{ color: level.color }}> | |
| {level.label} | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| /** | |
| * Get confidence level from score | |
| */ | |
| function getConfidenceLevel(score) { | |
| if (score <= 20) return CONFIDENCE_LEVELS.very_low; | |
| if (score <= 40) return CONFIDENCE_LEVELS.low; | |
| if (score <= 60) return CONFIDENCE_LEVELS.moderate; | |
| if (score <= 80) return CONFIDENCE_LEVELS.high; | |
| return CONFIDENCE_LEVELS.very_high; | |
| } | |
| /** | |
| * Format seconds to mm:ss | |
| */ | |
| function formatTime(seconds) { | |
| const mins = Math.floor(seconds / 60); | |
| const secs = seconds % 60; | |
| return `${mins}:${secs.toString().padStart(2, '0')}`; | |
| } | |
| /** | |
| * Generate gradient ID based on data | |
| */ | |
| function getGradientId(avgConfidence) { | |
| const level = getConfidenceLevel(avgConfidence); | |
| return `confidenceGradient-${level.label.toLowerCase().replace(' ', '')}`; | |
| } | |
| /** | |
| * ConfidenceGraph Component | |
| */ | |
| export default function ConfidenceGraph({ | |
| graphData = [], | |
| summary = {}, | |
| showGrid = true, | |
| showAverage = true, | |
| height = 300, | |
| className = '', | |
| }) { | |
| // Memoize processed data | |
| const processedData = useMemo(() => { | |
| if (!graphData || graphData.length === 0) return []; | |
| return graphData.map((point) => ({ | |
| time: point.time, | |
| confidence: point.confidence, | |
| formattedTime: formatTime(point.time), | |
| })); | |
| }, [graphData]); | |
| // Calculate derived values | |
| const avgConfidence = summary?.average_confidence || | |
| (processedData.length > 0 | |
| ? processedData.reduce((sum, p) => sum + p.confidence, 0) / processedData.length | |
| : 0); | |
| const confidenceLevel = getConfidenceLevel(avgConfidence); | |
| const gradientId = getGradientId(avgConfidence); | |
| // Empty state | |
| if (processedData.length === 0) { | |
| return ( | |
| <div className={`confidence-graph-empty ${className}`}> | |
| <div className="empty-icon">📊</div> | |
| <p>No confidence data available yet</p> | |
| <span className="empty-hint"> | |
| Video analysis will generate confidence metrics | |
| </span> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className={`confidence-graph ${className}`}> | |
| {/* Header with summary */} | |
| <div className="confidence-graph-header"> | |
| <div className="header-left"> | |
| <h3>Confidence Timeline</h3> | |
| <span className="header-subtitle"> | |
| {processedData.length} seconds analyzed | |
| </span> | |
| </div> | |
| <div className="header-right"> | |
| <div | |
| className="average-badge" | |
| style={{ | |
| backgroundColor: `${confidenceLevel.color}20`, | |
| borderColor: confidenceLevel.color, | |
| }} | |
| > | |
| <span className="average-value" style={{ color: confidenceLevel.color }}> | |
| {avgConfidence.toFixed(1)}% | |
| </span> | |
| <span className="average-label">{confidenceLevel.label}</span> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Chart */} | |
| <div className="confidence-graph-chart" style={{ height }}> | |
| <ResponsiveContainer width="100%" height="100%"> | |
| <LineChart | |
| data={processedData} | |
| margin={{ top: 20, right: 30, left: 10, bottom: 10 }} | |
| > | |
| {/* Gradient definition */} | |
| <defs> | |
| <linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1"> | |
| <stop offset="0%" stopColor={confidenceLevel.color} stopOpacity={0.4} /> | |
| <stop offset="100%" stopColor={confidenceLevel.color} stopOpacity={0.05} /> | |
| </linearGradient> | |
| </defs> | |
| {/* Grid */} | |
| {showGrid && ( | |
| <CartesianGrid | |
| strokeDasharray="3 3" | |
| stroke="var(--grid-color, rgba(128,128,128,0.2))" | |
| vertical={false} | |
| /> | |
| )} | |
| {/* Axes */} | |
| <XAxis | |
| dataKey="time" | |
| tickFormatter={formatTime} | |
| stroke="var(--axis-color, #888)" | |
| tick={{ fill: 'var(--text-secondary, #888)', fontSize: 12 }} | |
| axisLine={{ stroke: 'var(--axis-color, rgba(128,128,128,0.3))' }} | |
| interval="preserveStartEnd" | |
| /> | |
| <YAxis | |
| domain={[0, 100]} | |
| stroke="var(--axis-color, #888)" | |
| tick={{ fill: 'var(--text-secondary, #888)', fontSize: 12 }} | |
| axisLine={{ stroke: 'var(--axis-color, rgba(128,128,128,0.3))' }} | |
| ticks={[0, 25, 50, 75, 100]} | |
| tickFormatter={(v) => `${v}%`} | |
| /> | |
| {/* Average line */} | |
| {showAverage && ( | |
| <ReferenceLine | |
| y={avgConfidence} | |
| stroke={confidenceLevel.color} | |
| strokeDasharray="5 5" | |
| strokeOpacity={0.7} | |
| label={{ | |
| value: `Avg: ${avgConfidence.toFixed(0)}%`, | |
| position: 'right', | |
| fill: confidenceLevel.color, | |
| fontSize: 11, | |
| }} | |
| /> | |
| )} | |
| {/* Tooltip */} | |
| <Tooltip content={<CustomTooltip />} /> | |
| {/* Area fill */} | |
| <Area | |
| type="monotone" | |
| dataKey="confidence" | |
| stroke="transparent" | |
| fill={`url(#${gradientId})`} | |
| /> | |
| {/* Main line */} | |
| <Line | |
| type="monotone" | |
| dataKey="confidence" | |
| stroke={confidenceLevel.color} | |
| strokeWidth={2.5} | |
| dot={false} | |
| activeDot={{ | |
| r: 6, | |
| fill: confidenceLevel.color, | |
| stroke: '#fff', | |
| strokeWidth: 2, | |
| }} | |
| /> | |
| </LineChart> | |
| </ResponsiveContainer> | |
| </div> | |
| {/* Summary stats */} | |
| {summary && Object.keys(summary).length > 0 && ( | |
| <div className="confidence-graph-stats"> | |
| <div className="stat"> | |
| <span className="stat-label">Eye Contact</span> | |
| <span className="stat-value">{(summary.average_eye_contact || 0).toFixed(0)}%</span> | |
| </div> | |
| <div className="stat"> | |
| <span className="stat-label">High Conf. Time</span> | |
| <span className="stat-value">{(summary.high_confidence_percentage || 0).toFixed(0)}%</span> | |
| </div> | |
| <div className="stat"> | |
| <span className="stat-label">Stability</span> | |
| <span className="stat-value">{((summary.stability || 0) * 100).toFixed(0)}%</span> | |
| </div> | |
| <div className="stat"> | |
| <span className="stat-label">Drops</span> | |
| <span className="stat-value">{summary.drops || 0}</span> | |
| </div> | |
| </div> | |
| )} | |
| {/* Primary issue indicator */} | |
| {summary?.primary_issue && ( | |
| <div className="confidence-issue"> | |
| <span className="issue-icon">⚠️</span> | |
| <span className="issue-text"> | |
| Primary area for improvement: {formatIssue(summary.primary_issue)} | |
| </span> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| /** | |
| * Format issue ID to readable text | |
| */ | |
| function formatIssue(issue) { | |
| const issueMap = { | |
| low_eye_contact: 'Eye contact', | |
| excessive_head_movement: 'Head movement', | |
| poor_face_visibility: 'Face visibility', | |
| looking_away: 'Looking away from camera', | |
| }; | |
| return issueMap[issue] || issue.replace(/_/g, ' '); | |
| } | |