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, -}) => ( -
-
-
-

SIA Control Interface

-
-
-
- {wsState} -
-
- {agentState} -
-
-
-
-
- {Object.values(Tabs).map((tab) => ( - - ))} -
-
-
+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, +}) => ( +
+
+
+

SIA Control Interface

+
+
+
+ {wsState} +
+ {agentState && ( +
+ {agentState} +
+ )} +
+
+
+
+ {Object.values(Tabs).map((tab) => ( + + ))} +
+
+
); \ 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