Initial commit: CureSelect AI Voice Onboarding Web Frontend
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
import React from "react";
|
||||
import { Mic, Volume2, Sparkles, CheckCircle2, AlertCircle } from "lucide-react";
|
||||
import { VoiceState } from "../types/voice";
|
||||
|
||||
interface VoiceOrbProps {
|
||||
state: VoiceState;
|
||||
audioLevel: number;
|
||||
isSpeechDetected?: boolean;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export const VoiceOrb: React.FC<VoiceOrbProps> = ({
|
||||
state,
|
||||
audioLevel,
|
||||
onClick,
|
||||
}) => {
|
||||
const getOrbVisuals = () => {
|
||||
switch (state) {
|
||||
case "listening":
|
||||
return {
|
||||
gradient: "radial-gradient(circle, #f43f5e 0%, #be123c 100%)",
|
||||
glow: "rgba(244, 63, 94, 0.6)",
|
||||
icon: <Mic size={42} color="#ffffff" className="animate-pulse" />,
|
||||
statusText: "Listening to you...",
|
||||
subText: "Speak naturally, AI will respond automatically",
|
||||
color: "#f43f5e",
|
||||
};
|
||||
case "ai_speaking":
|
||||
return {
|
||||
gradient: "radial-gradient(circle, #06b6d4 0%, #2563eb 100%)",
|
||||
glow: "rgba(6, 182, 212, 0.7)",
|
||||
icon: <Volume2 size={42} color="#ffffff" />,
|
||||
statusText: "Assistant Speaking...",
|
||||
subText: "Mic activates automatically when speech ends",
|
||||
color: "#06b6d4",
|
||||
};
|
||||
case "processing":
|
||||
return {
|
||||
gradient: "radial-gradient(circle, #8b5cf6 0%, #6d28d9 100%)",
|
||||
glow: "rgba(139, 92, 246, 0.7)",
|
||||
icon: <Sparkles size={42} color="#ffffff" className="animate-spin-slow" />,
|
||||
statusText: "Thinking...",
|
||||
subText: "Analyzing symptoms & clinical details",
|
||||
color: "#8b5cf6",
|
||||
};
|
||||
case "confirmation":
|
||||
return {
|
||||
gradient: "radial-gradient(circle, #10b981 0%, #047857 100%)",
|
||||
glow: "rgba(16, 185, 129, 0.6)",
|
||||
icon: <CheckCircle2 size={42} color="#ffffff" />,
|
||||
statusText: "Intake Complete",
|
||||
subText: "Review your summary below to confirm",
|
||||
color: "#10b981",
|
||||
};
|
||||
case "completed":
|
||||
return {
|
||||
gradient: "radial-gradient(circle, #10b981 0%, #065f46 100%)",
|
||||
glow: "rgba(16, 185, 129, 0.5)",
|
||||
icon: <CheckCircle2 size={44} color="#ffffff" />,
|
||||
statusText: "Saved to Hospital Record",
|
||||
subText: "Transferred to OPD Consultation Queue",
|
||||
color: "#10b981",
|
||||
};
|
||||
case "error":
|
||||
return {
|
||||
gradient: "radial-gradient(circle, #ef4444 0%, #991b1b 100%)",
|
||||
glow: "rgba(239, 68, 68, 0.6)",
|
||||
icon: <AlertCircle size={42} color="#ffffff" />,
|
||||
statusText: "Tap to Reconnect",
|
||||
subText: "Reconnecting voice intake channel",
|
||||
color: "#ef4444",
|
||||
};
|
||||
default:
|
||||
return {
|
||||
gradient: "radial-gradient(circle, #06b6d4 0%, #3b82f6 100%)",
|
||||
glow: "rgba(6, 182, 212, 0.4)",
|
||||
icon: <Mic size={42} color="#ffffff" />,
|
||||
statusText: "Hands-Free Voice Active",
|
||||
subText: "Speak naturally to start conversation",
|
||||
color: "#06b6d4",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const visuals = getOrbVisuals();
|
||||
const scaleByAudio = state === "listening"
|
||||
? 1 + Math.min(0.22, (audioLevel / 220))
|
||||
: state === "ai_speaking"
|
||||
? 1.05
|
||||
: 1;
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "24px 0 16px 0",
|
||||
position: "relative",
|
||||
}}>
|
||||
{/* Orb Outer Container */}
|
||||
<div style={{ position: "relative", width: "170px", height: "170px", margin: "20px 0" }}>
|
||||
{/* Multi-layered dynamic aura glow */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: "-24px",
|
||||
borderRadius: "50%",
|
||||
background: visuals.glow,
|
||||
filter: "blur(36px)",
|
||||
opacity: state === "listening" || state === "ai_speaking" ? 0.95 : 0.4,
|
||||
transition: "all 0.3s ease",
|
||||
transform: `scale(${scaleByAudio})`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Dynamic Sound Ripple Aura */}
|
||||
{(state === "listening" || state === "ai_speaking") && (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: "-16px",
|
||||
borderRadius: "50%",
|
||||
border: `2px solid ${visuals.color}`,
|
||||
opacity: 0.6,
|
||||
animation: "pulse-slow 2s infinite ease-out",
|
||||
transform: `scale(${scaleByAudio})`,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
inset: "-32px",
|
||||
borderRadius: "50%",
|
||||
border: `1px solid ${visuals.color}`,
|
||||
opacity: 0.3,
|
||||
animation: "pulse-slow 2s 0.6s infinite ease-out",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Clean, Modern Floating Voice Sphere */}
|
||||
<button
|
||||
onClick={onClick}
|
||||
aria-label={visuals.statusText}
|
||||
style={{
|
||||
position: "relative",
|
||||
width: "170px",
|
||||
height: "170px",
|
||||
borderRadius: "50%",
|
||||
background: visuals.gradient,
|
||||
border: "2px solid rgba(255, 255, 255, 0.35)",
|
||||
boxShadow: `0 0 50px ${visuals.glow}, inset 0 0 30px rgba(255, 255, 255, 0.4)`,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
cursor: "pointer",
|
||||
outline: "none",
|
||||
transition: "transform 0.15s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease",
|
||||
transform: `scale(${scaleByAudio})`,
|
||||
}}
|
||||
>
|
||||
{visuals.icon}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* State Text & Feedback */}
|
||||
<div style={{ textAlign: "center", marginTop: "12px" }}>
|
||||
<h2 style={{
|
||||
fontSize: "22px",
|
||||
fontWeight: "700",
|
||||
color: visuals.color,
|
||||
marginBottom: "4px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: "8px",
|
||||
letterSpacing: "-0.01em",
|
||||
}}>
|
||||
{visuals.statusText}
|
||||
</h2>
|
||||
<p style={{
|
||||
fontSize: "13px",
|
||||
color: "var(--text-secondary)",
|
||||
maxWidth: "380px",
|
||||
lineHeight: "1.4",
|
||||
margin: "0 auto",
|
||||
}}>
|
||||
{visuals.subText}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user