OSVauco/opax-web/frontend/src/App.tsx

105 lines
2.4 KiB
TypeScript

import { useEffect, useRef, useState } from 'react';
import { io, Socket } from 'socket.io-client';
const tools: string[] = [
'get_health',
'get_build_status',
'get_state',
'get_telemetry',
'get_git_status',
'get_git_branch',
'get_git_log',
'get_git_diff',
'list_repo_files',
'read_repo_file',
];
interface User {
email: string;
name?: string;
}
export default function App() {
const [user, setUser] = useState<User | null>(null);
const [output, setOutput] = useState<string[]>(['OPAX Web TUI', 'Type tools or: mcp get_health', '']);
const [input, setInput] = useState('');
const socketRef = useRef<Socket | null>(null);
useEffect(() => {
fetch('/auth/me', { credentials: 'include' })
.then((r) => (r.ok ? r.json() : null))
.then(setUser);
}, []);
useEffect(() => {
if (!user) return;
const socket = io('/');
socketRef.current = socket;
socket.on('mcp-result', (x) => {
setOutput((a) => [...a, x.error ? 'ERROR ' + x.error : JSON.stringify(x.result, null, 2), '']);
});
return () => {
socket.close();
};
}, [user]);
if (!user) {
return (
<main className="login">
<h1>OPAX</h1>
<a href="/auth/google">Continue with Google</a>
</main>
);
}
function go(e: React.FormEvent) {
e.preventDefault();
let v = input.trim();
setInput('');
setOutput((a) => [...a, '$ ' + v]);
if (v === 'tools') {
return setOutput((a) => [...a, ...tools, '']);
}
let m = v.match(/^mcp\s+(\S+)(?:\s+(.+))?$/);
if (!m) {
return setOutput((a) => [...a, 'Use mcp <tool> [JSON]', '']);
}
try {
socketRef.current?.emit('mcp-call', {
id: crypto.randomUUID(),
tool: m[1],
args: m[2] ? JSON.parse(m[2]) : {},
});
} catch {
setOutput((a) => [...a, 'JSON arguments invalid', '']);
}
}
return (
<main>
<header>
<b>OPAX / Web TUI</b>
<span>{user.email}</span>
</header>
<section>
<aside>
{tools.map((tool) => (
<button key={tool} onClick={() => setInput('mcp ' + tool)}>
{tool}
</button>
))}
</aside>
<div className="term">
<pre>{output.join('\n')}</pre>
<form onSubmit={go}>
$ <input autoFocus value={input} onChange={(e) => setInput(e.target.value)} />
</form>
</div>
</section>
</main>
);
}