Pyae Sone Automation
feat: upgrade god-agent-os to distributed 22-space architecture
9b0e1ea
'use client'
import { useState, useRef, useEffect } from 'react'
import { useAgentStore } from '@/hooks/useAgentStore'
import { fetchAPI } from '@/lib/api'
const sandboxExecute = (cmd: string, sid: string) => fetchAPI('/api/v1/spaces/sandbox-worker-space/execute', { method: 'POST', body: JSON.stringify({ task: cmd, role: 'execution', session_id: sid }) })
const sandboxWriteFile = (path: string, content: string) => fetchAPI('/api/v1/files/write', { method: 'POST', body: JSON.stringify({ path, content }) })
const getWorkspaceInfo = () => fetchAPI('/api/v1/files/workspace')
import { Terminal, Play, FolderOpen, File, RefreshCw, ChevronRight, Zap, ExternalLink, Code2 } from 'lucide-react'
const VSCODE_HF_URL = 'https://pyae1994-god-agent-vscode.hf.space'
interface TerminalLine {
type: 'input' | 'output' | 'error'
text: string
time: string
}
export default function SandboxPanel() {
const { locale } = useAgentStore()
const [cmd, setCmd] = useState('')
const [lines, setLines] = useState<TerminalLine[]>([
{ type: 'output', text: '🚀 God Mode+ Sandbox — Persistent VS Code Workspace', time: '' },
{ type: 'output', text: 'Type commands to execute in the sandbox...', time: '' },
])
const [loading, setLoading] = useState(false)
const [workspace, setWorkspace] = useState<any>(null)
const [tab, setTab] = useState<'terminal' | 'files' | 'vscode'>('terminal')
const endRef = useRef<HTMLDivElement>(null)
const inputRef = useRef<HTMLInputElement>(null)
const [history, setHistory] = useState<string[]>([])
const [histIdx, setHistIdx] = useState(-1)
useEffect(() => { endRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [lines])
const loadWorkspace = async () => { try { const data = await getWorkspaceInfo(); setWorkspace(data) } catch {} }
useEffect(() => { loadWorkspace() }, [])
const run = async () => {
const c = cmd.trim()
if (!c || loading) return
const now = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })
setLines(lines => [...lines, { type: 'input', text: `$ ${c}`, time: now }])
setHistory(history => [c, ...history.slice(0, 49)])
setHistIdx(-1)
setCmd('')
setLoading(true)
try {
const res = await sandboxExecute(c, 'sandbox_panel')
const output = res.result || ''
output.split('\n').forEach((line: string) => setLines(lines => [...lines, { type: 'output', text: line, time: '' }]))
} catch (e: any) {
setLines(lines => [...lines, { type: 'error', text: `❌ ${e.message}`, time: '' }])
}
setLoading(false)
if (tab === 'files') loadWorkspace()
}
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') { run(); return }
if (e.key === 'ArrowUp') {
const idx = Math.min(histIdx + 1, history.length - 1)
setHistIdx(idx)
setCmd(history[idx] || '')
}
if (e.key === 'ArrowDown') {
const idx = Math.max(histIdx - 1, -1)
setHistIdx(idx)
setCmd(idx === -1 ? '' : history[idx])
}
}
const QUICK_CMDS = ['ls -la', 'pwd', 'python3 --version', 'node --version', 'git status', 'pip list | head -10']
return (
<div className="flex flex-col h-full" style={{ background: 'var(--bg-2)' }}>
<div className="flex items-center justify-between px-4 py-2.5 border-b shrink-0" style={{ borderColor: 'var(--border)', background: 'var(--bg-3)' }}>
<div className="flex items-center gap-2"><Terminal size={14} className="text-green-400" /><span className="text-sm font-semibold" style={{ color: 'var(--text-primary)' }}>{locale === 'my' ? 'Sandbox' : 'Sandbox'}</span><div className="w-1.5 h-1.5 rounded-full bg-green-400 animate-pulse" /></div>
<div className="flex gap-0.5 p-0.5 rounded-lg" style={{ background: 'var(--bg-0)', border: '1px solid var(--border)' }}>
{(['terminal', 'files', 'vscode'] as const).map(t => (
<button key={t} onClick={() => { setTab(t); if (t === 'files') loadWorkspace() }} className="px-2.5 py-0.5 rounded-md text-[10px] font-medium transition-all capitalize" style={{ background: tab === t ? 'var(--brand)' : 'transparent', color: tab === t ? '#fff' : 'var(--text-muted)' }}>{t === 'vscode' ? '⚡ VS Code' : t}</button>
))}
</div>
</div>
{tab === 'terminal' ? (
<>
<div className="px-3 py-1.5 border-b flex gap-1 overflow-x-auto" style={{ borderColor: 'var(--border)' }}>
{QUICK_CMDS.map(q => <button key={q} onClick={() => { setCmd(q); inputRef.current?.focus() }} className="flex-shrink-0 px-2 py-0.5 rounded-full text-[9px] font-mono transition-all hover:opacity-80" style={{ background: 'var(--bg-3)', color: 'var(--text-muted)', border: '1px solid var(--border)' }}>{q}</button>)}
</div>
<div className="flex-1 overflow-y-auto p-3 font-mono text-xs space-y-1.5">
{lines.map((line, i) => <div key={i} className={line.type === 'error' ? 'text-red-400' : line.type === 'input' ? 'text-cyan-300' : 'text-slate-300'}>{line.text}</div>)}
{loading && <div className="text-slate-500 animate-pulse">Running...</div>}
<div ref={endRef} />
</div>
<div className="p-3 border-t" style={{ borderColor: 'var(--border)' }}>
<div className="flex items-center gap-2">
<ChevronRight size={14} className="text-green-400" />
<input ref={inputRef} value={cmd} onChange={e => setCmd(e.target.value)} onKeyDown={handleKeyDown} placeholder="Enter command..." className="flex-1 bg-transparent outline-none text-sm text-slate-100" />
<button onClick={run} className="p-2 rounded-lg bg-violet-600 text-white disabled:opacity-50" disabled={loading || !cmd.trim()}><Play size={14} /></button>
</div>
</div>
</>
) : tab === 'files' ? (
<div className="p-4 text-sm text-slate-300">Workspace: {workspace?.workspace || '/tmp/god_workspace'}</div>
) : (
<div className="p-4 text-sm text-slate-300">VS Code Space: <a className="text-cyan-400 underline" href={VSCODE_HF_URL} target="_blank">Open</a></div>
)}
</div>
)
}