Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 51 additions & 16 deletions demo/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -279,12 +279,13 @@ export default function App() {
{ id: 'privacy', label: 'Privacy Demo', icon: <Lock className="w-4 h-4" /> },
{ id: 'federated', label: 'Federated Learning', icon: <GitBranch className="w-4 h-4" /> },
]
const isDetectionLoading = isCallActive && sentences.length === 0

return (
<div className="min-h-screen bg-dark-bg text-gray-100 font-sans">
{/* Header */}
<header className="border-b border-dark-border/50 bg-dark-card/50 backdrop-blur-xl sticky top-0 z-50">
<div className="max-w-[1440px] mx-auto px-6 py-4 flex items-center justify-between">
<div className="mx-auto flex max-w-[1440px] flex-col gap-4 px-4 py-4 sm:px-6 lg:flex-row lg:items-center lg:justify-between">
<div className="flex items-center gap-3">
<div className="relative">
<Shield className="w-8 h-8 text-brand-teal" />
Expand All @@ -302,14 +303,14 @@ export default function App() {
</div>

{/* Tabs */}
<nav className="flex items-center gap-1">
<nav className="flex flex-wrap items-center gap-2">
{tabs.map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`flex items-center gap-2 px-4 py-2 text-sm font-medium transition-all duration-200 rounded-lg ${
className={`control-button px-3 py-2 text-sm font-medium sm:px-4 ${
activeTab === tab.id
? 'bg-brand-teal/10 text-brand-teal'
? 'border-brand-teal/30 bg-brand-teal/10 text-brand-teal'
: 'text-gray-400 hover:text-gray-200 hover:bg-white/5'
}`}
>
Expand All @@ -320,7 +321,7 @@ export default function App() {
</nav>

{/* Status indicator */}
<div className="flex items-center gap-2 text-xs">
<div className="flex items-center gap-2 text-xs lg:justify-end">
<Activity className={`w-3.5 h-3.5 ${isConnected ? 'text-safe' : 'text-gray-500'}`} />
<span className={isConnected ? 'text-safe' : 'text-gray-500'}>
{isConnected ? 'Backend Connected' : 'Local Playback Mode'}
Expand All @@ -330,7 +331,7 @@ export default function App() {
</header>

{/* Main Content */}
<main className="max-w-[1440px] mx-auto px-6 py-6">
<main className="mx-auto max-w-[1440px] px-4 py-5 sm:px-6 sm:py-6">
{activeTab === 'detection' && (
<div className="space-y-6">
{/* Demo Controls */}
Expand All @@ -343,9 +344,9 @@ export default function App() {
/>

{/* Main detection layout */}
<div className="grid grid-cols-12 gap-6">
<div className="grid grid-cols-1 gap-6 lg:grid-cols-12">
{/* Phone Simulator */}
<div className="col-span-4 flex justify-center">
<div className="flex justify-center lg:col-span-4 lg:justify-start">
<PhoneSimulator>
<CallScreen
callerName={currentCallId ? callerNames[currentCallId] ?? 'Unknown' : 'No Active Call'}
Expand All @@ -368,23 +369,57 @@ export default function App() {
</div>

{/* Right side panels */}
<div className="col-span-8 space-y-6">
<div className="space-y-6 lg:col-span-8">
{/* Score + Features row */}
<div className="grid grid-cols-2 gap-6">
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
<div className="glass-card p-6 glow-teal">
<ScoreGauge score={emaScore} label="Fraud Score (EMA)" />
{isDetectionLoading ? (
<div className="panel-skeleton">
<div className="mx-auto h-48 w-48 rounded-full skeleton-block" />
<div className="mx-auto h-4 w-32 skeleton-block" />
<div className="mx-auto h-3 w-40 skeleton-block" />
</div>
) : (
<ScoreGauge score={emaScore} label="Fraud Score (EMA)" />
)}
</div>
<div className="glass-card p-6">
<FeatureBreakdown features={features} />
{isDetectionLoading ? (
<div className="panel-skeleton">
{[0, 1, 2, 3].map(item => (
<div key={item} className="panel-skeleton-card space-y-2">
<div className="flex items-center justify-between gap-3">
<div className="h-3 w-24 skeleton-block" />
<div className="h-3 w-10 skeleton-block" />
</div>
<div className="h-2 w-full rounded-full skeleton-block" />
</div>
))}
</div>
) : (
<FeatureBreakdown features={features} />
)}
</div>
</div>

{/* Transcript */}
<div className="glass-card p-6">
<TranscriptPanel
sentences={sentences}
isStreaming={isCallActive}
/>
{isDetectionLoading ? (
<div className="panel-skeleton">
{[0, 1, 2].map(item => (
<div key={item} className="panel-skeleton-card space-y-3">
<div className="h-3 w-20 skeleton-block" />
<div className="h-3 w-full skeleton-block" />
<div className="h-3 w-5/6 skeleton-block" />
</div>
))}
</div>
) : (
<TranscriptPanel
sentences={sentences}
isStreaming={isCallActive}
/>
)}
</div>
</div>
</div>
Expand Down
14 changes: 7 additions & 7 deletions demo/frontend/src/components/CallHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export default function CallHistory({ entries }: CallHistoryProps) {
{/* Collapsible header */}
<button
onClick={() => setIsExpanded(!isExpanded)}
className="w-full flex items-center justify-between px-6 py-4 hover:bg-white/[0.02] transition-colors"
className="w-full flex items-center justify-between px-4 py-4 hover:bg-white/[0.02] transition-colors sm:px-6"
>
<div className="flex items-center gap-2.5">
<Clock className="w-4 h-4 text-brand-teal" />
Expand Down Expand Up @@ -144,7 +144,7 @@ export default function CallHistory({ entries }: CallHistoryProps) {
{/* Row */}
<button
onClick={() => setSelectedId(selectedId === entry.id ? null : entry.id)}
className="w-full flex items-center gap-4 px-6 py-3.5 hover:bg-white/[0.02] transition-colors text-left"
className="w-full flex flex-col items-start gap-3 px-4 py-3.5 text-left hover:bg-white/[0.02] transition-colors sm:px-6 lg:flex-row lg:items-center lg:gap-4"
>
{/* Call type icon */}
<div className={`w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 ${getCallIconColor(entry.callType)}`}>
Expand All @@ -162,7 +162,7 @@ export default function CallHistory({ entries }: CallHistoryProps) {
</div>

{/* Duration */}
<div className="flex items-center gap-1.5 text-xs text-gray-400 flex-shrink-0">
<div className="flex items-center gap-1.5 text-xs text-gray-400 lg:flex-shrink-0">
<Clock className="w-3 h-3" />
<span className="font-mono tabular-nums">{formatDuration(entry.duration)}</span>
</div>
Expand All @@ -175,7 +175,7 @@ export default function CallHistory({ entries }: CallHistoryProps) {
</div>

{/* Outcome badge */}
<div className="flex-shrink-0">
<div>
<span className={`text-[10px] font-semibold px-2 py-1 rounded-full ${getOutcomeStyle(entry.outcome).bg} ${getOutcomeStyle(entry.outcome).text}`}>
{entry.outcome}
</span>
Expand All @@ -191,10 +191,10 @@ export default function CallHistory({ entries }: CallHistoryProps) {

{/* Expanded detail */}
{selectedId === entry.id && (
<div className="px-6 pb-4 animate-fade-in">
<div className="ml-12 bg-dark-bg/50 rounded-xl p-4 border border-dark-border/20 space-y-3">
<div className="px-4 pb-4 animate-fade-in sm:px-6">
<div className="rounded-xl border border-dark-border/20 bg-dark-bg/50 p-4 space-y-3 lg:ml-12">
{/* Summary row */}
<div className="grid grid-cols-3 gap-4">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<p className="text-[10px] text-gray-600 uppercase tracking-wider mb-1">Total Sentences</p>
<p className="text-sm font-bold text-gray-200 tabular-nums">{entry.totalSentences}</p>
Expand Down
68 changes: 48 additions & 20 deletions demo/frontend/src/components/CallScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { useState } from 'react'
import { useEffect, useState } from 'react'
import {
Phone,
PhoneOff,
Mic,
MicOff,
Volume2,
Grid3X3,
UserCircle,
UserCircle2,
Wifi,
Battery,
Signal,
Camera,
} from 'lucide-react'
import FraudAlert from './FraudAlert'

Expand Down Expand Up @@ -51,24 +52,43 @@ export default function CallScreen({
}: CallScreenProps) {
const [isMuted, setIsMuted] = useState(false)
const [isSpeaker, setIsSpeaker] = useState(false)
const [isScreenShaking, setIsScreenShaking] = useState(false)

const showAlert = isActive && (alertLevel === 'high' || alertLevel === 'critical' || alertLevel === 'medium')
const callerInitials = callerName
.split(' ')
.filter(Boolean)
.slice(0, 2)
.map(part => part[0]?.toUpperCase())
.join('')

useEffect(() => {
if (!showAlert) return

setIsScreenShaking(true)
const timeout = window.setTimeout(() => {
setIsScreenShaking(false)
}, 480)

return () => window.clearTimeout(timeout)
}, [showAlert, alertLevel])

return (
<div className="w-full h-full flex flex-col bg-gradient-to-b from-dark-bg via-dark-card to-dark-bg">
<div className={`w-full h-full flex flex-col bg-gradient-to-b from-dark-bg via-dark-card to-dark-bg ${isScreenShaking ? 'phone-screen-shake' : ''}`}>
{/* Status bar */}
<div className="flex items-center justify-between px-6 pt-10 pb-1 text-[10px] text-gray-400">
<span className="font-medium">{getCurrentTime()}</span>
<div className="flex items-center gap-1.5">
<Signal className="w-3 h-3" />
<Wifi className="w-3 h-3" />
<Battery className="w-3 h-3" />
<div className="relative z-30 flex items-center justify-between px-7 pt-4 pb-2 text-[11px] text-white/85">
<span className="min-w-[52px] font-semibold tracking-[0.01em]">{getCurrentTime()}</span>
<div className="w-[126px]" />
<div className="flex min-w-[56px] items-center justify-end gap-1.5">
<Signal className="h-3.5 w-3.5 stroke-[2.2]" />
<Wifi className="h-3.5 w-3.5 stroke-[2.2]" />
<Battery className="h-3.5 w-3.5 stroke-[2.2]" />
</div>
</div>

{/* Fraud Alert Overlay */}
{showAlert && (
<div className="absolute inset-0 z-20 pt-10">
<div className="absolute inset-0 z-20 pt-[42px]">
<FraudAlert
riskLevel={alertLevel as 'medium' | 'high' | 'critical'}
score={fraudScore}
Expand All @@ -85,17 +105,25 @@ export default function CallScreen({
<>
{/* Caller avatar */}
<div className="relative mb-4">
<div className={`w-20 h-20 rounded-full flex items-center justify-center ${
alertLevel === 'critical' ? 'bg-alert/20 ring-2 ring-alert animate-pulse-alert' :
alertLevel === 'high' ? 'bg-warning/20 ring-2 ring-warning' :
'bg-brand-teal/20 ring-2 ring-brand-teal/50'
<div className={`relative w-[92px] h-[92px] rounded-full flex items-center justify-center overflow-hidden shadow-[0_20px_45px_rgba(15,23,42,0.38)] ${
alertLevel === 'critical' ? 'ring-2 ring-alert/80 animate-pulse-alert' :
alertLevel === 'high' ? 'ring-2 ring-warning/80' :
'ring-2 ring-brand-teal/50'
}`}>
<UserCircle className={`w-12 h-12 ${
alertLevel === 'critical' ? 'text-alert' :
alertLevel === 'high' ? 'text-warning' :
'text-brand-teal'
}`} />
<div className="absolute inset-0 bg-[radial-gradient(circle_at_30%_28%,rgba(255,255,255,0.28),transparent_28%),linear-gradient(160deg,#8892a6_0%,#646f84_45%,#3b4456_100%)]" />
<div className="absolute inset-x-5 bottom-0 h-[44px] rounded-t-[28px] bg-white/18 blur-[1px]" />
<div className="absolute left-1/2 top-[18px] h-[26px] w-[26px] -translate-x-1/2 rounded-full bg-white/28" />
<div className="absolute left-1/2 top-[38px] h-[34px] w-[54px] -translate-x-1/2 rounded-t-[26px] bg-white/22" />
<div className="absolute inset-0 bg-gradient-to-b from-white/5 via-transparent to-black/25" />
<div className="absolute bottom-2 left-1/2 -translate-x-1/2 rounded-full bg-black/28 px-2 py-0.5 backdrop-blur-sm">
<span className="text-[10px] font-semibold tracking-[0.2em] text-white/88">{callerInitials || 'CP'}</span>
</div>
</div>

<div className="absolute -top-1 -right-1 rounded-full border border-white/10 bg-black/45 p-1.5 shadow-lg backdrop-blur-md">
<Camera className="h-3.5 w-3.5 text-white/75" />
</div>

{/* SentinelEdge protection badge */}
<div className="absolute -bottom-1 -right-1 w-6 h-6 rounded-full bg-dark-card border-2 border-brand-teal flex items-center justify-center">
<div className="w-2 h-2 rounded-full bg-brand-teal" />
Expand Down Expand Up @@ -180,7 +208,7 @@ export default function CallScreen({

<button className="flex flex-col items-center gap-1">
<div className="w-11 h-11 rounded-full bg-dark-surface/80 flex items-center justify-center text-white">
<UserCircle className="w-5 h-5" />
<UserCircle2 className="w-5 h-5" />
</div>
<span className="text-[9px] text-gray-400">contacts</span>
</button>
Expand Down
65 changes: 31 additions & 34 deletions demo/frontend/src/components/DemoControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,56 +35,53 @@ export default function DemoControls({
availableCalls,
}: DemoControlsProps) {
return (
<div className="glass-card p-4">
<div className="flex items-center justify-between">
<div className="glass-card p-4 sm:p-5">
<div className="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
{/* Left: sample calls */}
<div className="flex items-center gap-3">
<div className="flex flex-col gap-3 lg:flex-row lg:items-center">
<div className="flex items-center gap-2 mr-2">
<Phone className="w-4 h-4 text-brand-teal" />
<span className="text-xs font-semibold text-gray-400 uppercase tracking-wider">
Sample Calls
</span>
</div>
{availableCalls.map((call) => {
const config = callIcons[call.id] || {
icon: <Play className="w-3.5 h-3.5" />,
color: 'hover:bg-brand-teal/10 hover:text-brand-teal hover:border-brand-teal/30',
}
return (
<button
key={call.id}
onClick={() => onSelectCall(call.id)}
disabled={isCallActive}
className={`
flex items-center gap-2 px-3 py-2 rounded-lg text-xs font-medium
border border-dark-border/50 transition-all duration-200
${isCallActive
? 'opacity-40 cursor-not-allowed bg-dark-card text-gray-600'
: `bg-dark-card text-gray-300 ${config.color} active:scale-[0.97]`
}
`}
>
<Play className="w-3 h-3" />
{call.description}
</button>
)
})}
<div className="flex flex-wrap gap-2">
{availableCalls.map((call) => {
const config = callIcons[call.id] || {
icon: <Play className="w-3.5 h-3.5" />,
color: 'hover:bg-brand-teal/10 hover:text-brand-teal hover:border-brand-teal/30',
}
return (
<button
key={call.id}
onClick={() => onSelectCall(call.id)}
disabled={isCallActive}
className={`
control-button px-3 py-2 text-xs font-medium
${isCallActive ? '' : config.color}
`}
>
<Play className="w-3 h-3" />
{call.description}
</button>
)
})}
</div>
</div>

{/* Right: mic toggle */}
<div className="flex items-center gap-3">
<div className="h-6 w-px bg-dark-border/30" />
<div className="flex flex-wrap items-center gap-3 xl:justify-end">
<div className="hidden h-6 w-px bg-dark-border/30 xl:block" />
<button
onClick={onToggleMic}
disabled={isCallActive}
className={`
flex items-center gap-2 px-4 py-2 rounded-lg text-xs font-medium
transition-all duration-200 border
control-button px-4 py-2 text-xs font-medium
${isMicActive
? 'bg-brand-teal/10 text-brand-teal border-brand-teal/30'
: isCallActive
? 'opacity-40 cursor-not-allowed bg-dark-card text-gray-600 border-dark-border/50'
: 'bg-dark-card text-gray-300 border-dark-border/50 hover:bg-brand-teal/10 hover:text-brand-teal hover:border-brand-teal/30'
: !isCallActive
? 'hover:bg-brand-teal/10 hover:text-brand-teal hover:border-brand-teal/30'
: ''
}
`}
>
Expand Down
Loading
Loading