diff --git a/sia/auto_approver.py b/sia/auto_approver.py
index 0844257..aecaef6 100644
--- a/sia/auto_approver.py
+++ b/sia/auto_approver.py
@@ -180,4 +180,5 @@ class AutoApprover:
return
if (self._response_enabled and
self.agent.llms[self._llm_name] == LlmState.IDLE):
- self.agent.approve_response(self._llm_name, self.agent.get_output(self._llm_name))
+ response = self.agent.response_buffer.get_text()
+ self.agent.approve_response()
diff --git a/sia/web/api.py b/sia/web/api.py
index 9e1d212..bb8edef 100644
--- a/sia/web/api.py
+++ b/sia/web/api.py
@@ -94,6 +94,8 @@ class Api:
"""Approve current buffer content"""
data = await request.json()
try:
+ response = data.get("response")
+ self._agent.response_buffer.set_text(response)
self._agent.approve_response()
return web.Response(status=200)
except ValueError as e:
@@ -147,10 +149,10 @@ class Api:
"""Get all LLMs and their current states."""
states = self._agent.llms
return web.Response(
- text=json.dumps({
- name: state.name
- for name, state in states.items()
- }),
+ text=json.dumps(
+ [{"name": name, "state": state.name}
+ for name, state in states.items()]
+ ),
content_type="application/json"
)
@@ -234,10 +236,12 @@ class Api:
"""Get complete working memory state."""
entries = self._working_memory.get_entries()
return web.Response(
- text=json.dumps([e.serialize() for e in entries]),
+ text=json.dumps({
+ "type": "memory_state",
+ "entries": [e.serialize() for e in entries]
+ }),
content_type="application/json"
)
-
async def _create_entry(self, request: web.Request) -> web.Response:
"""Create a new entry in working memory."""
data = await request.json()
diff --git a/sia/web/response_websocket.py b/sia/web/response_websocket.py
index e77934f..feb5b98 100644
--- a/sia/web/response_websocket.py
+++ b/sia/web/response_websocket.py
@@ -12,7 +12,7 @@ class ResponseWebSocket:
async def _handle_buffer_change(self, buffer):
await self._broadcast_message({
- "buffer": buffer,
+ "response": buffer,
})
async def _broadcast_message(self, message):
@@ -32,7 +32,7 @@ class ResponseWebSocket:
try:
await ws.send_json({
- "buffer": self._agent.response_buffer.get_text(),
+ "response": self._agent.response_buffer.get_text(),
})
async for msg in ws:
diff --git a/sia/web/websockets.py b/sia/web/websockets.py
index fc623d4..1fd6ede 100644
--- a/sia/web/websockets.py
+++ b/sia/web/websockets.py
@@ -22,7 +22,7 @@ class Websockets:
app.router.add_get("/ws/auto_approver", self._auto_approver_ws.handle_connection)
app.router.add_get("/ws/context", self._context_ws.handle_connection)
- app.router.add_get("/ws/llm", self._llm_ws.handle_connection)
+ app.router.add_get("/ws/llms", self._llm_ws.handle_connection)
app.router.add_get("/ws/memory", self._memory_ws.handle_connection)
app.router.add_get("/ws/response", self._response_ws.handle_connection)
app.router.add_get("/ws/stdout", self._stdout_ws.handle_connection)
diff --git a/sia/web_agent.py b/sia/web_agent.py
index 37208fd..70d0d43 100644
--- a/sia/web_agent.py
+++ b/sia/web_agent.py
@@ -132,6 +132,8 @@ class WebAgent(BaseAgent):
def approve_response(self) -> None:
"""Process approved response from specified LLM"""
+ if self.llms.get(llm_name) != LlmState.IDLE:
+ return
timestamp = datetime.now(timezone.utc)
self._iteration_logger.log_iteration(timestamp, self._context, self._response_buffer.get_text())
parse_result = self._parser.parse(timestamp, self._response_buffer.get_text())
diff --git a/web/src/components/App.jsx b/web/src/components/App.jsx
index ed4fb82..9436c9b 100644
--- a/web/src/components/App.jsx
+++ b/web/src/components/App.jsx
@@ -1,373 +1,349 @@
-import React, { useState, useEffect } from 'react';
-import { ContentEditor } from '@/components/editors/ContentEditor';
-import { FloatingButton } from '@/components/FloatingButton';
-import { Header, Tabs } from '@/components/Header';
-import { MemoryEditor } from '@/components/editors/MemoryEditor';
-import { Sidebar } from '@/components/Sidebar';
-import { StandardEditor } from '@/components/editors/StandardEditor';
-import { useWebSocket, WebSocketState } from '@/hooks/useWebSocket';
-
-export const LlmState = {
- NO_OUTPUT: 'NO_OUTPUT',
- INFERENCE: 'INFERENCE',
- OUTPUT: 'OUTPUT'
-};
-
-const App = () => {
- // Editor content state
- const [generatedContext, setGeneratedContext] = useState('');
- const [modifiedContext, setModifiedContext] = useState('');
- const [contextDirty, setContextDirty] = useState(false);
- const [generatedResponses, setGeneratedResponses] = useState({});
- const [modifiedResponses, setModifiedResponses] = useState({});
- const [input, setInput] = useState('');
- const [output, setOutput] = useState('');
-
- // LLM state
- const [llms, setLlms] = useState({});
- const [activeLlm, setActiveLlm] = useState(null);
-
- // Auto approver state
- const [autoApproverConfig, setAutoApproverConfig] = useState({
- context_enabled: false,
- response_enabled: false,
- context_timeout: 5.0,
- response_timeout: 10.0,
- llm_name: null
- });
-
- // UI state
- const [activeTab, setActiveTab] = useState(Tabs.CONTEXT);
- const [showDiff, setShowDiff] = useState(false);
- const [showSidebar, setShowSidebar] = useState(false);
- const [autoScroll, setAutoScroll] = useState(false);
-
- // WebSocket connections
- const wsRoot = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`
- const llmWs = useWebSocket(`${wsRoot}/llm`);
- const contextWs = useWebSocket(`${wsRoot}/context`);
- const tokenWs = useWebSocket(`${wsRoot}/token`);
- const stdoutWs = useWebSocket(`${wsRoot}/stdout`);
- const autoApproverWs = useWebSocket(`${wsRoot}/auto_approver`);
- const memoryWs = useWebSocket(`${wsRoot}/memory`);
- const [entries, setEntries] = useState([]);
- const [wsState, setWsState] = useState(WebSocketState.DISCONNECTED);
-
- // Handle llm state changes
- useEffect(() => {
- llmWs.addMessageHandler((data) => {
- setLlms(prevLlms => ({
- ...prevLlms,
- [data.llm]: data.state
- }));
- if (data.state === LlmState.NO_OUTPUT) {
- setGeneratedResponses(prev => {
- const updated = { ...prev };
- delete updated[data.llm];
- return updated;
- });
- setModifiedResponses(prev => {
- const updated = { ...prev };
- delete updated[data.llm];
- return updated;
- });
- }
- });
- }, []);
-
- // Handle context changes
- useEffect(() => {
- contextWs.addMessageHandler((data) => {
- setContextDirty(false);
- setModifiedContext(data.context);
- if (data.generated) {
- setGeneratedContext(data.context);
- }
- });
- }, []);
-
- // Handle incoming tokens
- useEffect(() => {
- tokenWs.addMessageHandler((data) => {
- setGeneratedResponses(prev => ({
- ...prev,
- [data.llm]: (prev[data.llm] || '') + data.token
- }));
- setModifiedResponses(prev => ({
- ...prev,
- [data.llm]: (prev[data.llm] || '') + data.token
- }));
- });
- }, []);
-
- // Handle stdout changes
- useEffect(() => {
- stdoutWs.addMessageHandler((data) => {
- setOutput(data.output);
- });
- }, []);
-
- // Handle auto approver config updates
- useEffect(() => {
- autoApproverWs.addMessageHandler((data) => {
- setAutoApproverConfig(data.config);
- });
- }, []);
-
- // Handle memory state changes
- useEffect(() => {
- memoryWs.addMessageHandler((data) => {
- if (data.type === 'memory_state') {
- setEntries(data.entries);
- }
- });
- }, []);
-
- // Initialize active llm
- useEffect(() => {
- if (activeLlm === null && Object.keys(llms).length > 0) {
- setActiveLlm(Object.keys(llms)[0]);
- }
- }, [llms, activeLlm]);
-
- const handleNextLlm = () => {
- const llmNames = Object.keys(llms);
- const currentIndex = llmNames.indexOf(activeLlm);
- const nextIndex = (currentIndex + 1) % llmNames.length;
- setActiveLlm(llmNames[nextIndex]);
- };
-
- const handleApprove = () => {
- const state = llms[activeLlm];
- if (state === LlmState.NO_OUTPUT) {
- if (contextDirty) {
- fetch('/api/context', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ context: modifiedContext })
- });
- }
- fetch(`/api/inference/${activeLlm}`, {
- method: 'POST',
- });
- } else if (state === LlmState.OUTPUT) {
- fetch(`/api/approve/${activeLlm}`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ response: modifiedResponses[activeLlm] })
- });
- }
- };
-
- const handleStop = () => {
- fetch(`/api/inference/${activeLlm}/stop`, {
- method: 'POST'
- });
- };
-
- const handleSendInput = () => {
- if (input.trim()) {
- fetch('/api/input', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ input: input })
- });
- setInput('');
- }
- };
-
- const handleClearOutput = () => {
- fetch('/api/clear', { method: 'POST' });
- };
-
- const handleContextEdit = (context) => {
- setContextDirty(true);
- setGeneratedResponses({});
- setModifiedResponses({});
- const resetLlms = {};
- for (const llm of Object.keys(llms)) {
- resetLlms[llm] = LlmState.NO_OUTPUT;
- }
- setLlms(resetLlms);
- setModifiedContext(context);
- };
-
- const handleAutoApproverConfigChange = async (config) => {
- try {
- const response = await fetch('/api/auto_approver/config', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(config)
- });
- if (!response.ok) {
- throw new Error('Failed to update auto approver config');
- }
- } catch (error) {
- console.error('Error updating auto approver config:', error);
- }
- };
-
- const handleCreateEntry = async (entry) => {
- const response = await fetch('/api/memory/entry', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(entry)
- });
- if (!response.ok) throw new Error('Failed to create entry');
- };
-
- const handleSaveEntry = async (id, entry) => {
- const response = await fetch(`/api/memory/entry/${id}`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(entry)
- });
- if (!response.ok) throw new Error('Failed to update entry');
- };
-
- const handleDeleteEntry = async (id) => {
- const response = await fetch(`/api/memory/entry/${id}`, {
- method: 'DELETE'
- });
- if (!response.ok) throw new Error('Failed to delete entry');
- };
-
- const handleResetEntry = async (id) => {
- const response = await fetch(`/api/memory/entry/${id}/reset`, {
- method: 'POST'
- });
- if (!response.ok) throw new Error('Failed to reset entry');
- };
-
- const handleUpdateEntry = async (id) => {
- const response = await fetch(`/api/memory/entry/${id}/update`, {
- method: 'POST'
- });
- if (!response.ok) throw new Error('Failed to update entry');
- };
-
- // Update active tab on state change
- useEffect(() => {
- if (activeTab === Tabs.MEMORY) return;
- const state = llms[activeLlm];
- switch (state) {
- case null:
- break;
- case LlmState.NO_OUTPUT:
- setActiveTab(Tabs.CONTEXT);
- setShowDiff(false);
- setShowSidebar(false);
- break;
- default:
- setActiveTab(Tabs.RESPONSE);
- setShowDiff(false);
- setShowSidebar(false);
- break;
- }
- }, [llms[activeLlm]]);
-
- useEffect(() => {
- if (llmWs.wsState === WebSocketState.CONNECTED &&
- contextWs.wsState === WebSocketState.CONNECTED &&
- tokenWs.wsState === WebSocketState.CONNECTED &&
- stdoutWs.wsState === WebSocketState.CONNECTED &&
- autoApproverWs.wsState === WebSocketState.CONNECTED &&
- memoryWs.wsState === WebSocketState.CONNECTED) {
- setWsState(WebSocketState.CONNECTED);
- } else if (llmWs.wsState === WebSocketState.CONNECTING ||
- contextWs.wsState === WebSocketState.CONNECTING ||
- tokenWs.wsState === WebSocketState.CONNECTING ||
- stdoutWs.wsState === WebSocketState.CONNECTING ||
- autoApproverWs.wsState === WebSocketState.CONNECTING ||
- memoryWs.wsState === WebSocketState.CONNECTING) {
- setWsState(WebSocketState.CONNECTING);
- } else {
- setWsState(WebSocketState.DISCONNECTED);
- }
- }, [llmWs.wsState, contextWs.wsState, tokenWs.wsState, stdoutWs.wsState, autoApproverWs.wsState, memoryWs.wsState]);
-
- return (
-
-
-
-
-
- {activeTab === Tabs.CONTEXT && (
-
- )}
- {activeTab === Tabs.RESPONSE && (
- setModifiedResponses(prev => ({
- ...prev,
- [activeLlm]: value
- }))}
- />
- )}
- {activeTab === Tabs.INPUT && (
- setInput(value)}
- language="plaintext"
- />
- )}
- {activeTab === Tabs.OUTPUT && (
-
- )}
- {activeTab === Tabs.MEMORY && (
-
- )}
-
-
- setAutoScroll(enabled)}
- input={input}
- output={output}
- autoApproverConfig={autoApproverConfig}
- onApprove={handleApprove}
- onStop={handleStop}
- onToggleDiff={() => setShowDiff(!showDiff)}
- onSendInput={handleSendInput}
- onClearOutput={handleClearOutput}
- onLlmChange={setActiveLlm}
- onNextLlm={handleNextLlm}
- onAutoApproverConfigChange={handleAutoApproverConfigChange}
- />
-
- setShowSidebar(!showSidebar)} />
-
-
- );
-};
-
-export default App;
+import React, { useState, useEffect } from 'react';
+import { ContentEditor } from '@/components/editors/ContentEditor';
+import { FloatingButton } from '@/components/FloatingButton';
+import { Header, Tabs } from '@/components/Header';
+import { MemoryEditor } from '@/components/editors/MemoryEditor';
+import { Sidebar } from '@/components/Sidebar';
+import { StandardEditor } from '@/components/editors/StandardEditor';
+import { useWebSocket, WebSocketState } from '@/hooks/useWebSocket';
+
+export const LlmState = {
+ IDLE: 'IDLE',
+ INFERENCE: 'INFERENCE'
+};
+
+const App = () => {
+ // Editor content state
+ const [generatedContext, setGeneratedContext] = useState('');
+ const [modifiedContext, setModifiedContext] = useState('');
+ const [contextDirty, setContextDirty] = useState(false);
+ const [generatedResponse, setGeneratedResponse] = useState('');
+ const [modifiedResponse, setModifiedResponse] = useState('');
+ const [input, setInput] = useState('');
+ const [output, setOutput] = useState('');
+
+ // LLM state
+ const [llms, setLlms] = useState({});
+ const [activeLlm, setActiveLlm] = useState(null);
+
+ // Auto approver state
+ const [autoApproverConfig, setAutoApproverConfig] = useState({
+ context_enabled: false,
+ response_enabled: false,
+ context_timeout: 5.0,
+ response_timeout: 10.0,
+ llm_name: null
+ });
+
+ // UI state
+ const [activeTab, setActiveTab] = useState(Tabs.CONTEXT);
+ const [showDiff, setShowDiff] = useState(false);
+ const [showSidebar, setShowSidebar] = useState(false);
+ const [autoScroll, setAutoScroll] = useState(false);
+
+ // WebSocket connections
+ const wsRoot = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`
+ const llmWs = useWebSocket(`${wsRoot}/llms`);
+ const contextWs = useWebSocket(`${wsRoot}/context`);
+ const responseWs = useWebSocket(`${wsRoot}/response`);
+ const stdoutWs = useWebSocket(`${wsRoot}/stdout`);
+ const autoApproverWs = useWebSocket(`${wsRoot}/auto_approver`);
+ const memoryWs = useWebSocket(`${wsRoot}/memory`);
+ const [entries, setEntries] = useState([]);
+ const [wsState, setWsState] = useState(WebSocketState.DISCONNECTED);
+
+ // Handle llm state changes
+ useEffect(() => {
+ llmWs.addMessageHandler((data) => {
+ setLlms(prevLlms => ({
+ ...prevLlms,
+ [data.llm]: data.state
+ }));
+ });
+ }, []);
+
+ // Handle context changes
+ useEffect(() => {
+ contextWs.addMessageHandler((data) => {
+ setContextDirty(false);
+ setModifiedContext(data.context);
+ if (data.generated) {
+ setGeneratedContext(data.context);
+ }
+ });
+ }, []);
+
+ // Handle response changes
+ useEffect(() => {
+ responseWs.addMessageHandler((data) => {
+ setModifiedResponse(data.response);
+ setGeneratedResponse(data.response);
+ });
+ }, []);
+
+ // Handle stdout changes
+ useEffect(() => {
+ stdoutWs.addMessageHandler((data) => {
+ setOutput(data.output);
+ });
+ }, []);
+
+ // Handle auto approver config updates
+ useEffect(() => {
+ autoApproverWs.addMessageHandler((data) => {
+ setAutoApproverConfig(data.config);
+ });
+ }, []);
+
+ // Handle memory state changes
+ useEffect(() => {
+ memoryWs.addMessageHandler((data) => {
+ if (data.type === 'memory_state') {
+ setEntries(data.entries);
+ }
+ });
+ }, []);
+
+ // Initialize active llm
+ useEffect(() => {
+ if (activeLlm === null && Object.keys(llms).length > 0) {
+ setActiveLlm(Object.keys(llms)[0]);
+ }
+ }, [llms, activeLlm]);
+
+ const handleNextLlm = () => {
+ const llmNames = Object.keys(llms);
+ const currentIndex = llmNames.indexOf(activeLlm);
+ const nextIndex = (currentIndex + 1) % llmNames.length;
+ setActiveLlm(llmNames[nextIndex]);
+ };
+
+ const handleApprove = () => {
+ fetch('/api/response/approve', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ response: modifiedResponse })
+ });
+ };
+
+ const handleInference = () => {
+ fetch('/api/context', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ context: modifiedContext })
+ });
+
+ fetch('/api/response', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ response: modifiedResponse })
+ });
+
+ fetch(`/api/inference/${activeLlm}`, {
+ method: 'POST',
+ });
+ };
+
+ const handleStop = () => {
+ // We still specify which LLM to stop inference for
+ fetch(`/api/inference/${activeLlm}/stop`, {
+ method: 'POST'
+ });
+ };
+
+ const handleSendInput = () => {
+ if (input.trim()) {
+ fetch('/api/input', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ input: input })
+ });
+ setInput('');
+ }
+ };
+
+ const handleClearOutput = () => {
+ fetch('/api/clear', { method: 'POST' });
+ };
+
+ const handleContextEdit = (context) => {
+ setContextDirty(true);
+ // When context is edited, we need to reset all LLM states to IDLE
+ // since responses are no longer valid
+ const resetLlms = {};
+ for (const llm of Object.keys(llms)) {
+ resetLlms[llm] = LlmState.IDLE;
+ }
+ setLlms(resetLlms);
+ setModifiedContext(context);
+
+ // Reset response buffers
+ setGeneratedResponse('');
+ setModifiedResponse('');
+ };
+
+ const handleResponseEdit = (response) => {
+ setModifiedResponse(response);
+ };
+
+ const handleAutoApproverConfigChange = async (config) => {
+ try {
+ const response = await fetch('/api/auto_approver/config', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(config)
+ });
+ if (!response.ok) {
+ throw new Error('Failed to update auto approver config');
+ }
+ } catch (error) {
+ console.error('Error updating auto approver config:', error);
+ }
+ };
+
+ const handleCreateEntry = async (entry) => {
+ const response = await fetch('/api/memory/entry', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(entry)
+ });
+ if (!response.ok) throw new Error('Failed to create entry');
+ };
+
+ const handleSaveEntry = async (id, entry) => {
+ const response = await fetch(`/api/memory/entry/${id}`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(entry)
+ });
+ if (!response.ok) throw new Error('Failed to update entry');
+ };
+
+ const handleDeleteEntry = async (id) => {
+ const response = await fetch(`/api/memory/entry/${id}`, {
+ method: 'DELETE'
+ });
+ if (!response.ok) throw new Error('Failed to delete entry');
+ };
+
+ const handleResetEntry = async (id) => {
+ const response = await fetch(`/api/memory/entry/${id}/reset`, {
+ method: 'POST'
+ });
+ if (!response.ok) throw new Error('Failed to reset entry');
+ };
+
+ const handleUpdateEntry = async (id) => {
+ const response = await fetch(`/api/memory/entry/${id}/update`, {
+ method: 'POST'
+ });
+ if (!response.ok) throw new Error('Failed to update entry');
+ };
+
+ // Determine active LLM's current state for UI feedback
+ const activeLlmState = activeLlm ? llms[activeLlm] : null;
+
+ // Track overall WebSocket connection state
+ useEffect(() => {
+ if (llmWs.wsState === WebSocketState.CONNECTED &&
+ contextWs.wsState === WebSocketState.CONNECTED &&
+ responseWs.wsState === WebSocketState.CONNECTED &&
+ stdoutWs.wsState === WebSocketState.CONNECTED &&
+ autoApproverWs.wsState === WebSocketState.CONNECTED &&
+ memoryWs.wsState === WebSocketState.CONNECTED) {
+ setWsState(WebSocketState.CONNECTED);
+ } else if (llmWs.wsState === WebSocketState.CONNECTING ||
+ contextWs.wsState === WebSocketState.CONNECTING ||
+ responseWs.wsState === WebSocketState.CONNECTING ||
+ stdoutWs.wsState === WebSocketState.CONNECTING ||
+ autoApproverWs.wsState === WebSocketState.CONNECTING ||
+ memoryWs.wsState === WebSocketState.CONNECTING) {
+ setWsState(WebSocketState.CONNECTING);
+ } else {
+ setWsState(WebSocketState.DISCONNECTED);
+ }
+ }, [llmWs.wsState, contextWs.wsState, responseWs.wsState, stdoutWs.wsState, autoApproverWs.wsState, memoryWs.wsState]);
+
+ return (
+
+
+
+
+
+ {activeTab === Tabs.CONTEXT && (
+
+ )}
+ {activeTab === Tabs.RESPONSE && (
+
+ )}
+ {activeTab === Tabs.INPUT && (
+ setInput(value)}
+ language="plaintext"
+ />
+ )}
+ {activeTab === Tabs.OUTPUT && (
+
+ )}
+ {activeTab === Tabs.MEMORY && (
+
+ )}
+
+
+ setShowDiff(!showDiff)}
+ onSendInput={handleSendInput}
+ onClearOutput={handleClearOutput}
+ onLlmChange={setActiveLlm}
+ onNextLlm={handleNextLlm}
+ onAutoApproverConfigChange={handleAutoApproverConfigChange}
+ />
+
+ setShowSidebar(!showSidebar)} />
+
+
+ );
+};
+
+export default App;
\ No newline at end of file
diff --git a/web/src/components/Header.jsx b/web/src/components/Header.jsx
index 6db06b1..7a99b29 100644
--- a/web/src/components/Header.jsx
+++ b/web/src/components/Header.jsx
@@ -1,57 +1,58 @@
-import React from 'react';
-import { WebSocketState } from '../hooks/useWebSocket';
-
-export const Tabs = {
- CONTEXT: 'CONTEXT',
- RESPONSE: 'RESPONSE',
- INPUT: 'INPUT',
- OUTPUT: 'OUTPUT',
- MEMORY: 'MEMORY'
-
-};
-
-export const Header = ({
- wsState,
- agentState,
- activeTab,
- setActiveTab,
-}) => (
-
+import React from 'react';
+import { WebSocketState } from '../hooks/useWebSocket';
+
+export const Tabs = {
+ CONTEXT: 'CONTEXT',
+ RESPONSE: 'RESPONSE',
+ INPUT: 'INPUT',
+ OUTPUT: 'OUTPUT',
+ MEMORY: 'MEMORY'
+};
+
+export const Header = ({
+ wsState,
+ agentState,
+ activeTab,
+ setActiveTab,
+}) => (
+
);
\ No newline at end of file
diff --git a/web/src/components/Sidebar.jsx b/web/src/components/Sidebar.jsx
index 185fbbf..5c3a937 100644
--- a/web/src/components/Sidebar.jsx
+++ b/web/src/components/Sidebar.jsx
@@ -1,161 +1,177 @@
-import React from 'react';
-import { Button } from '@/components/ui/button';
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
-import { Switch } from '@/components/ui/switch';
-import { Input } from '@/components/ui/input';
-import { LlmState } from '@/components/App';
-
-export const Sidebar = ({
- showSidebar,
- llms,
- activeLlm,
- llmState,
- showDiff,
- autoScroll,
- input,
- output,
- autoApproverConfig,
- onApprove,
- onStop,
- onToggleDiff,
- onSendInput,
- onClearOutput,
- onLlmChange,
- onNextLlm,
- onAutoScrollChange,
- onAutoApproverConfigChange,
-}) => (
-
-);
+import React from 'react';
+import { Button } from '@/components/ui/button';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
+import { Switch } from '@/components/ui/switch';
+import { Input } from '@/components/ui/input';
+import { LlmState } from '@/components/App';
+
+export const Sidebar = ({
+ showSidebar,
+ llms,
+ activeLlm,
+ llmState,
+ showDiff,
+ autoScroll,
+ input,
+ output,
+ autoApproverConfig,
+ onApprove,
+ onInference,
+ onStop,
+ onToggleDiff,
+ onSendInput,
+ onClearOutput,
+ onLlmChange,
+ onNextLlm,
+ onAutoScrollChange,
+ onAutoApproverConfigChange,
+}) => (
+
+);
\ No newline at end of file
diff --git a/web/src/components/editors/ContentEditor.jsx b/web/src/components/editors/ContentEditor.jsx
index df4a3d2..4311fbd 100644
--- a/web/src/components/editors/ContentEditor.jsx
+++ b/web/src/components/editors/ContentEditor.jsx
@@ -1,37 +1,37 @@
-import React from 'react';
-import { DiffEditorWrapper } from './DiffEditorWrapper';
-import { StandardEditor } from './StandardEditor';
-
-export const ContentEditor = ({
- showDiff,
- originalContent,
- modifiedContent,
- onChange,
- validationError = null,
- language = 'xml',
- autoScroll = false,
-}) => {
- if (showDiff) {
- return (
- {
- onChange(value);
- }}
- language={language}
- validationError={validationError}
- />
- );
- }
-
- return (
-
- );
-};
+import React from 'react';
+import { DiffEditorWrapper } from './DiffEditorWrapper';
+import { StandardEditor } from './StandardEditor';
+
+export const ContentEditor = ({
+ showDiff,
+ originalContent,
+ modifiedContent,
+ onChange,
+ validationError = null,
+ language = 'xml',
+ autoScroll = false,
+}) => {
+ if (showDiff) {
+ return (
+ {
+ onChange(value);
+ }}
+ language={language}
+ validationError={validationError}
+ />
+ );
+ }
+
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/web/src/components/editors/DiffEditorWrapper.jsx b/web/src/components/editors/DiffEditorWrapper.jsx
index 28dec68..030c095 100644
--- a/web/src/components/editors/DiffEditorWrapper.jsx
+++ b/web/src/components/editors/DiffEditorWrapper.jsx
@@ -1,60 +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 (
-
-
- {validationError && (
-
- {validationError}
-
- )}
-
-
-
- )
+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 (
+
+
+ {validationError && (
+
+ {validationError}
+
+ )}
+
+
+
+ )
};
\ No newline at end of file
diff --git a/web/src/components/editors/MemoryEditor.jsx b/web/src/components/editors/MemoryEditor.jsx
index d80a3a1..033d272 100644
--- a/web/src/components/editors/MemoryEditor.jsx
+++ b/web/src/components/editors/MemoryEditor.jsx
@@ -1,217 +1,217 @@
-import React, { useState } from 'react';
-import { BackgroundEntryEditor } from './BackgroundEntryEditor';
-import { ParseErrorEntryEditor } from './ParseErrorEntryEditor';
-import { ReadEntryEditor } from './ReadEntryEditor';
-import { ReasoningEntryEditor } from './ReasoningEntryEditor';
-import { RepeatEntryEditor } from './RepeatEntryEditor';
-import { SingleEntryEditor } from './SingleEntryEditor';
-import { WriteEntryEditor } from './WriteEntryEditor';
-
-const EntryTypes = {
- BACKGROUND: 'background',
- PARSE_ERROR: 'parse_error',
- READ_STDIN: 'read_stdin',
- REASONING: 'reasoning',
- REPEAT: 'repeat',
- SINGLE: 'single',
- WRITE: 'write'
-};
-
-const EntryEditors = {
- [EntryTypes.BACKGROUND]: BackgroundEntryEditor,
- [EntryTypes.PARSE_ERROR]: ParseErrorEntryEditor,
- [EntryTypes.READ_STDIN]: ReadEntryEditor,
- [EntryTypes.REASONING]: ReasoningEntryEditor,
- [EntryTypes.REPEAT]: RepeatEntryEditor,
- [EntryTypes.SINGLE]: SingleEntryEditor,
- [EntryTypes.WRITE]: WriteEntryEditor
-};
-
-export const MemoryEditor = ({
- entries = [],
- onCreateEntry,
- onSaveEntry,
- onDeleteEntry,
- onResetEntry,
- onUpdateEntry
-}) => {
- const [showCreateDialog, setShowCreateDialog] = useState(false);
- const [selectedType, setSelectedType] = useState(EntryTypes.SINGLE);
-
- const handleCreate = (_id, entry) => {
- onCreateEntry(entry);
- setShowCreateDialog(false);
- };
-
- const handleLoadIteration = async (event) => {
- const file = event.target.files[0];
- if (file) {
- try {
- const content = await file.text();
- const response = await fetch('/api/memory/load_iteration', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ content: content })
- });
- if (!response.ok) throw new Error('Failed to load iteration');
- } catch (error) {
- if (error instanceof DOMException) {
- alert('Failed to read the file. Check permissions. chown -R $USER:$USER iteration');
- } else {
- throw error;
- }
- }
- }
- };
-
- const handleLoadIterationClick = () => {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = '.xml';
- input.onchange = handleLoadIteration;
- input.click();
- };
-
- const generateId = () => {
- 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');
- };
-
- const getInitialEntry = (type) => {
- const baseEntry = {
- type: type,
- id: generateId()
- };
-
- switch (type) {
- case EntryTypes.BACKGROUND:
- case EntryTypes.REPEAT:
- case EntryTypes.SINGLE:
- return {
- ...baseEntry,
- script: '',
- timeout: null,
- limit: null
- };
- case EntryTypes.PARSE_ERROR:
- return {
- ...baseEntry,
- content: '',
- error: ''
- };
- case EntryTypes.READ_STDIN:
- return {
- ...baseEntry,
- content: '',
- read: false
- };
- case EntryTypes.REASONING:
- return {
- ...baseEntry,
- content: ''
- };
- case EntryTypes.WRITE:
- return {
- ...baseEntry,
- content: '',
- written: false
- };
- default:
- return baseEntry;
- }
- };
-
- const renderCreateDialog = () => {
- const EditorComponent = EntryEditors[selectedType];
- const entry = getInitialEntry(selectedType);
-
- return (
-
-
-
-
Create New Entry
-
-
-
-
-
-
-
setShowCreateDialog(false)}
- />
-
-
- );
- };
-
- const renderEntry = (entry) => {
- const EditorComponent = EntryEditors[entry.type];
- if (!EditorComponent) {
- console.warn(`Unknown entry type: ${entry.type}`);
- return null;
- }
-
- return (
-
- );
- };
-
- return (
-
-
-
-
-
-
-
- {entries.map(renderEntry)}
-
-
- {showCreateDialog && renderCreateDialog()}
-
- );
-};
-
-export default MemoryEditor;
+import React, { useState } from 'react';
+import { BackgroundEntryEditor } from './BackgroundEntryEditor';
+import { ParseErrorEntryEditor } from './ParseErrorEntryEditor';
+import { ReadEntryEditor } from './ReadEntryEditor';
+import { ReasoningEntryEditor } from './ReasoningEntryEditor';
+import { RepeatEntryEditor } from './RepeatEntryEditor';
+import { SingleEntryEditor } from './SingleEntryEditor';
+import { WriteEntryEditor } from './WriteEntryEditor';
+
+const EntryTypes = {
+ BACKGROUND: 'background',
+ PARSE_ERROR: 'parse_error',
+ READ_STDIN: 'read_stdin',
+ REASONING: 'reasoning',
+ REPEAT: 'repeat',
+ SINGLE: 'single',
+ WRITE: 'write'
+};
+
+const EntryEditors = {
+ [EntryTypes.BACKGROUND]: BackgroundEntryEditor,
+ [EntryTypes.PARSE_ERROR]: ParseErrorEntryEditor,
+ [EntryTypes.READ_STDIN]: ReadEntryEditor,
+ [EntryTypes.REASONING]: ReasoningEntryEditor,
+ [EntryTypes.REPEAT]: RepeatEntryEditor,
+ [EntryTypes.SINGLE]: SingleEntryEditor,
+ [EntryTypes.WRITE]: WriteEntryEditor
+};
+
+export const MemoryEditor = ({
+ entries = [],
+ onCreateEntry,
+ onSaveEntry,
+ onDeleteEntry,
+ onResetEntry,
+ onUpdateEntry
+}) => {
+ const [showCreateDialog, setShowCreateDialog] = useState(false);
+ const [selectedType, setSelectedType] = useState(EntryTypes.SINGLE);
+
+ const handleCreate = (_id, entry) => {
+ onCreateEntry(entry);
+ setShowCreateDialog(false);
+ };
+
+ const handleLoadIteration = async (event) => {
+ const file = event.target.files[0];
+ if (file) {
+ try {
+ const content = await file.text();
+ const response = await fetch('/api/memory/load_iteration', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ content: content })
+ });
+ if (!response.ok) throw new Error('Failed to load iteration');
+ } catch (error) {
+ if (error instanceof DOMException) {
+ alert('Failed to read the file. Check permissions. chown -R $USER:$USER iteration');
+ } else {
+ throw error;
+ }
+ }
+ }
+ };
+
+ const handleLoadIterationClick = () => {
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.accept = '.xml';
+ input.onchange = handleLoadIteration;
+ input.click();
+ };
+
+ const generateId = () => {
+ 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');
+ };
+
+ const getInitialEntry = (type) => {
+ const baseEntry = {
+ type: type,
+ id: generateId()
+ };
+
+ switch (type) {
+ case EntryTypes.BACKGROUND:
+ case EntryTypes.REPEAT:
+ case EntryTypes.SINGLE:
+ return {
+ ...baseEntry,
+ script: '',
+ timeout: null,
+ limit: null
+ };
+ case EntryTypes.PARSE_ERROR:
+ return {
+ ...baseEntry,
+ content: '',
+ error: ''
+ };
+ case EntryTypes.READ_STDIN:
+ return {
+ ...baseEntry,
+ content: '',
+ read: false
+ };
+ case EntryTypes.REASONING:
+ return {
+ ...baseEntry,
+ content: ''
+ };
+ case EntryTypes.WRITE:
+ return {
+ ...baseEntry,
+ content: '',
+ written: false
+ };
+ default:
+ return baseEntry;
+ }
+ };
+
+ const renderCreateDialog = () => {
+ const EditorComponent = EntryEditors[selectedType];
+ const entry = getInitialEntry(selectedType);
+
+ return (
+
+
+
+
Create New Entry
+
+
+
+
+
+
+
setShowCreateDialog(false)}
+ />
+
+
+ );
+ };
+
+ const renderEntry = (entry) => {
+ const EditorComponent = EntryEditors[entry.type];
+ if (!EditorComponent) {
+ console.warn(`Unknown entry type: ${entry.type}`);
+ return null;
+ }
+
+ return (
+
+ );
+ };
+
+ return (
+
+
+
+
+
+
+
+ {entries.map(renderEntry)}
+
+
+ {showCreateDialog && renderCreateDialog()}
+
+ );
+};
+
+export default MemoryEditor;
\ No newline at end of file
diff --git a/web/src/components/editors/StandardEditor.jsx b/web/src/components/editors/StandardEditor.jsx
index f895ed5..4da0a31 100644
--- a/web/src/components/editors/StandardEditor.jsx
+++ b/web/src/components/editors/StandardEditor.jsx
@@ -1,61 +1,61 @@
-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,
- autoScroll = false,
-}) => {
- const editorRef = React.useRef(null);
-
- const scrollToBottom = () => {
- if (editorRef.current && autoScroll) {
- const model = editorRef.current.getModel();
- const lineCount = model.getLineCount();
- editorRef.current.revealLine(lineCount, monaco.editor.ScrollType.Smooth);
- }
- };
-
- const handleEditorDidMount = (editor) => {
- editorRef.current = editor;
- setTimeout(() => {
- scrollToBottom();
- }, 10);
- };
-
- React.useEffect(() => {
- scrollToBottom();
- }, [content, autoScroll]);
-
- return (
-
-
- {validationError && (
-
- {validationError}
-
- )}
-
-
-
- );
+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,
+ autoScroll = false,
+}) => {
+ const editorRef = React.useRef(null);
+
+ const scrollToBottom = () => {
+ if (editorRef.current && autoScroll) {
+ const model = editorRef.current.getModel();
+ const lineCount = model.getLineCount();
+ editorRef.current.revealLine(lineCount, monaco.editor.ScrollType.Smooth);
+ }
+ };
+
+ const handleEditorDidMount = (editor) => {
+ editorRef.current = editor;
+ setTimeout(() => {
+ scrollToBottom();
+ }, 10);
+ };
+
+ React.useEffect(() => {
+ scrollToBottom();
+ }, [content, autoScroll]);
+
+ return (
+
+
+ {validationError && (
+
+ {validationError}
+
+ )}
+
+
+
+ );
};
\ No newline at end of file
diff --git a/web/src/hooks/useWebSocket.jsx b/web/src/hooks/useWebSocket.jsx
index 6e657ee..7053bc4 100644
--- a/web/src/hooks/useWebSocket.jsx
+++ b/web/src/hooks/useWebSocket.jsx
@@ -1,68 +1,68 @@
-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
- };
+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