Basic web interface

This commit is contained in:
2024-11-02 12:33:24 +01:00
parent 092a05c3aa
commit 1974769eb4
21 changed files with 679 additions and 98 deletions

View File

@@ -1,25 +1,337 @@
import React from 'react'
import { add } from '../utils/calculator.js' // Note the .js extension
import React, { useState, useEffect, useRef } from 'react';
import Editor from '@monaco-editor/react';
import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/card.jsx';
import { Button } from '../components/ui/button.jsx';
import { ScrollArea } from '../components/ui/scroll-area.jsx';
import { Alert, AlertDescription } from '../components/ui/alert.jsx';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs.jsx';
import { Textarea } from '../components/ui/textarea.jsx';
import { Input } from '../components/ui/input.jsx';
function App() {
const [result, setResult] = React.useState(0)
const WebSocketState = {
CONNECTING: 'CONNECTING',
CONNECTED: 'CONNECTED',
DISCONNECTED: 'DISCONNECTED',
};
const handleCalculate = () => {
setResult(add(5, 3))
}
const AgentState = {
UPDATE: 'UPDATE',
CONTEXT_APPROVAL: 'CONTEXT_APPROVAL',
INFERENCE: 'INFERENCE',
RESPONSE_APPROVAL: 'RESPONSE_APPROVAL',
};
const MessageType = {
STATE_CHANGE: 'STATE_CHANGE',
CONTEXT_UPDATE: 'CONTEXT_UPDATE',
RESPONSE_UPDATE: 'RESPONSE_UPDATE',
OUTPUT_UPDATE: 'OUTPUT_UPDATE',
VALIDATION_ERROR: 'VALIDATION_ERROR',
};
const SIAInterface = () => {
// Editor content state
const [context, setContext] = useState('');
const [originalResponse, setOriginalResponse] = useState('');
const [modifiedResponse, setModifiedResponse] = useState('');
const [input, setInput] = useState('');
const [output, setOutput] = useState('');
const [validationError, setValidationError] = useState(null);
// UI state
const [activeTab, setActiveTab] = useState('input');
const [agentState, setAgentState] = useState(AgentState.UPDATE);
const [wsState, setWsState] = useState(WebSocketState.DISCONNECTED);
// WebSocket ref
const wsRef = useRef(null);
// Monaco editor options
const editorOptions = {
minimap: { enabled: false },
lineNumbers: 'on',
roundedSelection: false,
scrollBeyondLastLine: false,
readOnly: false,
fontSize: 14,
automaticLayout: true,
};
useEffect(() => {
// Connect to WebSocket
connectWebSocket();
// Cleanup on unmount
return () => {
if (wsRef.current) {
wsRef.current.close();
}
};
}, []);
const connectWebSocket = () => {
setWsState(WebSocketState.CONNECTING);
const ws = new WebSocket('ws://localhost:8080/ws');
wsRef.current = ws;
ws.onopen = () => {
setWsState(WebSocketState.CONNECTED);
};
ws.onclose = () => {
setWsState(WebSocketState.DISCONNECTED);
// Attempt to reconnect after delay
setTimeout(connectWebSocket, 2000);
};
ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
handleWebSocketMessage(message);
} catch (error) {
console.error('Error parsing WebSocket message:', error);
}
};
};
const handleWebSocketMessage = (message) => {
switch (message.type) {
case MessageType.STATE_CHANGE:
setAgentState(message.data);
break;
case MessageType.CONTEXT_UPDATE:
setContext(message.data);
break;
case MessageType.RESPONSE_UPDATE:
setOriginalResponse(message.data);
setModifiedResponse(message.data);
setValidationError(message.validation_error || null);
break;
case MessageType.OUTPUT_UPDATE:
setOutput(prev => prev + message.data);
break;
}
};
const sendWebSocketMessage = (type, data) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(JSON.stringify({ type, data }));
}
};
const handleSendInput = (withStdin = false) => {
if (!input.trim()) return;
// Send input to backend
sendWebSocketMessage('SEND_INPUT', input);
setInput('');
if (withStdin) {
// Prepare read_stdin response
const readStdinXml = `<read_stdin>
<!-- Content will be populated with stdin -->
</read_stdin>`;
setOriginalResponse(readStdinXml);
setModifiedResponse(readStdinXml);
setActiveTab('modified');
}
};
const handleApproveContext = () => {
sendWebSocketMessage('APPROVE_CONTEXT');
};
const handleApproveResponse = (modified = false) => {
const response = modified ? modifiedResponse : originalResponse;
sendWebSocketMessage('APPROVE_RESPONSE', response);
};
const handleResetModified = () => {
setModifiedResponse(originalResponse);
};
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">React App</h1>
<button
className="bg-blue-500 text-white px-4 py-2 rounded"
onClick={handleCalculate}
>
Calculate 5 + 3
</button>
<p className="mt-4">Result: {result}</p>
</div>
)
}
<div className="min-h-screen bg-gray-100 p-8">
<div className="max-w-7xl mx-auto space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold">SIA Control Interface</h1>
<div className="flex items-center space-x-4">
<div className={`px-4 py-2 rounded-full ${
wsState === WebSocketState.CONNECTED
? 'bg-green-100 text-green-800'
: wsState === WebSocketState.CONNECTING
? 'bg-yellow-100 text-yellow-800'
: 'bg-red-100 text-red-800'
}`}>
{wsState}
</div>
<div className="px-4 py-2 rounded-full bg-blue-100 text-blue-800">
{agentState}
</div>
</div>
</div>
export default App
{/* Context */}
<Card>
<CardHeader>
<CardTitle>Context</CardTitle>
</CardHeader>
<CardContent>
<div className="h-64">
<Editor
height="100%"
defaultLanguage="xml"
value={context}
options={{ ...editorOptions, readOnly: true }}
/>
</div>
<div className="mt-4">
<Button
onClick={handleApproveContext}
disabled={agentState !== AgentState.CONTEXT_APPROVAL}
className="w-full"
>
Approve Context
</Button>
</div>
</CardContent>
</Card>
{/* Input/Response Tabs */}
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="input">Input</TabsTrigger>
<TabsTrigger value="original">Original Response</TabsTrigger>
<TabsTrigger value="modified">Modified Response</TabsTrigger>
</TabsList>
<TabsContent value="input">
<Card>
<CardContent className="pt-6">
<div className="space-y-4">
<div className="h-[200px]">
<Editor
height="100%"
defaultLanguage="plaintext"
value={input}
onChange={setInput}
options={editorOptions}
/>
</div>
<div className="flex space-x-2">
<Button
onClick={() => handleSendInput(false)}
className="flex-1"
>
Send Input
</Button>
<Button
onClick={() => handleSendInput(true)}
variant="secondary"
className="flex-1"
>
Send & Read Stdin
</Button>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="original">
<Card>
<CardContent className="pt-6">
<div className="space-y-4">
<div className="h-[200px]">
<Editor
height="100%"
defaultLanguage="xml"
value={originalResponse}
options={{ ...editorOptions, readOnly: true }}
/>
</div>
{validationError && (
<Alert variant="destructive">
<AlertDescription>{validationError}</AlertDescription>
</Alert>
)}
<Button
onClick={() => handleApproveResponse(false)}
disabled={agentState !== AgentState.RESPONSE_APPROVAL || validationError}
className="w-full"
>
Approve Original Response
</Button>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="modified">
<Card>
<CardContent className="pt-6">
<div className="space-y-4">
<div className="h-[200px]">
<Editor
height="100%"
defaultLanguage="xml"
value={modifiedResponse}
onChange={setModifiedResponse}
options={editorOptions}
/>
</div>
{validationError && (
<Alert variant="destructive">
<AlertDescription>{validationError}</AlertDescription>
</Alert>
)}
<div className="flex space-x-2">
<Button
onClick={handleResetModified}
variant="secondary"
className="flex-1"
>
Reset to Original
</Button>
<Button
onClick={() => handleApproveResponse(true)}
disabled={agentState !== AgentState.RESPONSE_APPROVAL || validationError}
className="flex-1"
>
Approve Modified Response
</Button>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
{/* Output Display */}
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>Output</CardTitle>
<Button
onClick={() => setOutput('')}
variant="outline"
size="sm"
>
Clear Output
</Button>
</CardHeader>
<CardContent>
<ScrollArea className="h-32">
<div className="font-mono whitespace-pre-wrap">
{output}
</div>
</ScrollArea>
</CardContent>
</Card>
</div>
</div>
);
};
export default SIAInterface;