Major UI update
This commit is contained in:
@@ -1,372 +1,141 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Header } from '@/components/Header';
|
||||
import { Sidebar } from '@/components/Sidebar';
|
||||
import { FloatingButton } from '@/components/FloatingButton';
|
||||
import { ContentEditor } from '@/components/editors/ContentEditor';
|
||||
import { StandardEditor } from '@/components/editors/StandardEditor';
|
||||
import { useWebSocket } from '@/hooks/useWebSocket';
|
||||
import { AgentState, Tabs, ClientMessageType, ServerMessageType } from '@/constants';
|
||||
|
||||
const WebSocketState = {
|
||||
CONNECTING: 'CONNECTING',
|
||||
CONNECTED: 'CONNECTED',
|
||||
DISCONNECTED: 'DISCONNECTED',
|
||||
};
|
||||
|
||||
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 ClientMessageType = {
|
||||
APPROVE_CONTEXT: 'APPROVE_CONTEXT',
|
||||
APPROVE_RESPONSE: 'APPROVE_RESPONSE',
|
||||
MODIFY_RESPONSE: 'MODIFY_RESPONSE',
|
||||
SEND_INPUT: 'SEND_INPUT',
|
||||
};
|
||||
|
||||
const SIAInterface = () => {
|
||||
const App = () => {
|
||||
// Editor content state
|
||||
const [context, setContext] = useState('');
|
||||
const [originalContext, setOriginalContext] = useState('');
|
||||
const [modifiedContext, setModifiedContext] = useState('');
|
||||
const [originalResponse, setOriginalResponse] = useState('');
|
||||
const [modifiedResponse, setModifiedResponse] = useState('');
|
||||
const [validationError, setValidationError] = useState(null);
|
||||
const [input, setInput] = useState('');
|
||||
const [output, setOutput] = useState('');
|
||||
const [validationError, setValidationError] = useState(null);
|
||||
|
||||
|
||||
// UI state
|
||||
const [activeTab, setActiveTab] = useState('original');
|
||||
const [activeTab, setActiveTab] = useState(Tabs.CONTEXT);
|
||||
const [showDiff, setShowDiff] = useState(false);
|
||||
const [showSidebar, setShowSidebar] = useState(false);
|
||||
const [agentState, setAgentState] = useState(AgentState.CONTEXT_APPROVAL);
|
||||
const [wsState, setWsState] = useState(WebSocketState.DISCONNECTED);
|
||||
|
||||
// WebSocket ref
|
||||
const wsRef = useRef(null);
|
||||
const { wsState, sendMessage, setMessageHandler } = useWebSocket('ws://localhost:8080/ws');
|
||||
|
||||
// Monaco editor options
|
||||
const editorOptions = {
|
||||
minimap: { enabled: false },
|
||||
lineNumbers: 'on',
|
||||
roundedSelection: false,
|
||||
scrollBeyondLastLine: false,
|
||||
readOnly: false,
|
||||
fontSize: 14,
|
||||
automaticLayout: true,
|
||||
// Set up WebSocket message handlers
|
||||
useEffect(() => {
|
||||
setMessageHandler(ServerMessageType.STATE_CHANGE, (message) => {
|
||||
setAgentState(message.state);
|
||||
});
|
||||
|
||||
setMessageHandler(ServerMessageType.CONTEXT_UPDATE, (message) => {
|
||||
setOriginalContext(message.context);
|
||||
setModifiedContext(message.context);
|
||||
});
|
||||
|
||||
setMessageHandler(ServerMessageType.RESPONSE_UPDATE, (message) => {
|
||||
setOriginalResponse(message.response);
|
||||
setModifiedResponse(message.response);
|
||||
setValidationError(message.validation_error);
|
||||
});
|
||||
|
||||
setMessageHandler(ServerMessageType.OUTPUT_UPDATE, (message) => {
|
||||
setOutput(prev => prev + message.data);
|
||||
});
|
||||
}, [setMessageHandler]);
|
||||
|
||||
const handleApprove = () => {
|
||||
if (agentState === AgentState.CONTEXT_APPROVAL) {
|
||||
sendMessage(ClientMessageType.APPROVE_CONTEXT);
|
||||
} else if (agentState === AgentState.RESPONSE_APPROVAL) {
|
||||
sendMessage(ClientMessageType.APPROVE_RESPONSE, modifiedResponse);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendInput = () => {
|
||||
if (input.trim()) {
|
||||
sendMessage(ClientMessageType.SEND_INPUT, input);
|
||||
setInput('');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
connectWebSocket();
|
||||
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);
|
||||
setTimeout(connectWebSocket, 2000);
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
|
||||
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.state);
|
||||
switch (agentState) {
|
||||
case AgentState.CONTEXT_APPROVAL:
|
||||
setActiveTab(Tabs.CONTEXT);
|
||||
break;
|
||||
case MessageType.CONTEXT_UPDATE:
|
||||
setContext(message.context);
|
||||
break;
|
||||
case MessageType.RESPONSE_UPDATE:
|
||||
setOriginalResponse(message.response);
|
||||
setModifiedResponse(message.response);
|
||||
setValidationError(message.validation_error);
|
||||
break;
|
||||
case MessageType.OUTPUT_UPDATE:
|
||||
setOutput(prev => prev + message.data);
|
||||
case AgentState.RESPONSE_APPROVAL:
|
||||
setActiveTab(Tabs.RESPONSE);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const sendWebSocketMessage = (type, data = null) => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
const message = {
|
||||
type: type, // Ensure exact string match
|
||||
data: data || undefined // Only include if not null
|
||||
};
|
||||
console.log('Sending message:', JSON.stringify(message)); // Log exact message being sent
|
||||
wsRef.current.send(JSON.stringify(message));
|
||||
} else {
|
||||
console.error('WebSocket not connected, state:', wsRef.current?.readyState);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendInput = (withStdin = false) => {
|
||||
if (!input.trim()) return;
|
||||
|
||||
sendWebSocketMessage(ClientMessageType.SEND_INPUT, input);
|
||||
setInput('');
|
||||
|
||||
if (withStdin) {
|
||||
const readStdinXml = `<read_stdin>
|
||||
<!-- Content will be populated with stdin -->
|
||||
</read_stdin>`;
|
||||
|
||||
setOriginalResponse(readStdinXml);
|
||||
setModifiedResponse(readStdinXml);
|
||||
setActiveTab('modified');
|
||||
}
|
||||
};
|
||||
|
||||
const handleApproveContext = () => {
|
||||
console.log('Current state:', agentState);
|
||||
console.log('WebSocket state:', wsRef.current?.readyState);
|
||||
if (agentState === AgentState.CONTEXT_APPROVAL &&
|
||||
wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
console.log('Sending approve context message');
|
||||
sendWebSocketMessage(ClientMessageType.APPROVE_CONTEXT);
|
||||
} else {
|
||||
console.warn(
|
||||
'Cannot approve context.',
|
||||
'Agent state:', agentState,
|
||||
'WebSocket state:', wsRef.current?.readyState
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleApproveResponse = (modified = false) => {
|
||||
if (agentState === AgentState.RESPONSE_APPROVAL) {
|
||||
const response = modified ? modifiedResponse : originalResponse;
|
||||
sendWebSocketMessage(ClientMessageType.APPROVE_RESPONSE, response);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetModified = () => {
|
||||
setModifiedResponse(originalResponse);
|
||||
};
|
||||
}, [agentState]);
|
||||
|
||||
return (
|
||||
<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>
|
||||
<div className="h-screen bg-gray-100 flex flex-col overflow-hidden">
|
||||
<Header
|
||||
wsState={wsState}
|
||||
agentState={agentState}
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
/>
|
||||
|
||||
{/* 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 || wsState !== WebSocketState.CONNECTED}
|
||||
className="w-full"
|
||||
>
|
||||
Approve Context
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
<main className="flex-1 p-4 overflow-auto">
|
||||
{activeTab === Tabs.CONTEXT && (
|
||||
<ContentEditor
|
||||
showDiff={showDiff}
|
||||
originalContent={originalContext}
|
||||
modifiedContent={modifiedContext}
|
||||
onChange={value => setModifiedContext(value)}
|
||||
validationError={null}
|
||||
/>
|
||||
)}
|
||||
{activeTab === Tabs.RESPONSE && (
|
||||
<ContentEditor
|
||||
showDiff={showDiff}
|
||||
originalContent={originalResponse}
|
||||
modifiedContent={modifiedResponse}
|
||||
onChange={value => setModifiedResponse(value)}
|
||||
validationError={validationError}
|
||||
/>
|
||||
)}
|
||||
{activeTab === Tabs.INPUT && (
|
||||
<StandardEditor
|
||||
content={input}
|
||||
onChange={value => setInput(value)}
|
||||
language="plaintext"
|
||||
/>
|
||||
)}
|
||||
{activeTab === Tabs.OUTPUT && (
|
||||
<StandardEditor
|
||||
content={output}
|
||||
readOnly={true}
|
||||
language="plaintext"
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
|
||||
{/* Input/Response Tabs */}
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="original">Original Response</TabsTrigger>
|
||||
<TabsTrigger value="modified">Modified Response</TabsTrigger>
|
||||
<TabsTrigger value="input">Input</TabsTrigger>
|
||||
</TabsList>
|
||||
<Sidebar
|
||||
showSidebar={showSidebar}
|
||||
activeTab={activeTab}
|
||||
agentState={agentState}
|
||||
validationError={validationError}
|
||||
showDiff={showDiff}
|
||||
input={input}
|
||||
onApprove={handleApprove}
|
||||
onToggleDiff={() => setShowDiff(!showDiff)}
|
||||
onSendInput={handleSendInput}
|
||||
onClearOutput={() => setOutput('')}
|
||||
/>
|
||||
|
||||
<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 ||
|
||||
wsState !== WebSocketState.CONNECTED
|
||||
}
|
||||
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 ||
|
||||
wsState !== WebSocketState.CONNECTED
|
||||
}
|
||||
className="flex-1"
|
||||
>
|
||||
Approve Modified Response
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
|
||||
<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)}
|
||||
disabled={wsState !== WebSocketState.CONNECTED}
|
||||
className="flex-1"
|
||||
>
|
||||
Send Input
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleSendInput(true)}
|
||||
variant="secondary"
|
||||
disabled={wsState !== WebSocketState.CONNECTED}
|
||||
className="flex-1"
|
||||
>
|
||||
Send & Read Stdin
|
||||
</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>
|
||||
<FloatingButton onClick={() => setShowSidebar(!showSidebar)} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SIAInterface;
|
||||
export default App;
|
||||
Reference in New Issue
Block a user