IDKHowToCodeFr/tinyml-backend
1
1"use client";2 3import { motion } from "framer-motion";4import Link from "next/link";5import { useState } from "react";6 7const easeFluid: [number, number, number, number] = [0.16, 1, 0.3, 1];8 9const staggerContainer = {10 hidden: { opacity: 0 },11 show: {12 opacity: 1,13 transition: { staggerChildren: 0.1, delayChildren: 0.1 }14 }15};16 17const fadeUp = {18 hidden: { opacity: 0, y: 20 },19 show: { opacity: 1, y: 0, transition: { duration: 0.8, ease: easeFluid } }20};21 22export default function MLOps() {23 const [file, setFile] = useState<File | null>(null);24 const [status, setStatus] = useState<"idle" | "uploading" | "success" | "error">("idle");25 const [message, setMessage] = useState("");26 27 const handleUpload = async () => {28 if (!file) return;29 setStatus("uploading");30 31 const formData = new FormData();32 formData.append("file", file);33 34 try {35 const res = await fetch((process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000") + "/retrain", {36 method: "POST",37 body: formData,38 });39 const data = await res.json();40 41 if (data.error) {42 setStatus("error");43 setMessage(data.error);44 } else {45 setStatus("success");46 setMessage(data.message);47 }48 } catch (err: any) {49 setStatus("error");50 setMessage(err.message || "Upload failed");51 }52 };53 54 return (55 <main className="min-h-dvh bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white">56 {/* Top Nav (Mechanical) */}57 58 <motion.div59 variants={staggerContainer}60 initial="hidden"61 animate="show"62 className="max-w-250 mx-auto w-full px-8 py-16 flex-1 flex flex-col"63 >64 <motion.header variants={fadeUp} className="mb-12 border-b-4 border-ink pb-8 flex justify-between items-end">65 <div>66 <div className="text-[12px] text-hazard font-bold tracking-widest mb-4">67 /// PIPELINE_CONTROL68 </div>69 <h1 className="text-[clamp(3rem,6vw,6rem)]">70 MODEL<br />OPERATIONS71 </h1>72 </div>73 </motion.header>74 75 <motion.div variants={fadeUp} className="border-2 border-ink bg-white p-8">76 <div className="border-b-2 border-ink pb-4 mb-8">77 <h2 className="text-[16px] font-bold tracking-wider uppercase text-ink">{'< BATCH_RETRAIN >'}</h2>78 <p className="text-[13px] text-slate uppercase mt-2">Append telemetry data and rebuild the edge ensemble.</p>79 </div>80 81 <div className="flex flex-col gap-6">82 <label83 htmlFor="dropzone-file"84 className="flex flex-col items-center justify-center w-full h-48 border-2 border-dashed border-ink bg-canvas-cream hover:bg-hazard hover:text-white transition-colors cursor-pointer group relative overflow-hidden"85 >86 <div className="absolute inset-0 pointer-events-none opacity-20 bg-[linear-gradient(45deg,var(--ink-black)_25%,transparent_25%,transparent_50%,var(--ink-black)_50%,var(--ink-black)_75%,transparent_75%,transparent)] bg-size-[20px_20px]" />87 <div className="flex flex-col items-center justify-center pt-5 pb-6 relative z-10">88 <motion.div89 initial={{ scale: 0.9, opacity: 0 }}90 animate={{ scale: 1, opacity: 1 }}91 key={file ? 'selected' : 'unselected'}92 className="text-[24px] font-bold mb-4 group-hover:text-white"93 >94 {file ? '[ FILE_SELECTED ]' : '[ SELECT_CSV ]'}95 </motion.div>96 <p className="mb-2 text-[14px] font-bold uppercase">97 {file ? file.name : "CLICK TO UPLOAD DATASET"}98 </p>99 <p className="text-[12px] uppercase opacity-70">REQUIRES MATCHING SCHEMA</p>100 </div>101 <input102 id="dropzone-file"103 type="file"104 className="sr-only"105 accept=".csv"106 onChange={(e) => setFile(e.target.files?.[0] || null)}107 aria-label="Upload CSV Dataset"108 />109 </label>110 111 <div className="flex justify-between items-center border-t-2 border-ink pt-6 mt-2" aria-live="polite">112 <motion.div113 key={status}114 initial={{ opacity: 0, x: -10 }}115 animate={{ opacity: 1, x: 0 }}116 className="text-[13px] font-bold uppercase tracking-wider"117 >118 {status === "uploading" && <span className="text-slate">PROCESSING...</span>}119 {status === "error" && <span className="text-hazard">ERR: {message}</span>}120 {status === "success" && <span className="text-ink bg-canvas-cream px-2 py-1 border border-ink">SYS: {message}</span>}121 </motion.div>122 123 <motion.button124 whileHover={{ scale: 1.02, backgroundColor: "var(--color-hazard)", borderColor: "var(--color-hazard)" }}125 whileTap={{ scale: 0.98 }}126 onClick={handleUpload}127 disabled={!file || status === "uploading"}128 className="inline-flex items-center justify-center h-12 px-8 bg-ink text-white text-[14px] font-bold uppercase tracking-widest border-2 border-ink disabled:opacity-50 disabled:pointer-events-none"129 >130 EXECUTE >>131 </motion.button>132 </div>133 </div>134 </motion.div>135 </motion.div>136 </main>137 );138}139 