Added memory editing functionality
This commit is contained in:
@@ -54,7 +54,7 @@ class EntryFactory:
|
|||||||
if "script" in data:
|
if "script" in data:
|
||||||
entry.script = data["script"]
|
entry.script = data["script"]
|
||||||
if "timeout" in data:
|
if "timeout" in data:
|
||||||
entry.timeout = float(data["timeout"]) if data["timeout"] else None
|
entry.timeout = float(data["timeout"]) if data["timeout"] is not None else None
|
||||||
if "limit" in data:
|
if "limit" in data:
|
||||||
entry.limit = int(data["limit"]) if data["limit"] else None
|
entry.limit = int(data["limit"]) if data["limit"] else None
|
||||||
if "stdout" in data:
|
if "stdout" in data:
|
||||||
@@ -62,7 +62,7 @@ class EntryFactory:
|
|||||||
if "stderr" in data:
|
if "stderr" in data:
|
||||||
entry.stderr = data["stderr"]
|
entry.stderr = data["stderr"]
|
||||||
if "exit_code" in data:
|
if "exit_code" in data:
|
||||||
entry.exit_code = int(data["exit_code"]) if data["exit_code"] else None
|
entry.exit_code = int(data["exit_code"]) if data["exit_code"] is not None else None
|
||||||
if "executed" in data:
|
if "executed" in data:
|
||||||
entry.executed = bool(data["executed"])
|
entry.executed = bool(data["executed"])
|
||||||
if "timed_out" in data:
|
if "timed_out" in data:
|
||||||
@@ -72,7 +72,7 @@ class EntryFactory:
|
|||||||
if "script" in data:
|
if "script" in data:
|
||||||
entry.script = data["script"]
|
entry.script = data["script"]
|
||||||
if "timeout" in data:
|
if "timeout" in data:
|
||||||
entry.timeout = float(data["timeout"]) if data["timeout"] else None
|
entry.timeout = float(data["timeout"]) if data["timeout"] is not None else None
|
||||||
if "limit" in data:
|
if "limit" in data:
|
||||||
entry.limit = int(data["limit"]) if data["limit"] else None
|
entry.limit = int(data["limit"]) if data["limit"] else None
|
||||||
if "stdout" in data:
|
if "stdout" in data:
|
||||||
@@ -80,7 +80,7 @@ class EntryFactory:
|
|||||||
if "stderr" in data:
|
if "stderr" in data:
|
||||||
entry.stderr = data["stderr"]
|
entry.stderr = data["stderr"]
|
||||||
if "exit_code" in data:
|
if "exit_code" in data:
|
||||||
entry.exit_code = int(data["exit_code"]) if data["exit_code"] else None
|
entry.exit_code = int(data["exit_code"]) if data["exit_code"] is not None else None
|
||||||
if "executed" in data:
|
if "executed" in data:
|
||||||
entry.executed = bool(data["executed"])
|
entry.executed = bool(data["executed"])
|
||||||
if "timed_out" in data:
|
if "timed_out" in data:
|
||||||
@@ -94,7 +94,7 @@ class EntryFactory:
|
|||||||
if "stderr" in data:
|
if "stderr" in data:
|
||||||
entry.stderr = data["stderr"]
|
entry.stderr = data["stderr"]
|
||||||
if "exit_code" in data:
|
if "exit_code" in data:
|
||||||
entry.exit_code = int(data["exit_code"]) if data["exit_code"] else None
|
entry.exit_code = int(data["exit_code"]) if data["exit_code"] is not None else None
|
||||||
|
|
||||||
elif isinstance(entry, ParseErrorEntry):
|
elif isinstance(entry, ParseErrorEntry):
|
||||||
if "content" in data:
|
if "content" in data:
|
||||||
|
|||||||
@@ -34,6 +34,14 @@ class ReadEntry(Entry):
|
|||||||
self.content = self._io_buffer.read()
|
self.content = self._io_buffer.read()
|
||||||
self.read = True
|
self.read = True
|
||||||
self.notify_change()
|
self.notify_change()
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
"""
|
||||||
|
Reset the entry state to its initial state.
|
||||||
|
"""
|
||||||
|
self.read = False
|
||||||
|
self.content = ""
|
||||||
|
self.notify_change()
|
||||||
|
|
||||||
def generate_context(self) -> ET.Element:
|
def generate_context(self) -> ET.Element:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ class RepeatEntry(Entry):
|
|||||||
self.stdout = process.stdout
|
self.stdout = process.stdout
|
||||||
self.stderr = process.stderr
|
self.stderr = process.stderr
|
||||||
self.exit_code = process.returncode
|
self.exit_code = process.returncode
|
||||||
self._timed_out = False
|
self.timed_out = False
|
||||||
except subprocess.TimeoutExpired as e:
|
except subprocess.TimeoutExpired as e:
|
||||||
self._timed_out = True
|
self.timed_out = True
|
||||||
self.notify_change()
|
self.notify_change()
|
||||||
|
|
||||||
def generate_context(self) -> ET.Element:
|
def generate_context(self) -> ET.Element:
|
||||||
@@ -69,7 +69,7 @@ class RepeatEntry(Entry):
|
|||||||
if self.timeout:
|
if self.timeout:
|
||||||
element.set("timeout", str(self.timeout))
|
element.set("timeout", str(self.timeout))
|
||||||
element.text = self.script
|
element.text = self.script
|
||||||
if self._timed_out:
|
if self.timed_out:
|
||||||
element.set("timed_out", "true")
|
element.set("timed_out", "true")
|
||||||
elif self.exit_code is not None:
|
elif self.exit_code is not None:
|
||||||
element.set("exit_code", str(self.exit_code))
|
element.set("exit_code", str(self.exit_code))
|
||||||
@@ -101,5 +101,5 @@ class RepeatEntry(Entry):
|
|||||||
"stdout": self.stdout,
|
"stdout": self.stdout,
|
||||||
"stderr": self.stderr,
|
"stderr": self.stderr,
|
||||||
"exit_code": self.exit_code,
|
"exit_code": self.exit_code,
|
||||||
"timed_out": self._timed_out
|
"timed_out": self.timed_out
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class SingleEntry(Entry):
|
|||||||
self.stdout = ""
|
self.stdout = ""
|
||||||
self.stderr = ""
|
self.stderr = ""
|
||||||
self.exit_code = None
|
self.exit_code = None
|
||||||
|
self.notify_change()
|
||||||
|
|
||||||
def update(self) -> None:
|
def update(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -36,6 +36,13 @@ class WriteEntry(Entry):
|
|||||||
self._io_buffer.write(self.content)
|
self._io_buffer.write(self.content)
|
||||||
self.written = True
|
self.written = True
|
||||||
self.notify_change()
|
self.notify_change()
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
"""
|
||||||
|
Reset the entry state to its initial state.
|
||||||
|
"""
|
||||||
|
self.written = False
|
||||||
|
self.notify_change()
|
||||||
|
|
||||||
def generate_context(self) -> ET.Element:
|
def generate_context(self) -> ET.Element:
|
||||||
"""
|
"""
|
||||||
@@ -56,4 +63,5 @@ class WriteEntry(Entry):
|
|||||||
"type": "write",
|
"type": "write",
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
"content": self.content,
|
"content": self.content,
|
||||||
|
"written": self.written,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,7 +240,6 @@ class Api:
|
|||||||
if not entry:
|
if not entry:
|
||||||
return web.Response(status=404, text="Entry not found")
|
return web.Response(status=404, text="Entry not found")
|
||||||
entry.reset()
|
entry.reset()
|
||||||
entry.notify_change()
|
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
async def _update_entry(self, request: web.Request) -> web.Response:
|
async def _update_entry(self, request: web.Request) -> web.Response:
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class WorkingMemory:
|
|||||||
|
|
||||||
def _notify_change(self) -> None:
|
def _notify_change(self) -> None:
|
||||||
"""Notify all handlers of working memory changes."""
|
"""Notify all handlers of working memory changes."""
|
||||||
|
self._sort_entries()
|
||||||
for handler in self._change_handlers:
|
for handler in self._change_handlers:
|
||||||
handler()
|
handler()
|
||||||
|
|
||||||
@@ -38,6 +39,10 @@ class WorkingMemory:
|
|||||||
self._changed_during_update.add(entry)
|
self._changed_during_update.add(entry)
|
||||||
else:
|
else:
|
||||||
self._notify_change()
|
self._notify_change()
|
||||||
|
|
||||||
|
def _sort_entries(self) -> None:
|
||||||
|
"""Sort entries by ID in chronological order."""
|
||||||
|
self._entries.sort(key=lambda x: x.id)
|
||||||
|
|
||||||
def add_entry(self, entry: Entry) -> None:
|
def add_entry(self, entry: Entry) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -50,6 +55,7 @@ class WorkingMemory:
|
|||||||
raise TypeError("Entry must be an instance of Entry class")
|
raise TypeError("Entry must be an instance of Entry class")
|
||||||
entry.add_change_handler(self._handle_entry_change)
|
entry.add_change_handler(self._handle_entry_change)
|
||||||
self._entries.append(entry)
|
self._entries.append(entry)
|
||||||
|
self._sort_entries()
|
||||||
self._notify_change()
|
self._notify_change()
|
||||||
|
|
||||||
def remove_entry(self, id: str) -> None:
|
def remove_entry(self, id: str) -> None:
|
||||||
@@ -64,6 +70,7 @@ class WorkingMemory:
|
|||||||
if entry is not None:
|
if entry is not None:
|
||||||
entry.cleanup()
|
entry.cleanup()
|
||||||
self._entries = [e for e in self._entries if e.id != id]
|
self._entries = [e for e in self._entries if e.id != id]
|
||||||
|
self._sort_entries()
|
||||||
self._notify_change()
|
self._notify_change()
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
@@ -101,7 +108,7 @@ class WorkingMemory:
|
|||||||
Get all entries in working memory.
|
Get all entries in working memory.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[Entry]: List of all entries
|
List[Entry]: List of all entries in chronological order
|
||||||
"""
|
"""
|
||||||
return self._entries.copy()
|
return self._entries.copy()
|
||||||
|
|
||||||
|
|||||||
124
web/src/components/editors/BackgroundEntryEditor.jsx
Normal file
124
web/src/components/editors/BackgroundEntryEditor.jsx
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
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>
|
||||||
|
<div className="flex items-center mb-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.exit_code !== null}
|
||||||
|
onChange={e => setFormData({
|
||||||
|
...formData,
|
||||||
|
exit_code: e.target.checked ? 0 : null
|
||||||
|
})}
|
||||||
|
className="rounded border-gray-300"
|
||||||
|
/>
|
||||||
|
<label className="ml-2 text-sm font-medium text-gray-700">Exit Code</label>
|
||||||
|
</div>
|
||||||
|
<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 disabled:bg-gray-100"
|
||||||
|
disabled={formData.exit_code === null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center mb-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.pid !== null}
|
||||||
|
onChange={e => setFormData({
|
||||||
|
...formData,
|
||||||
|
pid: e.target.checked ? 0 : null
|
||||||
|
})}
|
||||||
|
className="rounded border-gray-300"
|
||||||
|
/>
|
||||||
|
<label className="ml-2 text-sm font-medium text-gray-700">Process ID</label>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={formData.pid ?? ''}
|
||||||
|
onChange={e => setFormData({...formData, pid: e.target.value ? Number(e.target.value) : null})}
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm disabled:bg-gray-100"
|
||||||
|
disabled={formData.pid === null}
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
{formData.pid !== null && (
|
||||||
|
<div>
|
||||||
|
<span className="text-sm font-medium text-gray-700">Process ID:</span>
|
||||||
|
<span className="ml-2 text-sm text-gray-900">{formData.pid}</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>
|
||||||
|
{formData.exit_code !== null && (
|
||||||
|
<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}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const BackgroundEntryEditor = (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>
|
||||||
|
);
|
||||||
@@ -1,8 +1,30 @@
|
|||||||
import React, { useState } from 'react';
|
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 { SingleEntryEditor } from './SingleEntryEditor';
|
||||||
|
import { WriteEntryEditor } from './WriteEntryEditor';
|
||||||
|
|
||||||
const EntryTypes = {
|
const EntryTypes = {
|
||||||
SINGLE: 'single'
|
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 = ({
|
export const MemoryEditor = ({
|
||||||
@@ -14,33 +36,74 @@ export const MemoryEditor = ({
|
|||||||
onUpdateEntry
|
onUpdateEntry
|
||||||
}) => {
|
}) => {
|
||||||
const [showCreateDialog, setShowCreateDialog] = useState(false);
|
const [showCreateDialog, setShowCreateDialog] = useState(false);
|
||||||
|
const [selectedType, setSelectedType] = useState(EntryTypes.SINGLE);
|
||||||
|
|
||||||
const handleCreate = (_id, entry) => {
|
const handleCreate = (_id, entry) => {
|
||||||
onCreateEntry(entry);
|
onCreateEntry(entry);
|
||||||
setShowCreateDialog(false);
|
setShowCreateDialog(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderCreateDialog = () => {
|
const generateId = () => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const id = now.getFullYear() +
|
return now.getFullYear() +
|
||||||
String(now.getMonth() + 1).padStart(2, '0') +
|
String(now.getMonth() + 1).padStart(2, '0') +
|
||||||
String(now.getDate()).padStart(2, '0') + '_' +
|
String(now.getDate()).padStart(2, '0') + '_' +
|
||||||
String(now.getHours()).padStart(2, '0') +
|
String(now.getHours()).padStart(2, '0') +
|
||||||
String(now.getMinutes()).padStart(2, '0') +
|
String(now.getMinutes()).padStart(2, '0') +
|
||||||
String(now.getSeconds()).padStart(2, '0') + '_' +
|
String(now.getSeconds()).padStart(2, '0') + '_' +
|
||||||
String(now.getMilliseconds()).padStart(3, '0');
|
String(now.getMilliseconds()).padStart(3, '0');
|
||||||
|
};
|
||||||
|
|
||||||
const entry = {
|
const getInitialEntry = (type) => {
|
||||||
type: EntryTypes.SINGLE,
|
const baseEntry = {
|
||||||
id: id,
|
type: type,
|
||||||
script: '',
|
id: generateId()
|
||||||
timeout: null,
|
|
||||||
limit: null,
|
|
||||||
executed: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[100]">
|
||||||
<div className="bg-white p-4 rounded-lg max-w-2xl w-full m-4">
|
<div className="bg-white p-4 rounded-lg max-w-2xl w-full m-4">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h2 className="text-lg font-medium">Create New Entry</h2>
|
<h2 className="text-lg font-medium">Create New Entry</h2>
|
||||||
@@ -51,10 +114,23 @@ export const MemoryEditor = ({
|
|||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<SingleEntryEditor
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium text-gray-700">Entry Type</label>
|
||||||
|
<select
|
||||||
|
value={selectedType}
|
||||||
|
onChange={(e) => setSelectedType(e.target.value)}
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||||
|
>
|
||||||
|
{Object.entries(EntryTypes).map(([key, value]) => (
|
||||||
|
<option key={value} value={value}>
|
||||||
|
{key.toLowerCase().replace('_', ' ')}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<EditorComponent
|
||||||
entry={entry}
|
entry={entry}
|
||||||
isNew={true}
|
isNew={true}
|
||||||
isEditing={true}
|
|
||||||
onSave={handleCreate}
|
onSave={handleCreate}
|
||||||
onCancel={() => setShowCreateDialog(false)}
|
onCancel={() => setShowCreateDialog(false)}
|
||||||
/>
|
/>
|
||||||
@@ -63,6 +139,25 @@ export const MemoryEditor = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderEntry = (entry) => {
|
||||||
|
const EditorComponent = EntryEditors[entry.type];
|
||||||
|
if (!EditorComponent) {
|
||||||
|
console.warn(`Unknown entry type: ${entry.type}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EditorComponent
|
||||||
|
key={entry.id}
|
||||||
|
entry={entry}
|
||||||
|
onSave={onSaveEntry}
|
||||||
|
onDelete={onDeleteEntry}
|
||||||
|
onReset={onResetEntry}
|
||||||
|
onUpdate={onUpdateEntry}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="flex justify-end mb-4">
|
<div className="flex justify-end mb-4">
|
||||||
@@ -75,17 +170,7 @@ export const MemoryEditor = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{entries.map(entry => (
|
{entries.map(renderEntry)}
|
||||||
<SingleEntryEditor
|
|
||||||
key={entry.id}
|
|
||||||
entry={entry}
|
|
||||||
isEditing={true}
|
|
||||||
onSave={onSaveEntry}
|
|
||||||
onDelete={onDeleteEntry}
|
|
||||||
onReset={onResetEntry}
|
|
||||||
onUpdate={onUpdateEntry}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showCreateDialog && renderCreateDialog()}
|
{showCreateDialog && renderCreateDialog()}
|
||||||
|
|||||||
53
web/src/components/editors/ParseErrorEntryEditor.jsx
Normal file
53
web/src/components/editors/ParseErrorEntryEditor.jsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { BaseEntryEditor } from './BaseEntryEditor';
|
||||||
|
|
||||||
|
const CreateForm = ({ formData, setFormData }) => (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700">Content</label>
|
||||||
|
<textarea
|
||||||
|
value={formData.content || ''}
|
||||||
|
onChange={e => setFormData({...formData, content: 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">Error</label>
|
||||||
|
<textarea
|
||||||
|
value={formData.error || ''}
|
||||||
|
onChange={e => setFormData({...formData, error: e.target.value})}
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const EditForm = CreateForm;
|
||||||
|
|
||||||
|
const ViewMode = ({ formData }) => (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm font-medium text-gray-700">Content:</span>
|
||||||
|
<pre className="mt-1 text-sm text-gray-900 bg-gray-50 p-2 rounded">{formData.content || ''}</pre>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm font-medium text-red-700">Error:</span>
|
||||||
|
<pre className="mt-1 text-sm text-red-900 bg-red-50 p-2 rounded">{formData.error || ''}</pre>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ParseErrorEntryEditor = (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>
|
||||||
|
);
|
||||||
54
web/src/components/editors/ReadEntryEditor.jsx
Normal file
54
web/src/components/editors/ReadEntryEditor.jsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { BaseEntryEditor } from './BaseEntryEditor';
|
||||||
|
|
||||||
|
const CreateForm = ({ formData, setFormData }) => (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700">Content</label>
|
||||||
|
<textarea
|
||||||
|
value={formData.content || ''}
|
||||||
|
onChange={e => setFormData({...formData, content: e.target.value})}
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.read || false}
|
||||||
|
onChange={e => setFormData({...formData, read: e.target.checked})}
|
||||||
|
className="rounded border-gray-300"
|
||||||
|
/>
|
||||||
|
<span className="ml-2 text-sm text-gray-700">Read</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const EditForm = CreateForm;
|
||||||
|
|
||||||
|
const ViewMode = ({ formData }) => (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm font-medium text-gray-700">Content:</span>
|
||||||
|
<pre className="mt-1 text-sm text-gray-900 bg-gray-50 p-2 rounded">{formData.content || ''}</pre>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-gray-700">
|
||||||
|
{formData.read ? '✓ Read' : '✗ Not Read'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ReadEntryEditor = (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>
|
||||||
|
);
|
||||||
40
web/src/components/editors/ReasoningEntryEditor.jsx
Normal file
40
web/src/components/editors/ReasoningEntryEditor.jsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { BaseEntryEditor } from './BaseEntryEditor';
|
||||||
|
|
||||||
|
const CreateForm = ({ formData, setFormData }) => (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700">Content</label>
|
||||||
|
<textarea
|
||||||
|
value={formData.content || ''}
|
||||||
|
onChange={e => setFormData({...formData, content: e.target.value})}
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||||
|
rows={6}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const EditForm = CreateForm;
|
||||||
|
|
||||||
|
const ViewMode = ({ formData }) => (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm font-medium text-gray-700">Reasoning:</span>
|
||||||
|
<pre className="mt-1 text-sm text-gray-900 bg-gray-50 p-2 rounded whitespace-pre-wrap">{formData.content || ''}</pre>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ReasoningEntryEditor = (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>
|
||||||
|
);
|
||||||
164
web/src/components/editors/RepeatEntryEditor.jsx
Normal file
164
web/src/components/editors/RepeatEntryEditor.jsx
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
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>
|
||||||
|
<div className="flex items-center mb-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.timeout !== null}
|
||||||
|
onChange={e => setFormData({
|
||||||
|
...formData,
|
||||||
|
timeout: e.target.checked ? 1 : null
|
||||||
|
})}
|
||||||
|
className="rounded border-gray-300"
|
||||||
|
/>
|
||||||
|
<label className="ml-2 text-sm font-medium text-gray-700">Custom Timeout (seconds)</label>
|
||||||
|
</div>
|
||||||
|
<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 disabled:bg-gray-100"
|
||||||
|
step="0.1"
|
||||||
|
disabled={formData.timeout === null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center mb-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.limit !== null}
|
||||||
|
onChange={e => setFormData({
|
||||||
|
...formData,
|
||||||
|
limit: e.target.checked ? 1024 : null
|
||||||
|
})}
|
||||||
|
className="rounded border-gray-300"
|
||||||
|
/>
|
||||||
|
<label className="ml-2 text-sm font-medium text-gray-700">Custom Memory Limit (bytes)</label>
|
||||||
|
</div>
|
||||||
|
<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 disabled:bg-gray-100"
|
||||||
|
disabled={formData.limit === null}
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
<div className="flex items-center mb-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.exit_code !== null}
|
||||||
|
onChange={e => setFormData({
|
||||||
|
...formData,
|
||||||
|
exit_code: e.target.checked ? 0 : null
|
||||||
|
})}
|
||||||
|
className="rounded border-gray-300"
|
||||||
|
/>
|
||||||
|
<label className="ml-2 text-sm font-medium text-gray-700">Exit Code</label>
|
||||||
|
</div>
|
||||||
|
<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 disabled:bg-gray-100"
|
||||||
|
disabled={formData.exit_code === null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<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>
|
||||||
|
{formData.exit_code !== null && (
|
||||||
|
<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}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-gray-700">
|
||||||
|
{formData.timed_out ? '⚠ Timed Out' : '✓ Within Timeout'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const RepeatEntryEditor = (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>
|
||||||
|
);
|
||||||
@@ -12,22 +12,46 @@ const CreateForm = ({ formData, setFormData }) => (
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">Timeout (seconds)</label>
|
<div className="flex items-center mb-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.timeout !== null}
|
||||||
|
onChange={e => setFormData({
|
||||||
|
...formData,
|
||||||
|
timeout: e.target.checked ? 1 : null
|
||||||
|
})}
|
||||||
|
className="rounded border-gray-300"
|
||||||
|
/>
|
||||||
|
<label className="ml-2 text-sm font-medium text-gray-700">Custom Timeout (seconds)</label>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.timeout || ''}
|
value={formData.timeout || ''}
|
||||||
onChange={e => setFormData({...formData, timeout: e.target.value ? Number(e.target.value) : null})}
|
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"
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm disabled:bg-gray-100"
|
||||||
step="0.1"
|
step="0.1"
|
||||||
|
disabled={formData.timeout === null}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">Memory Limit (bytes)</label>
|
<div className="flex items-center mb-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.limit !== null}
|
||||||
|
onChange={e => setFormData({
|
||||||
|
...formData,
|
||||||
|
limit: e.target.checked ? 1024 : null
|
||||||
|
})}
|
||||||
|
className="rounded border-gray-300"
|
||||||
|
/>
|
||||||
|
<label className="ml-2 text-sm font-medium text-gray-700">Custom Memory Limit (bytes)</label>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.limit || ''}
|
value={formData.limit || ''}
|
||||||
onChange={e => setFormData({...formData, limit: e.target.value ? Number(e.target.value) : null})}
|
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"
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm disabled:bg-gray-100"
|
||||||
|
disabled={formData.limit === null}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -55,12 +79,24 @@ const EditForm = ({ formData, setFormData }) => (
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">Exit Code</label>
|
<div className="flex items-center mb-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.exit_code !== null}
|
||||||
|
onChange={e => setFormData({
|
||||||
|
...formData,
|
||||||
|
exit_code: e.target.checked ? 0 : null
|
||||||
|
})}
|
||||||
|
className="rounded border-gray-300"
|
||||||
|
/>
|
||||||
|
<label className="ml-2 text-sm font-medium text-gray-700">Exit Code</label>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.exit_code ?? ''}
|
value={formData.exit_code ?? ''}
|
||||||
onChange={e => setFormData({...formData, exit_code: e.target.value ? Number(e.target.value) : null})}
|
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"
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm disabled:bg-gray-100"
|
||||||
|
disabled={formData.exit_code === null}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-4">
|
<div className="flex space-x-4">
|
||||||
@@ -108,10 +144,12 @@ const ViewMode = ({ formData }) => (
|
|||||||
<span className="text-sm font-medium text-gray-700">Standard Error:</span>
|
<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>
|
<pre className="mt-1 text-sm text-gray-900 bg-gray-50 p-2 rounded">{formData.stderr || ''}</pre>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{formData.exit_code !== null && (
|
||||||
<span className="text-sm font-medium text-gray-700">Exit Code:</span>
|
<div>
|
||||||
<span className="ml-2 text-sm text-gray-900">{formData.exit_code ?? 'None'}</span>
|
<span className="text-sm font-medium text-gray-700">Exit Code:</span>
|
||||||
</div>
|
<span className="ml-2 text-sm text-gray-900">{formData.exit_code}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex space-x-4">
|
<div className="flex space-x-4">
|
||||||
<span className="text-sm text-gray-700">
|
<span className="text-sm text-gray-700">
|
||||||
{formData.executed ? '✓ Executed' : '✗ Not Executed'}
|
{formData.executed ? '✓ Executed' : '✗ Not Executed'}
|
||||||
@@ -135,4 +173,4 @@ export const SingleEntryEditor = (props) => (
|
|||||||
return <ViewMode formData={formData} />;
|
return <ViewMode formData={formData} />;
|
||||||
}}
|
}}
|
||||||
</BaseEntryEditor>
|
</BaseEntryEditor>
|
||||||
);
|
);
|
||||||
|
|||||||
54
web/src/components/editors/WriteEntryEditor.jsx
Normal file
54
web/src/components/editors/WriteEntryEditor.jsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { BaseEntryEditor } from './BaseEntryEditor';
|
||||||
|
|
||||||
|
const CreateForm = ({ formData, setFormData }) => (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700">Content</label>
|
||||||
|
<textarea
|
||||||
|
value={formData.content || ''}
|
||||||
|
onChange={e => setFormData({...formData, content: e.target.value})}
|
||||||
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
|
||||||
|
rows={6}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.written || false}
|
||||||
|
onChange={e => setFormData({...formData, written: e.target.checked})}
|
||||||
|
className="rounded border-gray-300"
|
||||||
|
/>
|
||||||
|
<span className="ml-2 text-sm text-gray-700">Written</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const EditForm = CreateForm;
|
||||||
|
|
||||||
|
const ViewMode = ({ formData }) => (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm font-medium text-gray-700">Content:</span>
|
||||||
|
<pre className="mt-1 text-sm text-gray-900 bg-gray-50 p-2 rounded whitespace-pre-wrap">{formData.content || ''}</pre>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-gray-700">
|
||||||
|
{formData.written ? '✓ Written' : '✗ Not Written'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const WriteEntryEditor = (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