diff --git a/claude.sh b/claude.sh index 6d692c1..b50a24b 100755 --- a/claude.sh +++ b/claude.sh @@ -1,15 +1,5 @@ #!/bin/bash -# Check if we're in a git repository -if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then - echo "Not in a git repository. Creating temporary one to use gitignore rules..." - # Initialize temporary git repo if not in one - git init >/dev/null 2>&1 - temp_git=true -else - temp_git=false -fi - # Clear/create output file > claude.txt @@ -32,6 +22,11 @@ echo "=============" >> claude.txt if [ "$file" = "claude.txt" ]; then continue fi + + # Skip non-existent files + if [ ! -f "$file" ]; then + continue + fi # Skip binary files if file "$file" | grep -q "binary"; then @@ -44,10 +39,4 @@ echo "=============" >> claude.txt cat "$file" >> claude.txt done -# Clean up temporary git repo if we created one -if [ "$temp_git" = true ]; then - rm -rf .git - echo "Cleaned up temporary git repository" -fi - echo "Concatenation complete. Output written to claude.txt" \ No newline at end of file diff --git a/web/.dockerignore b/web/.dockerignore index 0eab0ea..b440869 100644 --- a/web/.dockerignore +++ b/web/.dockerignore @@ -1,4 +1,4 @@ node_modules dist -coverage -.gitignore \ No newline at end of file +.git +*.log \ No newline at end of file diff --git a/web/index.html b/web/index.html index d61a977..1e04a06 100644 --- a/web/index.html +++ b/web/index.html @@ -3,7 +3,7 @@ - React App + SIA Web Interface
diff --git a/web/jest.config.js b/web/jest.config.js deleted file mode 100644 index b711267..0000000 --- a/web/jest.config.js +++ /dev/null @@ -1,10 +0,0 @@ -export default { - testEnvironment: 'jsdom', - transform: { - '^.+\\.(js|jsx)$': 'babel-jest', - }, - moduleNameMapper: { - '\\.(css|less|scss|sass)$': 'identity-obj-proxy', - }, - setupFilesAfterEnv: ['/src/setupTests.js'], -} \ No newline at end of file diff --git a/web/package.json b/web/package.json index 5b447e4..36f5bd2 100644 --- a/web/package.json +++ b/web/package.json @@ -1,7 +1,7 @@ { - "name": "my-react-app", + "name": "sia-web", "private": true, - "version": "0.0.0", + "version": "0.1.0", "type": "module", "scripts": { "dev": "vite", @@ -11,8 +11,17 @@ "test:watch": "jest --watch" }, "dependencies": { + "@monaco-editor/react": "^4.6.0", + "@radix-ui/react-alert-dialog": "^1.0.5", + "@radix-ui/react-scroll-area": "^1.0.5", + "@radix-ui/react-tabs": "^1.0.4", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.0", + "lucide-react": "^0.263.1", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "tailwind-merge": "^2.2.0", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { "@babel/preset-env": "^7.23.9", @@ -30,4 +39,4 @@ "tailwindcss": "^3.4.1", "vite": "^5.1.0" } -} +} \ No newline at end of file diff --git a/web/postcss.config.js b/web/postcss.config.js new file mode 100644 index 0000000..e99ebc2 --- /dev/null +++ b/web/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/web/src/components/App.jsx b/web/src/components/App.jsx index 1ea5e7a..9feb710 100644 --- a/web/src/components/App.jsx +++ b/web/src/components/App.jsx @@ -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 = ` + + `; + + 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 ( -
-

React App

- -

Result: {result}

-
- ) -} +
+
+ {/* Header */} +
+

SIA Control Interface

+
+
+ {wsState} +
+
+ {agentState} +
+
+
-export default App \ No newline at end of file + {/* Context */} + + + Context + + +
+ +
+
+ +
+
+
+ + {/* Input/Response Tabs */} + + + Input + Original Response + Modified Response + + + + + +
+
+ +
+
+ + +
+
+
+
+
+ + + + +
+
+ +
+ {validationError && ( + + {validationError} + + )} + +
+
+
+
+ + + + +
+
+ +
+ {validationError && ( + + {validationError} + + )} +
+ + +
+
+
+
+
+
+ + {/* Output Display */} + + + Output + + + + +
+ {output} +
+
+
+
+
+
+ ); +}; + +export default SIAInterface; \ No newline at end of file diff --git a/web/src/components/App.test.jsx b/web/src/components/App.test.jsx deleted file mode 100644 index eb632bc..0000000 --- a/web/src/components/App.test.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import { render, screen, fireEvent } from '@testing-library/react' -import App from './App' - -describe('App', () => { - it('renders without crashing', () => { - render() - expect(screen.getByText(/React App/i)).toBeInTheDocument() - }) - - it('calculates correctly when button is clicked', () => { - render() - fireEvent.click(screen.getByText(/Calculate/i)) - expect(screen.getByText(/Result: 8/i)).toBeInTheDocument() - }) -}) diff --git a/web/src/components/ui/alert.jsx b/web/src/components/ui/alert.jsx new file mode 100644 index 0000000..b594134 --- /dev/null +++ b/web/src/components/ui/alert.jsx @@ -0,0 +1,29 @@ +import * as React from "react" + +const Alert = React.forwardRef(({ className, variant = "default", ...props }, ref) => { + const variants = { + default: "bg-background text-foreground", + destructive: "bg-destructive text-destructive-foreground" + } + + return ( +
svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground ${variants[variant]} ${className}`} + {...props} + /> + ) +}) +Alert.displayName = "Alert" + +const AlertDescription = React.forwardRef(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertDescription } \ No newline at end of file diff --git a/web/src/components/ui/button.jsx b/web/src/components/ui/button.jsx new file mode 100644 index 0000000..9fe64d0 --- /dev/null +++ b/web/src/components/ui/button.jsx @@ -0,0 +1,36 @@ +import * as React from "react" + +const Button = React.forwardRef(({ + className = "", + variant = "default", + size = "default", + disabled = false, + ...props +}, ref) => { + const baseStyles = "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" + + const variants = { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", + outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground" + } + + const sizes = { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10" + } + + return ( +