Spaces:
Sleeping
Sleeping
Antigravity commited on
Commit ·
ef886da
1
Parent(s): 79fb5cc
Redesign UI with premium interactive glassmorphic dark theme
Browse files- frontend/src/components/Layout.jsx +37 -19
- frontend/src/components/Sidebar.jsx +45 -22
- frontend/src/components/UI.jsx +92 -23
- frontend/src/index.css +211 -34
- frontend/src/pages/AssociationRules.jsx +32 -30
- frontend/src/pages/CognitiveQA.jsx +48 -32
- frontend/src/pages/Dashboard.jsx +72 -24
- frontend/src/pages/DataClusters.jsx +16 -24
- frontend/src/pages/DbscanLab.jsx +16 -18
- frontend/src/pages/EmpathyEngine.jsx +64 -45
- frontend/src/pages/GenderDiscovery.jsx +36 -19
- frontend/src/pages/NeuralTranslate.jsx +19 -12
- frontend/src/pages/TextSynthesis.jsx +15 -12
- frontend/src/pages/ZeroShotLab.jsx +46 -20
frontend/src/components/Layout.jsx
CHANGED
|
@@ -1,41 +1,59 @@
|
|
| 1 |
import { useState } from 'react';
|
|
|
|
| 2 |
import Sidebar from './Sidebar';
|
| 3 |
-
import { Menu } from 'lucide-react';
|
| 4 |
-
import { motion } from 'framer-motion';
|
| 5 |
|
| 6 |
export default function Layout({ children }) {
|
| 7 |
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
|
|
| 8 |
|
| 9 |
return (
|
| 10 |
-
<div className="flex min-h-screen">
|
| 11 |
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
| 12 |
|
| 13 |
-
<main className="flex-1 min-h-screen">
|
| 14 |
{/* Top bar */}
|
| 15 |
-
<header className="flex items-center gap-4 px-4 sm:px-8 py-4 lg:py-
|
| 16 |
<button
|
| 17 |
onClick={() => setSidebarOpen(true)}
|
| 18 |
-
className="lg:hidden p-2 hover:bg-white/5 rounded-xl transition-
|
| 19 |
>
|
| 20 |
-
<Menu size={
|
| 21 |
</button>
|
|
|
|
| 22 |
<div className="flex-1" />
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
</div>
|
| 27 |
</header>
|
| 28 |
|
| 29 |
{/* Content */}
|
| 30 |
-
<
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
</main>
|
| 40 |
</div>
|
| 41 |
);
|
|
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
+
import { useLocation } from 'react-router-dom';
|
| 3 |
import Sidebar from './Sidebar';
|
| 4 |
+
import { Menu, Zap } from 'lucide-react';
|
| 5 |
+
import { motion, AnimatePresence } from 'framer-motion';
|
| 6 |
|
| 7 |
export default function Layout({ children }) {
|
| 8 |
const [sidebarOpen, setSidebarOpen] = useState(false);
|
| 9 |
+
const location = useLocation();
|
| 10 |
|
| 11 |
return (
|
| 12 |
+
<div className="relative flex min-h-screen z-10">
|
| 13 |
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
| 14 |
|
| 15 |
+
<main className="flex-1 min-h-screen flex flex-col">
|
| 16 |
{/* Top bar */}
|
| 17 |
+
<header className="flex items-center gap-4 px-4 sm:px-8 py-4 lg:py-5">
|
| 18 |
<button
|
| 19 |
onClick={() => setSidebarOpen(true)}
|
| 20 |
+
className="lg:hidden p-2.5 hover:bg-white/5 rounded-xl transition-all border border-white/[0.06] active:scale-95"
|
| 21 |
>
|
| 22 |
+
<Menu size={20} />
|
| 23 |
</button>
|
| 24 |
+
|
| 25 |
<div className="flex-1" />
|
| 26 |
+
|
| 27 |
+
{/* Status badge */}
|
| 28 |
+
<div className="flex items-center gap-3">
|
| 29 |
+
<div className="hidden sm:flex items-center gap-2 px-3 py-1.5 rounded-xl bg-white/[0.03] border border-white/[0.06] text-xs text-slate-400">
|
| 30 |
+
<Zap size={12} className="text-amber-400" />
|
| 31 |
+
<span>Models Loaded</span>
|
| 32 |
+
</div>
|
| 33 |
+
<div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-emerald-500/10 border border-emerald-500/20">
|
| 34 |
+
<span className="relative flex h-2 w-2">
|
| 35 |
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" />
|
| 36 |
+
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-500" />
|
| 37 |
+
</span>
|
| 38 |
+
<span className="text-xs font-semibold text-emerald-400">Online</span>
|
| 39 |
+
</div>
|
| 40 |
</div>
|
| 41 |
</header>
|
| 42 |
|
| 43 |
{/* Content */}
|
| 44 |
+
<div className="flex-1 px-4 sm:px-8 pb-8">
|
| 45 |
+
<AnimatePresence mode="wait">
|
| 46 |
+
<motion.div
|
| 47 |
+
key={location.pathname}
|
| 48 |
+
initial={{ opacity: 0, y: 16, filter: 'blur(4px)' }}
|
| 49 |
+
animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }}
|
| 50 |
+
exit={{ opacity: 0, y: -8, filter: 'blur(4px)' }}
|
| 51 |
+
transition={{ duration: 0.35, ease: [0.4, 0, 0.2, 1] }}
|
| 52 |
+
>
|
| 53 |
+
{children}
|
| 54 |
+
</motion.div>
|
| 55 |
+
</AnimatePresence>
|
| 56 |
+
</div>
|
| 57 |
</main>
|
| 58 |
</div>
|
| 59 |
);
|
frontend/src/components/Sidebar.jsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
-
import { NavLink
|
| 3 |
import { motion, AnimatePresence } from 'framer-motion';
|
| 4 |
import {
|
| 5 |
LayoutDashboard, User, PenTool, Languages, Smile, Brain,
|
| 6 |
-
Target, PieChart, Braces, ShoppingCart,
|
| 7 |
} from 'lucide-react';
|
| 8 |
|
| 9 |
const navItems = [
|
|
@@ -29,7 +29,7 @@ export default function Sidebar({ isOpen, onClose }) {
|
|
| 29 |
initial={{ opacity: 0 }}
|
| 30 |
animate={{ opacity: 1 }}
|
| 31 |
exit={{ opacity: 0 }}
|
| 32 |
-
className="fixed inset-0 bg-black/
|
| 33 |
onClick={onClose}
|
| 34 |
/>
|
| 35 |
)}
|
|
@@ -38,49 +38,72 @@ export default function Sidebar({ isOpen, onClose }) {
|
|
| 38 |
{/* Sidebar */}
|
| 39 |
<aside className={`
|
| 40 |
fixed top-0 left-0 h-screen w-[280px] z-50
|
| 41 |
-
bg-[#
|
| 42 |
-
|
|
|
|
| 43 |
transition-transform duration-300 ease-[cubic-bezier(0.4,0,0.2,1)]
|
| 44 |
lg:translate-x-0 lg:static
|
| 45 |
-
${isOpen ? 'translate-x-0 shadow-
|
| 46 |
`}>
|
| 47 |
{/* Brand */}
|
| 48 |
-
<div className="flex items-center gap-3 px-2">
|
| 49 |
-
<div className="
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
| 51 |
</div>
|
| 52 |
-
<
|
| 53 |
-
|
| 54 |
-
<
|
|
|
|
|
|
|
|
|
|
| 55 |
</button>
|
| 56 |
</div>
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
{/* Nav */}
|
| 59 |
-
<nav className="flex flex-col gap-
|
| 60 |
{navItems.map(({ path, label, icon: Icon }) => (
|
| 61 |
<NavLink
|
| 62 |
key={path}
|
| 63 |
to={path}
|
| 64 |
onClick={onClose}
|
| 65 |
className={({ isActive }) => `
|
| 66 |
-
flex items-center gap-3 px-4 py-
|
| 67 |
-
transition-all duration-200
|
| 68 |
${isActive
|
| 69 |
-
? 'bg-cyan-500/10 text-cyan-400'
|
| 70 |
-
: 'text-slate-400 hover:bg-white/
|
| 71 |
}
|
| 72 |
`}
|
| 73 |
>
|
| 74 |
-
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
</NavLink>
|
| 77 |
))}
|
| 78 |
</nav>
|
| 79 |
|
| 80 |
{/* Footer */}
|
| 81 |
-
<div className="
|
| 82 |
-
<
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
| 84 |
</div>
|
| 85 |
</aside>
|
| 86 |
</>
|
|
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
+
import { NavLink } from 'react-router-dom';
|
| 3 |
import { motion, AnimatePresence } from 'framer-motion';
|
| 4 |
import {
|
| 5 |
LayoutDashboard, User, PenTool, Languages, Smile, Brain,
|
| 6 |
+
Target, PieChart, Braces, ShoppingCart, X, Sparkles
|
| 7 |
} from 'lucide-react';
|
| 8 |
|
| 9 |
const navItems = [
|
|
|
|
| 29 |
initial={{ opacity: 0 }}
|
| 30 |
animate={{ opacity: 1 }}
|
| 31 |
exit={{ opacity: 0 }}
|
| 32 |
+
className="fixed inset-0 bg-black/70 backdrop-blur-md z-40 lg:hidden"
|
| 33 |
onClick={onClose}
|
| 34 |
/>
|
| 35 |
)}
|
|
|
|
| 38 |
{/* Sidebar */}
|
| 39 |
<aside className={`
|
| 40 |
fixed top-0 left-0 h-screen w-[280px] z-50
|
| 41 |
+
bg-[#060a15]/95 backdrop-blur-xl
|
| 42 |
+
border-r border-white/[0.06]
|
| 43 |
+
flex flex-col py-6 px-4 gap-4
|
| 44 |
transition-transform duration-300 ease-[cubic-bezier(0.4,0,0.2,1)]
|
| 45 |
lg:translate-x-0 lg:static
|
| 46 |
+
${isOpen ? 'translate-x-0 shadow-[4px_0_40px_rgba(0,0,0,0.5)]' : '-translate-x-full'}
|
| 47 |
`}>
|
| 48 |
{/* Brand */}
|
| 49 |
+
<div className="flex items-center gap-3 px-3 mb-2">
|
| 50 |
+
<div className="relative">
|
| 51 |
+
<div className="w-11 h-11 rounded-2xl bg-gradient-to-br from-cyan-500 via-blue-500 to-purple-600 grid place-items-center font-black text-white text-sm shadow-lg shadow-cyan-500/20">
|
| 52 |
+
AI
|
| 53 |
+
</div>
|
| 54 |
+
<div className="absolute -top-0.5 -right-0.5 w-3 h-3 bg-emerald-400 rounded-full border-2 border-[#060a15] animate-pulse" />
|
| 55 |
</div>
|
| 56 |
+
<div className="flex flex-col">
|
| 57 |
+
<span className="text-base font-bold tracking-tight">Quantum Hub</span>
|
| 58 |
+
<span className="text-[10px] text-cyan-500/80 font-semibold uppercase tracking-widest">AI Platform</span>
|
| 59 |
+
</div>
|
| 60 |
+
<button onClick={onClose} className="lg:hidden ml-auto p-1.5 hover:bg-white/5 rounded-xl transition-colors">
|
| 61 |
+
<X size={18} />
|
| 62 |
</button>
|
| 63 |
</div>
|
| 64 |
|
| 65 |
+
{/* Divider */}
|
| 66 |
+
<div className="h-px bg-gradient-to-r from-transparent via-white/10 to-transparent mx-2" />
|
| 67 |
+
|
| 68 |
+
{/* Label */}
|
| 69 |
+
<p className="text-[10px] font-bold uppercase tracking-[0.2em] text-slate-500 px-4 mt-1">Services</p>
|
| 70 |
+
|
| 71 |
{/* Nav */}
|
| 72 |
+
<nav className="flex flex-col gap-0.5 flex-1 overflow-y-auto px-1">
|
| 73 |
{navItems.map(({ path, label, icon: Icon }) => (
|
| 74 |
<NavLink
|
| 75 |
key={path}
|
| 76 |
to={path}
|
| 77 |
onClick={onClose}
|
| 78 |
className={({ isActive }) => `
|
| 79 |
+
flex items-center gap-3 px-4 py-2.5 rounded-xl text-[13px] font-medium
|
| 80 |
+
transition-all duration-200 group relative
|
| 81 |
${isActive
|
| 82 |
+
? 'bg-gradient-to-r from-cyan-500/10 to-purple-500/5 text-cyan-400 shadow-sm shadow-cyan-500/5'
|
| 83 |
+
: 'text-slate-400 hover:bg-white/[0.04] hover:text-slate-200'
|
| 84 |
}
|
| 85 |
`}
|
| 86 |
>
|
| 87 |
+
{({ isActive }) => (
|
| 88 |
+
<>
|
| 89 |
+
{isActive && (
|
| 90 |
+
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-[3px] h-5 bg-gradient-to-b from-cyan-400 to-purple-500 rounded-full" />
|
| 91 |
+
)}
|
| 92 |
+
<Icon size={16} className={`transition-transform duration-200 ${isActive ? '' : 'group-hover:scale-110'}`} />
|
| 93 |
+
<span>{label}</span>
|
| 94 |
+
</>
|
| 95 |
+
)}
|
| 96 |
</NavLink>
|
| 97 |
))}
|
| 98 |
</nav>
|
| 99 |
|
| 100 |
{/* Footer */}
|
| 101 |
+
<div className="mx-2 p-4 rounded-2xl bg-gradient-to-br from-cyan-500/[0.07] to-purple-500/[0.04] border border-white/[0.05]">
|
| 102 |
+
<div className="flex items-center gap-2 mb-1">
|
| 103 |
+
<Sparkles size={14} className="text-cyan-400" />
|
| 104 |
+
<p className="text-[11px] font-bold text-cyan-400">Pro Features</p>
|
| 105 |
+
</div>
|
| 106 |
+
<p className="text-[11px] text-slate-500 leading-relaxed">9 AI engines ready to process your data.</p>
|
| 107 |
</div>
|
| 108 |
</aside>
|
| 109 |
</>
|
frontend/src/components/UI.jsx
CHANGED
|
@@ -1,11 +1,23 @@
|
|
| 1 |
import { motion } from 'framer-motion';
|
| 2 |
-
import { Loader2 } from 'lucide-react';
|
|
|
|
| 3 |
|
| 4 |
-
export function PageHeader({ title, subtitle }) {
|
| 5 |
return (
|
| 6 |
<div className="mb-8">
|
| 7 |
-
<
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
</div>
|
| 10 |
);
|
| 11 |
}
|
|
@@ -13,9 +25,10 @@ export function PageHeader({ title, subtitle }) {
|
|
| 13 |
export function ResultBox({ children, className = '' }) {
|
| 14 |
return (
|
| 15 |
<motion.div
|
| 16 |
-
initial={{ opacity: 0, y:
|
| 17 |
-
animate={{ opacity: 1, y: 0 }}
|
| 18 |
-
|
|
|
|
| 19 |
>
|
| 20 |
{children}
|
| 21 |
</motion.div>
|
|
@@ -26,44 +39,100 @@ export function ErrorBox({ message }) {
|
|
| 26 |
if (!message) return null;
|
| 27 |
return (
|
| 28 |
<motion.div
|
| 29 |
-
initial={{ opacity: 0 }}
|
| 30 |
-
animate={{ opacity: 1 }}
|
| 31 |
-
className="mt-4 p-4 rounded-
|
| 32 |
>
|
| 33 |
-
|
|
|
|
| 34 |
</motion.div>
|
| 35 |
);
|
| 36 |
}
|
| 37 |
|
| 38 |
export function SubmitButton({ loading, children, onClick, type = 'submit' }) {
|
| 39 |
return (
|
| 40 |
-
<button
|
| 41 |
type={type}
|
| 42 |
onClick={onClick}
|
| 43 |
disabled={loading}
|
| 44 |
-
|
|
|
|
| 45 |
>
|
| 46 |
-
{loading ?
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
);
|
| 50 |
}
|
| 51 |
|
| 52 |
export function UploadZone({ accept, name, onChange, label, sublabel }) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
return (
|
| 54 |
-
<label
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
<input
|
| 61 |
type="file"
|
| 62 |
accept={accept}
|
| 63 |
name={name}
|
| 64 |
-
onChange={
|
| 65 |
className="hidden"
|
| 66 |
/>
|
| 67 |
</label>
|
| 68 |
);
|
| 69 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import { motion } from 'framer-motion';
|
| 2 |
+
import { Loader2, AlertTriangle, CloudUpload, CheckCircle2 } from 'lucide-react';
|
| 3 |
+
import { useState } from 'react';
|
| 4 |
|
| 5 |
+
export function PageHeader({ title, subtitle, icon: Icon }) {
|
| 6 |
return (
|
| 7 |
<div className="mb-8">
|
| 8 |
+
<motion.div
|
| 9 |
+
initial={{ opacity: 0, x: -12 }}
|
| 10 |
+
animate={{ opacity: 1, x: 0 }}
|
| 11 |
+
transition={{ duration: 0.4 }}
|
| 12 |
+
>
|
| 13 |
+
{Icon && (
|
| 14 |
+
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-cyan-500/20 to-purple-500/10 border border-white/[0.06] grid place-items-center mb-4">
|
| 15 |
+
<Icon size={22} className="text-cyan-400" />
|
| 16 |
+
</div>
|
| 17 |
+
)}
|
| 18 |
+
<h1 className="text-2xl sm:text-3xl font-extrabold tracking-tight">{title}</h1>
|
| 19 |
+
{subtitle && <p className="text-slate-400 mt-2 text-sm sm:text-base leading-relaxed max-w-xl">{subtitle}</p>}
|
| 20 |
+
</motion.div>
|
| 21 |
</div>
|
| 22 |
);
|
| 23 |
}
|
|
|
|
| 25 |
export function ResultBox({ children, className = '' }) {
|
| 26 |
return (
|
| 27 |
<motion.div
|
| 28 |
+
initial={{ opacity: 0, y: 20, scale: 0.98 }}
|
| 29 |
+
animate={{ opacity: 1, y: 0, scale: 1 }}
|
| 30 |
+
transition={{ duration: 0.5, ease: [0.4, 0, 0.2, 1] }}
|
| 31 |
+
className={`mt-6 p-6 sm:p-7 rounded-2xl bg-gradient-to-br from-cyan-500/[0.06] to-purple-500/[0.04] border border-cyan-500/10 backdrop-blur-sm ${className}`}
|
| 32 |
>
|
| 33 |
{children}
|
| 34 |
</motion.div>
|
|
|
|
| 39 |
if (!message) return null;
|
| 40 |
return (
|
| 41 |
<motion.div
|
| 42 |
+
initial={{ opacity: 0, y: 8 }}
|
| 43 |
+
animate={{ opacity: 1, y: 0 }}
|
| 44 |
+
className="mt-4 p-4 rounded-2xl bg-red-500/[0.08] border border-red-500/20 text-red-400 text-sm flex items-start gap-3"
|
| 45 |
>
|
| 46 |
+
<AlertTriangle size={18} className="mt-0.5 flex-shrink-0" />
|
| 47 |
+
<span>{message}</span>
|
| 48 |
</motion.div>
|
| 49 |
);
|
| 50 |
}
|
| 51 |
|
| 52 |
export function SubmitButton({ loading, children, onClick, type = 'submit' }) {
|
| 53 |
return (
|
| 54 |
+
<motion.button
|
| 55 |
type={type}
|
| 56 |
onClick={onClick}
|
| 57 |
disabled={loading}
|
| 58 |
+
whileTap={{ scale: 0.97 }}
|
| 59 |
+
className="btn-quantum w-full py-4 px-6 text-base font-bold rounded-2xl flex items-center justify-center gap-2.5 disabled:opacity-50 disabled:cursor-not-allowed"
|
| 60 |
>
|
| 61 |
+
{loading ? (
|
| 62 |
+
<>
|
| 63 |
+
<Loader2 className="animate-spin" size={20} />
|
| 64 |
+
<span>Processing...</span>
|
| 65 |
+
</>
|
| 66 |
+
) : children}
|
| 67 |
+
</motion.button>
|
| 68 |
);
|
| 69 |
}
|
| 70 |
|
| 71 |
export function UploadZone({ accept, name, onChange, label, sublabel }) {
|
| 72 |
+
const [fileName, setFileName] = useState('');
|
| 73 |
+
const [isDragging, setIsDragging] = useState(false);
|
| 74 |
+
|
| 75 |
+
const handleChange = (e) => {
|
| 76 |
+
if (e.target.files[0]) {
|
| 77 |
+
setFileName(e.target.files[0].name);
|
| 78 |
+
}
|
| 79 |
+
onChange?.(e);
|
| 80 |
+
};
|
| 81 |
+
|
| 82 |
return (
|
| 83 |
+
<label
|
| 84 |
+
className="block cursor-pointer"
|
| 85 |
+
onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }}
|
| 86 |
+
onDragLeave={() => setIsDragging(false)}
|
| 87 |
+
onDrop={() => setIsDragging(false)}
|
| 88 |
+
>
|
| 89 |
+
<motion.div
|
| 90 |
+
whileHover={{ scale: 1.01 }}
|
| 91 |
+
className={`
|
| 92 |
+
border-2 border-dashed rounded-2xl p-10 text-center
|
| 93 |
+
transition-all duration-300
|
| 94 |
+
${isDragging
|
| 95 |
+
? 'border-cyan-400/50 bg-cyan-500/[0.06] shadow-lg shadow-cyan-500/5'
|
| 96 |
+
: fileName
|
| 97 |
+
? 'border-emerald-500/30 bg-emerald-500/[0.04]'
|
| 98 |
+
: 'border-white/[0.08] hover:border-cyan-500/20 hover:bg-white/[0.02]'
|
| 99 |
+
}
|
| 100 |
+
`}
|
| 101 |
+
>
|
| 102 |
+
{fileName ? (
|
| 103 |
+
<div className="flex flex-col items-center gap-2">
|
| 104 |
+
<CheckCircle2 size={32} className="text-emerald-400" />
|
| 105 |
+
<p className="text-sm font-semibold text-emerald-400">{fileName}</p>
|
| 106 |
+
<p className="text-xs text-slate-500">Click to change file</p>
|
| 107 |
+
</div>
|
| 108 |
+
) : (
|
| 109 |
+
<div className="flex flex-col items-center gap-3">
|
| 110 |
+
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-cyan-500/10 to-purple-500/5 border border-white/[0.06] grid place-items-center">
|
| 111 |
+
<CloudUpload size={24} className="text-cyan-400" />
|
| 112 |
+
</div>
|
| 113 |
+
<div>
|
| 114 |
+
<p className="font-semibold text-sm text-slate-200">{label || 'Click to upload'}</p>
|
| 115 |
+
<p className="text-xs text-slate-500 mt-1">{sublabel || 'Drag and drop supported'}</p>
|
| 116 |
+
</div>
|
| 117 |
+
</div>
|
| 118 |
+
)}
|
| 119 |
+
</motion.div>
|
| 120 |
<input
|
| 121 |
type="file"
|
| 122 |
accept={accept}
|
| 123 |
name={name}
|
| 124 |
+
onChange={handleChange}
|
| 125 |
className="hidden"
|
| 126 |
/>
|
| 127 |
</label>
|
| 128 |
);
|
| 129 |
}
|
| 130 |
+
|
| 131 |
+
export function SectionLabel({ children }) {
|
| 132 |
+
return (
|
| 133 |
+
<p className="text-[11px] font-bold uppercase tracking-[0.15em] text-purple-400 mb-3 flex items-center gap-2">
|
| 134 |
+
<span className="w-1.5 h-1.5 rounded-full bg-purple-400 animate-pulse" />
|
| 135 |
+
{children}
|
| 136 |
+
</p>
|
| 137 |
+
);
|
| 138 |
+
}
|
frontend/src/index.css
CHANGED
|
@@ -2,30 +2,83 @@
|
|
| 2 |
|
| 3 |
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
|
| 4 |
|
|
|
|
| 5 |
:root {
|
| 6 |
-
--bg-deep: #
|
| 7 |
-
--
|
| 8 |
-
--card-bg: rgba(15, 23, 42, 0.
|
| 9 |
-
--glass-border: rgba(255, 255, 255, 0.
|
| 10 |
-
--
|
|
|
|
| 11 |
--accent-purple: #a855f7;
|
| 12 |
-
--
|
|
|
|
|
|
|
| 13 |
--text-secondary: #64748b;
|
|
|
|
|
|
|
|
|
|
| 14 |
}
|
| 15 |
|
| 16 |
* {
|
| 17 |
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
| 18 |
}
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
body {
|
| 21 |
background: var(--bg-deep);
|
| 22 |
color: var(--text-primary);
|
| 23 |
overflow-x: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
}
|
| 25 |
|
| 26 |
-
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
::-webkit-scrollbar {
|
| 28 |
-
width:
|
| 29 |
}
|
| 30 |
|
| 31 |
::-webkit-scrollbar-track {
|
|
@@ -33,75 +86,199 @@ body {
|
|
| 33 |
}
|
| 34 |
|
| 35 |
::-webkit-scrollbar-thumb {
|
| 36 |
-
background: rgba(255, 255, 255, 0.
|
| 37 |
-
border-radius:
|
| 38 |
}
|
| 39 |
|
| 40 |
::-webkit-scrollbar-thumb:hover {
|
| 41 |
-
background: rgba(255, 255, 255, 0.
|
| 42 |
}
|
| 43 |
|
| 44 |
-
/*
|
| 45 |
.glass-card {
|
|
|
|
| 46 |
background: var(--card-bg);
|
| 47 |
-
backdrop-filter: blur(
|
| 48 |
-
-webkit-backdrop-filter: blur(
|
| 49 |
border: 1px solid var(--glass-border);
|
| 50 |
-
border-radius:
|
| 51 |
-
transition: all 0.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
}
|
| 53 |
|
| 54 |
.glass-card:hover {
|
| 55 |
-
border-color:
|
|
|
|
| 56 |
}
|
| 57 |
|
| 58 |
-
/*
|
| 59 |
.gradient-text {
|
| 60 |
-
background: linear-gradient(135deg,
|
| 61 |
-webkit-background-clip: text;
|
| 62 |
-webkit-text-fill-color: transparent;
|
| 63 |
background-clip: text;
|
| 64 |
}
|
| 65 |
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
.btn-quantum {
|
| 68 |
-
|
|
|
|
| 69 |
color: white;
|
| 70 |
border: none;
|
| 71 |
-
border-radius:
|
| 72 |
font-weight: 700;
|
| 73 |
cursor: pointer;
|
| 74 |
-
transition: all 0.3s;
|
| 75 |
-
box-shadow: 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
}
|
| 77 |
|
| 78 |
.btn-quantum:hover {
|
| 79 |
-
transform: translateY(-
|
| 80 |
-
box-shadow: 0 12px
|
| 81 |
}
|
| 82 |
|
| 83 |
.btn-quantum:active {
|
| 84 |
-
transform: translateY(
|
| 85 |
}
|
| 86 |
|
| 87 |
-
/*
|
| 88 |
.quantum-input {
|
| 89 |
width: 100%;
|
| 90 |
background: rgba(0, 0, 0, 0.3);
|
| 91 |
border: 1px solid var(--glass-border);
|
| 92 |
-
border-radius:
|
| 93 |
-
padding:
|
| 94 |
color: var(--text-primary);
|
| 95 |
-
font-size:
|
| 96 |
-
transition: all 0.3s;
|
| 97 |
outline: none;
|
| 98 |
}
|
| 99 |
|
| 100 |
.quantum-input:focus {
|
| 101 |
border-color: var(--accent-cyan);
|
| 102 |
-
box-shadow: 0 0 0 4px rgba(
|
|
|
|
| 103 |
}
|
| 104 |
|
| 105 |
.quantum-input::placeholder {
|
| 106 |
-
color: var(--text-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
}
|
|
|
|
| 2 |
|
| 3 |
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
|
| 4 |
|
| 5 |
+
/* ===== DESIGN SYSTEM ===== */
|
| 6 |
:root {
|
| 7 |
+
--bg-deep: #030712;
|
| 8 |
+
--bg-surface: #0a0f1e;
|
| 9 |
+
--card-bg: rgba(15, 23, 42, 0.5);
|
| 10 |
+
--glass-border: rgba(255, 255, 255, 0.06);
|
| 11 |
+
--glass-border-hover: rgba(255, 255, 255, 0.12);
|
| 12 |
+
--accent-cyan: #06b6d4;
|
| 13 |
--accent-purple: #a855f7;
|
| 14 |
+
--accent-blue: #3b82f6;
|
| 15 |
+
--accent-pink: #ec4899;
|
| 16 |
+
--text-primary: #f1f5f9;
|
| 17 |
--text-secondary: #64748b;
|
| 18 |
+
--text-muted: #475569;
|
| 19 |
+
--glow-cyan: rgba(6, 182, 212, 0.15);
|
| 20 |
+
--glow-purple: rgba(168, 85, 247, 0.15);
|
| 21 |
}
|
| 22 |
|
| 23 |
* {
|
| 24 |
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
| 25 |
}
|
| 26 |
|
| 27 |
+
html {
|
| 28 |
+
scroll-behavior: smooth;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
body {
|
| 32 |
background: var(--bg-deep);
|
| 33 |
color: var(--text-primary);
|
| 34 |
overflow-x: hidden;
|
| 35 |
+
min-height: 100vh;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
/* ===== ANIMATED BACKGROUND ===== */
|
| 39 |
+
body::before {
|
| 40 |
+
content: '';
|
| 41 |
+
position: fixed;
|
| 42 |
+
top: 0;
|
| 43 |
+
left: 0;
|
| 44 |
+
width: 100vw;
|
| 45 |
+
height: 100vh;
|
| 46 |
+
background:
|
| 47 |
+
radial-gradient(ellipse 600px 400px at 15% 20%, rgba(6, 182, 212, 0.08) 0%, transparent 70%),
|
| 48 |
+
radial-gradient(ellipse 500px 500px at 85% 80%, rgba(168, 85, 247, 0.06) 0%, transparent 70%),
|
| 49 |
+
radial-gradient(ellipse 400px 300px at 50% 50%, rgba(59, 130, 246, 0.04) 0%, transparent 70%);
|
| 50 |
+
pointer-events: none;
|
| 51 |
+
z-index: 0;
|
| 52 |
+
animation: bgPulse 8s ease-in-out infinite alternate;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
@keyframes bgPulse {
|
| 56 |
+
0% {
|
| 57 |
+
opacity: 0.7;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
100% {
|
| 61 |
+
opacity: 1;
|
| 62 |
+
}
|
| 63 |
}
|
| 64 |
|
| 65 |
+
/* Noise texture overlay */
|
| 66 |
+
body::after {
|
| 67 |
+
content: '';
|
| 68 |
+
position: fixed;
|
| 69 |
+
top: 0;
|
| 70 |
+
left: 0;
|
| 71 |
+
width: 100%;
|
| 72 |
+
height: 100%;
|
| 73 |
+
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.04'/%3E%3C/svg%3E");
|
| 74 |
+
opacity: 0.4;
|
| 75 |
+
pointer-events: none;
|
| 76 |
+
z-index: 0;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
/* ===== SCROLLBAR ===== */
|
| 80 |
::-webkit-scrollbar {
|
| 81 |
+
width: 5px;
|
| 82 |
}
|
| 83 |
|
| 84 |
::-webkit-scrollbar-track {
|
|
|
|
| 86 |
}
|
| 87 |
|
| 88 |
::-webkit-scrollbar-thumb {
|
| 89 |
+
background: rgba(255, 255, 255, 0.08);
|
| 90 |
+
border-radius: 10px;
|
| 91 |
}
|
| 92 |
|
| 93 |
::-webkit-scrollbar-thumb:hover {
|
| 94 |
+
background: rgba(255, 255, 255, 0.15);
|
| 95 |
}
|
| 96 |
|
| 97 |
+
/* ===== GLASS CARD ===== */
|
| 98 |
.glass-card {
|
| 99 |
+
position: relative;
|
| 100 |
background: var(--card-bg);
|
| 101 |
+
backdrop-filter: blur(24px) saturate(1.2);
|
| 102 |
+
-webkit-backdrop-filter: blur(24px) saturate(1.2);
|
| 103 |
border: 1px solid var(--glass-border);
|
| 104 |
+
border-radius: 24px;
|
| 105 |
+
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
| 106 |
+
overflow: hidden;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
.glass-card::before {
|
| 110 |
+
content: '';
|
| 111 |
+
position: absolute;
|
| 112 |
+
top: 0;
|
| 113 |
+
left: 0;
|
| 114 |
+
right: 0;
|
| 115 |
+
height: 1px;
|
| 116 |
+
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
|
| 117 |
}
|
| 118 |
|
| 119 |
.glass-card:hover {
|
| 120 |
+
border-color: var(--glass-border-hover);
|
| 121 |
+
box-shadow: 0 8px 40px -12px rgba(0, 0, 0, 0.4), 0 0 20px var(--glow-cyan);
|
| 122 |
}
|
| 123 |
|
| 124 |
+
/* ===== GRADIENT TEXT ===== */
|
| 125 |
.gradient-text {
|
| 126 |
+
background: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple));
|
| 127 |
-webkit-background-clip: text;
|
| 128 |
-webkit-text-fill-color: transparent;
|
| 129 |
background-clip: text;
|
| 130 |
}
|
| 131 |
|
| 132 |
+
.gradient-text-warm {
|
| 133 |
+
background: linear-gradient(135deg, #f97316, #ec4899);
|
| 134 |
+
-webkit-background-clip: text;
|
| 135 |
+
-webkit-text-fill-color: transparent;
|
| 136 |
+
background-clip: text;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
/* ===== QUANTUM BUTTON ===== */
|
| 140 |
.btn-quantum {
|
| 141 |
+
position: relative;
|
| 142 |
+
background: linear-gradient(135deg, var(--accent-cyan) 0%, var(--accent-purple) 100%);
|
| 143 |
color: white;
|
| 144 |
border: none;
|
| 145 |
+
border-radius: 16px;
|
| 146 |
font-weight: 700;
|
| 147 |
cursor: pointer;
|
| 148 |
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 149 |
+
box-shadow: 0 4px 20px -4px rgba(6, 182, 212, 0.4);
|
| 150 |
+
overflow: hidden;
|
| 151 |
+
z-index: 1;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
.btn-quantum::before {
|
| 155 |
+
content: '';
|
| 156 |
+
position: absolute;
|
| 157 |
+
inset: 0;
|
| 158 |
+
background: linear-gradient(135deg, var(--accent-purple) 0%, var(--accent-cyan) 100%);
|
| 159 |
+
opacity: 0;
|
| 160 |
+
transition: opacity 0.4s;
|
| 161 |
+
z-index: -1;
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
.btn-quantum:hover::before {
|
| 165 |
+
opacity: 1;
|
| 166 |
}
|
| 167 |
|
| 168 |
.btn-quantum:hover {
|
| 169 |
+
transform: translateY(-3px);
|
| 170 |
+
box-shadow: 0 12px 40px -8px rgba(6, 182, 212, 0.5), 0 0 20px rgba(168, 85, 247, 0.2);
|
| 171 |
}
|
| 172 |
|
| 173 |
.btn-quantum:active {
|
| 174 |
+
transform: translateY(-1px);
|
| 175 |
}
|
| 176 |
|
| 177 |
+
/* ===== INPUTS ===== */
|
| 178 |
.quantum-input {
|
| 179 |
width: 100%;
|
| 180 |
background: rgba(0, 0, 0, 0.3);
|
| 181 |
border: 1px solid var(--glass-border);
|
| 182 |
+
border-radius: 16px;
|
| 183 |
+
padding: 16px 20px;
|
| 184 |
color: var(--text-primary);
|
| 185 |
+
font-size: 0.95rem;
|
| 186 |
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 187 |
outline: none;
|
| 188 |
}
|
| 189 |
|
| 190 |
.quantum-input:focus {
|
| 191 |
border-color: var(--accent-cyan);
|
| 192 |
+
box-shadow: 0 0 0 4px rgba(6, 182, 212, 0.1), 0 0 20px rgba(6, 182, 212, 0.05);
|
| 193 |
+
background: rgba(0, 0, 0, 0.4);
|
| 194 |
}
|
| 195 |
|
| 196 |
.quantum-input::placeholder {
|
| 197 |
+
color: var(--text-muted);
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
/* Select dropdown */
|
| 201 |
+
select.quantum-input {
|
| 202 |
+
appearance: none;
|
| 203 |
+
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
| 204 |
+
background-position: right 12px center;
|
| 205 |
+
background-repeat: no-repeat;
|
| 206 |
+
background-size: 20px;
|
| 207 |
+
padding-right: 40px;
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
select.quantum-input option {
|
| 211 |
+
background: #0f172a;
|
| 212 |
+
color: white;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
/* ===== FLOATING ORB ANIMATION ===== */
|
| 216 |
+
@keyframes float {
|
| 217 |
+
|
| 218 |
+
0%,
|
| 219 |
+
100% {
|
| 220 |
+
transform: translateY(0px) rotate(0deg);
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
50% {
|
| 224 |
+
transform: translateY(-20px) rotate(3deg);
|
| 225 |
+
}
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
@keyframes glow {
|
| 229 |
+
|
| 230 |
+
0%,
|
| 231 |
+
100% {
|
| 232 |
+
opacity: 0.5;
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
50% {
|
| 236 |
+
opacity: 1;
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
@keyframes shimmer {
|
| 241 |
+
0% {
|
| 242 |
+
background-position: -200% 0;
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
100% {
|
| 246 |
+
background-position: 200% 0;
|
| 247 |
+
}
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
.animate-float {
|
| 251 |
+
animation: float 6s ease-in-out infinite;
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
.animate-glow {
|
| 255 |
+
animation: glow 3s ease-in-out infinite;
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
/* Shimmer loading effect */
|
| 259 |
+
.shimmer {
|
| 260 |
+
background: linear-gradient(90deg, transparent 25%, rgba(255, 255, 255, 0.05) 50%, transparent 75%);
|
| 261 |
+
background-size: 200% 100%;
|
| 262 |
+
animation: shimmer 2s infinite;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
/* ===== STAT COUNTER ===== */
|
| 266 |
+
.stat-glow {
|
| 267 |
+
position: relative;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
.stat-glow::after {
|
| 271 |
+
content: '';
|
| 272 |
+
position: absolute;
|
| 273 |
+
inset: -2px;
|
| 274 |
+
border-radius: 20px;
|
| 275 |
+
background: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple));
|
| 276 |
+
opacity: 0;
|
| 277 |
+
z-index: -1;
|
| 278 |
+
transition: opacity 0.3s;
|
| 279 |
+
filter: blur(8px);
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
.stat-glow:hover::after {
|
| 283 |
+
opacity: 0.3;
|
| 284 |
}
|
frontend/src/pages/AssociationRules.jsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
import { ShoppingCart } from 'lucide-react';
|
| 4 |
-
import {
|
|
|
|
| 5 |
|
| 6 |
export default function AssociationRules() {
|
| 7 |
const [file, setFile] = useState(null);
|
|
@@ -17,14 +18,12 @@ export default function AssociationRules() {
|
|
| 17 |
e.preventDefault();
|
| 18 |
if (!file) return setError('Please upload a file');
|
| 19 |
setLoading(true); setError(''); setResult(null);
|
| 20 |
-
|
| 21 |
const fd = new FormData();
|
| 22 |
fd.append('file', file);
|
| 23 |
fd.append('metric', metric);
|
| 24 |
fd.append('min_support', minSupport);
|
| 25 |
fd.append('min_threshold', minThreshold);
|
| 26 |
fd.append('has_header', hasHeader);
|
| 27 |
-
|
| 28 |
try {
|
| 29 |
const res = await axios.post('/api/apriori', fd);
|
| 30 |
setResult(res.data);
|
|
@@ -35,31 +34,30 @@ export default function AssociationRules() {
|
|
| 35 |
|
| 36 |
return (
|
| 37 |
<div className="max-w-4xl mx-auto">
|
| 38 |
-
<PageHeader title="Association Rules" subtitle="Discover hidden relationships in transactional datasets using Apriori." />
|
| 39 |
|
| 40 |
-
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-
|
| 41 |
<UploadZone accept=".csv,.xlsx" onChange={(e) => setFile(e.target.files[0])} label="Upload Transaction Data" sublabel=".CSV or .XLSX (each row = transaction)" />
|
| 42 |
-
{file && <p className="text-sm text-cyan-400 text-center font-medium">📊 {file.name}</p>}
|
| 43 |
|
| 44 |
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
| 45 |
<div>
|
| 46 |
-
<label className="block text-sm font-semibold text-slate-300 mb-2">Metric</label>
|
| 47 |
<select value={metric} onChange={(e) => setMetric(e.target.value)} className="quantum-input">
|
| 48 |
<option value="lift">Lift</option>
|
| 49 |
<option value="confidence">Confidence</option>
|
| 50 |
</select>
|
| 51 |
</div>
|
| 52 |
<div>
|
| 53 |
-
<label className="block text-sm font-semibold text-slate-300 mb-2">Min Support</label>
|
| 54 |
<input type="number" value={minSupport} onChange={(e) => setMinSupport(e.target.value)} step="0.01" min="0.01" max="1" className="quantum-input" />
|
| 55 |
</div>
|
| 56 |
<div>
|
| 57 |
-
<label className="block text-sm font-semibold text-slate-300 mb-2">Min Threshold</label>
|
| 58 |
<input type="number" value={minThreshold} onChange={(e) => setMinThreshold(e.target.value)} step="0.1" min="0.1" className="quantum-input" />
|
| 59 |
</div>
|
| 60 |
<div className="flex items-end">
|
| 61 |
-
<label className="flex items-center gap-3 cursor-pointer text-sm text-slate-300">
|
| 62 |
-
<input type="checkbox" checked={hasHeader} onChange={(e) => setHasHeader(e.target.checked)} className="w-4 h-4 rounded" />
|
| 63 |
File has header row
|
| 64 |
</label>
|
| 65 |
</div>
|
|
@@ -74,37 +72,41 @@ export default function AssociationRules() {
|
|
| 74 |
|
| 75 |
{result && (
|
| 76 |
<ResultBox>
|
| 77 |
-
<
|
| 78 |
-
|
| 79 |
-
</p>
|
| 80 |
-
<div className="overflow-x-auto -mx-2">
|
| 81 |
<table className="w-full text-sm">
|
| 82 |
<thead>
|
| 83 |
-
<tr className="text-left text-
|
| 84 |
-
<th className="p-
|
| 85 |
-
<th className="p-
|
| 86 |
-
<th className="p-
|
| 87 |
-
<th className="p-
|
| 88 |
-
<th className="p-
|
| 89 |
</tr>
|
| 90 |
</thead>
|
| 91 |
<tbody>
|
| 92 |
{result.rules?.map((rule, i) => (
|
| 93 |
-
<tr
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
{rule.antecedents.join(', ')}
|
| 97 |
</span>
|
| 98 |
</td>
|
| 99 |
-
<td className="p-
|
| 100 |
-
<span className="px-2 py-
|
| 101 |
{rule.consequents.join(', ')}
|
| 102 |
</span>
|
| 103 |
</td>
|
| 104 |
-
<td className="p-
|
| 105 |
-
<td className="p-
|
| 106 |
-
<td className="p-
|
| 107 |
-
</tr>
|
| 108 |
))}
|
| 109 |
</tbody>
|
| 110 |
</table>
|
|
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
import { ShoppingCart } from 'lucide-react';
|
| 4 |
+
import { motion } from 'framer-motion';
|
| 5 |
+
import { PageHeader, ResultBox, ErrorBox, SubmitButton, UploadZone, SectionLabel } from '../components/UI';
|
| 6 |
|
| 7 |
export default function AssociationRules() {
|
| 8 |
const [file, setFile] = useState(null);
|
|
|
|
| 18 |
e.preventDefault();
|
| 19 |
if (!file) return setError('Please upload a file');
|
| 20 |
setLoading(true); setError(''); setResult(null);
|
|
|
|
| 21 |
const fd = new FormData();
|
| 22 |
fd.append('file', file);
|
| 23 |
fd.append('metric', metric);
|
| 24 |
fd.append('min_support', minSupport);
|
| 25 |
fd.append('min_threshold', minThreshold);
|
| 26 |
fd.append('has_header', hasHeader);
|
|
|
|
| 27 |
try {
|
| 28 |
const res = await axios.post('/api/apriori', fd);
|
| 29 |
setResult(res.data);
|
|
|
|
| 34 |
|
| 35 |
return (
|
| 36 |
<div className="max-w-4xl mx-auto">
|
| 37 |
+
<PageHeader icon={ShoppingCart} title="Association Rules" subtitle="Discover hidden relationships in transactional datasets using the Apriori algorithm." />
|
| 38 |
|
| 39 |
+
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-6">
|
| 40 |
<UploadZone accept=".csv,.xlsx" onChange={(e) => setFile(e.target.files[0])} label="Upload Transaction Data" sublabel=".CSV or .XLSX (each row = transaction)" />
|
|
|
|
| 41 |
|
| 42 |
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
| 43 |
<div>
|
| 44 |
+
<label className="block text-sm font-semibold text-slate-300 mb-2.5">Metric</label>
|
| 45 |
<select value={metric} onChange={(e) => setMetric(e.target.value)} className="quantum-input">
|
| 46 |
<option value="lift">Lift</option>
|
| 47 |
<option value="confidence">Confidence</option>
|
| 48 |
</select>
|
| 49 |
</div>
|
| 50 |
<div>
|
| 51 |
+
<label className="block text-sm font-semibold text-slate-300 mb-2.5">Min Support</label>
|
| 52 |
<input type="number" value={minSupport} onChange={(e) => setMinSupport(e.target.value)} step="0.01" min="0.01" max="1" className="quantum-input" />
|
| 53 |
</div>
|
| 54 |
<div>
|
| 55 |
+
<label className="block text-sm font-semibold text-slate-300 mb-2.5">Min Threshold</label>
|
| 56 |
<input type="number" value={minThreshold} onChange={(e) => setMinThreshold(e.target.value)} step="0.1" min="0.1" className="quantum-input" />
|
| 57 |
</div>
|
| 58 |
<div className="flex items-end">
|
| 59 |
+
<label className="flex items-center gap-3 cursor-pointer text-sm text-slate-300 bg-white/[0.02] border border-white/[0.05] rounded-2xl px-4 py-3 w-full hover:bg-white/[0.04] transition-colors">
|
| 60 |
+
<input type="checkbox" checked={hasHeader} onChange={(e) => setHasHeader(e.target.checked)} className="w-4 h-4 rounded accent-cyan-500" />
|
| 61 |
File has header row
|
| 62 |
</label>
|
| 63 |
</div>
|
|
|
|
| 72 |
|
| 73 |
{result && (
|
| 74 |
<ResultBox>
|
| 75 |
+
<SectionLabel>Mining Results: {result.count} rules discovered</SectionLabel>
|
| 76 |
+
<div className="overflow-x-auto -mx-2 rounded-2xl border border-white/[0.05]">
|
|
|
|
|
|
|
| 77 |
<table className="w-full text-sm">
|
| 78 |
<thead>
|
| 79 |
+
<tr className="text-left text-[11px] uppercase tracking-wider text-slate-500 bg-white/[0.02]">
|
| 80 |
+
<th className="p-4">Antecedents</th>
|
| 81 |
+
<th className="p-4">Consequents</th>
|
| 82 |
+
<th className="p-4">Support</th>
|
| 83 |
+
<th className="p-4">Confidence</th>
|
| 84 |
+
<th className="p-4">Lift</th>
|
| 85 |
</tr>
|
| 86 |
</thead>
|
| 87 |
<tbody>
|
| 88 |
{result.rules?.map((rule, i) => (
|
| 89 |
+
<motion.tr
|
| 90 |
+
key={i}
|
| 91 |
+
initial={{ opacity: 0 }}
|
| 92 |
+
animate={{ opacity: 1 }}
|
| 93 |
+
transition={{ delay: i * 0.05 }}
|
| 94 |
+
className="border-t border-white/[0.04] hover:bg-white/[0.02] transition-colors"
|
| 95 |
+
>
|
| 96 |
+
<td className="p-4">
|
| 97 |
+
<span className="px-2.5 py-1 rounded-lg text-xs font-semibold bg-blue-500/15 text-blue-400">
|
| 98 |
{rule.antecedents.join(', ')}
|
| 99 |
</span>
|
| 100 |
</td>
|
| 101 |
+
<td className="p-4">
|
| 102 |
+
<span className="px-2.5 py-1 rounded-lg text-xs font-semibold bg-purple-500/15 text-purple-400">
|
| 103 |
{rule.consequents.join(', ')}
|
| 104 |
</span>
|
| 105 |
</td>
|
| 106 |
+
<td className="p-4 text-slate-300 font-mono text-xs">{rule.support.toFixed(4)}</td>
|
| 107 |
+
<td className="p-4 text-slate-300 font-mono text-xs">{rule.confidence.toFixed(4)}</td>
|
| 108 |
+
<td className="p-4 text-cyan-400 font-mono text-xs font-bold">{rule.lift.toFixed(4)}</td>
|
| 109 |
+
</motion.tr>
|
| 110 |
))}
|
| 111 |
</tbody>
|
| 112 |
</table>
|
frontend/src/pages/CognitiveQA.jsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
-
import { Brain, Volume2 } from 'lucide-react';
|
| 4 |
-
import {
|
|
|
|
| 5 |
|
| 6 |
export default function CognitiveQA() {
|
| 7 |
const [tab, setTab] = useState('text');
|
|
@@ -15,7 +16,6 @@ export default function CognitiveQA() {
|
|
| 15 |
const handleSubmit = async (e) => {
|
| 16 |
e.preventDefault();
|
| 17 |
setLoading(true); setError(''); setResult(null);
|
| 18 |
-
|
| 19 |
try {
|
| 20 |
let res;
|
| 21 |
if (tab === 'text') {
|
|
@@ -41,11 +41,11 @@ export default function CognitiveQA() {
|
|
| 41 |
|
| 42 |
return (
|
| 43 |
<div className="max-w-3xl mx-auto">
|
| 44 |
-
<PageHeader title="Cognitive QA" subtitle="Knowledge extraction engine with vocal synthesis output." />
|
| 45 |
|
| 46 |
-
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-
|
| 47 |
<div>
|
| 48 |
-
<label className="block text-sm font-semibold text-slate-300 mb-2">Context Repository</label>
|
| 49 |
<textarea
|
| 50 |
value={context}
|
| 51 |
onChange={(e) => setContext(e.target.value)}
|
|
@@ -55,30 +55,35 @@ export default function CognitiveQA() {
|
|
| 55 |
/>
|
| 56 |
</div>
|
| 57 |
|
| 58 |
-
<div className="flex gap-
|
| 59 |
{['text', 'voice'].map(t => (
|
| 60 |
<button
|
| 61 |
key={t}
|
| 62 |
type="button"
|
| 63 |
onClick={() => setTab(t)}
|
| 64 |
-
className={`px-4 py-2 rounded-xl text-sm font-semibold transition-all ${tab === t
|
|
|
|
|
|
|
| 65 |
}`}
|
| 66 |
>
|
| 67 |
-
{t === 'text' ? 'Type Question' : 'Voice Question'}
|
| 68 |
</button>
|
| 69 |
))}
|
| 70 |
</div>
|
| 71 |
|
| 72 |
{tab === 'text' ? (
|
| 73 |
<div>
|
| 74 |
-
<label className="block text-sm font-semibold text-slate-300 mb-2">Query</label>
|
| 75 |
-
<
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
| 82 |
</div>
|
| 83 |
) : (
|
| 84 |
<UploadZone accept="audio/*" onChange={(e) => setFile(e.target.files[0])} label="Record Your Query" sublabel="Upload audio for voice-to-voice QA" />
|
|
@@ -93,35 +98,46 @@ export default function CognitiveQA() {
|
|
| 93 |
|
| 94 |
{result && (
|
| 95 |
<ResultBox>
|
| 96 |
-
<
|
| 97 |
|
| 98 |
-
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 p-5 rounded-
|
| 99 |
<div className="flex-1">
|
| 100 |
-
<span className="text-
|
| 101 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
</div>
|
| 103 |
{result.audio_url && (
|
| 104 |
-
<button
|
| 105 |
type="button"
|
| 106 |
onClick={playAudio}
|
| 107 |
-
|
|
|
|
|
|
|
| 108 |
>
|
| 109 |
<Volume2 size={22} />
|
| 110 |
-
</button>
|
| 111 |
)}
|
| 112 |
</div>
|
| 113 |
|
| 114 |
{result.score > 0 && (
|
| 115 |
-
<div className="mt-
|
| 116 |
-
<div className="h-2 bg-white/
|
| 117 |
-
<div
|
| 118 |
-
|
| 119 |
-
|
|
|
|
|
|
|
| 120 |
/>
|
| 121 |
</div>
|
| 122 |
-
<div className="flex justify-between text-
|
| 123 |
-
<span>Confidence</span>
|
| 124 |
-
<span>{result.score}%</span>
|
| 125 |
</div>
|
| 126 |
</div>
|
| 127 |
)}
|
|
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
+
import { Brain, Volume2, MessageSquare } from 'lucide-react';
|
| 4 |
+
import { motion } from 'framer-motion';
|
| 5 |
+
import { PageHeader, ResultBox, ErrorBox, SubmitButton, UploadZone, SectionLabel } from '../components/UI';
|
| 6 |
|
| 7 |
export default function CognitiveQA() {
|
| 8 |
const [tab, setTab] = useState('text');
|
|
|
|
| 16 |
const handleSubmit = async (e) => {
|
| 17 |
e.preventDefault();
|
| 18 |
setLoading(true); setError(''); setResult(null);
|
|
|
|
| 19 |
try {
|
| 20 |
let res;
|
| 21 |
if (tab === 'text') {
|
|
|
|
| 41 |
|
| 42 |
return (
|
| 43 |
<div className="max-w-3xl mx-auto">
|
| 44 |
+
<PageHeader icon={Brain} title="Cognitive QA" subtitle="Knowledge extraction engine with vocal synthesis output using DistilBERT." />
|
| 45 |
|
| 46 |
+
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-6">
|
| 47 |
<div>
|
| 48 |
+
<label className="block text-sm font-semibold text-slate-300 mb-2.5">Context Repository</label>
|
| 49 |
<textarea
|
| 50 |
value={context}
|
| 51 |
onChange={(e) => setContext(e.target.value)}
|
|
|
|
| 55 |
/>
|
| 56 |
</div>
|
| 57 |
|
| 58 |
+
<div className="flex gap-1 p-1 rounded-2xl bg-white/[0.03] border border-white/[0.05]">
|
| 59 |
{['text', 'voice'].map(t => (
|
| 60 |
<button
|
| 61 |
key={t}
|
| 62 |
type="button"
|
| 63 |
onClick={() => setTab(t)}
|
| 64 |
+
className={`flex-1 px-4 py-2.5 rounded-xl text-sm font-semibold transition-all ${tab === t
|
| 65 |
+
? 'bg-gradient-to-r from-cyan-500/15 to-purple-500/10 text-cyan-400 shadow-sm'
|
| 66 |
+
: 'text-slate-500 hover:text-slate-300'
|
| 67 |
}`}
|
| 68 |
>
|
| 69 |
+
{t === 'text' ? '⌨️ Type Question' : '🎙 Voice Question'}
|
| 70 |
</button>
|
| 71 |
))}
|
| 72 |
</div>
|
| 73 |
|
| 74 |
{tab === 'text' ? (
|
| 75 |
<div>
|
| 76 |
+
<label className="block text-sm font-semibold text-slate-300 mb-2.5">Query Expression</label>
|
| 77 |
+
<div className="relative">
|
| 78 |
+
<MessageSquare size={16} className="absolute left-4 top-1/2 -translate-y-1/2 text-cyan-500/60" />
|
| 79 |
+
<input
|
| 80 |
+
type="text"
|
| 81 |
+
value={question}
|
| 82 |
+
onChange={(e) => setQuestion(e.target.value)}
|
| 83 |
+
placeholder="Ask a question about the context..."
|
| 84 |
+
className="quantum-input pl-11"
|
| 85 |
+
/>
|
| 86 |
+
</div>
|
| 87 |
</div>
|
| 88 |
) : (
|
| 89 |
<UploadZone accept="audio/*" onChange={(e) => setFile(e.target.files[0])} label="Record Your Query" sublabel="Upload audio for voice-to-voice QA" />
|
|
|
|
| 98 |
|
| 99 |
{result && (
|
| 100 |
<ResultBox>
|
| 101 |
+
<SectionLabel>Reasoning Output</SectionLabel>
|
| 102 |
|
| 103 |
+
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 p-5 rounded-2xl bg-black/20 border-l-4 border-cyan-400">
|
| 104 |
<div className="flex-1">
|
| 105 |
+
<span className="text-[10px] font-bold text-cyan-400 uppercase tracking-widest">Extracted Answer</span>
|
| 106 |
+
<motion.p
|
| 107 |
+
initial={{ opacity: 0 }}
|
| 108 |
+
animate={{ opacity: 1 }}
|
| 109 |
+
transition={{ delay: 0.2 }}
|
| 110 |
+
className="text-lg sm:text-xl font-bold text-slate-100 mt-2 leading-relaxed"
|
| 111 |
+
>
|
| 112 |
+
{result.answer}
|
| 113 |
+
</motion.p>
|
| 114 |
</div>
|
| 115 |
{result.audio_url && (
|
| 116 |
+
<motion.button
|
| 117 |
type="button"
|
| 118 |
onClick={playAudio}
|
| 119 |
+
whileHover={{ scale: 1.1 }}
|
| 120 |
+
whileTap={{ scale: 0.95 }}
|
| 121 |
+
className="w-14 h-14 rounded-full bg-gradient-to-br from-cyan-500 to-purple-600 grid place-items-center text-white shadow-lg shadow-cyan-500/20 flex-shrink-0"
|
| 122 |
>
|
| 123 |
<Volume2 size={22} />
|
| 124 |
+
</motion.button>
|
| 125 |
)}
|
| 126 |
</div>
|
| 127 |
|
| 128 |
{result.score > 0 && (
|
| 129 |
+
<div className="mt-5">
|
| 130 |
+
<div className="h-2 bg-white/[0.04] rounded-full overflow-hidden">
|
| 131 |
+
<motion.div
|
| 132 |
+
initial={{ width: 0 }}
|
| 133 |
+
animate={{ width: `${result.score}%` }}
|
| 134 |
+
transition={{ duration: 1.2, ease: [0.4, 0, 0.2, 1] }}
|
| 135 |
+
className="h-full bg-gradient-to-r from-cyan-500 to-purple-500 rounded-full shadow-sm shadow-cyan-500/30"
|
| 136 |
/>
|
| 137 |
</div>
|
| 138 |
+
<div className="flex justify-between text-xs text-slate-500 mt-1.5 font-medium">
|
| 139 |
+
<span>Confidence Score</span>
|
| 140 |
+
<span className="text-cyan-400">{result.score}%</span>
|
| 141 |
</div>
|
| 142 |
</div>
|
| 143 |
)}
|
frontend/src/pages/Dashboard.jsx
CHANGED
|
@@ -2,37 +2,77 @@ import { Link } from 'react-router-dom';
|
|
| 2 |
import { motion } from 'framer-motion';
|
| 3 |
import {
|
| 4 |
User, PenTool, Languages, Smile, Brain,
|
| 5 |
-
Target, PieChart, Braces, ShoppingCart
|
|
|
|
| 6 |
} from 'lucide-react';
|
| 7 |
-
import { PageHeader } from '../components/UI';
|
| 8 |
|
| 9 |
const services = [
|
| 10 |
-
{ path: '/gender', label: 'Gender Discovery', desc: 'Vision Transformer for high-precision gender classification.', icon: User,
|
| 11 |
-
{ path: '/textgen', label: 'Text Synthesis', desc: 'Creative language generation powered by GPT-2 architecture.', icon: PenTool,
|
| 12 |
-
{ path: '/translate', label: 'Neural Translate', desc: 'Advanced English
|
| 13 |
-
{ path: '/sentiment', label: 'Empathy Engine', desc: 'Analyze emotional valence in text and vocal inputs.', icon: Smile,
|
| 14 |
-
{ path: '/qa', label: 'Cognitive QA', desc: 'Extract precise
|
| 15 |
-
{ path: '/zsl', label: 'Zero-Shot Lab', desc: 'BART-based classification for any unseen categories.', icon: Target,
|
| 16 |
-
{ path: '/clustering', label: 'Data Clusters', desc: 'Automated pattern discovery using K-Means clustering.', icon: PieChart,
|
| 17 |
-
{ path: '/dbscan', label: 'DBSCAN Lab', desc: 'Density-based clustering for complex patterns and outliers.', icon: Braces,
|
| 18 |
-
{ path: '/apriori', label: 'Market Analytics', desc: 'Generate association rules from transactional data.', icon: ShoppingCart,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
];
|
| 20 |
|
| 21 |
const container = {
|
| 22 |
hidden: {},
|
| 23 |
-
show: { transition: { staggerChildren: 0.
|
| 24 |
};
|
| 25 |
|
| 26 |
const item = {
|
| 27 |
-
hidden: { opacity: 0, y:
|
| 28 |
-
show: { opacity: 1, y: 0, transition: { duration: 0.4 } }
|
| 29 |
};
|
| 30 |
|
| 31 |
export default function Dashboard() {
|
| 32 |
return (
|
| 33 |
<div>
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
|
|
|
| 36 |
<motion.div
|
| 37 |
variants={container}
|
| 38 |
initial="hidden"
|
|
@@ -43,18 +83,26 @@ export default function Dashboard() {
|
|
| 43 |
<motion.div key={s.path} variants={item}>
|
| 44 |
<Link
|
| 45 |
to={s.path}
|
| 46 |
-
className="glass-card p-6 flex flex-col gap-4 group hover:translate-y-[-6px]
|
| 47 |
>
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
</div>
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
|
|
|
|
|
|
| 54 |
</div>
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
|
|
|
|
|
|
| 58 |
</span>
|
| 59 |
</div>
|
| 60 |
</Link>
|
|
|
|
| 2 |
import { motion } from 'framer-motion';
|
| 3 |
import {
|
| 4 |
User, PenTool, Languages, Smile, Brain,
|
| 5 |
+
Target, PieChart, Braces, ShoppingCart,
|
| 6 |
+
ArrowRight, Sparkles, Activity
|
| 7 |
} from 'lucide-react';
|
|
|
|
| 8 |
|
| 9 |
const services = [
|
| 10 |
+
{ path: '/gender', label: 'Gender Discovery', desc: 'Vision Transformer for high-precision gender classification from facial images.', icon: User, gradient: 'from-pink-500 to-rose-500', glow: 'shadow-pink-500/20' },
|
| 11 |
+
{ path: '/textgen', label: 'Text Synthesis', desc: 'Creative language generation powered by GPT-2 neural architecture.', icon: PenTool, gradient: 'from-cyan-500 to-blue-500', glow: 'shadow-cyan-500/20' },
|
| 12 |
+
{ path: '/translate', label: 'Neural Translate', desc: 'Advanced English → Urdu translation using sequence-to-sequence models.', icon: Languages, gradient: 'from-emerald-500 to-teal-500', glow: 'shadow-emerald-500/20' },
|
| 13 |
+
{ path: '/sentiment', label: 'Empathy Engine', desc: 'Analyze emotional valence and sentiment in text and vocal inputs.', icon: Smile, gradient: 'from-amber-500 to-orange-500', glow: 'shadow-amber-500/20' },
|
| 14 |
+
{ path: '/qa', label: 'Cognitive QA', desc: 'Extract precise answers from context documents with DistilBERT.', icon: Brain, gradient: 'from-violet-500 to-purple-500', glow: 'shadow-violet-500/20' },
|
| 15 |
+
{ path: '/zsl', label: 'Zero-Shot Lab', desc: 'BART-based classification for any unseen categories without training.', icon: Target, gradient: 'from-red-500 to-pink-500', glow: 'shadow-red-500/20' },
|
| 16 |
+
{ path: '/clustering', label: 'Data Clusters', desc: 'Automated pattern discovery using K-Means unsupervised clustering.', icon: PieChart, gradient: 'from-sky-500 to-indigo-500', glow: 'shadow-sky-500/20' },
|
| 17 |
+
{ path: '/dbscan', label: 'DBSCAN Lab', desc: 'Density-based spatial clustering for complex patterns and outliers.', icon: Braces, gradient: 'from-lime-500 to-emerald-500', glow: 'shadow-lime-500/20' },
|
| 18 |
+
{ path: '/apriori', label: 'Market Analytics', desc: 'Generate association rules from transactional data with Apriori algorithm.', icon: ShoppingCart, gradient: 'from-fuchsia-500 to-violet-500', glow: 'shadow-fuchsia-500/20' },
|
| 19 |
+
];
|
| 20 |
+
|
| 21 |
+
const stats = [
|
| 22 |
+
{ label: 'AI Models', value: '9', icon: Sparkles, color: 'text-cyan-400' },
|
| 23 |
+
{ label: 'NLP Engines', value: '5', icon: Brain, color: 'text-purple-400' },
|
| 24 |
+
{ label: 'ML Pipelines', value: '3', icon: Activity, color: 'text-emerald-400' },
|
| 25 |
];
|
| 26 |
|
| 27 |
const container = {
|
| 28 |
hidden: {},
|
| 29 |
+
show: { transition: { staggerChildren: 0.05, delayChildren: 0.15 } }
|
| 30 |
};
|
| 31 |
|
| 32 |
const item = {
|
| 33 |
+
hidden: { opacity: 0, y: 24, scale: 0.96 },
|
| 34 |
+
show: { opacity: 1, y: 0, scale: 1, transition: { duration: 0.45, ease: [0.4, 0, 0.2, 1] } }
|
| 35 |
};
|
| 36 |
|
| 37 |
export default function Dashboard() {
|
| 38 |
return (
|
| 39 |
<div>
|
| 40 |
+
{/* Hero Section */}
|
| 41 |
+
<motion.div
|
| 42 |
+
initial={{ opacity: 0, y: -10 }}
|
| 43 |
+
animate={{ opacity: 1, y: 0 }}
|
| 44 |
+
className="mb-10"
|
| 45 |
+
>
|
| 46 |
+
<div className="flex items-center gap-3 mb-3">
|
| 47 |
+
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-cyan-500 to-purple-600 grid place-items-center">
|
| 48 |
+
<Sparkles size={18} className="text-white" />
|
| 49 |
+
</div>
|
| 50 |
+
<div>
|
| 51 |
+
<h1 className="text-2xl sm:text-3xl font-extrabold tracking-tight">Quantum Analytics</h1>
|
| 52 |
+
</div>
|
| 53 |
+
</div>
|
| 54 |
+
<p className="text-slate-400 text-sm sm:text-base max-w-lg leading-relaxed">
|
| 55 |
+
Select a specialized AI engine to begin processing. Each service is powered by state-of-the-art deep learning models.
|
| 56 |
+
</p>
|
| 57 |
+
</motion.div>
|
| 58 |
+
|
| 59 |
+
{/* Stats Bar */}
|
| 60 |
+
<motion.div
|
| 61 |
+
initial={{ opacity: 0, y: 12 }}
|
| 62 |
+
animate={{ opacity: 1, y: 0 }}
|
| 63 |
+
transition={{ delay: 0.1 }}
|
| 64 |
+
className="grid grid-cols-3 gap-3 sm:gap-4 mb-8"
|
| 65 |
+
>
|
| 66 |
+
{stats.map((s, i) => (
|
| 67 |
+
<div key={i} className="glass-card stat-glow p-4 sm:p-5 text-center group cursor-default">
|
| 68 |
+
<s.icon size={20} className={`${s.color} mx-auto mb-2 group-hover:scale-110 transition-transform`} />
|
| 69 |
+
<p className="text-xl sm:text-2xl font-black">{s.value}</p>
|
| 70 |
+
<p className="text-[11px] text-slate-500 font-medium uppercase tracking-wider mt-1">{s.label}</p>
|
| 71 |
+
</div>
|
| 72 |
+
))}
|
| 73 |
+
</motion.div>
|
| 74 |
|
| 75 |
+
{/* Service Cards Grid */}
|
| 76 |
<motion.div
|
| 77 |
variants={container}
|
| 78 |
initial="hidden"
|
|
|
|
| 83 |
<motion.div key={s.path} variants={item}>
|
| 84 |
<Link
|
| 85 |
to={s.path}
|
| 86 |
+
className="glass-card p-6 flex flex-col gap-4 group block hover:translate-y-[-6px] transition-all duration-300"
|
| 87 |
>
|
| 88 |
+
{/* Icon */}
|
| 89 |
+
<div className="flex items-center justify-between">
|
| 90 |
+
<div className={`w-12 h-12 rounded-2xl bg-gradient-to-br ${s.gradient} grid place-items-center shadow-lg ${s.glow} opacity-90 group-hover:opacity-100 group-hover:scale-110 transition-all duration-300`}>
|
| 91 |
+
<s.icon size={20} className="text-white" />
|
| 92 |
+
</div>
|
| 93 |
+
<ArrowRight size={16} className="text-slate-600 group-hover:text-cyan-400 group-hover:translate-x-1 transition-all duration-300" />
|
| 94 |
</div>
|
| 95 |
+
|
| 96 |
+
{/* Content */}
|
| 97 |
+
<div className="flex-1">
|
| 98 |
+
<h3 className="text-base font-bold mb-1.5 group-hover:text-cyan-300 transition-colors">{s.label}</h3>
|
| 99 |
+
<p className="text-[13px] text-slate-500 leading-relaxed">{s.desc}</p>
|
| 100 |
</div>
|
| 101 |
+
|
| 102 |
+
{/* Footer */}
|
| 103 |
+
<div className="pt-3 border-t border-white/[0.05]">
|
| 104 |
+
<span className="text-[11px] font-bold uppercase tracking-[0.15em] text-cyan-500/70 group-hover:text-cyan-400 transition-colors">
|
| 105 |
+
Launch Engine
|
| 106 |
</span>
|
| 107 |
</div>
|
| 108 |
</Link>
|
frontend/src/pages/DataClusters.jsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
import { PieChart } from 'lucide-react';
|
| 4 |
-
import {
|
|
|
|
| 5 |
|
| 6 |
export default function DataClusters() {
|
| 7 |
const [file, setFile] = useState(null);
|
|
@@ -14,11 +15,9 @@ export default function DataClusters() {
|
|
| 14 |
e.preventDefault();
|
| 15 |
if (!file) return setError('Please upload a file');
|
| 16 |
setLoading(true); setError(''); setResult(null);
|
| 17 |
-
|
| 18 |
const fd = new FormData();
|
| 19 |
fd.append('file', file);
|
| 20 |
fd.append('clusters', clusters);
|
| 21 |
-
|
| 22 |
try {
|
| 23 |
const res = await axios.post('/api/clustering', fd);
|
| 24 |
setResult(res.data);
|
|
@@ -29,23 +28,14 @@ export default function DataClusters() {
|
|
| 29 |
|
| 30 |
return (
|
| 31 |
<div className="max-w-3xl mx-auto">
|
| 32 |
-
<PageHeader title="Data Clusters" subtitle="Unsupervised grouping of multivariate datasets using K-Means." />
|
| 33 |
|
| 34 |
-
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-
|
| 35 |
<UploadZone accept=".csv,.xlsx" onChange={(e) => setFile(e.target.files[0])} label="Upload Data Structure" sublabel=".CSV or .XLSX datasets" />
|
| 36 |
-
{file && <p className="text-sm text-cyan-400 text-center font-medium">📊 {file.name}</p>}
|
| 37 |
-
|
| 38 |
<div>
|
| 39 |
-
<label className="block text-sm font-semibold text-slate-300 mb-2">Cluster Centroids (K)</label>
|
| 40 |
-
<input
|
| 41 |
-
type="number"
|
| 42 |
-
value={clusters}
|
| 43 |
-
onChange={(e) => setClusters(e.target.value)}
|
| 44 |
-
min="2" max="10"
|
| 45 |
-
className="quantum-input w-32"
|
| 46 |
-
/>
|
| 47 |
</div>
|
| 48 |
-
|
| 49 |
<SubmitButton loading={loading}>
|
| 50 |
<PieChart size={18} /> Map Clusters
|
| 51 |
</SubmitButton>
|
|
@@ -55,17 +45,19 @@ export default function DataClusters() {
|
|
| 55 |
|
| 56 |
{result && (
|
| 57 |
<ResultBox>
|
| 58 |
-
<
|
| 59 |
-
<div className="bg-white p-4 rounded-2xl">
|
| 60 |
<img src={`data:image/png;base64,${result.plot}`} alt="Cluster Plot" className="w-full rounded-xl" />
|
| 61 |
-
</div>
|
| 62 |
{result.cluster_info && (
|
| 63 |
<div className="mt-5 grid grid-cols-2 sm:grid-cols-3 gap-3">
|
| 64 |
-
{Object.entries(result.cluster_info).map(([k, v]) => (
|
| 65 |
-
<div key={k}
|
| 66 |
-
|
| 67 |
-
<
|
| 68 |
-
|
|
|
|
|
|
|
| 69 |
))}
|
| 70 |
</div>
|
| 71 |
)}
|
|
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
import { PieChart } from 'lucide-react';
|
| 4 |
+
import { motion } from 'framer-motion';
|
| 5 |
+
import { PageHeader, ResultBox, ErrorBox, SubmitButton, UploadZone, SectionLabel } from '../components/UI';
|
| 6 |
|
| 7 |
export default function DataClusters() {
|
| 8 |
const [file, setFile] = useState(null);
|
|
|
|
| 15 |
e.preventDefault();
|
| 16 |
if (!file) return setError('Please upload a file');
|
| 17 |
setLoading(true); setError(''); setResult(null);
|
|
|
|
| 18 |
const fd = new FormData();
|
| 19 |
fd.append('file', file);
|
| 20 |
fd.append('clusters', clusters);
|
|
|
|
| 21 |
try {
|
| 22 |
const res = await axios.post('/api/clustering', fd);
|
| 23 |
setResult(res.data);
|
|
|
|
| 28 |
|
| 29 |
return (
|
| 30 |
<div className="max-w-3xl mx-auto">
|
| 31 |
+
<PageHeader icon={PieChart} title="Data Clusters" subtitle="Unsupervised grouping of multivariate datasets using K-Means algorithm." />
|
| 32 |
|
| 33 |
+
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-6">
|
| 34 |
<UploadZone accept=".csv,.xlsx" onChange={(e) => setFile(e.target.files[0])} label="Upload Data Structure" sublabel=".CSV or .XLSX datasets" />
|
|
|
|
|
|
|
| 35 |
<div>
|
| 36 |
+
<label className="block text-sm font-semibold text-slate-300 mb-2.5">Cluster Centroids (K)</label>
|
| 37 |
+
<input type="number" value={clusters} onChange={(e) => setClusters(e.target.value)} min="2" max="10" className="quantum-input w-32" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
</div>
|
|
|
|
| 39 |
<SubmitButton loading={loading}>
|
| 40 |
<PieChart size={18} /> Map Clusters
|
| 41 |
</SubmitButton>
|
|
|
|
| 45 |
|
| 46 |
{result && (
|
| 47 |
<ResultBox>
|
| 48 |
+
<SectionLabel>Clustering Visualization</SectionLabel>
|
| 49 |
+
<motion.div initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} className="bg-white p-4 rounded-2xl">
|
| 50 |
<img src={`data:image/png;base64,${result.plot}`} alt="Cluster Plot" className="w-full rounded-xl" />
|
| 51 |
+
</motion.div>
|
| 52 |
{result.cluster_info && (
|
| 53 |
<div className="mt-5 grid grid-cols-2 sm:grid-cols-3 gap-3">
|
| 54 |
+
{Object.entries(result.cluster_info).map(([k, v], i) => (
|
| 55 |
+
<motion.div key={k} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: i * 0.1 }}
|
| 56 |
+
className="stat-glow p-4 rounded-2xl bg-white/[0.02] border border-white/[0.05]">
|
| 57 |
+
<span className="text-[10px] font-bold text-purple-400 uppercase tracking-widest">Cluster {k}</span>
|
| 58 |
+
<p className="text-xl font-black mt-1">{v}</p>
|
| 59 |
+
<span className="text-[11px] text-slate-500">Entities</span>
|
| 60 |
+
</motion.div>
|
| 61 |
))}
|
| 62 |
</div>
|
| 63 |
)}
|
frontend/src/pages/DbscanLab.jsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
import { Braces } from 'lucide-react';
|
| 4 |
-
import {
|
|
|
|
| 5 |
|
| 6 |
export default function DbscanLab() {
|
| 7 |
const [file, setFile] = useState(null);
|
|
@@ -15,12 +16,10 @@ export default function DbscanLab() {
|
|
| 15 |
e.preventDefault();
|
| 16 |
if (!file) return setError('Please upload a file');
|
| 17 |
setLoading(true); setError(''); setResult(null);
|
| 18 |
-
|
| 19 |
const fd = new FormData();
|
| 20 |
fd.append('file', file);
|
| 21 |
fd.append('eps', eps);
|
| 22 |
fd.append('min_samples', minSamples);
|
| 23 |
-
|
| 24 |
try {
|
| 25 |
const res = await axios.post('/api/dbscan', fd);
|
| 26 |
setResult(res.data);
|
|
@@ -31,23 +30,20 @@ export default function DbscanLab() {
|
|
| 31 |
|
| 32 |
return (
|
| 33 |
<div className="max-w-3xl mx-auto">
|
| 34 |
-
<PageHeader title="DBSCAN Lab" subtitle="Density-based clustering to identify complex patterns and outliers." />
|
| 35 |
|
| 36 |
-
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-
|
| 37 |
<UploadZone accept=".csv,.xlsx" onChange={(e) => setFile(e.target.files[0])} label="Upload Data Structure" sublabel=".CSV or .XLSX datasets" />
|
| 38 |
-
{file && <p className="text-sm text-cyan-400 text-center font-medium">📊 {file.name}</p>}
|
| 39 |
-
|
| 40 |
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
| 41 |
<div>
|
| 42 |
-
<label className="block text-sm font-semibold text-slate-300 mb-2">Epsilon (eps)</label>
|
| 43 |
<input type="number" value={eps} onChange={(e) => setEps(e.target.value)} step="0.01" min="0.01" className="quantum-input" />
|
| 44 |
</div>
|
| 45 |
<div>
|
| 46 |
-
<label className="block text-sm font-semibold text-slate-300 mb-2">Min Samples</label>
|
| 47 |
<input type="number" value={minSamples} onChange={(e) => setMinSamples(e.target.value)} min="1" className="quantum-input" />
|
| 48 |
</div>
|
| 49 |
</div>
|
| 50 |
-
|
| 51 |
<SubmitButton loading={loading}>
|
| 52 |
<Braces size={18} /> Run DBSCAN
|
| 53 |
</SubmitButton>
|
|
@@ -57,17 +53,19 @@ export default function DbscanLab() {
|
|
| 57 |
|
| 58 |
{result && (
|
| 59 |
<ResultBox>
|
| 60 |
-
<
|
| 61 |
-
<div className="bg-white p-4 rounded-2xl">
|
| 62 |
<img src={`data:image/png;base64,${result.plot}`} alt="DBSCAN Plot" className="w-full rounded-xl" />
|
| 63 |
-
</div>
|
| 64 |
{result.cluster_info && (
|
| 65 |
<div className="mt-5 grid grid-cols-2 sm:grid-cols-3 gap-3">
|
| 66 |
-
{Object.entries(result.cluster_info).map(([k, v]) => (
|
| 67 |
-
<div key={k}
|
| 68 |
-
|
| 69 |
-
<
|
| 70 |
-
|
|
|
|
|
|
|
| 71 |
))}
|
| 72 |
</div>
|
| 73 |
)}
|
|
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
import { Braces } from 'lucide-react';
|
| 4 |
+
import { motion } from 'framer-motion';
|
| 5 |
+
import { PageHeader, ResultBox, ErrorBox, SubmitButton, UploadZone, SectionLabel } from '../components/UI';
|
| 6 |
|
| 7 |
export default function DbscanLab() {
|
| 8 |
const [file, setFile] = useState(null);
|
|
|
|
| 16 |
e.preventDefault();
|
| 17 |
if (!file) return setError('Please upload a file');
|
| 18 |
setLoading(true); setError(''); setResult(null);
|
|
|
|
| 19 |
const fd = new FormData();
|
| 20 |
fd.append('file', file);
|
| 21 |
fd.append('eps', eps);
|
| 22 |
fd.append('min_samples', minSamples);
|
|
|
|
| 23 |
try {
|
| 24 |
const res = await axios.post('/api/dbscan', fd);
|
| 25 |
setResult(res.data);
|
|
|
|
| 30 |
|
| 31 |
return (
|
| 32 |
<div className="max-w-3xl mx-auto">
|
| 33 |
+
<PageHeader icon={Braces} title="DBSCAN Lab" subtitle="Density-based spatial clustering to identify complex patterns and outliers." />
|
| 34 |
|
| 35 |
+
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-6">
|
| 36 |
<UploadZone accept=".csv,.xlsx" onChange={(e) => setFile(e.target.files[0])} label="Upload Data Structure" sublabel=".CSV or .XLSX datasets" />
|
|
|
|
|
|
|
| 37 |
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
| 38 |
<div>
|
| 39 |
+
<label className="block text-sm font-semibold text-slate-300 mb-2.5">Epsilon (eps)</label>
|
| 40 |
<input type="number" value={eps} onChange={(e) => setEps(e.target.value)} step="0.01" min="0.01" className="quantum-input" />
|
| 41 |
</div>
|
| 42 |
<div>
|
| 43 |
+
<label className="block text-sm font-semibold text-slate-300 mb-2.5">Min Samples</label>
|
| 44 |
<input type="number" value={minSamples} onChange={(e) => setMinSamples(e.target.value)} min="1" className="quantum-input" />
|
| 45 |
</div>
|
| 46 |
</div>
|
|
|
|
| 47 |
<SubmitButton loading={loading}>
|
| 48 |
<Braces size={18} /> Run DBSCAN
|
| 49 |
</SubmitButton>
|
|
|
|
| 53 |
|
| 54 |
{result && (
|
| 55 |
<ResultBox>
|
| 56 |
+
<SectionLabel>DBSCAN Visualization</SectionLabel>
|
| 57 |
+
<motion.div initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} className="bg-white p-4 rounded-2xl">
|
| 58 |
<img src={`data:image/png;base64,${result.plot}`} alt="DBSCAN Plot" className="w-full rounded-xl" />
|
| 59 |
+
</motion.div>
|
| 60 |
{result.cluster_info && (
|
| 61 |
<div className="mt-5 grid grid-cols-2 sm:grid-cols-3 gap-3">
|
| 62 |
+
{Object.entries(result.cluster_info).map(([k, v], i) => (
|
| 63 |
+
<motion.div key={k} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: i * 0.1 }}
|
| 64 |
+
className="stat-glow p-4 rounded-2xl bg-white/[0.02] border border-white/[0.05]">
|
| 65 |
+
<span className="text-[10px] font-bold text-purple-400 uppercase tracking-widest">{k === '-1' ? 'Noise Points' : `Cluster ${k}`}</span>
|
| 66 |
+
<p className="text-xl font-black mt-1">{v}</p>
|
| 67 |
+
<span className="text-[11px] text-slate-500">Entities</span>
|
| 68 |
+
</motion.div>
|
| 69 |
))}
|
| 70 |
</div>
|
| 71 |
)}
|
frontend/src/pages/EmpathyEngine.jsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
-
import { Smile, Frown, Meh, Mic } from 'lucide-react';
|
| 4 |
-
import {
|
|
|
|
| 5 |
|
| 6 |
export default function EmpathyEngine() {
|
| 7 |
const [tab, setTab] = useState('text');
|
|
@@ -14,7 +15,6 @@ export default function EmpathyEngine() {
|
|
| 14 |
const handleSubmit = async (e) => {
|
| 15 |
e.preventDefault();
|
| 16 |
setLoading(true); setError(''); setResult(null);
|
| 17 |
-
|
| 18 |
try {
|
| 19 |
let res;
|
| 20 |
if (tab === 'text') {
|
|
@@ -30,43 +30,38 @@ export default function EmpathyEngine() {
|
|
| 30 |
} finally { setLoading(false); }
|
| 31 |
};
|
| 32 |
|
| 33 |
-
const
|
| 34 |
const l = (label || '').toLowerCase();
|
| 35 |
-
if (l === 'positive') return
|
| 36 |
-
if (l === 'negative') return
|
| 37 |
-
return
|
| 38 |
-
};
|
| 39 |
-
|
| 40 |
-
const sentimentColor = (label) => {
|
| 41 |
-
const l = (label || '').toLowerCase();
|
| 42 |
-
if (l === 'positive') return 'text-emerald-400';
|
| 43 |
-
if (l === 'negative') return 'text-red-400';
|
| 44 |
-
return 'text-cyan-400';
|
| 45 |
};
|
| 46 |
|
| 47 |
return (
|
| 48 |
<div className="max-w-2xl mx-auto">
|
| 49 |
-
<PageHeader title="Empathy Engine" subtitle="Contextual sentiment analysis for text and vocal recordings." />
|
| 50 |
|
| 51 |
-
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-
|
| 52 |
{/* Tabs */}
|
| 53 |
-
<div className="flex gap-
|
| 54 |
{['text', 'voice'].map(t => (
|
| 55 |
<button
|
| 56 |
key={t}
|
| 57 |
type="button"
|
| 58 |
onClick={() => setTab(t)}
|
| 59 |
-
className={`px-4 py-2 rounded-xl text-sm font-semibold transition-all ${tab === t
|
|
|
|
|
|
|
| 60 |
}`}
|
| 61 |
>
|
| 62 |
-
{t === 'text' ? 'Text Analysis' : 'Vocal Analysis'}
|
| 63 |
</button>
|
| 64 |
))}
|
| 65 |
</div>
|
| 66 |
|
| 67 |
{tab === 'text' ? (
|
| 68 |
<div>
|
| 69 |
-
<label className="block text-sm font-semibold text-slate-300 mb-2">Input Text</label>
|
| 70 |
<textarea
|
| 71 |
value={text}
|
| 72 |
onChange={(e) => setText(e.target.value)}
|
|
@@ -75,40 +70,64 @@ export default function EmpathyEngine() {
|
|
| 75 |
/>
|
| 76 |
</div>
|
| 77 |
) : (
|
| 78 |
-
<
|
| 79 |
-
<UploadZone accept="audio/*" onChange={(e) => setFile(e.target.files[0])} label="Upload Voice Recording" sublabel="WAV or MP3 format" />
|
| 80 |
-
{file && <p className="text-sm text-cyan-400 text-center mt-2 font-medium">🎙 {file.name}</p>}
|
| 81 |
-
</div>
|
| 82 |
)}
|
| 83 |
|
| 84 |
<SubmitButton loading={loading}>
|
| 85 |
-
<
|
| 86 |
</SubmitButton>
|
| 87 |
</form>
|
| 88 |
|
| 89 |
<ErrorBox message={error} />
|
| 90 |
|
| 91 |
-
{result && (
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
<
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
</div>
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
)}
|
| 112 |
</div>
|
| 113 |
);
|
| 114 |
}
|
|
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
+
import { Smile, Frown, Meh, Mic, HeartPulse } from 'lucide-react';
|
| 4 |
+
import { motion } from 'framer-motion';
|
| 5 |
+
import { PageHeader, ResultBox, ErrorBox, SubmitButton, UploadZone, SectionLabel } from '../components/UI';
|
| 6 |
|
| 7 |
export default function EmpathyEngine() {
|
| 8 |
const [tab, setTab] = useState('text');
|
|
|
|
| 15 |
const handleSubmit = async (e) => {
|
| 16 |
e.preventDefault();
|
| 17 |
setLoading(true); setError(''); setResult(null);
|
|
|
|
| 18 |
try {
|
| 19 |
let res;
|
| 20 |
if (tab === 'text') {
|
|
|
|
| 30 |
} finally { setLoading(false); }
|
| 31 |
};
|
| 32 |
|
| 33 |
+
const sentimentConfig = (label) => {
|
| 34 |
const l = (label || '').toLowerCase();
|
| 35 |
+
if (l === 'positive') return { icon: Smile, color: 'text-emerald-400', bg: 'bg-emerald-500/10', border: 'border-emerald-500/20' };
|
| 36 |
+
if (l === 'negative') return { icon: Frown, color: 'text-red-400', bg: 'bg-red-500/10', border: 'border-red-500/20' };
|
| 37 |
+
return { icon: Meh, color: 'text-cyan-400', bg: 'bg-cyan-500/10', border: 'border-cyan-500/20' };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
};
|
| 39 |
|
| 40 |
return (
|
| 41 |
<div className="max-w-2xl mx-auto">
|
| 42 |
+
<PageHeader icon={HeartPulse} title="Empathy Engine" subtitle="Contextual sentiment analysis for text and vocal recordings." />
|
| 43 |
|
| 44 |
+
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-6">
|
| 45 |
{/* Tabs */}
|
| 46 |
+
<div className="flex gap-1 p-1 rounded-2xl bg-white/[0.03] border border-white/[0.05]">
|
| 47 |
{['text', 'voice'].map(t => (
|
| 48 |
<button
|
| 49 |
key={t}
|
| 50 |
type="button"
|
| 51 |
onClick={() => setTab(t)}
|
| 52 |
+
className={`flex-1 px-4 py-2.5 rounded-xl text-sm font-semibold transition-all ${tab === t
|
| 53 |
+
? 'bg-gradient-to-r from-cyan-500/15 to-purple-500/10 text-cyan-400 shadow-sm'
|
| 54 |
+
: 'text-slate-500 hover:text-slate-300'
|
| 55 |
}`}
|
| 56 |
>
|
| 57 |
+
{t === 'text' ? '📝 Text Analysis' : '🎙 Vocal Analysis'}
|
| 58 |
</button>
|
| 59 |
))}
|
| 60 |
</div>
|
| 61 |
|
| 62 |
{tab === 'text' ? (
|
| 63 |
<div>
|
| 64 |
+
<label className="block text-sm font-semibold text-slate-300 mb-2.5">Input Text</label>
|
| 65 |
<textarea
|
| 66 |
value={text}
|
| 67 |
onChange={(e) => setText(e.target.value)}
|
|
|
|
| 70 |
/>
|
| 71 |
</div>
|
| 72 |
) : (
|
| 73 |
+
<UploadZone accept="audio/*" onChange={(e) => setFile(e.target.files[0])} label="Upload Voice Recording" sublabel="WAV or MP3 format" />
|
|
|
|
|
|
|
|
|
|
| 74 |
)}
|
| 75 |
|
| 76 |
<SubmitButton loading={loading}>
|
| 77 |
+
<HeartPulse size={18} /> Analyze Sentiment
|
| 78 |
</SubmitButton>
|
| 79 |
</form>
|
| 80 |
|
| 81 |
<ErrorBox message={error} />
|
| 82 |
|
| 83 |
+
{result && (() => {
|
| 84 |
+
const cfg = sentimentConfig(result.result);
|
| 85 |
+
const Icon = cfg.icon;
|
| 86 |
+
return (
|
| 87 |
+
<ResultBox>
|
| 88 |
+
<SectionLabel>Engine Output</SectionLabel>
|
| 89 |
+
|
| 90 |
+
{result.transcript && (
|
| 91 |
+
<div className="mb-5 p-4 rounded-2xl bg-black/20 border border-white/[0.05]">
|
| 92 |
+
<span className="text-[10px] font-bold text-cyan-400 uppercase tracking-widest">Transcription</span>
|
| 93 |
+
<p className="mt-1.5 text-slate-300 text-sm leading-relaxed">"{result.transcript}"</p>
|
| 94 |
+
</div>
|
| 95 |
+
)}
|
| 96 |
+
|
| 97 |
+
<div className={`flex items-center gap-6 p-5 rounded-2xl ${cfg.bg} border ${cfg.border}`}>
|
| 98 |
+
<div className="flex-1">
|
| 99 |
+
<span className="text-xs text-slate-400 font-medium">Detected Sentiment</span>
|
| 100 |
+
<motion.p
|
| 101 |
+
initial={{ scale: 0.5 }}
|
| 102 |
+
animate={{ scale: 1 }}
|
| 103 |
+
transition={{ type: 'spring', stiffness: 200 }}
|
| 104 |
+
className={`text-3xl font-black capitalize ${cfg.color}`}
|
| 105 |
+
>
|
| 106 |
+
{result.result}
|
| 107 |
+
</motion.p>
|
| 108 |
+
<div className="mt-2 flex items-center gap-2">
|
| 109 |
+
<div className="h-1.5 flex-1 bg-black/20 rounded-full overflow-hidden">
|
| 110 |
+
<motion.div
|
| 111 |
+
initial={{ width: 0 }}
|
| 112 |
+
animate={{ width: `${result.score}%` }}
|
| 113 |
+
transition={{ duration: 1, delay: 0.3 }}
|
| 114 |
+
className="h-full bg-gradient-to-r from-cyan-500 to-purple-500 rounded-full"
|
| 115 |
+
/>
|
| 116 |
+
</div>
|
| 117 |
+
<span className="text-xs text-slate-400 font-semibold">{result.score}%</span>
|
| 118 |
+
</div>
|
| 119 |
+
</div>
|
| 120 |
+
<motion.div
|
| 121 |
+
initial={{ rotate: -20, scale: 0 }}
|
| 122 |
+
animate={{ rotate: 0, scale: 1 }}
|
| 123 |
+
transition={{ type: 'spring', delay: 0.2 }}
|
| 124 |
+
>
|
| 125 |
+
<Icon size={52} className={cfg.color} />
|
| 126 |
+
</motion.div>
|
| 127 |
</div>
|
| 128 |
+
</ResultBox>
|
| 129 |
+
);
|
| 130 |
+
})()}
|
|
|
|
| 131 |
</div>
|
| 132 |
);
|
| 133 |
}
|
frontend/src/pages/GenderDiscovery.jsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
-
import { Upload } from 'lucide-react';
|
| 4 |
-
import {
|
|
|
|
| 5 |
|
| 6 |
export default function GenderDiscovery() {
|
| 7 |
const [file, setFile] = useState(null);
|
|
@@ -22,10 +23,8 @@ export default function GenderDiscovery() {
|
|
| 22 |
e.preventDefault();
|
| 23 |
if (!file) return setError('Please select an image');
|
| 24 |
setLoading(true); setError(''); setResult('');
|
| 25 |
-
|
| 26 |
const fd = new FormData();
|
| 27 |
fd.append('image', file);
|
| 28 |
-
|
| 29 |
try {
|
| 30 |
const res = await axios.post('/api/gender', fd);
|
| 31 |
setResult(res.data.result);
|
|
@@ -36,21 +35,29 @@ export default function GenderDiscovery() {
|
|
| 36 |
|
| 37 |
return (
|
| 38 |
<div className="max-w-2xl mx-auto">
|
| 39 |
-
<PageHeader title="Gender Discovery" subtitle="Upload a visual specimen for neural gender classification." />
|
| 40 |
-
|
| 41 |
-
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-5">
|
| 42 |
-
<UploadZone accept="image/*" name="image" onChange={handleFile} label="Upload Image" sublabel="PNG, JPG or WEBP (max 10MB)" />
|
| 43 |
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
)}
|
| 49 |
|
| 50 |
-
{file && <p className="text-sm text-cyan-400 text-center font-medium">📎 {file.name}</p>}
|
| 51 |
-
|
| 52 |
<SubmitButton loading={loading}>
|
| 53 |
-
<
|
| 54 |
</SubmitButton>
|
| 55 |
</form>
|
| 56 |
|
|
@@ -58,11 +65,21 @@ export default function GenderDiscovery() {
|
|
| 58 |
|
| 59 |
{result && (
|
| 60 |
<ResultBox>
|
| 61 |
-
<
|
| 62 |
-
<div className="flex justify-between
|
| 63 |
-
<span className="text-slate-400">Detected Gender</span>
|
| 64 |
-
<span
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
</div>
|
|
|
|
|
|
|
|
|
|
| 66 |
</ResultBox>
|
| 67 |
)}
|
| 68 |
</div>
|
|
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
+
import { Upload, User, Scan } from 'lucide-react';
|
| 4 |
+
import { motion } from 'framer-motion';
|
| 5 |
+
import { PageHeader, ResultBox, ErrorBox, SubmitButton, UploadZone, SectionLabel } from '../components/UI';
|
| 6 |
|
| 7 |
export default function GenderDiscovery() {
|
| 8 |
const [file, setFile] = useState(null);
|
|
|
|
| 23 |
e.preventDefault();
|
| 24 |
if (!file) return setError('Please select an image');
|
| 25 |
setLoading(true); setError(''); setResult('');
|
|
|
|
| 26 |
const fd = new FormData();
|
| 27 |
fd.append('image', file);
|
|
|
|
| 28 |
try {
|
| 29 |
const res = await axios.post('/api/gender', fd);
|
| 30 |
setResult(res.data.result);
|
|
|
|
| 35 |
|
| 36 |
return (
|
| 37 |
<div className="max-w-2xl mx-auto">
|
| 38 |
+
<PageHeader icon={User} title="Gender Discovery" subtitle="Upload a visual specimen for neural gender classification using Vision Transformer." />
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
+
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-6">
|
| 41 |
+
{!preview ? (
|
| 42 |
+
<UploadZone accept="image/*" name="image" onChange={handleFile} label="Upload Image" sublabel="PNG, JPG or WEBP (max 10MB)" />
|
| 43 |
+
) : (
|
| 44 |
+
<motion.div
|
| 45 |
+
initial={{ opacity: 0, scale: 0.95 }}
|
| 46 |
+
animate={{ opacity: 1, scale: 1 }}
|
| 47 |
+
className="relative rounded-2xl overflow-hidden border border-white/10 group"
|
| 48 |
+
>
|
| 49 |
+
<img src={preview} alt="Preview" className="w-full h-60 object-cover" />
|
| 50 |
+
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity flex items-end p-4">
|
| 51 |
+
<label className="text-xs text-white/80 cursor-pointer hover:text-white font-medium">
|
| 52 |
+
Click to change →
|
| 53 |
+
<input type="file" accept="image/*" onChange={handleFile} className="hidden" />
|
| 54 |
+
</label>
|
| 55 |
+
</div>
|
| 56 |
+
</motion.div>
|
| 57 |
)}
|
| 58 |
|
|
|
|
|
|
|
| 59 |
<SubmitButton loading={loading}>
|
| 60 |
+
<Scan size={18} /> Run Discovery Engine
|
| 61 |
</SubmitButton>
|
| 62 |
</form>
|
| 63 |
|
|
|
|
| 65 |
|
| 66 |
{result && (
|
| 67 |
<ResultBox>
|
| 68 |
+
<SectionLabel>Engine Output</SectionLabel>
|
| 69 |
+
<div className="flex items-center justify-between p-5 rounded-2xl bg-white/[0.02] border border-white/[0.05]">
|
| 70 |
+
<span className="text-sm text-slate-400 font-medium">Detected Gender</span>
|
| 71 |
+
<motion.span
|
| 72 |
+
initial={{ scale: 0.5, opacity: 0 }}
|
| 73 |
+
animate={{ scale: 1, opacity: 1 }}
|
| 74 |
+
transition={{ type: 'spring', stiffness: 200 }}
|
| 75 |
+
className="text-3xl font-black gradient-text"
|
| 76 |
+
>
|
| 77 |
+
{result}
|
| 78 |
+
</motion.span>
|
| 79 |
</div>
|
| 80 |
+
<p className="text-xs text-slate-500 mt-3 flex items-center gap-1.5">
|
| 81 |
+
<Scan size={12} /> Vision model identifies facial features for classification
|
| 82 |
+
</p>
|
| 83 |
</ResultBox>
|
| 84 |
)}
|
| 85 |
</div>
|
frontend/src/pages/NeuralTranslate.jsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
-
import { Languages, Copy, Check } from 'lucide-react';
|
| 4 |
-
import { PageHeader, ResultBox, ErrorBox, SubmitButton } from '../components/UI';
|
| 5 |
|
| 6 |
export default function NeuralTranslate() {
|
| 7 |
const [text, setText] = useState('');
|
|
@@ -30,20 +30,24 @@ export default function NeuralTranslate() {
|
|
| 30 |
|
| 31 |
return (
|
| 32 |
<div className="max-w-2xl mx-auto">
|
| 33 |
-
<PageHeader title="Neural Translate" subtitle="Advanced English
|
| 34 |
|
| 35 |
-
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-
|
| 36 |
<div>
|
| 37 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
<textarea
|
| 39 |
value={text}
|
| 40 |
onChange={(e) => setText(e.target.value)}
|
| 41 |
placeholder="Type or paste English text here..."
|
| 42 |
-
className="quantum-input min-h-[
|
| 43 |
/>
|
| 44 |
</div>
|
| 45 |
<SubmitButton loading={loading}>
|
| 46 |
-
<Languages size={18} /> Translate
|
| 47 |
</SubmitButton>
|
| 48 |
</form>
|
| 49 |
|
|
@@ -51,14 +55,17 @@ export default function NeuralTranslate() {
|
|
| 51 |
|
| 52 |
{result && (
|
| 53 |
<ResultBox>
|
| 54 |
-
<div className="flex items-center justify-between mb-
|
| 55 |
-
<
|
| 56 |
-
<button
|
| 57 |
-
|
|
|
|
|
|
|
|
|
|
| 58 |
{copied ? 'Copied!' : 'Copy'}
|
| 59 |
</button>
|
| 60 |
</div>
|
| 61 |
-
<p className="text-xl text-slate-200 leading-
|
| 62 |
</ResultBox>
|
| 63 |
)}
|
| 64 |
</div>
|
|
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
+
import { Languages, Copy, Check, ArrowRight } from 'lucide-react';
|
| 4 |
+
import { PageHeader, ResultBox, ErrorBox, SubmitButton, SectionLabel } from '../components/UI';
|
| 5 |
|
| 6 |
export default function NeuralTranslate() {
|
| 7 |
const [text, setText] = useState('');
|
|
|
|
| 30 |
|
| 31 |
return (
|
| 32 |
<div className="max-w-2xl mx-auto">
|
| 33 |
+
<PageHeader icon={Languages} title="Neural Translate" subtitle="Advanced English → Urdu translation using sequence-to-sequence models." />
|
| 34 |
|
| 35 |
+
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-6">
|
| 36 |
<div>
|
| 37 |
+
<div className="flex items-center gap-2 mb-2.5">
|
| 38 |
+
<span className="text-xs font-bold bg-blue-500/10 text-blue-400 px-2 py-0.5 rounded-lg">English</span>
|
| 39 |
+
<ArrowRight size={14} className="text-slate-600" />
|
| 40 |
+
<span className="text-xs font-bold bg-emerald-500/10 text-emerald-400 px-2 py-0.5 rounded-lg">Urdu</span>
|
| 41 |
+
</div>
|
| 42 |
<textarea
|
| 43 |
value={text}
|
| 44 |
onChange={(e) => setText(e.target.value)}
|
| 45 |
placeholder="Type or paste English text here..."
|
| 46 |
+
className="quantum-input min-h-[150px] resize-y"
|
| 47 |
/>
|
| 48 |
</div>
|
| 49 |
<SubmitButton loading={loading}>
|
| 50 |
+
<Languages size={18} /> Translate
|
| 51 |
</SubmitButton>
|
| 52 |
</form>
|
| 53 |
|
|
|
|
| 55 |
|
| 56 |
{result && (
|
| 57 |
<ResultBox>
|
| 58 |
+
<div className="flex items-center justify-between mb-4">
|
| 59 |
+
<SectionLabel>Urdu Translation</SectionLabel>
|
| 60 |
+
<button
|
| 61 |
+
onClick={copyText}
|
| 62 |
+
className="flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-xl bg-white/[0.04] border border-white/[0.06] text-slate-400 hover:text-white hover:border-white/10 transition-all"
|
| 63 |
+
>
|
| 64 |
+
{copied ? <Check size={13} className="text-emerald-400" /> : <Copy size={13} />}
|
| 65 |
{copied ? 'Copied!' : 'Copy'}
|
| 66 |
</button>
|
| 67 |
</div>
|
| 68 |
+
<p className="text-xl text-slate-200 leading-loose font-medium text-right" dir="rtl">{result}</p>
|
| 69 |
</ResultBox>
|
| 70 |
)}
|
| 71 |
</div>
|
frontend/src/pages/TextSynthesis.jsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
-
import { PenTool, Copy, Check } from 'lucide-react';
|
| 4 |
-
import { PageHeader, ResultBox, ErrorBox, SubmitButton } from '../components/UI';
|
| 5 |
|
| 6 |
export default function TextSynthesis() {
|
| 7 |
const [prompt, setPrompt] = useState('');
|
|
@@ -30,20 +30,20 @@ export default function TextSynthesis() {
|
|
| 30 |
|
| 31 |
return (
|
| 32 |
<div className="max-w-2xl mx-auto">
|
| 33 |
-
<PageHeader title="Text Synthesis" subtitle="Creative language generation powered by GPT-2 architecture." />
|
| 34 |
|
| 35 |
-
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-
|
| 36 |
<div>
|
| 37 |
-
<label className="block text-sm font-semibold text-slate-300 mb-2">Synthesis Prompt</label>
|
| 38 |
<textarea
|
| 39 |
value={prompt}
|
| 40 |
onChange={(e) => setPrompt(e.target.value)}
|
| 41 |
placeholder="Enter a seed sentence for the AI to expand upon..."
|
| 42 |
-
className="quantum-input min-h-[
|
| 43 |
/>
|
| 44 |
</div>
|
| 45 |
<SubmitButton loading={loading}>
|
| 46 |
-
<
|
| 47 |
</SubmitButton>
|
| 48 |
</form>
|
| 49 |
|
|
@@ -51,14 +51,17 @@ export default function TextSynthesis() {
|
|
| 51 |
|
| 52 |
{result && (
|
| 53 |
<ResultBox>
|
| 54 |
-
<div className="flex items-center justify-between mb-
|
| 55 |
-
<
|
| 56 |
-
<button
|
| 57 |
-
|
|
|
|
|
|
|
|
|
|
| 58 |
{copied ? 'Copied!' : 'Copy'}
|
| 59 |
</button>
|
| 60 |
</div>
|
| 61 |
-
<p className="text-slate-200 leading-relaxed">{result}</p>
|
| 62 |
</ResultBox>
|
| 63 |
)}
|
| 64 |
</div>
|
|
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
+
import { PenTool, Copy, Check, Wand2 } from 'lucide-react';
|
| 4 |
+
import { PageHeader, ResultBox, ErrorBox, SubmitButton, SectionLabel } from '../components/UI';
|
| 5 |
|
| 6 |
export default function TextSynthesis() {
|
| 7 |
const [prompt, setPrompt] = useState('');
|
|
|
|
| 30 |
|
| 31 |
return (
|
| 32 |
<div className="max-w-2xl mx-auto">
|
| 33 |
+
<PageHeader icon={PenTool} title="Text Synthesis" subtitle="Creative language generation powered by GPT-2 neural architecture." />
|
| 34 |
|
| 35 |
+
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-6">
|
| 36 |
<div>
|
| 37 |
+
<label className="block text-sm font-semibold text-slate-300 mb-2.5">Synthesis Prompt</label>
|
| 38 |
<textarea
|
| 39 |
value={prompt}
|
| 40 |
onChange={(e) => setPrompt(e.target.value)}
|
| 41 |
placeholder="Enter a seed sentence for the AI to expand upon..."
|
| 42 |
+
className="quantum-input min-h-[150px] resize-y"
|
| 43 |
/>
|
| 44 |
</div>
|
| 45 |
<SubmitButton loading={loading}>
|
| 46 |
+
<Wand2 size={18} /> Synthesize Text
|
| 47 |
</SubmitButton>
|
| 48 |
</form>
|
| 49 |
|
|
|
|
| 51 |
|
| 52 |
{result && (
|
| 53 |
<ResultBox>
|
| 54 |
+
<div className="flex items-center justify-between mb-4">
|
| 55 |
+
<SectionLabel>Generated Output</SectionLabel>
|
| 56 |
+
<button
|
| 57 |
+
onClick={copyText}
|
| 58 |
+
className="flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-xl bg-white/[0.04] border border-white/[0.06] text-slate-400 hover:text-white hover:border-white/10 transition-all"
|
| 59 |
+
>
|
| 60 |
+
{copied ? <Check size={13} className="text-emerald-400" /> : <Copy size={13} />}
|
| 61 |
{copied ? 'Copied!' : 'Copy'}
|
| 62 |
</button>
|
| 63 |
</div>
|
| 64 |
+
<p className="text-slate-200 leading-relaxed text-[15px]">{result}</p>
|
| 65 |
</ResultBox>
|
| 66 |
)}
|
| 67 |
</div>
|
frontend/src/pages/ZeroShotLab.jsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
-
import { Target } from 'lucide-react';
|
| 4 |
-
import {
|
|
|
|
| 5 |
|
| 6 |
export default function ZeroShotLab() {
|
| 7 |
const [text, setText] = useState('');
|
|
@@ -22,13 +23,21 @@ export default function ZeroShotLab() {
|
|
| 22 |
} finally { setLoading(false); }
|
| 23 |
};
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
return (
|
| 26 |
<div className="max-w-2xl mx-auto">
|
| 27 |
-
<PageHeader title="Zero-Shot Lab" subtitle="BART-based classification for any unseen categories." />
|
| 28 |
|
| 29 |
-
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-
|
| 30 |
<div>
|
| 31 |
-
<label className="block text-sm font-semibold text-slate-300 mb-2">Input Text</label>
|
| 32 |
<textarea
|
| 33 |
value={text}
|
| 34 |
onChange={(e) => setText(e.target.value)}
|
|
@@ -38,7 +47,7 @@ export default function ZeroShotLab() {
|
|
| 38 |
/>
|
| 39 |
</div>
|
| 40 |
<div>
|
| 41 |
-
<label className="block text-sm font-semibold text-slate-300 mb-2">Candidate Labels</label>
|
| 42 |
<input
|
| 43 |
type="text"
|
| 44 |
value={labels}
|
|
@@ -47,7 +56,7 @@ export default function ZeroShotLab() {
|
|
| 47 |
className="quantum-input"
|
| 48 |
required
|
| 49 |
/>
|
| 50 |
-
<p className="text-xs text-slate-500 mt-1">Separate labels with commas</p>
|
| 51 |
</div>
|
| 52 |
<SubmitButton loading={loading}>
|
| 53 |
<Target size={18} /> Classify Text
|
|
@@ -58,24 +67,41 @@ export default function ZeroShotLab() {
|
|
| 58 |
|
| 59 |
{result && (
|
| 60 |
<ResultBox>
|
| 61 |
-
<
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
<span className="text-sm text-slate-500">{result.best_score}% confidence</span>
|
| 66 |
</div>
|
| 67 |
-
|
|
|
|
| 68 |
{result.results?.map((r, i) => (
|
| 69 |
-
<div
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
/>
|
| 76 |
</div>
|
| 77 |
-
<span className="text-xs text-slate-400 w-
|
| 78 |
-
</div>
|
| 79 |
))}
|
| 80 |
</div>
|
| 81 |
</ResultBox>
|
|
|
|
| 1 |
import { useState } from 'react';
|
| 2 |
import axios from 'axios';
|
| 3 |
+
import { Target, BarChart3 } from 'lucide-react';
|
| 4 |
+
import { motion } from 'framer-motion';
|
| 5 |
+
import { PageHeader, ResultBox, ErrorBox, SubmitButton, SectionLabel } from '../components/UI';
|
| 6 |
|
| 7 |
export default function ZeroShotLab() {
|
| 8 |
const [text, setText] = useState('');
|
|
|
|
| 23 |
} finally { setLoading(false); }
|
| 24 |
};
|
| 25 |
|
| 26 |
+
const barColors = [
|
| 27 |
+
'from-cyan-500 to-blue-500',
|
| 28 |
+
'from-purple-500 to-pink-500',
|
| 29 |
+
'from-emerald-500 to-teal-500',
|
| 30 |
+
'from-amber-500 to-orange-500',
|
| 31 |
+
'from-red-500 to-rose-500',
|
| 32 |
+
];
|
| 33 |
+
|
| 34 |
return (
|
| 35 |
<div className="max-w-2xl mx-auto">
|
| 36 |
+
<PageHeader icon={Target} title="Zero-Shot Lab" subtitle="BART-based classification for any unseen categories without fine-tuning." />
|
| 37 |
|
| 38 |
+
<form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-6">
|
| 39 |
<div>
|
| 40 |
+
<label className="block text-sm font-semibold text-slate-300 mb-2.5">Input Text</label>
|
| 41 |
<textarea
|
| 42 |
value={text}
|
| 43 |
onChange={(e) => setText(e.target.value)}
|
|
|
|
| 47 |
/>
|
| 48 |
</div>
|
| 49 |
<div>
|
| 50 |
+
<label className="block text-sm font-semibold text-slate-300 mb-2.5">Candidate Labels</label>
|
| 51 |
<input
|
| 52 |
type="text"
|
| 53 |
value={labels}
|
|
|
|
| 56 |
className="quantum-input"
|
| 57 |
required
|
| 58 |
/>
|
| 59 |
+
<p className="text-xs text-slate-500 mt-1.5">Separate labels with commas</p>
|
| 60 |
</div>
|
| 61 |
<SubmitButton loading={loading}>
|
| 62 |
<Target size={18} /> Classify Text
|
|
|
|
| 67 |
|
| 68 |
{result && (
|
| 69 |
<ResultBox>
|
| 70 |
+
<SectionLabel>Classification Results</SectionLabel>
|
| 71 |
+
|
| 72 |
+
<div className="mb-5 p-5 rounded-2xl bg-gradient-to-br from-cyan-500/[0.08] to-purple-500/[0.04] border border-cyan-500/10 text-center">
|
| 73 |
+
<span className="text-xs text-slate-400 font-medium">Best Match</span>
|
| 74 |
+
<motion.p
|
| 75 |
+
initial={{ scale: 0.5 }}
|
| 76 |
+
animate={{ scale: 1 }}
|
| 77 |
+
transition={{ type: 'spring', stiffness: 200 }}
|
| 78 |
+
className="text-2xl font-black gradient-text mt-1"
|
| 79 |
+
>
|
| 80 |
+
{result.best_label}
|
| 81 |
+
</motion.p>
|
| 82 |
<span className="text-sm text-slate-500">{result.best_score}% confidence</span>
|
| 83 |
</div>
|
| 84 |
+
|
| 85 |
+
<div className="space-y-3">
|
| 86 |
{result.results?.map((r, i) => (
|
| 87 |
+
<motion.div
|
| 88 |
+
key={i}
|
| 89 |
+
initial={{ opacity: 0, x: -20 }}
|
| 90 |
+
animate={{ opacity: 1, x: 0 }}
|
| 91 |
+
transition={{ delay: i * 0.1 }}
|
| 92 |
+
className="flex items-center gap-3"
|
| 93 |
+
>
|
| 94 |
+
<span className="text-sm text-slate-300 w-28 truncate capitalize font-medium">{r.label}</span>
|
| 95 |
+
<div className="flex-1 h-3 bg-white/[0.04] rounded-full overflow-hidden">
|
| 96 |
+
<motion.div
|
| 97 |
+
initial={{ width: 0 }}
|
| 98 |
+
animate={{ width: `${r.score}%` }}
|
| 99 |
+
transition={{ duration: 0.8, delay: 0.3 + i * 0.1 }}
|
| 100 |
+
className={`h-full bg-gradient-to-r ${barColors[i % barColors.length]} rounded-full`}
|
| 101 |
/>
|
| 102 |
</div>
|
| 103 |
+
<span className="text-xs text-slate-400 w-14 text-right font-semibold">{r.score}%</span>
|
| 104 |
+
</motion.div>
|
| 105 |
))}
|
| 106 |
</div>
|
| 107 |
</ResultBox>
|