2026-08-18 18:33:49 -07:00
|
|
|
|
'use client'
|
|
|
|
|
|
|
|
|
|
|
|
import { motion } from 'framer-motion'
|
|
|
|
|
|
import { useEffect, useRef, useState } from 'react'
|
2026-08-31 13:59:46 -07:00
|
|
|
|
import { useTerminal } from '@/lib/TerminalContext'
|
2026-08-18 18:33:49 -07:00
|
|
|
|
|
|
|
|
|
|
const commands: Record<string, string> = {
|
|
|
|
|
|
help: `Available commands:
|
|
|
|
|
|
ls - List services
|
|
|
|
|
|
kubectl - Cluster info
|
|
|
|
|
|
terraform - Infrastructure
|
|
|
|
|
|
argocd - Deployments
|
|
|
|
|
|
kafka - Message broker
|
|
|
|
|
|
about - About me
|
|
|
|
|
|
clear - Clear terminal`,
|
|
|
|
|
|
ls: `storage/
|
|
|
|
|
|
monitoring/
|
|
|
|
|
|
cicd/
|
|
|
|
|
|
sqs/
|
|
|
|
|
|
temporal/
|
|
|
|
|
|
iam/`,
|
|
|
|
|
|
kubectl: `Nodes: 3 (talos-cp-1, talos-worker-1, talos-worker-2)
|
|
|
|
|
|
Pods: 42 running
|
|
|
|
|
|
Uptime: 99.2%
|
|
|
|
|
|
Cluster Version: v1.36.1`,
|
|
|
|
|
|
terraform: `Modules: 18 deployed
|
|
|
|
|
|
Services: Kafka, PostgreSQL, MinIO, Grafana
|
|
|
|
|
|
Storage: Longhorn (3-replica)
|
|
|
|
|
|
Network: Cilium eBPF CNI`,
|
|
|
|
|
|
argocd: `Applications: 12
|
|
|
|
|
|
Synced: 11/12
|
|
|
|
|
|
Last Sync: 2 min ago
|
|
|
|
|
|
Health: Healthy`,
|
|
|
|
|
|
kafka: `Brokers: 3 (KRaft)
|
|
|
|
|
|
Replication Factor: 3
|
|
|
|
|
|
Min ISR: 2
|
|
|
|
|
|
Topics: 5 active
|
|
|
|
|
|
Throughput: ~1K msgs/sec`,
|
|
|
|
|
|
about: `Rock Liang - Senior Full-Stack Systems Engineer
|
|
|
|
|
|
Experience: 5+ years (AWS, RBC, Homelab)
|
|
|
|
|
|
Focus: Infrastructure × Backend × LLM Systems
|
|
|
|
|
|
Tech: Go, Java, Python, C++ | Kubernetes, Terraform, gRPC
|
|
|
|
|
|
Current: Building production homelab + LLM inference optimization
|
|
|
|
|
|
Open Source: go-flink (distributed DataLakeHouse)`,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function InteractiveTerminal() {
|
|
|
|
|
|
const [input, setInput] = useState('')
|
|
|
|
|
|
const [history, setHistory] = useState<{ cmd: string; output: string }[]>([])
|
2026-08-31 13:59:46 -07:00
|
|
|
|
const { isOpen, setIsOpen } = useTerminal()
|
|
|
|
|
|
const [isMac, setIsMac] = useState(true)
|
2026-08-18 18:33:49 -07:00
|
|
|
|
const terminalRef = useRef<HTMLDivElement>(null)
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-08-31 13:59:46 -07:00
|
|
|
|
// Detect if user is on Mac
|
|
|
|
|
|
const isMacOS = /Mac|iPhone|iPad|iPod/.test(navigator.platform)
|
|
|
|
|
|
setIsMac(isMacOS)
|
|
|
|
|
|
|
2026-08-18 18:33:49 -07:00
|
|
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
|
|
|
|
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
|
setIsOpen(!isOpen)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
window.addEventListener('keydown', handleKeyDown)
|
|
|
|
|
|
return () => window.removeEventListener('keydown', handleKeyDown)
|
2026-08-31 13:59:46 -07:00
|
|
|
|
}, [isOpen, setIsOpen])
|
2026-08-18 18:33:49 -07:00
|
|
|
|
|
|
|
|
|
|
const handleCommand = (cmd: string) => {
|
|
|
|
|
|
const trimmed = cmd.trim().toLowerCase()
|
|
|
|
|
|
if (trimmed === 'clear') {
|
|
|
|
|
|
setHistory([])
|
|
|
|
|
|
setInput('')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const output = commands[trimmed] || `command not found: ${cmd}`
|
|
|
|
|
|
setHistory([...history, { cmd, output }])
|
|
|
|
|
|
setInput('')
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
terminalRef.current?.scrollTo(0, terminalRef.current.scrollHeight)
|
|
|
|
|
|
}, 0)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
className="fixed bottom-6 right-6 z-40"
|
|
|
|
|
|
initial={{ opacity: 0, scale: 0.9 }}
|
|
|
|
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
|
|
|
|
>
|
|
|
|
|
|
{!isOpen ? (
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setIsOpen(true)}
|
|
|
|
|
|
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-mono text-sm shadow-lg"
|
|
|
|
|
|
>
|
2026-08-31 13:59:46 -07:00
|
|
|
|
Ask Poimen ({isMac ? 'Cmd' : 'Ctrl'}+K)
|
2026-08-18 18:33:49 -07:00
|
|
|
|
</button>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<motion.div
|
|
|
|
|
|
initial={{ opacity: 0, y: 10 }}
|
|
|
|
|
|
animate={{ opacity: 1, y: 0 }}
|
|
|
|
|
|
className="bg-gray-950 border border-gray-700 rounded-lg shadow-2xl w-96 h-96 flex flex-col"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="bg-gray-900 border-b border-gray-700 px-4 py-3 flex justify-between items-center">
|
2026-08-31 13:59:46 -07:00
|
|
|
|
<span className="text-gray-300 font-mono text-xs">Poimen (Agent Terminal)</span>
|
2026-08-18 18:33:49 -07:00
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setIsOpen(false)}
|
|
|
|
|
|
className="text-gray-400 hover:text-white"
|
|
|
|
|
|
>
|
|
|
|
|
|
✕
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
ref={terminalRef}
|
|
|
|
|
|
className="flex-1 overflow-y-auto px-4 py-3 font-mono text-sm text-gray-200 space-y-2"
|
|
|
|
|
|
>
|
|
|
|
|
|
{history.length === 0 && (
|
|
|
|
|
|
<div className="text-gray-500">type 'help' for commands</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{history.map((entry, i) => (
|
|
|
|
|
|
<div key={i}>
|
|
|
|
|
|
<div className="text-green-400"><span className="text-red-400">➜</span> % {entry.cmd}</div>
|
|
|
|
|
|
<div className="text-gray-300 whitespace-pre-wrap text-xs mt-1">
|
|
|
|
|
|
{entry.output}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="border-t border-gray-700 px-4 py-2">
|
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
|
<span className="text-red-400 font-mono text-sm">➜</span>
|
|
|
|
|
|
<span className="text-green-400 font-mono text-sm ml-2">% </span>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={input}
|
|
|
|
|
|
onChange={(e) => setInput(e.target.value)}
|
|
|
|
|
|
onKeyPress={(e) => {
|
|
|
|
|
|
if (e.key === 'Enter') handleCommand(input)
|
|
|
|
|
|
}}
|
|
|
|
|
|
placeholder=""
|
|
|
|
|
|
className="flex-1 bg-transparent text-green-400 font-mono text-sm outline-none ml-1"
|
|
|
|
|
|
autoFocus
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|