105 lines
2.4 KiB
TypeScript
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>
|
|
);
|
|
}
|