emit-ai / frontend /src /components /InputPanel.jsx
aasthav18's picture
Add React frontend + Render deploy config
05b4ab1
Raw
History Blame Contribute Delete
4.74 kB
// src/components/InputPanel.jsx
import React, { useRef, useState } from 'react'
export default function InputPanel({ text, onTextChange, onImageChange, onAnalyze, loading, imagePreview }) {
const fileRef = useRef(null)
const [dragOver, setDragOver] = useState(false)
function handleDrop(e) {
e.preventDefault()
setDragOver(false)
const file = e.dataTransfer.files[0]
if (file && file.type.startsWith('image/')) onImageChange(file)
}
function handleFile(e) {
const file = e.target.files[0]
if (file) onImageChange(file)
}
return (
<div className="card overflow-hidden">
<div className="panel-header">
<span className="w-[7px] h-[7px] rounded-full bg-[#cfe2f7] flex-shrink-0" />
<span>Input Stream</span>
<span className="ml-auto font-mono text-[9px] text-muted">Vision + NLP</span>
</div>
<div className="p-4 flex flex-col gap-4">
{/* Image drop zone */}
<div
className={`relative border rounded-lg transition-all duration-200 cursor-pointer overflow-hidden
${dragOver
? 'border-teal bg-[#0ec9a815]'
: 'border-[#1a3a6e50] hover:border-[#1a3a6e] bg-[#081527]'}`}
style={{ minHeight: '120px' }}
onClick={() => fileRef.current?.click()}
onDragOver={e => { e.preventDefault(); setDragOver(true) }}
onDragLeave={() => setDragOver(false)}
onDrop={handleDrop}
>
<input
ref={fileRef}
type="file"
accept="image/*"
className="hidden"
onChange={handleFile}
/>
{imagePreview ? (
<img
src={imagePreview}
alt="Face preview"
className="w-full h-full object-cover"
style={{ maxHeight: '200px' }}
/>
) : (
<div className="flex flex-col items-center justify-center gap-2 py-8 text-center">
<div className="font-mono text-3xl opacity-30"></div>
<p className="font-mono text-[11px] text-muted">Drop face image or click to upload</p>
<p className="font-mono text-[9px] text-[#3a5a7a]">JPEG · PNG · WEBP</p>
</div>
)}
{/* scanline overlay while no image */}
{!imagePreview && (
<div className="absolute inset-0 pointer-events-none overflow-hidden">
<div className="scanline-animate absolute left-0 right-0 h-px bg-teal opacity-20" />
</div>
)}
</div>
{/* Text area */}
<div className="flex flex-col gap-1">
<label className="font-mono text-[10px] text-muted tracking-[0.1em] uppercase">
Transcript / Message
</label>
<textarea
value={text}
onChange={e => onTextChange(e.target.value)}
placeholder="Enter customer speech or chat message…"
rows={4}
className="
w-full bg-[#081527] border border-[#1a3a6e40] rounded-lg p-3
font-mono text-[12px] text-[#cfe2f7] leading-[1.8] resize-none
placeholder:text-[#2a4a6a] outline-none
focus:border-teal focus:bg-[#08182e]
transition-all duration-200
"
/>
</div>
{/* Analyze button */}
<button
onClick={onAnalyze}
disabled={loading || (!text.trim())}
className="
relative overflow-hidden w-full py-3 rounded-md
font-display font-semibold text-sm
transition-all duration-200
disabled:opacity-40 disabled:cursor-not-allowed
enabled:cursor-pointer
"
style={{
background: loading
? 'linear-gradient(135deg, #0a6b5b, #0a4b7a)'
: 'linear-gradient(135deg, #0ec9a8, #0a8aff)',
color: '#050d1b',
}}
>
{loading ? (
<span className="flex items-center justify-center gap-2">
<span className="inline-block w-3 h-3 border-2 border-[#050d1b] border-t-transparent rounded-full animate-spin" />
Analyzing…
</span>
) : (
'▶ Analyze Emotion'
)}
{/* shimmer on idle */}
{!loading && (
<div className="absolute inset-0 bg-white opacity-0 hover:opacity-10 transition-opacity duration-200" />
)}
</button>
{/* API status hint */}
<p className="font-mono text-[9px] text-[#2a4a6a] text-center">
Demo mode active — connects to FastAPI on :8000 when available
</p>
</div>
</div>
)
}