Wip web interface
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -8,9 +8,8 @@ import { StandardEditor } from '@/components/editors/StandardEditor';
|
||||
import { useWebSocket, WebSocketState } from '@/hooks/useWebSocket';
|
||||
|
||||
export const LlmState = {
|
||||
NO_OUTPUT: 'NO_OUTPUT',
|
||||
INFERENCE: 'INFERENCE',
|
||||
OUTPUT: 'OUTPUT'
|
||||
IDLE: 'IDLE',
|
||||
INFERENCE: 'INFERENCE'
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
@@ -18,8 +17,8 @@ const App = () => {
|
||||
const [generatedContext, setGeneratedContext] = useState('');
|
||||
const [modifiedContext, setModifiedContext] = useState('');
|
||||
const [contextDirty, setContextDirty] = useState(false);
|
||||
const [generatedResponses, setGeneratedResponses] = useState({});
|
||||
const [modifiedResponses, setModifiedResponses] = useState({});
|
||||
const [generatedResponse, setGeneratedResponse] = useState('');
|
||||
const [modifiedResponse, setModifiedResponse] = useState('');
|
||||
const [input, setInput] = useState('');
|
||||
const [output, setOutput] = useState('');
|
||||
|
||||
@@ -44,9 +43,9 @@ const App = () => {
|
||||
|
||||
// WebSocket connections
|
||||
const wsRoot = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`
|
||||
const llmWs = useWebSocket(`${wsRoot}/llm`);
|
||||
const llmWs = useWebSocket(`${wsRoot}/llms`);
|
||||
const contextWs = useWebSocket(`${wsRoot}/context`);
|
||||
const tokenWs = useWebSocket(`${wsRoot}/token`);
|
||||
const responseWs = useWebSocket(`${wsRoot}/response`);
|
||||
const stdoutWs = useWebSocket(`${wsRoot}/stdout`);
|
||||
const autoApproverWs = useWebSocket(`${wsRoot}/auto_approver`);
|
||||
const memoryWs = useWebSocket(`${wsRoot}/memory`);
|
||||
@@ -60,18 +59,6 @@ const App = () => {
|
||||
...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;
|
||||
});
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -86,17 +73,11 @@ const App = () => {
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Handle incoming tokens
|
||||
// Handle response changes
|
||||
useEffect(() => {
|
||||
tokenWs.addMessageHandler((data) => {
|
||||
setGeneratedResponses(prev => ({
|
||||
...prev,
|
||||
[data.llm]: (prev[data.llm] || '') + data.token
|
||||
}));
|
||||
setModifiedResponses(prev => ({
|
||||
...prev,
|
||||
[data.llm]: (prev[data.llm] || '') + data.token
|
||||
}));
|
||||
responseWs.addMessageHandler((data) => {
|
||||
setModifiedResponse(data.response);
|
||||
setGeneratedResponse(data.response);
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -138,28 +119,33 @@ const App = () => {
|
||||
};
|
||||
|
||||
const handleApprove = () => {
|
||||
const state = llms[activeLlm];
|
||||
if (state === LlmState.NO_OUTPUT) {
|
||||
if (contextDirty) {
|
||||
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',
|
||||
});
|
||||
} else if (state === LlmState.OUTPUT) {
|
||||
fetch(`/api/approve/${activeLlm}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ response: modifiedResponses[activeLlm] })
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleStop = () => {
|
||||
// We still specify which LLM to stop inference for
|
||||
fetch(`/api/inference/${activeLlm}/stop`, {
|
||||
method: 'POST'
|
||||
});
|
||||
@@ -182,14 +168,22 @@ const App = () => {
|
||||
|
||||
const handleContextEdit = (context) => {
|
||||
setContextDirty(true);
|
||||
setGeneratedResponses({});
|
||||
setModifiedResponses({});
|
||||
// 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.NO_OUTPUT;
|
||||
resetLlms[llm] = LlmState.IDLE;
|
||||
}
|
||||
setLlms(resetLlms);
|
||||
setModifiedContext(context);
|
||||
|
||||
// Reset response buffers
|
||||
setGeneratedResponse('');
|
||||
setModifiedResponse('');
|
||||
};
|
||||
|
||||
const handleResponseEdit = (response) => {
|
||||
setModifiedResponse(response);
|
||||
};
|
||||
|
||||
const handleAutoApproverConfigChange = async (config) => {
|
||||
@@ -246,37 +240,21 @@ const App = () => {
|
||||
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]]);
|
||||
// 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 &&
|
||||
tokenWs.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 ||
|
||||
tokenWs.wsState === WebSocketState.CONNECTING ||
|
||||
responseWs.wsState === WebSocketState.CONNECTING ||
|
||||
stdoutWs.wsState === WebSocketState.CONNECTING ||
|
||||
autoApproverWs.wsState === WebSocketState.CONNECTING ||
|
||||
memoryWs.wsState === WebSocketState.CONNECTING) {
|
||||
@@ -284,13 +262,13 @@ const App = () => {
|
||||
} else {
|
||||
setWsState(WebSocketState.DISCONNECTED);
|
||||
}
|
||||
}, [llmWs.wsState, contextWs.wsState, tokenWs.wsState, stdoutWs.wsState, autoApproverWs.wsState, memoryWs.wsState]);
|
||||
}, [llmWs.wsState, contextWs.wsState, responseWs.wsState, stdoutWs.wsState, autoApproverWs.wsState, memoryWs.wsState]);
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-gray-100 flex flex-col overflow-hidden">
|
||||
<Header
|
||||
wsState={wsState}
|
||||
agentState={llms[activeLlm]}
|
||||
agentState={activeLlmState}
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
/>
|
||||
@@ -309,12 +287,9 @@ const App = () => {
|
||||
{activeTab === Tabs.RESPONSE && (
|
||||
<ContentEditor
|
||||
showDiff={showDiff}
|
||||
originalContent={generatedResponses[activeLlm] || ''}
|
||||
modifiedContent={modifiedResponses[activeLlm] || ''}
|
||||
onChange={value => setModifiedResponses(prev => ({
|
||||
...prev,
|
||||
[activeLlm]: value
|
||||
}))}
|
||||
originalContent={generatedResponse}
|
||||
modifiedContent={modifiedResponse}
|
||||
onChange={handleResponseEdit}
|
||||
/>
|
||||
)}
|
||||
{activeTab === Tabs.INPUT && (
|
||||
@@ -347,14 +322,15 @@ const App = () => {
|
||||
showSidebar={showSidebar}
|
||||
llms={llms}
|
||||
activeLlm={activeLlm}
|
||||
llmState={llms[activeLlm]}
|
||||
llmState={activeLlmState}
|
||||
showDiff={showDiff}
|
||||
autoScroll={autoScroll}
|
||||
onAutoScrollChange={(enabled) => setAutoScroll(enabled)}
|
||||
onAutoScrollChange={setAutoScroll}
|
||||
input={input}
|
||||
output={output}
|
||||
autoApproverConfig={autoApproverConfig}
|
||||
onApprove={handleApprove}
|
||||
onInference={handleInference}
|
||||
onStop={handleStop}
|
||||
onToggleDiff={() => setShowDiff(!showDiff)}
|
||||
onSendInput={handleSendInput}
|
||||
|
||||
@@ -7,7 +7,6 @@ export const Tabs = {
|
||||
INPUT: 'INPUT',
|
||||
OUTPUT: 'OUTPUT',
|
||||
MEMORY: 'MEMORY'
|
||||
|
||||
};
|
||||
|
||||
export const Header = ({
|
||||
@@ -31,9 +30,11 @@ export const Header = ({
|
||||
}`}>
|
||||
{wsState}
|
||||
</div>
|
||||
{agentState && (
|
||||
<div className="px-3 py-1 rounded-full bg-blue-100 text-blue-800 text-sm">
|
||||
{agentState}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t">
|
||||
@@ -48,7 +49,7 @@ export const Header = ({
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
{tab.charAt(0).toUpperCase() + tab.slice(1)}
|
||||
{tab.charAt(0).toUpperCase() + tab.slice(1).toLowerCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@ export const Sidebar = ({
|
||||
output,
|
||||
autoApproverConfig,
|
||||
onApprove,
|
||||
onInference,
|
||||
onStop,
|
||||
onToggleDiff,
|
||||
onSendInput,
|
||||
@@ -61,15 +62,27 @@ export const Sidebar = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{llmState === LlmState.INFERENCE ? (
|
||||
<Button
|
||||
onClick={llmState === LlmState.INFERENCE ? onStop : onApprove}
|
||||
onClick={onStop}
|
||||
className="w-full"
|
||||
>
|
||||
{
|
||||
llmState === LlmState.NO_OUTPUT ? 'Inference' :
|
||||
llmState === LlmState.INFERENCE ? 'Stop' :
|
||||
'Approve'
|
||||
}
|
||||
Stop
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={onInference}
|
||||
className="w-full"
|
||||
>
|
||||
Start Inference
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={onApprove}
|
||||
className="w-full"
|
||||
>
|
||||
Approve Response
|
||||
</Button>
|
||||
|
||||
<Button onClick={onSendInput} disabled={!input.trim()} className="w-full">
|
||||
@@ -136,7 +149,7 @@ export const Sidebar = ({
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm">Auto-Approve LLM</label>
|
||||
<label className="text-sm">LLM for Inference</label>
|
||||
<Select
|
||||
value={autoApproverConfig.llm_name}
|
||||
onValueChange={(llm) => onAutoApproverConfigChange({
|
||||
@@ -153,6 +166,9 @@ export const Sidebar = ({
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
This selects which model will generate content during auto-approval.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user