arcisvlm / dashboard /components /AddCameraModal.tsx
Hardik Sanghvi
feat: integrate Gemma 4 E2B backbone for production-quality VLM inference
7a564e3
Raw
History Blame Contribute Delete
3.53 kB
"use client";
import { useState } from "react";
import { startStream } from "../lib/api";
interface AddCameraModalProps {
open: boolean;
onClose: () => void;
onAdded: () => void;
}
export default function AddCameraModal({ open, onClose, onAdded }: AddCameraModalProps) {
const [cameraId, setCameraId] = useState("");
const [rtspUrl, setRtspUrl] = useState("");
const [fps, setFps] = useState("2.0");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
if (!open) return null;
async function handleSubmit() {
if (!cameraId.trim() || !rtspUrl.trim()) {
setError("Camera ID and RTSP URL are required");
return;
}
setLoading(true);
setError(null);
try {
await startStream(cameraId.trim(), rtspUrl.trim(), parseFloat(fps));
setCameraId("");
setRtspUrl("");
onAdded();
onClose();
} catch (e) {
setError(e instanceof Error ? e.message : "Failed to start stream");
} finally {
setLoading(false);
}
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="absolute inset-0 bg-black/60" onClick={onClose} />
<div className="relative bg-[var(--surface)] border border-[var(--border)] rounded-2xl p-6 w-full max-w-md shadow-2xl">
<h2 className="text-lg font-semibold mb-4">Add Camera</h2>
<div className="space-y-3">
<div>
<label className="text-xs text-[var(--muted)] mb-1 block">Camera ID</label>
<input
value={cameraId}
onChange={(e) => setCameraId(e.target.value)}
placeholder="e.g. lobby-cam-01"
className="w-full bg-[var(--background)] border border-[var(--border)] rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)]/50"
/>
</div>
<div>
<label className="text-xs text-[var(--muted)] mb-1 block">RTSP URL</label>
<input
value={rtspUrl}
onChange={(e) => setRtspUrl(e.target.value)}
placeholder="rtsp://user:pass@192.168.1.100:554/stream"
className="w-full bg-[var(--background)] border border-[var(--border)] rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)]/50"
/>
</div>
<div>
<label className="text-xs text-[var(--muted)] mb-1 block">Target FPS</label>
<input
type="number"
value={fps}
onChange={(e) => setFps(e.target.value)}
min="0.1"
max="30"
step="0.5"
className="w-full bg-[var(--background)] border border-[var(--border)] rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-[var(--accent)]/50"
/>
</div>
</div>
{error && <p className="text-xs text-[var(--danger)] mt-3">{error}</p>}
<div className="flex gap-2 mt-5 justify-end">
<button onClick={onClose} className="px-4 py-2 text-sm text-[var(--muted)] hover:text-[var(--foreground)]">Cancel</button>
<button
onClick={handleSubmit}
disabled={loading}
className="px-4 py-2 bg-[var(--accent)] hover:bg-[var(--accent-hover)] text-white rounded-lg text-sm font-medium disabled:opacity-40"
>
{loading ? "Connecting..." : "Add Camera"}
</button>
</div>
</div>
</div>
);
}