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
deleted file mode 100644
index 9fe64d0..0000000
--- a/web/src/components/ui/button.jsx
+++ /dev/null
@@ -1,36 +0,0 @@
-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 (
-
- )
-})
-Button.displayName = "Button"
-
-export { Button }
\ No newline at end of file
diff --git a/web/src/components/ui/card.jsx b/web/src/components/ui/card.jsx
deleted file mode 100644
index b2d2d16..0000000
--- a/web/src/components/ui/card.jsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import * as React from "react"
-
-const Card = React.forwardRef(({ className, ...props }, ref) => (
-
-))
-Card.displayName = "Card"
-
-const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
-
-))
-CardHeader.displayName = "CardHeader"
-
-const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
-
-))
-CardTitle.displayName = "CardTitle"
-
-const CardContent = React.forwardRef(({ className, ...props }, ref) => (
-
-))
-CardContent.displayName = "CardContent"
-
-export { Card, CardHeader, CardTitle, CardContent }
\ No newline at end of file
diff --git a/web/src/components/ui/input.jsx b/web/src/components/ui/input.jsx
deleted file mode 100644
index 5c80894..0000000
--- a/web/src/components/ui/input.jsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import * as React from "react"
-
-const Input = React.forwardRef(({ className, type, ...props }, ref) => {
- return (
-
- )
-})
-Input.displayName = "Input"
-
-export { Input }
\ No newline at end of file
diff --git a/web/src/components/ui/scroll-area.jsx b/web/src/components/ui/scroll-area.jsx
deleted file mode 100644
index 4f17a54..0000000
--- a/web/src/components/ui/scroll-area.jsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import * as React from "react"
-import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
-
-const ScrollArea = React.forwardRef(({ className, children, ...props }, ref) => (
-
-
- {children}
-
-
-
-
-
-
-
-
-))
-ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
-
-export { ScrollArea }
\ No newline at end of file
diff --git a/web/src/components/ui/select.jsx b/web/src/components/ui/select.jsx
deleted file mode 100644
index 4dbf9f1..0000000
--- a/web/src/components/ui/select.jsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import * as React from "react"
-import * as SelectPrimitive from "@radix-ui/react-select"
-import { Check, ChevronDown } from "lucide-react"
-
-const Select = SelectPrimitive.Root
-
-const SelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
-
- {children}
-
-
-
-
-))
-SelectTrigger.displayName = "SelectTrigger"
-
-const SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => (
-
-
-
- {children}
-
-
-
-))
-SelectContent.displayName = "SelectContent"
-
-const SelectItem = React.forwardRef(({ className, children, ...props }, ref) => (
-
-
-
-
-
-
- {children}
-
-))
-SelectItem.displayName = "SelectItem"
-
-const SelectValue = SelectPrimitive.Value
-
-export { Select, SelectContent, SelectItem, SelectTrigger, SelectValue }
diff --git a/web/src/components/ui/switch.jsx b/web/src/components/ui/switch.jsx
deleted file mode 100644
index 7bf86ab..0000000
--- a/web/src/components/ui/switch.jsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-
-export const Switch = ({ checked, onCheckedChange, disabled }) => (
-
-);
diff --git a/web/src/components/ui/tabs.jsx b/web/src/components/ui/tabs.jsx
deleted file mode 100644
index 89f92b5..0000000
--- a/web/src/components/ui/tabs.jsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import * as React from "react"
-import * as TabsPrimitive from "@radix-ui/react-tabs"
-
-const Tabs = TabsPrimitive.Root
-
-const TabsList = React.forwardRef(({ className, ...props }, ref) => (
-
-))
-TabsList.displayName = TabsPrimitive.List.displayName
-
-const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => (
-
-))
-TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
-
-const TabsContent = React.forwardRef(({ className, ...props }, ref) => (
-
-))
-TabsContent.displayName = TabsPrimitive.Content.displayName
-
-export { Tabs, TabsList, TabsTrigger, TabsContent }
\ No newline at end of file
diff --git a/web/src/components/ui/textarea.jsx b/web/src/components/ui/textarea.jsx
deleted file mode 100644
index 257ac8b..0000000
--- a/web/src/components/ui/textarea.jsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import * as React from "react"
-
-const Textarea = React.forwardRef(({ className, ...props }, ref) => {
- return (
-
- )
-})
-Textarea.displayName = "Textarea"
-
-export { Textarea }
\ No newline at end of file
diff --git a/web/src/contexts/MonacoContext.tsx b/web/src/contexts/MonacoContext.tsx
new file mode 100644
index 0000000..8cd7178
--- /dev/null
+++ b/web/src/contexts/MonacoContext.tsx
@@ -0,0 +1,41 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+import * as monaco from 'monaco-editor';
+
+interface MonacoContextType {
+ monaco: typeof monaco | null;
+ isMonacoLoading: boolean;
+}
+
+const MonacoContext = createContext
({
+ monaco: null,
+ isMonacoLoading: true
+});
+
+export const useMonaco = () => useContext(MonacoContext);
+
+export const MonacoProvider: React.FC<{children: React.ReactNode}> = ({ children }) => {
+ const [monacoInstance, setMonacoInstance] = useState(null);
+ const [isMonacoLoading, setIsMonacoLoading] = useState(true);
+
+ useEffect(() => {
+ if (!monacoInstance) {
+ import('monaco-editor').then(monaco => {
+ if (!monaco.languages.getLanguages().some(lang => lang.id === 'xml')) {
+ monaco.languages.register({ id: 'xml' });
+ }
+
+ setMonacoInstance(monaco);
+ setIsMonacoLoading(false);
+ }).catch(error => {
+ console.error('Failed to load Monaco editor:', error);
+ setIsMonacoLoading(false);
+ });
+ }
+ }, [monacoInstance]);
+
+ return (
+
+ {children}
+
+ );
+};
\ No newline at end of file
diff --git a/web/src/contexts/WebSocketContext.tsx b/web/src/contexts/WebSocketContext.tsx
new file mode 100644
index 0000000..b06bbc9
--- /dev/null
+++ b/web/src/contexts/WebSocketContext.tsx
@@ -0,0 +1,243 @@
+import React, { createContext, useContext, useEffect, useRef, useState } from 'react';
+import { AutoApproverConfig, ChatMessage } from '../types';
+
+interface WebSocketContextType {
+ state: {
+ agentState: 'IDLE' | 'INFERENCE' | 'PROCESSING_RESPONSE';
+ activeLLM: string;
+ };
+ response: string;
+ setResponse: (response: string) => void;
+ memory: any[];
+ messages: ChatMessage[];
+ lastReadTimestamp: string | null;
+ autoApprover: AutoApproverConfig;
+ setAutoApproverConfig: (config: Partial) => Promise;
+ isConnected: boolean;
+ llms: {name: string}[];
+ setActiveLLM: (llm: string) => Promise;
+ connectionStatus: Record;
+}
+
+const defaultState: WebSocketContextType = {
+ state: {
+ agentState: 'IDLE',
+ activeLLM: '',
+ },
+ response: '',
+ setResponse: () => {},
+ memory: [],
+ messages: [],
+ lastReadTimestamp: null,
+ autoApprover: {
+ context_enabled: false,
+ response_enabled: false,
+ context_timeout: 1.0,
+ response_timeout: 1.0
+ },
+ setAutoApproverConfig: async () => {},
+ isConnected: false,
+ llms: [],
+ setActiveLLM: async () => {},
+ connectionStatus: {},
+};
+
+const WebSocketContext = createContext(defaultState);
+
+export const useWebSocketContext = () => useContext(WebSocketContext);
+
+export const WebSocketProvider: React.FC<{children: React.ReactNode}> = ({ children }) => {
+ const [isConnected, setIsConnected] = useState(false);
+ const [state, setState] = useState(defaultState.state);
+ const [response, setResponseState] = useState('');
+ const [memory, setMemory] = useState([]);
+ const [messages, setMessages] = useState([]);
+ const [lastReadTimestamp, setLastReadTimestamp] = useState(null);
+ const [autoApprover, setAutoApprover] = useState(defaultState.autoApprover);
+ const [llms, setLLMs] = useState<{name: string}[]>([]);
+ const [connectionStatus, setConnectionStatus] = useState>({});
+
+ const sockets = useRef<{[key: string]: WebSocket | null}>({});
+
+ const setResponse = async (newResponse: string) => {
+ setResponseState(newResponse);
+
+ await fetch('/api/response', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ response: newResponse })
+ });
+ };
+
+ const setAutoApproverConfig = async (updates: Partial) => {
+ const newConfig = { ...autoApprover, ...updates };
+ await fetch('/api/auto_approver', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(newConfig)
+ });
+ setAutoApprover(newConfig);
+ };
+
+ const setActiveLLM = async (llm: string) => {
+ await fetch('/api/llms/active', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ active_llm: llm })
+ });
+
+ setState(prev => ({ ...prev, activeLLM: llm }));
+ };
+
+ useEffect(() => {
+ const loadInitialData = async () => {
+ const llmsResponse = await fetch('/api/llms');
+ if (llmsResponse.ok) {
+ const llmsData = await llmsResponse.json();
+ setLLMs(llmsData);
+ }
+ };
+ loadInitialData();
+ }, []);
+
+ useEffect(() => {
+ const endpoints = ['ws/state', 'ws/response', 'ws/memory', 'ws/auto_approver', 'ws/chat'];
+ const newConnectionStatus: Record = {};
+
+ const createWebSocket = (endpoint: string) => {
+ if (
+ sockets.current[endpoint] &&
+ (sockets.current[endpoint]?.readyState === WebSocket.CONNECTING ||
+ sockets.current[endpoint]?.readyState === WebSocket.OPEN)
+ ) {
+ return;
+ }
+
+ try {
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
+ const host = window.location.host;
+ const fullUrl = `${protocol}//${host}/${endpoint}`;
+
+ console.log(`Connecting to WebSocket: ${fullUrl}`);
+ newConnectionStatus[endpoint] = 'connecting';
+
+ const socket = new WebSocket(fullUrl);
+ sockets.current[endpoint] = socket;
+
+ socket.onopen = () => {
+ console.log(`WebSocket connected: ${endpoint}`);
+ newConnectionStatus[endpoint] = 'connected';
+ setConnectionStatus({...newConnectionStatus});
+ setIsConnected(true);
+ };
+
+ socket.onmessage = (event) => {
+ try {
+ const data = JSON.parse(event.data);
+
+ if (endpoint === 'ws/state') {
+ setState({
+ agentState: data.state,
+ activeLLM: data.active_llm
+ });
+ } else if (endpoint === 'ws/response') {
+ if (data.response !== undefined) {
+ setResponseState(data.response);
+ }
+ } else if (endpoint === 'ws/memory') {
+ if (data.entries) {
+ setMemory(data.entries);
+ }
+ } else if (endpoint === 'ws/auto_approver') {
+ if (data.config) {
+ setAutoApprover(data.config);
+ }
+ } else if (endpoint === 'ws/chat') {
+ if (data.messages) {
+ setMessages(prevMessages => {
+ const updatedIds = data.messages.map((m: ChatMessage) => m.id);
+ const filteredMessages = prevMessages.filter(m => !updatedIds.includes(m.id));
+
+ return [...filteredMessages, ...data.messages];
+ });
+ }
+
+ if (data.deleted_message_ids) {
+ setMessages(prevMessages =>
+ prevMessages.filter(m => !data.deleted_message_ids.includes(m.id))
+ );
+ }
+
+ if (data.last_read_timestamp) {
+ setLastReadTimestamp(data.last_read_timestamp);
+ }
+ }
+ } catch (error) {
+ console.error(`Failed to parse message from ${endpoint}:`, error);
+ }
+ };
+
+ socket.onclose = (event) => {
+ console.log(`WebSocket closed: ${endpoint} (code: ${event.code})`);
+ newConnectionStatus[endpoint] = 'disconnected';
+ setConnectionStatus({...newConnectionStatus});
+
+ sockets.current[endpoint] = null;
+
+ if (event.code === 1000 || event.code === 1001) {
+ return;
+ }
+
+ setTimeout(() => createWebSocket(endpoint), 3000);
+ };
+
+ socket.onerror = (error) => {
+ console.error(`WebSocket error: ${endpoint}`, error);
+ newConnectionStatus[endpoint] = 'error';
+ setConnectionStatus({...newConnectionStatus});
+ };
+ } catch (error) {
+ console.error(`Failed to create WebSocket: ${endpoint}`, error);
+ newConnectionStatus[endpoint] = 'error';
+ setConnectionStatus({...newConnectionStatus});
+
+ setTimeout(() => createWebSocket(endpoint), 3000);
+ }
+ };
+
+ endpoints.forEach(endpoint => {
+ createWebSocket(endpoint);
+ });
+
+ return () => {
+ Object.entries(sockets.current).forEach(([endpoint, socket]) => {
+ if (socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)) {
+ console.log(`Closing WebSocket: ${endpoint}`);
+ socket.close(1000, 'Component unmounting');
+ }
+ });
+ sockets.current = {};
+ };
+ }, []);
+
+ const value = {
+ state,
+ response,
+ setResponse,
+ memory,
+ messages,
+ lastReadTimestamp,
+ autoApprover,
+ setAutoApproverConfig,
+ isConnected,
+ llms,
+ setActiveLLM,
+ connectionStatus
+ };
+
+ return (
+
+ {children}
+
+ );
+};
\ No newline at end of file
diff --git a/web/src/hooks/useScreenSize.ts b/web/src/hooks/useScreenSize.ts
new file mode 100644
index 0000000..dc1e0d6
--- /dev/null
+++ b/web/src/hooks/useScreenSize.ts
@@ -0,0 +1,24 @@
+import { useState, useEffect } from 'react';
+
+const useScreenSize = () => {
+ const [screenSize, setScreenSize] = useState<'mobile' | 'tablet' | 'desktop'>('desktop');
+
+ useEffect(() => {
+ const handleResize = () => {
+ const width = window.innerWidth;
+ setScreenSize(
+ width < 640 ? 'mobile' :
+ width < 768 ? 'tablet' :
+ 'desktop'
+ );
+ };
+
+ handleResize();
+ window.addEventListener('resize', handleResize);
+ return () => window.removeEventListener('resize', handleResize);
+ }, []);
+
+ return screenSize;
+};
+
+export default useScreenSize;
\ No newline at end of file
diff --git a/web/src/hooks/useWebSocket.jsx b/web/src/hooks/useWebSocket.jsx
deleted file mode 100644
index 7053bc4..0000000
--- a/web/src/hooks/useWebSocket.jsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { useState, useEffect, useRef, useCallback } from 'react';
-
-export const WebSocketState = {
- CONNECTING: 'CONNECTING',
- CONNECTED: 'CONNECTED',
- DISCONNECTED: 'DISCONNECTED',
-};
-
-export const useWebSocket = (url) => {
- const [wsState, setWsState] = useState(WebSocketState.DISCONNECTED);
- const wsRef = useRef(null);
- const handlersRef = 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);
- handlersRef.current.forEach(handler => handler(message));
- } 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((data) => {
- if (wsRef.current?.readyState === WebSocket.OPEN) {
- wsRef.current.send(JSON.stringify(data));
- } else {
- console.error('WebSocket not connected, state:', wsRef.current?.readyState);
- }
- }, []);
-
- const addMessageHandler = useCallback((handler) => {
- handlersRef.current.push(handler);
- }, []);
-
- return {
- wsState,
- sendMessage,
- addMessageHandler
- };
-};
\ No newline at end of file
diff --git a/web/src/index.css b/web/src/index.css
index 8756161..bd6213e 100644
--- a/web/src/index.css
+++ b/web/src/index.css
@@ -1,46 +1,3 @@
@tailwind base;
@tailwind components;
-@tailwind utilities;
-
-@layer base {
- :root {
- --background: 0 0% 100%;
- --foreground: 222.2 84% 4.9%;
-
- --card: 0 0% 100%;
- --card-foreground: 222.2 84% 4.9%;
-
- --popover: 0 0% 100%;
- --popover-foreground: 222.2 84% 4.9%;
-
- --primary: 222.2 47.4% 11.2%;
- --primary-foreground: 210 40% 98%;
-
- --secondary: 210 40% 96.1%;
- --secondary-foreground: 222.2 47.4% 11.2%;
-
- --muted: 210 40% 96.1%;
- --muted-foreground: 215.4 16.3% 46.9%;
-
- --accent: 210 40% 96.1%;
- --accent-foreground: 222.2 47.4% 11.2%;
-
- --destructive: 0 84.2% 60.2%;
- --destructive-foreground: 210 40% 98%;
-
- --border: 214.3 31.8% 91.4%;
- --input: 214.3 31.8% 91.4%;
- --ring: 222.2 84% 4.9%;
-
- --radius: 0.5rem;
- }
-}
-
-@layer base {
- * {
- @apply border-border;
- }
- body {
- @apply bg-background text-foreground;
- }
-}
\ No newline at end of file
+@tailwind utilities;
\ No newline at end of file
diff --git a/web/src/index.jsx b/web/src/index.tsx
similarity index 60%
rename from web/src/index.jsx
rename to web/src/index.tsx
index 54ef0fd..b3c11a7 100644
--- a/web/src/index.jsx
+++ b/web/src/index.tsx
@@ -1,9 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
-import App from './components/App';
+import App from './App';
import './index.css';
-ReactDOM.createRoot(document.getElementById('root')).render(
+ReactDOM.createRoot(document.getElementById('root')!).render(
diff --git a/web/src/services/api.ts b/web/src/services/api.ts
new file mode 100644
index 0000000..824caaf
--- /dev/null
+++ b/web/src/services/api.ts
@@ -0,0 +1,254 @@
+import { AutoApproverConfig } from '../types';
+import {
+ EntryData,
+ ReasoningEntryData,
+ ScriptEntryData,
+ IOEntryData,
+ ParseErrorEntryData,
+} from '../types/entries';
+
+const API_BASE = '/api';
+
+const generateId = (): string => {
+ const now = new Date();
+ const utc = new Date(now.getTime() + now.getTimezoneOffset() * 60000);
+ return utc.getFullYear() +
+ String(utc.getMonth() + 1).padStart(2, '0') +
+ String(utc.getDate()).padStart(2, '0') + '_' +
+ String(utc.getHours()).padStart(2, '0') +
+ String(utc.getMinutes()).padStart(2, '0') +
+ String(utc.getSeconds()).padStart(2, '0') + '_' +
+ String(utc.getMilliseconds()).padStart(3, '0');
+};
+
+export const EntryTypes = {
+ BACKGROUND: 'background',
+ PARSE_ERROR: 'parse_error',
+ READ_STDIN: 'read_stdin',
+ REASONING: 'reasoning',
+ REPEAT: 'repeat',
+ SINGLE: 'single',
+ WRITE: 'write'
+} as const;
+
+export type EntryTypeValue = typeof EntryTypes[keyof typeof EntryTypes];
+
+export const getInitialEntryData = (type: EntryTypeValue): Partial => {
+ const baseEntry = {
+ type,
+ id: generateId()
+ };
+
+ switch (type) {
+ case EntryTypes.BACKGROUND:
+ case EntryTypes.REPEAT:
+ case EntryTypes.SINGLE:
+ return {
+ ...baseEntry,
+ script: '',
+ } as Partial;
+ case EntryTypes.PARSE_ERROR:
+ return {
+ ...baseEntry,
+ content: '',
+ error: ''
+ } as Partial;
+ case EntryTypes.READ_STDIN:
+ return {
+ ...baseEntry,
+ } as Partial;
+ case EntryTypes.REASONING:
+ return {
+ ...baseEntry,
+ content: ''
+ } as Partial;
+ case EntryTypes.WRITE:
+ return {
+ ...baseEntry,
+ content: '',
+ } as Partial;
+ default:
+ return baseEntry;
+ }
+};
+
+interface LLMResponse {
+ name: string;
+}
+
+interface ActiveLLMResponse {
+ active_llm: string;
+}
+
+interface ResponseData {
+ response: string;
+}
+
+interface MemoryResponse {
+ entries: EntryData[];
+}
+
+export const api = {
+ // LLMs
+ getLLMs: async (): Promise => {
+ const response = await fetch(`${API_BASE}/llms`);
+ if (!response.ok) throw new Error('Failed to get LLMs');
+ return response.json();
+ },
+
+ getActiveLLM: async (): Promise => {
+ const response = await fetch(`${API_BASE}/llms/active`);
+ if (!response.ok) throw new Error('Failed to get active LLM');
+ return response.json();
+ },
+
+ setActiveLLM: async (llm: string): Promise => {
+ const response = await fetch(`${API_BASE}/llms/active`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ active_llm: llm })
+ });
+ if (!response.ok) throw new Error('Failed to set active LLM');
+ },
+
+ startInference: async (): Promise => {
+ const response = await fetch(`${API_BASE}/inference`, { method: 'POST' });
+ if (!response.ok) throw new Error('Failed to start inference');
+ return response;
+ },
+
+ stopInference: async (): Promise => {
+ const response = await fetch(`${API_BASE}/inference/stop`, { method: 'POST' });
+ if (!response.ok) throw new Error('Failed to stop inference');
+ return response;
+ },
+
+ getResponse: async (): Promise => {
+ const response = await fetch(`${API_BASE}/response`);
+ if (!response.ok) throw new Error('Failed to get response');
+ return response.json();
+ },
+
+ setResponse: async (responseText: string): Promise => {
+ const response = await fetch(`${API_BASE}/response`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ response: responseText })
+ });
+ if (!response.ok) throw new Error('Failed to set response');
+ return response;
+ },
+
+ approveResponse: async (): Promise => {
+ const response = await fetch(`${API_BASE}/response/approve`, { method: 'POST' });
+ if (!response.ok) throw new Error('Failed to approve response');
+ return response;
+ },
+
+ setAutoApproverConfig: async (config: Partial): Promise => {
+ const response = await fetch(`${API_BASE}/auto_approver`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(config)
+ });
+ if (!response.ok) throw new Error('Failed to set auto approver config');
+ return response;
+ },
+
+ getMemory: async (): Promise => {
+ const response = await fetch(`${API_BASE}/memory`);
+ if (!response.ok) throw new Error('Failed to get memory');
+ return response.json();
+ },
+
+ createMemoryEntry: async (entryData: Partial): Promise => {
+ const data = {
+ ...getInitialEntryData(entryData.type as EntryTypeValue),
+ ...entryData
+ };
+
+ const response = await fetch(`${API_BASE}/memory/entry`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(data)
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ console.error('Server response:', errorText);
+ throw new Error('Failed to create entry');
+ }
+
+ return response.json();
+ },
+
+ loadIteration: async (content: string): Promise => {
+ const response = await fetch(`${API_BASE}/memory/load_iteration`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ content })
+ });
+ if (!response.ok) throw new Error('Failed to load iteration');
+ return response;
+ },
+
+ deleteMemoryEntry: async (id: string): Promise => {
+ const response = await fetch(`${API_BASE}/memory/entry/${id}`, {
+ method: 'DELETE'
+ });
+ if (!response.ok) throw new Error(`Failed to delete entry ${id}`);
+ return response;
+ },
+
+ updateMemoryEntry: async (id: string): Promise => {
+ const response = await fetch(`${API_BASE}/memory/entry/${id}/update`, {
+ method: 'POST'
+ });
+ if (!response.ok) throw new Error(`Failed to update entry ${id}`);
+ return response;
+ },
+
+ saveMemoryEntry: async (id: string, data: T): Promise => {
+ const response = await fetch(`${API_BASE}/memory/entry/${id}`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(data)
+ });
+ if (!response.ok) throw new Error(`Failed to save entry ${id}`);
+ return response;
+ },
+
+ resetMemoryEntry: async (id: string): Promise => {
+ const response = await fetch(`${API_BASE}/memory/entry/${id}/reset`, {
+ method: 'POST'
+ });
+ if (!response.ok) throw new Error(`Failed to reset entry ${id}`);
+ return response;
+ },
+
+ addChatMessage: async (data: { message: string }): Promise => {
+ const response = await fetch(`${API_BASE}/chat/message`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(data)
+ });
+ if (!response.ok) throw new Error('Failed to add chat message');
+ return response;
+ },
+
+ deleteChatMessage: async (id: string): Promise => {
+ const response = await fetch(`${API_BASE}/chat/message/${id}`, {
+ method: 'DELETE'
+ });
+ if (!response.ok) throw new Error(`Failed to delete chat message ${id}`);
+ return response;
+ },
+
+ clearChat: async (): Promise => {
+ const response = await fetch(`${API_BASE}/chat`, {
+ method: 'DELETE'
+ });
+ if (!response.ok) throw new Error('Failed to clear chat');
+ return response;
+ }
+};
\ No newline at end of file
diff --git a/web/src/types/entries.ts b/web/src/types/entries.ts
new file mode 100644
index 0000000..714196d
--- /dev/null
+++ b/web/src/types/entries.ts
@@ -0,0 +1,54 @@
+// src/types/entries.ts
+import { EntryTypes } from '../services/api';
+
+export interface BaseEntryData {
+ id: string;
+ type: string;
+ timestamp?: number;
+}
+
+export interface ReasoningEntryData extends BaseEntryData {
+ type: typeof EntryTypes.REASONING;
+ content: string;
+}
+
+export interface ScriptEntryData extends BaseEntryData {
+ type: typeof EntryTypes.SINGLE | typeof EntryTypes.REPEAT;
+ script: string;
+ stdout?: string;
+ stderr?: string;
+ exit_code?: number | null;
+ timeout?: number | null;
+ limit?: number | null;
+ timed_out?: boolean;
+ executed?: boolean;
+}
+
+export interface IOEntryData extends BaseEntryData {
+ type: typeof EntryTypes.READ_STDIN | typeof EntryTypes.WRITE;
+ content: string;
+ read?: boolean;
+ written?: boolean;
+}
+
+export interface ParseErrorEntryData extends BaseEntryData {
+ type: typeof EntryTypes.PARSE_ERROR;
+ content: string;
+ error: string;
+}
+
+export interface BackgroundEntryData extends BaseEntryData {
+ type: typeof EntryTypes.BACKGROUND;
+ script: string;
+ stdout?: string;
+ stderr?: string;
+ exit_code?: number | null;
+ pid?: number | null;
+}
+
+export type EntryData =
+ | ReasoningEntryData
+ | ScriptEntryData
+ | IOEntryData
+ | ParseErrorEntryData
+ | BackgroundEntryData;
\ No newline at end of file
diff --git a/web/src/types/index.ts b/web/src/types/index.ts
new file mode 100644
index 0000000..3d6b693
--- /dev/null
+++ b/web/src/types/index.ts
@@ -0,0 +1,49 @@
+// src/types/index.ts
+export interface AgentState {
+ connected: boolean;
+ state: 'IDLE' | 'INFERENCE' | 'PROCESSING_RESPONSE';
+ activeLLM: string;
+}
+
+export interface LLM {
+ name: string;
+}
+
+// Memory types
+export interface MemoryEntry {
+ id: string;
+ type: EntryType;
+ timestamp: number;
+}
+
+export type EntryType =
+ | 'background'
+ | 'parse_error'
+ | 'read_stdin'
+ | 'reasoning'
+ | 'repeat'
+ | 'single'
+ | 'write';
+
+// Chat types
+export interface ChatMessage {
+ id: string;
+ content: string;
+ message_type: 'user' | 'sia'; // Changed from messageType to message_type
+ timestamp: number;
+ read?: boolean;
+}
+
+// WebSocket message types
+export interface StateWebSocketMessage {
+ state: AgentState['state'];
+ active_llm: string;
+}
+
+// Auto Approver types
+export interface AutoApproverConfig {
+ context_enabled: boolean;
+ response_enabled: boolean;
+ context_timeout: number;
+ response_timeout: number;
+}
\ No newline at end of file
diff --git a/web/tailwind.config.js b/web/tailwind.config.js
index 1622761..89a305e 100644
--- a/web/tailwind.config.js
+++ b/web/tailwind.config.js
@@ -1,69 +1,11 @@
-import tailwindcssAnimate from "tailwindcss-animate";
-
/** @type {import('tailwindcss').Config} */
export default {
- darkMode: ["class"],
- content: ["./src/**/*.{js,jsx}"],
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
theme: {
- container: {
- center: true,
- padding: "2rem",
- screens: {
- "2xl": "1400px",
- },
- },
- extend: {
- colors: {
- border: "hsl(var(--border))",
- input: "hsl(var(--input))",
- ring: "hsl(var(--ring))",
- background: "hsl(var(--background))",
- foreground: "hsl(var(--foreground))",
- primary: {
- DEFAULT: "hsl(var(--primary))",
- foreground: "hsl(var(--primary-foreground))",
- },
- secondary: {
- DEFAULT: "hsl(var(--secondary))",
- foreground: "hsl(var(--secondary-foreground))",
- },
- destructive: {
- DEFAULT: "hsl(var(--destructive))",
- foreground: "hsl(var(--destructive-foreground))",
- },
- muted: {
- DEFAULT: "hsl(var(--muted))",
- foreground: "hsl(var(--muted-foreground))",
- },
- accent: {
- DEFAULT: "hsl(var(--accent))",
- foreground: "hsl(var(--accent-foreground))",
- },
- card: {
- DEFAULT: "hsl(var(--card))",
- foreground: "hsl(var(--card-foreground))",
- },
- },
- borderRadius: {
- lg: "var(--radius)",
- md: "calc(var(--radius) - 2px)",
- sm: "calc(var(--radius) - 4px)",
- },
- keyframes: {
- "accordion-down": {
- from: { height: 0 },
- to: { height: "var(--radix-accordion-content-height)" },
- },
- "accordion-up": {
- from: { height: "var(--radix-accordion-content-height)" },
- to: { height: 0 },
- },
- },
- animation: {
- "accordion-down": "accordion-down 0.2s ease-out",
- "accordion-up": "accordion-up 0.2s ease-out",
- },
- },
+ extend: {},
},
- plugins: [tailwindcssAnimate],
+ plugins: [],
}
\ No newline at end of file
diff --git a/web/tsconfig.json b/web/tsconfig.json
new file mode 100644
index 0000000..7a7611e
--- /dev/null
+++ b/web/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
\ No newline at end of file
diff --git a/web/tsconfig.node.json b/web/tsconfig.node.json
new file mode 100644
index 0000000..099658c
--- /dev/null
+++ b/web/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
\ No newline at end of file
diff --git a/web/vite.config.js b/web/vite.config.js
deleted file mode 100644
index 8c66c0f..0000000
--- a/web/vite.config.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { defineConfig } from 'vite';
-import react from '@vitejs/plugin-react';
-import path from 'path';
-
-export default defineConfig({
- plugins: [react()],
- resolve: {
- alias: {
- '@': path.resolve(__dirname, './src'),
- 'components': path.resolve(__dirname, './src/components')
- },
- extensions: ['.js', '.jsx']
- },
- build: {
- outDir: 'dist',
- assetsDir: 'assets',
- base: '/'
- }
-});
\ No newline at end of file
diff --git a/web/vite.config.ts b/web/vite.config.ts
new file mode 100644
index 0000000..38ab57e
--- /dev/null
+++ b/web/vite.config.ts
@@ -0,0 +1,31 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 3000,
+ proxy: {
+ '/api': 'http://localhost:8080',
+ '/ws': {
+ target: 'ws://localhost:8080',
+ ws: true
+ }
+ }
+ },
+ optimizeDeps: {
+ include: ['monaco-editor/esm/vs/language/json/json.worker'],
+ },
+ build: {
+ rollupOptions: {
+ output: {
+ manualChunks: {
+ jsonWorker: ['monaco-editor/esm/vs/language/json/json.worker'],
+ cssWorker: ['monaco-editor/esm/vs/language/css/css.worker'],
+ htmlWorker: ['monaco-editor/esm/vs/language/html/html.worker'],
+ editorWorker: ['monaco-editor/esm/vs/editor/editor.worker'],
+ }
+ }
+ }
+ }
+})
\ No newline at end of file