Spaces:
Sleeping
Sleeping
File size: 6,203 Bytes
a20767f f131af7 9b0e1ea f131af7 865fa58 a20767f 865fa58 a20767f 9b0e1ea a20767f 9b0e1ea a20767f f131af7 a20767f 9b0e1ea a20767f 9b0e1ea a20767f 9b0e1ea a20767f 865fa58 9b0e1ea a20767f 9b0e1ea a20767f 9b0e1ea a20767f 9b0e1ea a20767f 865fa58 9b0e1ea 865fa58 9b0e1ea a20767f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | '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>
)
}
|