Major UI update
This commit is contained in:
47
claude.sh
47
claude.sh
@@ -1,16 +1,41 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
target_dir="." # Default to current directory
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-d|--directory)
|
||||||
|
if [ -n "$2" ] && [ -d "$2" ]; then
|
||||||
|
target_dir="$2"
|
||||||
|
shift 2
|
||||||
|
else
|
||||||
|
echo "Error: Directory '$2' does not exist or is not specified" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 [-d|--directory <path>]" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Change to target directory while storing original directory
|
||||||
|
original_dir=$(pwd)
|
||||||
|
cd "$target_dir" || exit 1
|
||||||
|
|
||||||
# Clear/create output file
|
# Clear/create output file
|
||||||
> claude.txt
|
output_file="$original_dir/claude.txt"
|
||||||
|
> "$output_file"
|
||||||
|
|
||||||
# Generate and add directory tree
|
# Generate and add directory tree
|
||||||
echo "Directory Tree:" > claude.txt
|
echo "Directory Tree:" > "$output_file"
|
||||||
echo "=============" >> claude.txt
|
echo "=============" >> "$output_file"
|
||||||
# Use tree with gitignore patterns
|
# Use tree with gitignore patterns
|
||||||
tree -I "$(git check-ignore * .*)" >> claude.txt
|
tree -I "$(git check-ignore * .*)" >> "$output_file"
|
||||||
|
|
||||||
echo -e "\nFile Contents:" >> claude.txt
|
echo -e "\nFile Contents:" >> "$output_file"
|
||||||
echo "=============" >> claude.txt
|
echo "=============" >> "$output_file"
|
||||||
|
|
||||||
# Use git ls-files to get tracked files and untracked files that aren't ignored
|
# Use git ls-files to get tracked files and untracked files that aren't ignored
|
||||||
# The --exclude-standard flag makes git ls-files respect .gitignore
|
# The --exclude-standard flag makes git ls-files respect .gitignore
|
||||||
@@ -22,7 +47,6 @@ echo "=============" >> claude.txt
|
|||||||
if [ "$file" = "claude.txt" ]; then
|
if [ "$file" = "claude.txt" ]; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Skip non-existent files
|
# Skip non-existent files
|
||||||
if [ ! -f "$file" ]; then
|
if [ ! -f "$file" ]; then
|
||||||
continue
|
continue
|
||||||
@@ -34,9 +58,12 @@ echo "=============" >> claude.txt
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\n=== File: $file ===" >> claude.txt
|
echo -e "\n=== File: $file ===" >> "$output_file"
|
||||||
echo -e "------------------------" >> claude.txt
|
echo -e "------------------------" >> "$output_file"
|
||||||
cat "$file" >> claude.txt
|
cat "$file" >> "$output_file"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Return to original directory
|
||||||
|
cd "$original_dir" || exit 1
|
||||||
|
|
||||||
echo "Concatenation complete. Output written to claude.txt"
|
echo "Concatenation complete. Output written to claude.txt"
|
||||||
@@ -70,7 +70,8 @@ class Main:
|
|||||||
self._app = web.Application()
|
self._app = web.Application()
|
||||||
self._init_routes()
|
self._init_routes()
|
||||||
|
|
||||||
async def app(self):
|
@property
|
||||||
|
def app(self):
|
||||||
return self._app
|
return self._app
|
||||||
|
|
||||||
def _init_routes(self):
|
def _init_routes(self):
|
||||||
@@ -114,6 +115,5 @@ class Main:
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main = Main()
|
main = Main()
|
||||||
app = asyncio.run(main.app())
|
|
||||||
print("Web server started at http://localhost:8080")
|
print("Web server started at http://localhost:8080")
|
||||||
web.run_app(app, port=8080)
|
web.run_app(main.app, port=8080)
|
||||||
@@ -1,372 +1,141 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import Editor from '@monaco-editor/react';
|
import { Header } from '@/components/Header';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Sidebar } from '@/components/Sidebar';
|
||||||
import { Button } from '@/components/ui/button';
|
import { FloatingButton } from '@/components/FloatingButton';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ContentEditor } from '@/components/editors/ContentEditor';
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { StandardEditor } from '@/components/editors/StandardEditor';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { useWebSocket } from '@/hooks/useWebSocket';
|
||||||
|
import { AgentState, Tabs, ClientMessageType, ServerMessageType } from '@/constants';
|
||||||
|
|
||||||
const WebSocketState = {
|
|
||||||
CONNECTING: 'CONNECTING',
|
|
||||||
CONNECTED: 'CONNECTED',
|
|
||||||
DISCONNECTED: 'DISCONNECTED',
|
|
||||||
};
|
|
||||||
|
|
||||||
const AgentState = {
|
const App = () => {
|
||||||
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 = () => {
|
|
||||||
// Editor content state
|
// Editor content state
|
||||||
const [context, setContext] = useState('');
|
const [originalContext, setOriginalContext] = useState('');
|
||||||
|
const [modifiedContext, setModifiedContext] = useState('');
|
||||||
const [originalResponse, setOriginalResponse] = useState('');
|
const [originalResponse, setOriginalResponse] = useState('');
|
||||||
const [modifiedResponse, setModifiedResponse] = useState('');
|
const [modifiedResponse, setModifiedResponse] = useState('');
|
||||||
|
const [validationError, setValidationError] = useState(null);
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
const [output, setOutput] = useState('');
|
const [output, setOutput] = useState('');
|
||||||
const [validationError, setValidationError] = useState(null);
|
|
||||||
|
|
||||||
// UI state
|
// 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 [agentState, setAgentState] = useState(AgentState.CONTEXT_APPROVAL);
|
||||||
const [wsState, setWsState] = useState(WebSocketState.DISCONNECTED);
|
|
||||||
|
|
||||||
// WebSocket ref
|
const { wsState, sendMessage, setMessageHandler } = useWebSocket('ws://localhost:8080/ws');
|
||||||
const wsRef = useRef(null);
|
|
||||||
|
|
||||||
// Monaco editor options
|
// Set up WebSocket message handlers
|
||||||
const editorOptions = {
|
useEffect(() => {
|
||||||
minimap: { enabled: false },
|
setMessageHandler(ServerMessageType.STATE_CHANGE, (message) => {
|
||||||
lineNumbers: 'on',
|
setAgentState(message.state);
|
||||||
roundedSelection: false,
|
});
|
||||||
scrollBeyondLastLine: false,
|
|
||||||
readOnly: false,
|
setMessageHandler(ServerMessageType.CONTEXT_UPDATE, (message) => {
|
||||||
fontSize: 14,
|
setOriginalContext(message.context);
|
||||||
automaticLayout: true,
|
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(() => {
|
useEffect(() => {
|
||||||
connectWebSocket();
|
switch (agentState) {
|
||||||
return () => {
|
case AgentState.CONTEXT_APPROVAL:
|
||||||
if (wsRef.current) {
|
setActiveTab(Tabs.CONTEXT);
|
||||||
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);
|
|
||||||
break;
|
break;
|
||||||
case MessageType.CONTEXT_UPDATE:
|
case AgentState.RESPONSE_APPROVAL:
|
||||||
setContext(message.context);
|
setActiveTab(Tabs.RESPONSE);
|
||||||
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);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
}, [agentState]);
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-100 p-8">
|
<div className="h-screen bg-gray-100 flex flex-col overflow-hidden">
|
||||||
<div className="max-w-7xl mx-auto space-y-6">
|
<Header
|
||||||
{/* Header */}
|
wsState={wsState}
|
||||||
<div className="flex items-center justify-between">
|
agentState={agentState}
|
||||||
<h1 className="text-3xl font-bold">SIA Control Interface</h1>
|
activeTab={activeTab}
|
||||||
<div className="flex items-center space-x-4">
|
setActiveTab={setActiveTab}
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* Context */}
|
<div className="flex flex-1 overflow-hidden">
|
||||||
<Card>
|
<main className="flex-1 p-4 overflow-auto">
|
||||||
<CardHeader>
|
{activeTab === Tabs.CONTEXT && (
|
||||||
<CardTitle>Context</CardTitle>
|
<ContentEditor
|
||||||
</CardHeader>
|
showDiff={showDiff}
|
||||||
<CardContent>
|
originalContent={originalContext}
|
||||||
<div className="h-64">
|
modifiedContent={modifiedContext}
|
||||||
<Editor
|
onChange={value => setModifiedContext(value)}
|
||||||
height="100%"
|
validationError={null}
|
||||||
defaultLanguage="xml"
|
/>
|
||||||
value={context}
|
)}
|
||||||
options={{ ...editorOptions, readOnly: true }}
|
{activeTab === Tabs.RESPONSE && (
|
||||||
/>
|
<ContentEditor
|
||||||
</div>
|
showDiff={showDiff}
|
||||||
<div className="mt-4">
|
originalContent={originalResponse}
|
||||||
<Button
|
modifiedContent={modifiedResponse}
|
||||||
onClick={handleApproveContext}
|
onChange={value => setModifiedResponse(value)}
|
||||||
disabled={agentState !== AgentState.CONTEXT_APPROVAL || wsState !== WebSocketState.CONNECTED}
|
validationError={validationError}
|
||||||
className="w-full"
|
/>
|
||||||
>
|
)}
|
||||||
Approve Context
|
{activeTab === Tabs.INPUT && (
|
||||||
</Button>
|
<StandardEditor
|
||||||
</div>
|
content={input}
|
||||||
</CardContent>
|
onChange={value => setInput(value)}
|
||||||
</Card>
|
language="plaintext"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{activeTab === Tabs.OUTPUT && (
|
||||||
|
<StandardEditor
|
||||||
|
content={output}
|
||||||
|
readOnly={true}
|
||||||
|
language="plaintext"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
|
|
||||||
{/* Input/Response Tabs */}
|
<Sidebar
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
showSidebar={showSidebar}
|
||||||
<TabsList className="grid w-full grid-cols-3">
|
activeTab={activeTab}
|
||||||
<TabsTrigger value="original">Original Response</TabsTrigger>
|
agentState={agentState}
|
||||||
<TabsTrigger value="modified">Modified Response</TabsTrigger>
|
validationError={validationError}
|
||||||
<TabsTrigger value="input">Input</TabsTrigger>
|
showDiff={showDiff}
|
||||||
</TabsList>
|
input={input}
|
||||||
|
onApprove={handleApprove}
|
||||||
|
onToggleDiff={() => setShowDiff(!showDiff)}
|
||||||
|
onSendInput={handleSendInput}
|
||||||
|
onClearOutput={() => setOutput('')}
|
||||||
|
/>
|
||||||
|
|
||||||
<TabsContent value="original">
|
<FloatingButton onClick={() => setShowSidebar(!showSidebar)} />
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SIAInterface;
|
export default App;
|
||||||
14
web/src/components/FloatingButton.jsx
Normal file
14
web/src/components/FloatingButton.jsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Menu } from 'lucide-react';
|
||||||
|
|
||||||
|
export const FloatingButton = ({ onClick }) => (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="lg"
|
||||||
|
className="fixed bottom-4 right-4 rounded-full shadow-lg md:hidden z-[60]"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<Menu className="h-6 w-6" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
48
web/src/components/Header.jsx
Normal file
48
web/src/components/Header.jsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { WebSocketState, AgentState, Tabs } from '../constants';
|
||||||
|
|
||||||
|
export const Header = ({
|
||||||
|
wsState,
|
||||||
|
agentState,
|
||||||
|
activeTab,
|
||||||
|
setActiveTab,
|
||||||
|
}) => (
|
||||||
|
<header className="bg-white shadow-sm">
|
||||||
|
<div className="flex items-center justify-between px-4 py-3">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<h1 className="text-xl font-semibold">SIA Control Interface</h1>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className={`px-3 py-1 rounded-full text-sm ${
|
||||||
|
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-3 py-1 rounded-full bg-blue-100 text-blue-800 text-sm">
|
||||||
|
{agentState}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="border-t">
|
||||||
|
<div className="flex px-4">
|
||||||
|
{Object.values(Tabs).map((tab) => (
|
||||||
|
<button
|
||||||
|
key={tab}
|
||||||
|
onClick={() => setActiveTab(tab)}
|
||||||
|
className={`px-4 py-2 font-medium text-sm border-b-2 ${
|
||||||
|
activeTab === tab
|
||||||
|
? 'border-blue-500 text-blue-600'
|
||||||
|
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tab.charAt(0).toUpperCase() + tab.slice(1)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
55
web/src/components/Sidebar.jsx
Normal file
55
web/src/components/Sidebar.jsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { AgentState, Tabs } from '../constants';
|
||||||
|
|
||||||
|
export const Sidebar = ({
|
||||||
|
showSidebar,
|
||||||
|
agentState,
|
||||||
|
showDiff,
|
||||||
|
input,
|
||||||
|
onApprove,
|
||||||
|
onToggleDiff,
|
||||||
|
onSendInput,
|
||||||
|
onClearOutput,
|
||||||
|
}) => (
|
||||||
|
<aside className={`
|
||||||
|
fixed top-0 right-0 h-screen w-64 bg-white shadow-lg
|
||||||
|
transition-transform duration-200 ease-in-out z-50
|
||||||
|
md:sticky md:h-[calc(100vh-4rem)] md:top-16
|
||||||
|
${showSidebar ? 'translate-x-0' : 'translate-x-full md:translate-x-0'}
|
||||||
|
`}>
|
||||||
|
<div className="p-4 space-y-4 md:pt-0">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={onToggleDiff}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{showDiff ? 'Hide Diff' : 'Show Diff'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={onApprove}
|
||||||
|
disabled={(agentState !== AgentState.CONTEXT_APPROVAL) && (agentState !== AgentState.RESPONSE_APPROVAL)}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
Approve
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={onSendInput}
|
||||||
|
disabled={!input.trim()}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
Send Input
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={onClearOutput}
|
||||||
|
variant="outline"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
Clear Output
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
35
web/src/components/editors/BaseEditor.jsx
Normal file
35
web/src/components/editors/BaseEditor.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Editor from '@monaco-editor/react';
|
||||||
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
|
|
||||||
|
export const BaseEditor = ({
|
||||||
|
content,
|
||||||
|
onChange,
|
||||||
|
readOnly = false,
|
||||||
|
language = 'xml',
|
||||||
|
validationError = null,
|
||||||
|
}) => (
|
||||||
|
<Card className="h-full">
|
||||||
|
<CardContent className="p-0 h-full">
|
||||||
|
{validationError && (
|
||||||
|
<Alert variant="destructive" className="m-4">
|
||||||
|
<AlertDescription>{validationError}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
<Editor
|
||||||
|
height="100%"
|
||||||
|
defaultLanguage={language}
|
||||||
|
value={content}
|
||||||
|
onChange={onChange}
|
||||||
|
options={{
|
||||||
|
minimap: { enabled: false },
|
||||||
|
lineNumbers: 'on',
|
||||||
|
readOnly,
|
||||||
|
fontSize: 14,
|
||||||
|
automaticLayout: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
35
web/src/components/editors/ContentEditor.jsx
Normal file
35
web/src/components/editors/ContentEditor.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { DiffEditorWrapper } from './DiffEditorWrapper';
|
||||||
|
import { StandardEditor } from './StandardEditor';
|
||||||
|
|
||||||
|
export const ContentEditor = ({
|
||||||
|
showDiff,
|
||||||
|
originalContent,
|
||||||
|
modifiedContent,
|
||||||
|
onChange,
|
||||||
|
validationError = null,
|
||||||
|
language = 'xml',
|
||||||
|
}) => {
|
||||||
|
if (showDiff) {
|
||||||
|
return (
|
||||||
|
<DiffEditorWrapper
|
||||||
|
originalContent={originalContent}
|
||||||
|
modifiedContent={modifiedContent}
|
||||||
|
onChange={value => {
|
||||||
|
onChange(value);
|
||||||
|
}}
|
||||||
|
language={language}
|
||||||
|
validationError={validationError}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StandardEditor
|
||||||
|
content={modifiedContent}
|
||||||
|
onChange={onChange}
|
||||||
|
language={language}
|
||||||
|
validationError={validationError}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
15
web/src/components/editors/ContextEditor.jsx
Normal file
15
web/src/components/editors/ContextEditor.jsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { BaseEditor } from './BaseEditor';
|
||||||
|
|
||||||
|
export const ContextEditor = ({
|
||||||
|
originalContent,
|
||||||
|
modifiedContent,
|
||||||
|
showOriginal,
|
||||||
|
onChange,
|
||||||
|
}) => (
|
||||||
|
<BaseEditor
|
||||||
|
content={showOriginal ? originalContent : modifiedContent}
|
||||||
|
onChange={onChange}
|
||||||
|
readOnly={showOriginal}
|
||||||
|
/>
|
||||||
|
);
|
||||||
60
web/src/components/editors/DiffEditorWrapper.jsx
Normal file
60
web/src/components/editors/DiffEditorWrapper.jsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { DiffEditor } from '@monaco-editor/react';
|
||||||
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
|
|
||||||
|
export const DiffEditorWrapper = ({
|
||||||
|
originalContent,
|
||||||
|
modifiedContent,
|
||||||
|
onChange,
|
||||||
|
language = 'xml',
|
||||||
|
validationError = null,
|
||||||
|
}) => {
|
||||||
|
const handleMount = (editor, monaco) => {
|
||||||
|
const modified = editor.getModifiedEditor();
|
||||||
|
modified.onDidChangeModelContent(() => {
|
||||||
|
if (onChange) {
|
||||||
|
onChange(modified.getValue());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Card className="h-[calc(100vh-8rem)]">
|
||||||
|
<CardContent className="p-0 h-full">
|
||||||
|
{validationError && (
|
||||||
|
<Alert variant="destructive" className="m-4">
|
||||||
|
<AlertDescription>{validationError}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
<DiffEditor
|
||||||
|
height="100%"
|
||||||
|
original={originalContent}
|
||||||
|
modified={modifiedContent}
|
||||||
|
language={language}
|
||||||
|
onChange={onChange}
|
||||||
|
onMount={handleMount}
|
||||||
|
options={{
|
||||||
|
readOnly: false,
|
||||||
|
originalEditable: false,
|
||||||
|
modifiedEditable: true,
|
||||||
|
minimap: { enabled: true },
|
||||||
|
lineNumbers: 'on',
|
||||||
|
fontSize: 14,
|
||||||
|
renderSideBySide: true,
|
||||||
|
automaticLayout: true,
|
||||||
|
diffAlgorithm: 'advanced',
|
||||||
|
diffWordWrap: 'off',
|
||||||
|
originalEditor: {
|
||||||
|
readOnly: true,
|
||||||
|
wordWrap: 'on',
|
||||||
|
},
|
||||||
|
modifiedEditor: {
|
||||||
|
readOnly: false,
|
||||||
|
wordWrap: 'on',
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
};
|
||||||
10
web/src/components/editors/InputEditor.jsx
Normal file
10
web/src/components/editors/InputEditor.jsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { BaseEditor } from './BaseEditor';
|
||||||
|
|
||||||
|
export const InputEditor = ({ content, onChange }) => (
|
||||||
|
<BaseEditor
|
||||||
|
content={content}
|
||||||
|
onChange={onChange}
|
||||||
|
language="plaintext"
|
||||||
|
/>
|
||||||
|
);
|
||||||
10
web/src/components/editors/OutputEditor.jsx
Normal file
10
web/src/components/editors/OutputEditor.jsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { BaseEditor } from './BaseEditor';
|
||||||
|
|
||||||
|
export const OutputEditor = ({ content }) => (
|
||||||
|
<BaseEditor
|
||||||
|
content={content}
|
||||||
|
readOnly={true}
|
||||||
|
language="plaintext"
|
||||||
|
/>
|
||||||
|
);
|
||||||
17
web/src/components/editors/ResponseEditor.jsx
Normal file
17
web/src/components/editors/ResponseEditor.jsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { BaseEditor } from './BaseEditor';
|
||||||
|
|
||||||
|
export const ResponseEditor = ({
|
||||||
|
originalContent,
|
||||||
|
modifiedContent,
|
||||||
|
showOriginal,
|
||||||
|
onChange,
|
||||||
|
validationError,
|
||||||
|
}) => (
|
||||||
|
<BaseEditor
|
||||||
|
content={showOriginal ? originalContent : modifiedContent}
|
||||||
|
onChange={onChange}
|
||||||
|
readOnly={showOriginal}
|
||||||
|
validationError={validationError}
|
||||||
|
/>
|
||||||
|
);
|
||||||
35
web/src/components/editors/StandardEditor.jsx
Normal file
35
web/src/components/editors/StandardEditor.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Editor from '@monaco-editor/react';
|
||||||
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
|
|
||||||
|
export const StandardEditor = ({
|
||||||
|
content,
|
||||||
|
onChange,
|
||||||
|
readOnly = false,
|
||||||
|
language = 'xml',
|
||||||
|
validationError = null,
|
||||||
|
}) => (
|
||||||
|
<Card className="h-[calc(100vh-8rem)]">
|
||||||
|
<CardContent className="p-0 h-full">
|
||||||
|
{validationError && (
|
||||||
|
<Alert variant="destructive" className="m-4">
|
||||||
|
<AlertDescription>{validationError}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
<Editor
|
||||||
|
height="100%"
|
||||||
|
language={language}
|
||||||
|
value={content}
|
||||||
|
onChange={onChange}
|
||||||
|
options={{
|
||||||
|
minimap: { enabled: false },
|
||||||
|
lineNumbers: 'on',
|
||||||
|
readOnly,
|
||||||
|
fontSize: 14,
|
||||||
|
automaticLayout: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
34
web/src/constants.jsx
Normal file
34
web/src/constants.jsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
export const WebSocketState = {
|
||||||
|
CONNECTING: 'CONNECTING',
|
||||||
|
CONNECTED: 'CONNECTED',
|
||||||
|
DISCONNECTED: 'DISCONNECTED',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AgentState = {
|
||||||
|
UPDATE: 'UPDATE',
|
||||||
|
CONTEXT_APPROVAL: 'CONTEXT_APPROVAL',
|
||||||
|
INFERENCE: 'INFERENCE',
|
||||||
|
RESPONSE_APPROVAL: 'RESPONSE_APPROVAL',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Tabs = {
|
||||||
|
CONTEXT: 'context',
|
||||||
|
RESPONSE: 'response',
|
||||||
|
INPUT: 'input',
|
||||||
|
OUTPUT: 'output'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ClientMessageType = {
|
||||||
|
MODIFY_CONTEXT: 'MODIFY_CONTEXT',
|
||||||
|
APPROVE_CONTEXT: 'APPROVE_CONTEXT',
|
||||||
|
MODIFY_RESPONSE: 'MODIFY_RESPONSE',
|
||||||
|
APPROVE_RESPONSE: 'APPROVE_RESPONSE',
|
||||||
|
SEND_INPUT: 'SEND_INPUT',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ServerMessageType = {
|
||||||
|
STATE_CHANGE: 'STATE_CHANGE',
|
||||||
|
CONTEXT_UPDATE: 'CONTEXT_UPDATE',
|
||||||
|
RESPONSE_UPDATE: 'RESPONSE_UPDATE',
|
||||||
|
OUTPUT_UPDATE: 'OUTPUT_UPDATE',
|
||||||
|
};
|
||||||
72
web/src/hooks/useWebSocket.jsx
Normal file
72
web/src/hooks/useWebSocket.jsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
|
import { WebSocketState, MessageType } from '../constants';
|
||||||
|
|
||||||
|
export const useWebSocket = (url) => {
|
||||||
|
const [wsState, setWsState] = useState(WebSocketState.DISCONNECTED);
|
||||||
|
const wsRef = useRef(null);
|
||||||
|
const messageHandlersRef = useRef({});
|
||||||
|
|
||||||
|
const connect = useCallback(() => {
|
||||||
|
setWsState(WebSocketState.CONNECTING);
|
||||||
|
const ws = new WebSocket(url);
|
||||||
|
wsRef.current = ws;
|
||||||
|
|
||||||
|
ws.onopen = () => {
|
||||||
|
setWsState(WebSocketState.CONNECTED);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = () => {
|
||||||
|
setWsState(WebSocketState.DISCONNECTED);
|
||||||
|
setTimeout(connect, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = (error) => {
|
||||||
|
console.error('WebSocket error:', error);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const message = JSON.parse(event.data);
|
||||||
|
const handler = messageHandlersRef.current[message.type];
|
||||||
|
if (handler) {
|
||||||
|
handler(message);
|
||||||
|
} else {
|
||||||
|
console.warn('No handler for message type:', message.type);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error handling WebSocket message:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return ws;
|
||||||
|
}, [url]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const ws = connect();
|
||||||
|
return () => {
|
||||||
|
if (ws) ws.close();
|
||||||
|
};
|
||||||
|
}, [connect]);
|
||||||
|
|
||||||
|
const sendMessage = useCallback((type, data = null) => {
|
||||||
|
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||||
|
const message = {
|
||||||
|
type,
|
||||||
|
data: data || undefined
|
||||||
|
};
|
||||||
|
wsRef.current.send(JSON.stringify(message));
|
||||||
|
} else {
|
||||||
|
console.error('WebSocket not connected, state:', wsRef.current?.readyState);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setMessageHandler = useCallback((type, handler) => {
|
||||||
|
messageHandlersRef.current[type] = handler;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
wsState,
|
||||||
|
sendMessage,
|
||||||
|
setMessageHandler
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user