WIP memory editor
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -117,4 +117,3 @@ class EntryFactory:
|
||||
entry.content = data["content"]
|
||||
if "written" in data:
|
||||
entry.written = bool(data["written"])
|
||||
entry.notify_change()
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -54,6 +54,6 @@ class WriteEntry(Entry):
|
||||
"""
|
||||
return {
|
||||
"type": "write",
|
||||
"id": self._id,
|
||||
"id": self.id,
|
||||
"content": self.content,
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
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))
|
||||
@@ -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 (
|
||||
<div className="h-screen bg-gray-100 flex flex-col overflow-hidden">
|
||||
@@ -268,6 +323,16 @@ const App = () => {
|
||||
language="plaintext"
|
||||
/>
|
||||
)}
|
||||
{activeTab === Tabs.MEMORY && (
|
||||
<MemoryEditor
|
||||
entries={entries}
|
||||
onCreateEntry={handleCreateEntry}
|
||||
onSaveEntry={handleSaveEntry}
|
||||
onDeleteEntry={handleDeleteEntry}
|
||||
onResetEntry={handleResetEntry}
|
||||
onUpdateEntry={handleUpdateEntry}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
|
||||
<Sidebar
|
||||
|
||||
@@ -5,7 +5,9 @@ export const Tabs = {
|
||||
CONTEXT: 'CONTEXT',
|
||||
RESPONSE: 'RESPONSE',
|
||||
INPUT: 'INPUT',
|
||||
OUTPUT: 'OUTPUT'
|
||||
OUTPUT: 'OUTPUT',
|
||||
MEMORY: 'MEMORY'
|
||||
|
||||
};
|
||||
|
||||
export const Header = ({
|
||||
|
||||
117
web/src/components/editors/BaseEntryEditor.jsx
Normal file
117
web/src/components/editors/BaseEntryEditor.jsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
export const BaseEntryEditor = ({
|
||||
entry,
|
||||
isNew = false,
|
||||
onSave,
|
||||
onCancel,
|
||||
onDelete,
|
||||
onReset,
|
||||
onUpdate,
|
||||
|
||||
children
|
||||
}) => {
|
||||
const [isEditing, setIsEditing] = useState(isNew);
|
||||
const [formData, setFormData] = useState(entry);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditing) {
|
||||
setFormData(entry);
|
||||
}
|
||||
}, [entry]);
|
||||
|
||||
const handleSave = () => {
|
||||
const id = isNew ? formData.id : entry.id;
|
||||
onSave(id, formData);
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
const handleEdit = () => {
|
||||
setIsEditing(true);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setFormData(entry);
|
||||
setIsEditing(false);
|
||||
if (onCancel) onCancel();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border rounded-lg shadow-sm bg-white p-4 mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900 mb-4">{formData.type}</h2>
|
||||
{isEditing ? (
|
||||
<form className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">ID (Timestamp)</label>
|
||||
<input
|
||||
value={formData.id}
|
||||
onChange={e => setFormData({...formData, id: e.target.value})}
|
||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Child components inject their specific form fields here */}
|
||||
{children({ formData, setFormData, isEditing, isNew })}
|
||||
|
||||
<div className="flex justify-end space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCancel}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSave}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700"
|
||||
>
|
||||
{isNew ? 'Create' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-700">ID:</span>
|
||||
<span className="ml-2 text-sm text-gray-900">{entry.id}</span>
|
||||
</div>
|
||||
|
||||
{/* Child components inject their specific read-only fields here */}
|
||||
{children({ formData, setFormData, isEditing, isNew })}
|
||||
|
||||
<div className="flex justify-end space-x-2">
|
||||
{onReset && (
|
||||
<button
|
||||
onClick={() => onReset(entry.id)}
|
||||
className="px-4 py-2 text-sm font-medium text-green-600 bg-white border border-green-600 rounded-md hover:bg-green-50"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
)}
|
||||
{onUpdate && (
|
||||
<button
|
||||
onClick={() => onUpdate(entry.id)}
|
||||
className="px-4 py-2 text-sm font-medium text-green-600 bg-white border border-green-600 rounded-md hover:bg-green-50"
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={handleEdit}
|
||||
className="px-4 py-2 text-sm font-medium text-blue-600 bg-white border border-blue-300 rounded-md hover:bg-blue-50"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onDelete(entry.id)}
|
||||
className="px-4 py-2 text-sm font-medium text-red-600 bg-white border border-red-300 rounded-md hover:bg-red-50"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
96
web/src/components/editors/MemoryEditor.jsx
Normal file
96
web/src/components/editors/MemoryEditor.jsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import React, { useState } from 'react';
|
||||
import { SingleEntryEditor } from './SingleEntryEditor';
|
||||
|
||||
const EntryTypes = {
|
||||
SINGLE: 'single'
|
||||
};
|
||||
|
||||
export const MemoryEditor = ({
|
||||
entries = [],
|
||||
onCreateEntry,
|
||||
onSaveEntry,
|
||||
onDeleteEntry,
|
||||
onResetEntry,
|
||||
onUpdateEntry
|
||||
}) => {
|
||||
const [showCreateDialog, setShowCreateDialog] = useState(false);
|
||||
|
||||
const handleCreate = (_id, entry) => {
|
||||
onCreateEntry(entry);
|
||||
setShowCreateDialog(false);
|
||||
};
|
||||
|
||||
const renderCreateDialog = () => {
|
||||
const now = new Date();
|
||||
const id = now.getFullYear() +
|
||||
String(now.getMonth() + 1).padStart(2, '0') +
|
||||
String(now.getDate()).padStart(2, '0') + '_' +
|
||||
String(now.getHours()).padStart(2, '0') +
|
||||
String(now.getMinutes()).padStart(2, '0') +
|
||||
String(now.getSeconds()).padStart(2, '0') + '_' +
|
||||
String(now.getMilliseconds()).padStart(3, '0');
|
||||
|
||||
const entry = {
|
||||
type: EntryTypes.SINGLE,
|
||||
id: id,
|
||||
script: '',
|
||||
timeout: null,
|
||||
limit: null,
|
||||
executed: false
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
|
||||
<div className="bg-white p-4 rounded-lg max-w-2xl w-full m-4">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-lg font-medium">Create New Entry</h2>
|
||||
<button
|
||||
onClick={() => setShowCreateDialog(false)}
|
||||
className="text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<SingleEntryEditor
|
||||
entry={entry}
|
||||
isNew={true}
|
||||
isEditing={true}
|
||||
onSave={handleCreate}
|
||||
onCancel={() => setShowCreateDialog(false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="flex justify-end mb-4">
|
||||
<button
|
||||
onClick={() => setShowCreateDialog(true)}
|
||||
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700"
|
||||
>
|
||||
Add Entry
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{entries.map(entry => (
|
||||
<SingleEntryEditor
|
||||
key={entry.id}
|
||||
entry={entry}
|
||||
isEditing={true}
|
||||
onSave={onSaveEntry}
|
||||
onDelete={onDeleteEntry}
|
||||
onReset={onResetEntry}
|
||||
onUpdate={onUpdateEntry}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{showCreateDialog && renderCreateDialog()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MemoryEditor;
|
||||
138
web/src/components/editors/SingleEntryEditor.jsx
Normal file
138
web/src/components/editors/SingleEntryEditor.jsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import { BaseEntryEditor } from './BaseEntryEditor';
|
||||
|
||||
const CreateForm = ({ formData, setFormData }) => (
|
||||
<>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Script</label>
|
||||
<textarea
|
||||
value={formData.script || ''}
|
||||
onChange={e => setFormData({...formData, script: e.target.value})}
|
||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Timeout (seconds)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.timeout || ''}
|
||||
onChange={e => setFormData({...formData, timeout: e.target.value ? Number(e.target.value) : null})}
|
||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||
step="0.1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Memory Limit (bytes)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.limit || ''}
|
||||
onChange={e => setFormData({...formData, limit: e.target.value ? Number(e.target.value) : null})}
|
||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const EditForm = ({ formData, setFormData }) => (
|
||||
<>
|
||||
<CreateForm formData={formData} setFormData={setFormData} />
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Standard Output</label>
|
||||
<textarea
|
||||
value={formData.stdout || ''}
|
||||
onChange={e => setFormData({...formData, stdout: e.target.value})}
|
||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Standard Error</label>
|
||||
<textarea
|
||||
value={formData.stderr || ''}
|
||||
onChange={e => setFormData({...formData, stderr: e.target.value})}
|
||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Exit Code</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.exit_code ?? ''}
|
||||
onChange={e => setFormData({...formData, exit_code: e.target.value ? Number(e.target.value) : null})}
|
||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex space-x-4">
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.executed || false}
|
||||
onChange={e => setFormData({...formData, executed: e.target.checked})}
|
||||
className="rounded border-gray-300"
|
||||
/>
|
||||
<span className="ml-2 text-sm text-gray-700">Executed</span>
|
||||
</label>
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.timed_out || false}
|
||||
onChange={e => setFormData({...formData, timed_out: e.target.checked})}
|
||||
className="rounded border-gray-300"
|
||||
/>
|
||||
<span className="ml-2 text-sm text-gray-700">Timed Out</span>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const ViewMode = ({ formData }) => (
|
||||
<>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-700">Script:</span>
|
||||
<pre className="mt-1 text-sm text-gray-900 bg-gray-50 p-2 rounded">{formData.script || ''}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-700">Timeout:</span>
|
||||
<span className="ml-2 text-sm text-gray-900">{formData.timeout || 'None'}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-700">Memory Limit:</span>
|
||||
<span className="ml-2 text-sm text-gray-900">{formData.limit || 'None'}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-700">Standard Output:</span>
|
||||
<pre className="mt-1 text-sm text-gray-900 bg-gray-50 p-2 rounded">{formData.stdout || ''}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-700">Standard Error:</span>
|
||||
<pre className="mt-1 text-sm text-gray-900 bg-gray-50 p-2 rounded">{formData.stderr || ''}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-700">Exit Code:</span>
|
||||
<span className="ml-2 text-sm text-gray-900">{formData.exit_code ?? 'None'}</span>
|
||||
</div>
|
||||
<div className="flex space-x-4">
|
||||
<span className="text-sm text-gray-700">
|
||||
{formData.executed ? '✓ Executed' : '✗ Not Executed'}
|
||||
</span>
|
||||
<span className="text-sm text-gray-700">
|
||||
{formData.timed_out ? '⚠ Timed Out' : '✓ Within Timeout'}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
export const SingleEntryEditor = (props) => (
|
||||
<BaseEntryEditor {...props}>
|
||||
{({ formData, setFormData, isEditing, isNew }) => {
|
||||
if (isNew) {
|
||||
return <CreateForm formData={formData} setFormData={setFormData} />;
|
||||
}
|
||||
if (isEditing) {
|
||||
return <EditForm formData={formData} setFormData={setFormData} />;
|
||||
}
|
||||
return <ViewMode formData={formData} />;
|
||||
}}
|
||||
</BaseEntryEditor>
|
||||
);
|
||||
Reference in New Issue
Block a user