diff --git a/sia/entry/__init__.py b/sia/entry/__init__.py index ea374ec..dd5c2f6 100644 --- a/sia/entry/__init__.py +++ b/sia/entry/__init__.py @@ -15,14 +15,9 @@ class Entry(ABC): Args: id: Unique identifier for this entry """ - self._id = id + self.id = id self._change_handlers: List[Callable[['Entry'], None]] = [] - @property - def id(self) -> str: - """Get entry's unique identifier.""" - return self._id - def add_change_handler(self, handler: Callable[['Entry'], None]) -> None: """Add a callback for entry changes.""" if handler not in self._change_handlers: diff --git a/sia/entry/background_entry.py b/sia/entry/background_entry.py index cd98358..a25b1c0 100644 --- a/sia/entry/background_entry.py +++ b/sia/entry/background_entry.py @@ -135,7 +135,7 @@ class BackgroundEntry(Entry): Returns: ET.Element: XML element containing the entry's data """ - element = ET.Element("background", {"id": self._id}) + element = ET.Element("background", {"id": self.id}) if self._process is not None: element.set("pid", str(self._process.pid)) elif self.exit_code is not None: @@ -154,7 +154,7 @@ class BackgroundEntry(Entry): """ return { "type": "background", - "id": self._id, + "id": self.id, "script": self.script, "stdout": self.stdout, "stderr": self.stderr, diff --git a/sia/entry/entry_factory.py b/sia/entry/entry_factory.py index d9a32a9..8d6e453 100644 --- a/sia/entry/entry_factory.py +++ b/sia/entry/entry_factory.py @@ -116,5 +116,4 @@ class EntryFactory: if "content" in data: entry.content = data["content"] if "written" in data: - entry.written = bool(data["written"]) - entry.notify_change() \ No newline at end of file + entry.written = bool(data["written"]) \ No newline at end of file diff --git a/sia/entry/parse_error_entry.py b/sia/entry/parse_error_entry.py index f2d4be9..6b51c8c 100644 --- a/sia/entry/parse_error_entry.py +++ b/sia/entry/parse_error_entry.py @@ -35,7 +35,7 @@ class ParseErrorEntry(Entry): Returns: ET.Element: XML element containing the entry's data """ - element = ET.Element("parse_error", {"id": self._id}) + element = ET.Element("parse_error", {"id": self.id}) error_elem = ET.SubElement(element, "error") error_elem.text = self.error content_elem = ET.SubElement(element, "content") @@ -49,7 +49,7 @@ class ParseErrorEntry(Entry): """ return { "type": "parse_error", - "id": self._id, + "id": self.id, "content": self.content, "error": self.error, } \ No newline at end of file diff --git a/sia/entry/read_entry.py b/sia/entry/read_entry.py index f841917..9fb2f50 100644 --- a/sia/entry/read_entry.py +++ b/sia/entry/read_entry.py @@ -21,7 +21,7 @@ class ReadEntry(Entry): io_buffer: Buffer to use for IO operations """ super().__init__(id) - self.content: str = "", + self.content: str = "" self.read = False self._io_buffer = io_buffer @@ -42,7 +42,7 @@ class ReadEntry(Entry): Returns: ET.Element: XML element containing the entry's data """ - element = ET.Element("read_stdin", {"id": self._id}) + element = ET.Element("read_stdin", {"id": self.id}) if self.read: element.text = self.content @@ -54,7 +54,7 @@ class ReadEntry(Entry): """ return { "type": "read_stdin", - "id": self._id, + "id": self.id, "content": self.content, "read": self.read, } \ No newline at end of file diff --git a/sia/entry/reasoning_entry.py b/sia/entry/reasoning_entry.py index 439e149..2061fef 100644 --- a/sia/entry/reasoning_entry.py +++ b/sia/entry/reasoning_entry.py @@ -34,7 +34,7 @@ class ReasoningEntry(Entry): Returns: ET.Element: XML element containing the entry's data """ - element = ET.Element("reasoning", {"id": self._id}) + element = ET.Element("reasoning", {"id": self.id}) element.text = self.content return element @@ -45,6 +45,6 @@ class ReasoningEntry(Entry): """ return { "type": "reasoning", - "id": self._id, + "id": self.id, "content": self.content, } \ No newline at end of file diff --git a/sia/entry/repeat_entry.py b/sia/entry/repeat_entry.py index 1929a0d..a9ad12f 100644 --- a/sia/entry/repeat_entry.py +++ b/sia/entry/repeat_entry.py @@ -32,9 +32,9 @@ class RepeatEntry(Entry): self.script = script self.timeout = timeout self.limit = limit - self.stdout: str = "", - self.stderr: str = "", - self.exit_code: Optional[int] = None, + self.stdout: str = "" + self.stderr: str = "" + self.exit_code: Optional[int] = None self.timed_out: bool = False def update(self) -> None: diff --git a/sia/entry/single_entry.py b/sia/entry/single_entry.py index b066a28..15b10b0 100644 --- a/sia/entry/single_entry.py +++ b/sia/entry/single_entry.py @@ -32,10 +32,10 @@ class SingleEntry(Entry): self.script = script self.timeout = timeout self.limit = limit - self.stdout: str = "", - self.stderr: str = "", + self.stdout: str = "" + self.stderr: str = "" self.exit_code: Optional[int] = None - self.executed: bool = False, + self.executed: bool = False self.timed_out: bool = False def reset(self) -> None: @@ -111,4 +111,6 @@ class SingleEntry(Entry): "stdout": self.stdout, "stderr": self.stderr, "exit_code": self.exit_code, + "executed": self.executed, + "timed_out": self.timed_out } \ No newline at end of file diff --git a/sia/entry/write_entry.py b/sia/entry/write_entry.py index e4a92b4..e32f1c3 100644 --- a/sia/entry/write_entry.py +++ b/sia/entry/write_entry.py @@ -54,6 +54,6 @@ class WriteEntry(Entry): """ return { "type": "write", - "id": self._id, + "id": self.id, "content": self.content, } diff --git a/sia/web/api.py b/sia/web/api.py index 405d531..8767865 100644 --- a/sia/web/api.py +++ b/sia/web/api.py @@ -43,9 +43,10 @@ class Api: self._app.router.add_post("/api/auto_approver/llm", self._set_llm_name) self._app.router.add_get("/api/memory", self._get_memory) self._app.router.add_post("/api/memory/entry", self._create_entry) - self._app.router.add_put("/api/memory/entry/{id}", self._update_entry) + self._app.router.add_put("/api/memory/entry/{id}", self._save_entry) self._app.router.add_delete("/api/memory/entry/{id}", self._delete_entry) self._app.router.add_post("/api/memory/entry/{id}/reset", self._reset_entry) + self._app.router.add_post("/api/memory/entry/{id}/update", self._update_entry) async def _run_inference(self, request: web.Request) -> web.Response: """Start inference on specified LLM.""" @@ -212,7 +213,7 @@ class Api: except (ValueError, TypeError) as e: return web.Response(status=400, text=str(e)) - async def _update_entry(self, request: web.Request) -> web.Response: + async def _save_entry(self, request: web.Request) -> web.Response: """Update properties of an existing entry.""" entry_id = request.match_info["id"] data = await request.json() @@ -221,6 +222,7 @@ class Api: return web.Response(status=404, text="Entry not found") try: EntryFactory.update_entry(entry, data) + entry.notify_change() return web.Response(status=200) except ValueError as e: return web.Response(status=400, text=str(e)) @@ -238,4 +240,18 @@ class Api: if not entry: return web.Response(status=404, text="Entry not found") entry.reset() - return web.Response(status=200) \ No newline at end of file + entry.notify_change() + return web.Response(status=200) + + async def _update_entry(self, request: web.Request) -> web.Response: + """Update an entry's state.""" + entry_id = request.match_info["id"] + entry = self._working_memory.get_entry(entry_id) + if not entry: + return web.Response(status=404, text="Entry not found") + try: + entry.update() + entry.notify_change() + return web.Response(status=200) + except ValueError as e: + return web.Response(status=400, text=str(e)) \ No newline at end of file diff --git a/web/src/components/App.jsx b/web/src/components/App.jsx index b7c9b28..a9f57de 100644 --- a/web/src/components/App.jsx +++ b/web/src/components/App.jsx @@ -1,8 +1,9 @@ import React, { useState, useEffect } from 'react'; -import { Header, Tabs } from '@/components/Header'; -import { Sidebar } from '@/components/Sidebar'; -import { FloatingButton } from '@/components/FloatingButton'; 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'; @@ -48,6 +49,8 @@ const App = () => { 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 @@ -111,6 +114,15 @@ const App = () => { }); }, []); + // 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) { @@ -189,7 +201,48 @@ const App = () => { } }; + 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 LlmState.NO_OUTPUT: @@ -210,18 +263,20 @@ const App = () => { contextWs.wsState === WebSocketState.CONNECTED && tokenWs.wsState === WebSocketState.CONNECTED && stdoutWs.wsState === WebSocketState.CONNECTED && - autoApproverWs.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) { + 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]); + }, [llmWs.wsState, contextWs.wsState, tokenWs.wsState, stdoutWs.wsState, autoApproverWs.wsState, memoryWs.wsState]); return (
{formData.script || ''}
+ {formData.stdout || ''}
+ {formData.stderr || ''}
+