Download frontend/src/components/Terminal.jsx from parthib07/Portfolio: direct link, hf CLI and curl.
- Browser
- Download file 19.9 kB
-
https://huggingface.co/spaces/parthib07/Portfolio/resolve/main/frontend/src/components/Terminal.jsx
- Command line
-
hf download hf://spaces/parthib07/Portfolio/frontend/src/components/Terminal.jsx
-
curl -L -o Terminal.jsx https://huggingface.co/spaces/parthib07/Portfolio/resolve/main/frontend/src/components/Terminal.jsx
19.9 kB
| import { useState, useEffect, useRef } from 'react' | |
| import { motion, AnimatePresence } from 'framer-motion' | |
| import { FiTerminal, FiX, FiMinus, FiMaximize2 } from 'react-icons/fi' | |
| import resumePDF from '../assets/Resume.pdf' | |
| const PROFILE = { | |
| name: 'Parthib Karak', | |
| title: 'Software & AI Engineer', | |
| location: 'West Bengal, India', | |
| college: 'IEM Kolkata (B.Tech CSE)', | |
| cgpa: '8.9', | |
| email: 'parthibkarak2004@gmail.com', | |
| github: 'https://github.com/babaiii07', | |
| linkedin: 'https://www.linkedin.com/in/parthib-karak', | |
| leetcode: 'https://leetcode.com/u/Parthib_007', | |
| } | |
| const COMMANDS = { | |
| help: { | |
| desc: 'Show all available commands', | |
| run: () => [ | |
| { type: 'title', text: '📋 Available Commands' }, | |
| { type: 'divider' }, | |
| { type: 'cmd', text: 'whoami → Who is Parthib?' }, | |
| { type: 'cmd', text: 'skills → Technical skill stack' }, | |
| { type: 'cmd', text: 'projects → Featured AI projects' }, | |
| { type: 'cmd', text: 'experience → Work experience' }, | |
| { type: 'cmd', text: 'education → Academic background' }, | |
| { type: 'cmd', text: 'contact → Get in touch' }, | |
| { type: 'cmd', text: 'resume → Download resume' }, | |
| { type: 'cmd', text: 'leetcode → DSA stats' }, | |
| { type: 'cmd', text: 'hire → Why hire Parthib?' }, | |
| { type: 'cmd', text: 'clear → Clear terminal' }, | |
| { type: 'cmd', text: 'help → Show this menu' }, | |
| { type: 'divider' }, | |
| { type: 'info', text: '💡 Tip: Use ↑ ↓ arrow keys for command history' }, | |
| ], | |
| }, | |
| whoami: { | |
| desc: 'Who is Parthib?', | |
| run: () => [ | |
| { type: 'title', text: '👤 Parthib Karak' }, | |
| { type: 'divider' }, | |
| { type: 'kv', key: 'Role ', value: 'Software & AI Engineer' }, | |
| { type: 'kv', key: 'Location', value: 'West Bengal, India' }, | |
| { type: 'kv', key: 'College ', value: 'IEM Kolkata — B.Tech CSE' }, | |
| { type: 'kv', key: 'CGPA ', value: '8.9 / 10.0' }, | |
| { type: 'kv', key: 'Status ', value: '🟢 Open to Opportunities' }, | |
| { type: 'divider' }, | |
| { type: 'text', text: 'Building production-grade AI systems that don\'t break in the' }, | |
| { type: 'text', text: 'real world. Specialising in RAG, multi-agent systems, and LLM' }, | |
| { type: 'text', text: 'engineering with LangChain, LangGraph, and Groq.' }, | |
| ], | |
| }, | |
| skills: { | |
| desc: 'Technical skill stack', | |
| run: () => [ | |
| { type: 'title', text: '🛠️ Technical Skills' }, | |
| { type: 'divider' }, | |
| { type: 'category', text: 'AI / ML / LLMs' }, | |
| { type: 'tags', items: ['LangChain', 'LangGraph', 'Groq', 'RAG', 'AutoGen', 'HuggingFace', 'Transformers'] }, | |
| { type: 'category', text: 'Backend' }, | |
| { type: 'tags', items: ['FastAPI', 'Django', 'Go / Gin', 'REST APIs', 'WebSockets'] }, | |
| { type: 'category', text: 'Databases & Vector Stores' }, | |
| { type: 'tags', items: ['MongoDB', 'MySQL', 'FAISS', 'ChromaDB', 'Pinecone'] }, | |
| { type: 'category', text: 'Languages' }, | |
| { type: 'tags', items: ['Python', 'JavaScript', 'TypeScript', 'Go', 'C++'] }, | |
| { type: 'category', text: 'Tools & DevOps' }, | |
| { type: 'tags', items: ['Docker', 'Git', 'GitHub', 'Postman', 'VS Code'] }, | |
| ], | |
| }, | |
| projects: { | |
| desc: 'Featured AI projects', | |
| run: () => [ | |
| { type: 'title', text: '🚀 Featured Projects' }, | |
| { type: 'divider' }, | |
| { type: 'proj', num: '01', name: 'Healthcare Chatbot', tech: 'RAG · Pinecone · LangChain · Groq' }, | |
| { type: 'proj', num: '02', name: 'AI Virtual Dev POD', tech: 'Multi-Agent · LangGraph · ChromaDB' }, | |
| { type: 'proj', num: '03', name: 'Virtual Research Assistant', tech: 'AutoGen · Groq · MySQL' }, | |
| { type: 'proj', num: '04', name: 'OwnGPT', tech: 'FastAPI · LangGraph · MongoDB · Google Auth', url: 'https://parthib07-owngpt-v2.hf.space' }, | |
| { type: 'proj', num: '05', name: 'CodeFusion', tech: 'DeepSeek AI · FastAPI · React', url: 'https://codefusion-v2-parthib.onrender.com' }, | |
| { type: 'proj', num: '06', name: 'AnySITE', tech: 'FastAPI · React · AI Models', url: 'https://anysite-vibecoding-parthib.onrender.com' }, | |
| { type: 'divider' }, | |
| { type: 'info', text: '🔗 Run "contact" to connect or visit GitHub for source code' }, | |
| ], | |
| }, | |
| experience: { | |
| desc: 'Work experience', | |
| run: () => [ | |
| { type: 'title', text: '💼 Work Experience' }, | |
| { type: 'divider' }, | |
| { type: 'kv', key: 'Company ', value: 'BAAR Technologies' }, | |
| { type: 'kv', key: 'Role ', value: 'Jr. Automation Engineer' }, | |
| { type: 'kv', key: 'Type ', value: 'Full-time' }, | |
| { type: 'kv', key: 'Period ', value: 'Feb 2026 – Mar 2026 · 2 mos' }, | |
| { type: 'kv', key: 'Location', value: 'Kolkata, West Bengal · On-site' }, | |
| { type: 'text', text: '' }, | |
| { type: 'text', text: '→ Built automation-driven backend systems using Python & Django' }, | |
| { type: 'text', text: '→ Managed high-performance databases with Microsoft SQL Server' }, | |
| { type: 'text', text: '→ Developed scalable APIs and optimised SQL queries & procedures' }, | |
| { type: 'text', text: '→ Collaborated cross-functionally to deliver reliable automation solutions' }, | |
| ], | |
| }, | |
| education: { | |
| desc: 'Academic background', | |
| run: () => [ | |
| { type: 'title', text: '🎓 Education' }, | |
| { type: 'divider' }, | |
| { type: 'kv', key: 'Degree ', value: 'B.Tech — Computer Science & Engineering' }, | |
| { type: 'kv', key: 'College ', value: 'Institute of Engineering & Management, Kolkata' }, | |
| { type: 'kv', key: 'CGPA ', value: '8.9 / 10.0' }, | |
| { type: 'kv', key: 'Year ', value: '2021 – 2025' }, | |
| { type: 'divider' }, | |
| { type: 'kv', key: 'Class XII', value: '93.6% — Kapsit High School (2021)' }, | |
| { type: 'kv', key: 'Class X ', value: '84.5% — Kamarpukur RKM School (2019)' }, | |
| ], | |
| }, | |
| contact: { | |
| desc: 'Get in touch', | |
| run: () => [ | |
| { type: 'title', text: '📬 Contact Parthib' }, | |
| { type: 'divider' }, | |
| { type: 'link', label: 'Email ', value: 'parthibkarak2004@gmail.com', href: 'mailto:parthibkarak2004@gmail.com' }, | |
| { type: 'link', label: 'LinkedIn', value: 'linkedin.com/in/parthib-karak', href: 'https://www.linkedin.com/in/parthib-karak' }, | |
| { type: 'link', label: 'GitHub ', value: 'github.com/babaiii07', href: 'https://github.com/babaiii07' }, | |
| { type: 'link', label: 'LeetCode', value: 'leetcode.com/u/Parthib_007', href: 'https://leetcode.com/u/Parthib_007' }, | |
| { type: 'divider' }, | |
| { type: 'info', text: '💌 Available for freelance, internship & full-time roles' }, | |
| ], | |
| }, | |
| resume: { | |
| desc: 'Download resume', | |
| run: () => { | |
| setTimeout(() => { window.open(resumePDF, '_blank') }, 300) | |
| return [ | |
| { type: 'title', text: '📄 Resume' }, | |
| { type: 'divider' }, | |
| { type: 'success', text: '✅ Opening resume in a new tab...' }, | |
| { type: 'info', text: '📥 Download will start automatically' }, | |
| ] | |
| }, | |
| }, | |
| leetcode: { | |
| desc: 'DSA stats', | |
| run: () => [ | |
| { type: 'title', text: '⚡ LeetCode Stats' }, | |
| { type: 'divider' }, | |
| { type: 'kv', key: 'Profile ', value: 'leetcode.com/u/Parthib_007' }, | |
| { type: 'kv', key: 'Solved ', value: '500+ Problems' }, | |
| { type: 'kv', key: 'Strength', value: 'Arrays · DP · Graphs · Trees · Strings' }, | |
| { type: 'divider' }, | |
| { type: 'text', text: '→ Strong DSA foundation for system design & optimization' }, | |
| { type: 'text', text: '→ Active in competitive programming contests' }, | |
| ], | |
| }, | |
| hire: { | |
| desc: 'Why hire Parthib?', | |
| run: () => [ | |
| { type: 'title', text: '🌟 Why Hire Parthib?' }, | |
| { type: 'divider' }, | |
| { type: 'text', text: '✦ Builds production-ready AI — not just demos' }, | |
| { type: 'text', text: '✦ Specialised in RAG, Agents, LangChain & LangGraph' }, | |
| { type: 'text', text: '✦ 500+ LeetCode — strong algorithmic foundations' }, | |
| { type: 'text', text: '✦ CGPA 8.9 — academic excellence under real workload' }, | |
| { type: 'text', text: '✦ End-to-end: from LLM to Docker to production deploy' }, | |
| { type: 'text', text: '✦ Fast learner, self-driven, ships things that work' }, | |
| { type: 'divider' }, | |
| { type: 'success', text: '🚀 Ready to contribute from day one — run "contact" to connect!' }, | |
| ], | |
| }, | |
| clear: { | |
| desc: 'Clear terminal', | |
| run: () => null, | |
| }, | |
| } | |
| function renderLine(line, i) { | |
| const base = { fontSize: '0.78rem', lineHeight: 1.7, fontFamily: 'var(--font-mono)' } | |
| switch (line.type) { | |
| case 'title': | |
| return <div key={i} style={{ ...base, color: '#a78bfa', fontWeight: 700, marginTop: 4 }}>{line.text}</div> | |
| case 'divider': | |
| return <div key={i} style={{ ...base, color: 'rgba(99,102,241,0.3)', margin: '2px 0' }}>{'─'.repeat(44)}</div> | |
| case 'kv': | |
| return ( | |
| <div key={i} style={{ ...base, display: 'flex', gap: 8 }}> | |
| <span style={{ color: '#64748b', minWidth: 80 }}>{line.key}</span> | |
| <span style={{ color: '#e2e8f0' }}>{line.value}</span> | |
| </div> | |
| ) | |
| case 'text': | |
| return <div key={i} style={{ ...base, color: '#94a3b8' }}>{line.text || '\u00A0'}</div> | |
| case 'info': | |
| return <div key={i} style={{ ...base, color: '#38bdf8' }}>{line.text}</div> | |
| case 'success': | |
| return <div key={i} style={{ ...base, color: '#4ade80', fontWeight: 600 }}>{line.text}</div> | |
| case 'cmd': | |
| return <div key={i} style={{ ...base, color: '#94a3b8' }}>{line.text}</div> | |
| case 'category': | |
| return <div key={i} style={{ ...base, color: '#06b6d4', fontWeight: 600, marginTop: 6 }}>{line.text}</div> | |
| case 'tags': | |
| return ( | |
| <div key={i} style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginBottom: 4 }}> | |
| {line.items.map((t, j) => ( | |
| <span key={j} style={{ | |
| fontSize: '0.68rem', fontFamily: 'var(--font-mono)', | |
| background: 'rgba(99,102,241,0.12)', border: '1px solid rgba(99,102,241,0.2)', | |
| color: '#a78bfa', borderRadius: 4, padding: '1px 7px', | |
| }}>{t}</span> | |
| ))} | |
| </div> | |
| ) | |
| case 'proj': | |
| return ( | |
| <div key={i} style={{ ...base, display: 'flex', gap: 8, alignItems: 'baseline' }}> | |
| <span style={{ color: '#6366f1', minWidth: 24 }}>{line.num}</span> | |
| <span style={{ color: '#f0f4ff', fontWeight: 600, minWidth: 160 }}>{line.name}</span> | |
| <span style={{ color: '#64748b', fontSize: '0.68rem' }}>{line.tech}</span> | |
| {line.url && ( | |
| <a href={line.url} target="_blank" rel="noopener noreferrer" | |
| style={{ color: '#38bdf8', fontSize: '0.68rem', textDecoration: 'none', marginLeft: 4 }}>↗ live</a> | |
| )} | |
| </div> | |
| ) | |
| case 'link': | |
| return ( | |
| <div key={i} style={{ ...base, display: 'flex', gap: 8 }}> | |
| <span style={{ color: '#64748b', minWidth: 80 }}>{line.label}</span> | |
| <a href={line.href} target="_blank" rel="noopener noreferrer" | |
| style={{ color: '#38bdf8', textDecoration: 'none' }} | |
| onMouseEnter={e => e.target.style.textDecoration = 'underline'} | |
| onMouseLeave={e => e.target.style.textDecoration = 'none'}> | |
| {line.value} | |
| </a> | |
| </div> | |
| ) | |
| default: | |
| return null | |
| } | |
| } | |
| export default function Terminal() { | |
| const [open, setOpen] = useState(false) | |
| const [input, setInput] = useState('') | |
| const [history, setHistory] = useState([]) | |
| const [cmdHistory, setCmdHistory] = useState([]) | |
| const [histIdx, setHistIdx] = useState(-1) | |
| const inputRef = useRef(null) | |
| const bottomRef = useRef(null) | |
| // Welcome message on first open | |
| useEffect(() => { | |
| if (open && history.length === 0) { | |
| setHistory([{ | |
| type: 'welcome', | |
| lines: [ | |
| { type: 'title', text: '⚡ Parthib\'s Portfolio Terminal' }, | |
| { type: 'divider' }, | |
| { type: 'text', text: 'Welcome, recruiter! Type "help" to see all commands.' }, | |
| { type: 'text', text: 'Explore my profile interactively below.' }, | |
| { type: 'divider' }, | |
| { type: 'info', text: '→ Try: whoami · skills · projects · hire · resume' }, | |
| ], | |
| }]) | |
| } | |
| }, [open]) | |
| useEffect(() => { | |
| if (open) { | |
| setTimeout(() => inputRef.current?.focus(), 80) | |
| } | |
| }, [open]) | |
| useEffect(() => { | |
| bottomRef.current?.scrollIntoView({ behavior: 'smooth' }) | |
| }, [history]) | |
| const runCommand = (raw) => { | |
| const cmd = raw.trim().toLowerCase() | |
| if (!cmd) return | |
| setCmdHistory(prev => [cmd, ...prev]) | |
| setHistIdx(-1) | |
| if (cmd === 'clear') { | |
| setHistory([]) | |
| return | |
| } | |
| const def = COMMANDS[cmd] | |
| const outputLines = def ? def.run() : [ | |
| { type: 'text', text: `bash: ${cmd}: command not found` }, | |
| { type: 'info', text: 'Type "help" to see available commands.' }, | |
| ] | |
| setHistory(prev => [ | |
| ...prev, | |
| { type: 'entry', cmd, lines: outputLines || [] }, | |
| ]) | |
| } | |
| const handleKey = (e) => { | |
| if (e.key === 'Enter') { | |
| runCommand(input) | |
| setInput('') | |
| } else if (e.key === 'ArrowUp') { | |
| e.preventDefault() | |
| const next = Math.min(histIdx + 1, cmdHistory.length - 1) | |
| setHistIdx(next) | |
| setInput(cmdHistory[next] ?? '') | |
| } else if (e.key === 'ArrowDown') { | |
| e.preventDefault() | |
| const next = Math.max(histIdx - 1, -1) | |
| setHistIdx(next) | |
| setInput(next === -1 ? '' : cmdHistory[next] ?? '') | |
| } | |
| } | |
| return ( | |
| <> | |
| {/* Floating Toggle Button */} | |
| <motion.button | |
| onClick={() => setOpen(v => !v)} | |
| style={{ | |
| position: 'fixed', bottom: 28, right: 28, zIndex: 9998, | |
| width: 52, height: 52, borderRadius: '50%', | |
| background: 'linear-gradient(135deg, #6366f1, #06b6d4)', | |
| border: 'none', cursor: 'pointer', | |
| display: 'flex', alignItems: 'center', justifyContent: 'center', | |
| color: '#fff', fontSize: '1.25rem', | |
| boxShadow: '0 8px 32px rgba(99,102,241,0.55), 0 2px 8px rgba(0,0,0,0.4)', | |
| }} | |
| whileHover={{ scale: 1.1, boxShadow: '0 12px 40px rgba(99,102,241,0.7)' }} | |
| whileTap={{ scale: 0.95 }} | |
| title="Open Portfolio Terminal" | |
| aria-label="Open terminal" | |
| > | |
| <motion.span | |
| animate={{ rotate: open ? 90 : 0 }} | |
| transition={{ duration: 0.25 }} | |
| style={{ display: 'flex' }} | |
| > | |
| {open ? <FiX size={22} /> : <FiTerminal size={22} />} | |
| </motion.span> | |
| </motion.button> | |
| {/* Terminal Window */} | |
| <AnimatePresence> | |
| {open && ( | |
| <motion.div | |
| initial={{ opacity: 0, y: 40, scale: 0.95 }} | |
| animate={{ opacity: 1, y: 0, scale: 1 }} | |
| exit={{ opacity: 0, y: 40, scale: 0.95 }} | |
| transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }} | |
| style={{ | |
| position: 'fixed', bottom: 90, right: 28, zIndex: 9997, | |
| width: 'min(600px, calc(100vw - 32px))', | |
| height: 'min(480px, 60vh)', | |
| background: 'rgba(8,10,26,0.97)', | |
| backdropFilter: 'blur(24px)', | |
| borderRadius: 16, | |
| border: '1px solid rgba(99,102,241,0.3)', | |
| boxShadow: '0 24px 80px rgba(0,0,0,0.7), 0 0 0 1px rgba(99,102,241,0.1)', | |
| display: 'flex', flexDirection: 'column', | |
| overflow: 'hidden', | |
| }} | |
| onClick={() => inputRef.current?.focus()} | |
| > | |
| {/* Title Bar */} | |
| <div style={{ | |
| display: 'flex', alignItems: 'center', gap: 8, | |
| padding: '10px 16px', | |
| background: 'rgba(99,102,241,0.06)', | |
| borderBottom: '1px solid rgba(99,102,241,0.15)', | |
| flexShrink: 0, | |
| }}> | |
| <div style={{ display: 'flex', gap: 6 }}> | |
| {['#f43f5e', '#f59e0b', '#4ade80'].map((c, i) => ( | |
| <div key={i} style={{ width: 11, height: 11, borderRadius: '50%', background: c, opacity: 0.85, cursor: i === 0 ? 'pointer' : 'default' }} | |
| onClick={i === 0 ? () => setOpen(false) : undefined} /> | |
| ))} | |
| </div> | |
| <div style={{ | |
| flex: 1, textAlign: 'center', | |
| fontSize: '0.72rem', fontFamily: 'var(--font-mono)', | |
| color: '#475569', letterSpacing: '0.05em', | |
| }}> | |
| <FiTerminal size={11} style={{ display: 'inline', marginRight: 5, verticalAlign: 'middle' }} /> | |
| parthib@portfolio ~ zsh | |
| </div> | |
| <button onClick={() => setOpen(false)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#475569', display: 'flex' }}> | |
| <FiX size={14} /> | |
| </button> | |
| </div> | |
| {/* Output Area */} | |
| <div style={{ | |
| flex: 1, overflowY: 'auto', padding: '12px 16px', | |
| scrollbarWidth: 'thin', scrollbarColor: 'rgba(99,102,241,0.3) transparent', | |
| }}> | |
| {history.map((entry, ei) => ( | |
| <div key={ei} style={{ marginBottom: 10 }}> | |
| {entry.type === 'entry' && ( | |
| <div style={{ | |
| display: 'flex', gap: 6, alignItems: 'baseline', | |
| fontSize: '0.78rem', fontFamily: 'var(--font-mono)', | |
| marginBottom: 4, | |
| }}> | |
| <span style={{ color: '#4ade80' }}>parthib</span> | |
| <span style={{ color: '#475569' }}>@portfolio</span> | |
| <span style={{ color: '#38bdf8' }}>~</span> | |
| <span style={{ color: '#64748b' }}>$</span> | |
| <span style={{ color: '#e2e8f0' }}>{entry.cmd}</span> | |
| </div> | |
| )} | |
| {entry.lines?.map((line, li) => renderLine(line, li))} | |
| </div> | |
| ))} | |
| <div ref={bottomRef} /> | |
| </div> | |
| {/* Input Row */} | |
| <div style={{ | |
| display: 'flex', alignItems: 'center', gap: 8, | |
| padding: '10px 16px', | |
| borderTop: '1px solid rgba(99,102,241,0.12)', | |
| background: 'rgba(0,0,0,0.2)', | |
| flexShrink: 0, | |
| }}> | |
| <span style={{ fontSize: '0.78rem', fontFamily: 'var(--font-mono)', color: '#4ade80', flexShrink: 0 }}>parthib</span> | |
| <span style={{ fontSize: '0.78rem', fontFamily: 'var(--font-mono)', color: '#475569', flexShrink: 0 }}>@portfolio</span> | |
| <span style={{ fontSize: '0.78rem', fontFamily: 'var(--font-mono)', color: '#38bdf8', flexShrink: 0 }}>~</span> | |
| <span style={{ fontSize: '0.78rem', fontFamily: 'var(--font-mono)', color: '#64748b', flexShrink: 0 }}>$</span> | |
| <input | |
| ref={inputRef} | |
| value={input} | |
| onChange={e => setInput(e.target.value)} | |
| onKeyDown={handleKey} | |
| placeholder="type a command..." | |
| autoComplete="off" | |
| spellCheck={false} | |
| style={{ | |
| flex: 1, background: 'none', border: 'none', outline: 'none', | |
| fontFamily: 'var(--font-mono)', fontSize: '0.78rem', | |
| color: '#e2e8f0', caretColor: '#a78bfa', | |
| }} | |
| /> | |
| <span style={{ | |
| width: 8, height: 15, background: '#6366f1', borderRadius: 1, | |
| animation: 'blink-cursor 0.8s step-end infinite', flexShrink: 0, | |
| }} /> | |
| </div> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| </> | |
| ) | |
| } | |