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>
  )
}