Update: Updated the ui
This commit is contained in:
@@ -1,329 +1,418 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import axios from 'axios';
|
||||
import './App.css';
|
||||
import { colors } from './colors';
|
||||
import React, { useState, useEffect, useRef, useCallback } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import axios from "axios";
|
||||
import "./App.css";
|
||||
import { ThemeToggle } from "./components/ThemeToggle";
|
||||
import { ResizableLayout } from "./components/ResizableLayout";
|
||||
import faviconPng from "./logo.png";
|
||||
|
||||
const BACKEND_URL = 'http://localhost:7777' || process.env.REACT_APP_BACKEND_URL;
|
||||
console.log('Using backend URL:', BACKEND_URL);
|
||||
const BACKEND_URL =
|
||||
"http://localhost:7777" || process.env.REACT_APP_BACKEND_URL;
|
||||
console.log("Using backend URL:", BACKEND_URL);
|
||||
|
||||
function App() {
|
||||
const [query, setQuery] = useState('');
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [currentView, setCurrentView] = useState('blocks');
|
||||
const [responseData, setResponseData] = useState(null);
|
||||
const [isOnline, setIsOnline] = useState(false);
|
||||
const [status, setStatus] = useState('Agents ready');
|
||||
const [expandedReasoning, setExpandedReasoning] = useState(new Set());
|
||||
const messagesEndRef = useRef(null);
|
||||
const [query, setQuery] = useState("");
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [currentView, setCurrentView] = useState("blocks");
|
||||
const [responseData, setResponseData] = useState(null);
|
||||
const [isOnline, setIsOnline] = useState(false);
|
||||
const [status, setStatus] = useState("Agents ready");
|
||||
const [expandedReasoning, setExpandedReasoning] = useState(new Set());
|
||||
const messagesEndRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => {
|
||||
checkHealth();
|
||||
fetchLatestAnswer();
|
||||
fetchScreenshot();
|
||||
}, 3000);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [messages]);
|
||||
const fetchLatestAnswer = useCallback(async () => {
|
||||
try {
|
||||
const res = await axios.get(`${BACKEND_URL}/latest_answer`);
|
||||
const data = res.data;
|
||||
|
||||
const checkHealth = async () => {
|
||||
try {
|
||||
await axios.get(`${BACKEND_URL}/health`);
|
||||
setIsOnline(true);
|
||||
console.log('System is online');
|
||||
} catch {
|
||||
setIsOnline(false);
|
||||
console.log('System is offline');
|
||||
}
|
||||
};
|
||||
|
||||
const fetchScreenshot = async () => {
|
||||
try {
|
||||
const timestamp = new Date().getTime();
|
||||
const res = await axios.get(`${BACKEND_URL}/screenshots/updated_screen.png?timestamp=${timestamp}`, {
|
||||
responseType: 'blob'
|
||||
});
|
||||
console.log('Screenshot fetched successfully');
|
||||
const imageUrl = URL.createObjectURL(res.data);
|
||||
setResponseData((prev) => {
|
||||
if (prev?.screenshot && prev.screenshot !== 'placeholder.png') {
|
||||
URL.revokeObjectURL(prev.screenshot);
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
screenshot: imageUrl,
|
||||
screenshotTimestamp: new Date().getTime()
|
||||
};
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error fetching screenshot:', err);
|
||||
setResponseData((prev) => ({
|
||||
...prev,
|
||||
screenshot: 'placeholder.png',
|
||||
screenshotTimestamp: new Date().getTime()
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeAnswer = (answer) => {
|
||||
return answer
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/[.,!?]/g, '')
|
||||
};
|
||||
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
const toggleReasoning = (messageIndex) => {
|
||||
setExpandedReasoning(prev => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(messageIndex)) {
|
||||
newSet.delete(messageIndex);
|
||||
} else {
|
||||
newSet.add(messageIndex);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
const fetchLatestAnswer = async () => {
|
||||
try {
|
||||
const res = await axios.get(`${BACKEND_URL}/latest_answer`);
|
||||
const data = res.data;
|
||||
|
||||
updateData(data);
|
||||
if (!data.answer || data.answer.trim() === '') {
|
||||
return;
|
||||
}
|
||||
const normalizedNewAnswer = normalizeAnswer(data.answer);
|
||||
const answerExists = messages.some(
|
||||
(msg) => normalizeAnswer(msg.content) === normalizedNewAnswer
|
||||
);
|
||||
if (!answerExists) {
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{
|
||||
type: 'agent',
|
||||
content: data.answer,
|
||||
reasoning: data.reasoning,
|
||||
agentName: data.agent_name,
|
||||
status: data.status,
|
||||
uid: data.uid,
|
||||
},
|
||||
]);
|
||||
setStatus(data.status);
|
||||
scrollToBottom();
|
||||
} else {
|
||||
console.log('Duplicate answer detected, skipping:', data.answer);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching latest answer:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const updateData = (data) => {
|
||||
setResponseData((prev) => ({
|
||||
...prev,
|
||||
blocks: data.blocks || prev.blocks || null,
|
||||
done: data.done,
|
||||
answer: data.answer,
|
||||
agent_name: data.agent_name,
|
||||
updateData(data);
|
||||
if (!data.answer || data.answer.trim() === "") {
|
||||
return;
|
||||
}
|
||||
const normalizedNewAnswer = normalizeAnswer(data.answer);
|
||||
const answerExists = messages.some(
|
||||
(msg) => normalizeAnswer(msg.content) === normalizedNewAnswer
|
||||
);
|
||||
if (!answerExists) {
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{
|
||||
type: "agent",
|
||||
content: data.answer,
|
||||
reasoning: data.reasoning,
|
||||
agentName: data.agent_name,
|
||||
status: data.status,
|
||||
uid: data.uid,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleStop = async (e) => {
|
||||
e.preventDefault();
|
||||
checkHealth();
|
||||
setIsLoading(false);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await axios.get(`${BACKEND_URL}/stop`);
|
||||
setStatus("Requesting stop...");
|
||||
} catch (err) {
|
||||
console.error('Error stopping the agent:', err);
|
||||
}
|
||||
},
|
||||
]);
|
||||
setStatus(data.status);
|
||||
scrollToBottom();
|
||||
} else {
|
||||
console.log("Duplicate answer detected, skipping:", data.answer);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching latest answer:", error);
|
||||
}
|
||||
}, [messages]);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
checkHealth();
|
||||
if (!query.trim()) {
|
||||
console.log('Empty query');
|
||||
return;
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => {
|
||||
checkHealth();
|
||||
fetchLatestAnswer();
|
||||
fetchScreenshot();
|
||||
}, 3000);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [fetchLatestAnswer]);
|
||||
|
||||
const checkHealth = async () => {
|
||||
try {
|
||||
await axios.get(`${BACKEND_URL}/health`);
|
||||
setIsOnline(true);
|
||||
console.log("System is online");
|
||||
} catch {
|
||||
setIsOnline(false);
|
||||
console.log("System is offline");
|
||||
}
|
||||
};
|
||||
|
||||
const fetchScreenshot = async () => {
|
||||
try {
|
||||
const timestamp = new Date().getTime();
|
||||
const res = await axios.get(
|
||||
`${BACKEND_URL}/screenshots/updated_screen.png?timestamp=${timestamp}`,
|
||||
{
|
||||
responseType: "blob",
|
||||
}
|
||||
setMessages((prev) => [...prev, { type: 'user', content: query }]);
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
console.log('Sending query:', query);
|
||||
setQuery('waiting for response...');
|
||||
const res = await axios.post(`${BACKEND_URL}/query`, {
|
||||
query,
|
||||
tts_enabled: false
|
||||
});
|
||||
setQuery('Enter your query...');
|
||||
console.log('Response:', res.data);
|
||||
const data = res.data;
|
||||
updateData(data);
|
||||
} catch (err) {
|
||||
console.error('Error:', err);
|
||||
setError('Failed to process query.');
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{ type: 'error', content: 'Error: Unable to get a response.' },
|
||||
]);
|
||||
} finally {
|
||||
console.log('Query completed');
|
||||
setIsLoading(false);
|
||||
setQuery('');
|
||||
);
|
||||
console.log("Screenshot fetched successfully");
|
||||
const imageUrl = URL.createObjectURL(res.data);
|
||||
setResponseData((prev) => {
|
||||
if (prev?.screenshot && prev.screenshot !== "placeholder.png") {
|
||||
URL.revokeObjectURL(prev.screenshot);
|
||||
}
|
||||
};
|
||||
return {
|
||||
...prev,
|
||||
screenshot: imageUrl,
|
||||
screenshotTimestamp: new Date().getTime(),
|
||||
};
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Error fetching screenshot:", err);
|
||||
setResponseData((prev) => ({
|
||||
...prev,
|
||||
screenshot: "placeholder.png",
|
||||
screenshotTimestamp: new Date().getTime(),
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleGetScreenshot = async () => {
|
||||
try {
|
||||
setCurrentView('screenshot');
|
||||
} catch (err) {
|
||||
setError('Browser not in use');
|
||||
}
|
||||
};
|
||||
const normalizeAnswer = (answer) => {
|
||||
return answer
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, " ")
|
||||
.replace(/[.,!?]/g, "");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<header className="header">
|
||||
<h1>AgenticSeek</h1>
|
||||
</header>
|
||||
<main className="main">
|
||||
<div className="app-sections">
|
||||
<div className="chat-section">
|
||||
<h2>Chat Interface</h2>
|
||||
<div className="messages">
|
||||
{messages.length === 0 ? (
|
||||
<p className="placeholder">No messages yet. Type below to start!</p>
|
||||
) : (
|
||||
messages.map((msg, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`message ${
|
||||
msg.type === 'user'
|
||||
? 'user-message'
|
||||
: msg.type === 'agent'
|
||||
? 'agent-message'
|
||||
: 'error-message'
|
||||
}`}
|
||||
>
|
||||
<div className="message-header">
|
||||
{msg.type === 'agent' && (
|
||||
<span className="agent-name">{msg.agentName}</span>
|
||||
)}
|
||||
{msg.type === 'agent' && msg.reasoning && expandedReasoning.has(index) && (
|
||||
<div className="reasoning-content">
|
||||
<ReactMarkdown>{msg.reasoning}</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
{msg.type === 'agent' && (
|
||||
<button
|
||||
className="reasoning-toggle"
|
||||
onClick={() => toggleReasoning(index)}
|
||||
title={expandedReasoning.has(index) ? "Hide reasoning" : "Show reasoning"}
|
||||
>
|
||||
{expandedReasoning.has(index) ? '▼' : '▶'} Reasoning
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="message-content">
|
||||
<ReactMarkdown>{msg.content}</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
{isOnline && <div className="loading-animation">{status}</div>}
|
||||
{!isLoading && !isOnline && <p className="loading-animation">System offline. Deploy backend first.</p>}
|
||||
<form onSubmit={handleSubmit} className="input-form">
|
||||
<input
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Type your query..."
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<button type="submit" disabled={isLoading}>
|
||||
Send
|
||||
</button>
|
||||
<button onClick={handleStop}>
|
||||
Stop
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
};
|
||||
|
||||
<div className="computer-section">
|
||||
<h2>Computer View</h2>
|
||||
<div className="view-selector">
|
||||
<button
|
||||
className={currentView === 'blocks' ? 'active' : ''}
|
||||
onClick={() => setCurrentView('blocks')}
|
||||
>
|
||||
Editor View
|
||||
</button>
|
||||
<button
|
||||
className={currentView === 'screenshot' ? 'active' : ''}
|
||||
onClick={responseData?.screenshot ? () => setCurrentView('screenshot') : handleGetScreenshot}
|
||||
>
|
||||
Browser View
|
||||
</button>
|
||||
</div>
|
||||
<div className="content">
|
||||
{error && <p className="error">{error}</p>}
|
||||
{currentView === 'blocks' ? (
|
||||
<div className="blocks">
|
||||
{responseData && responseData.blocks && Object.values(responseData.blocks).length > 0 ? (
|
||||
Object.values(responseData.blocks).map((block, index) => (
|
||||
<div key={index} className="block">
|
||||
<p className="block-tool">Tool: {block.tool_type}</p>
|
||||
<pre>{block.block}</pre>
|
||||
<p className="block-feedback">Feedback: {block.feedback}</p>
|
||||
{block.success ? (
|
||||
<p className="block-success">Success</p>
|
||||
) : (
|
||||
<p className="block-failure">Failure</p>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="block">
|
||||
<p className="block-tool">Tool: No tool in use</p>
|
||||
<pre>No file opened</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="screenshot">
|
||||
<img
|
||||
src={responseData?.screenshot || 'placeholder.png'}
|
||||
alt="Screenshot"
|
||||
onError={(e) => {
|
||||
e.target.src = 'placeholder.png';
|
||||
console.error('Failed to load screenshot');
|
||||
}}
|
||||
key={responseData?.screenshotTimestamp || 'default'}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
const toggleReasoning = (messageIndex) => {
|
||||
setExpandedReasoning((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(messageIndex)) {
|
||||
newSet.delete(messageIndex);
|
||||
} else {
|
||||
newSet.add(messageIndex);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
const updateData = (data) => {
|
||||
setResponseData((prev) => ({
|
||||
...prev,
|
||||
blocks: data.blocks || prev.blocks || null,
|
||||
done: data.done,
|
||||
answer: data.answer,
|
||||
agent_name: data.agent_name,
|
||||
status: data.status,
|
||||
uid: data.uid,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleStop = async (e) => {
|
||||
e.preventDefault();
|
||||
checkHealth();
|
||||
setIsLoading(false);
|
||||
setError(null);
|
||||
try {
|
||||
await axios.get(`${BACKEND_URL}/stop`);
|
||||
setStatus("Requesting stop...");
|
||||
} catch (err) {
|
||||
console.error("Error stopping the agent:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
checkHealth();
|
||||
if (!query.trim()) {
|
||||
console.log("Empty query");
|
||||
return;
|
||||
}
|
||||
setMessages((prev) => [...prev, { type: "user", content: query }]);
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
console.log("Sending query:", query);
|
||||
setQuery("waiting for response...");
|
||||
const res = await axios.post(`${BACKEND_URL}/query`, {
|
||||
query,
|
||||
tts_enabled: false,
|
||||
});
|
||||
setQuery("Enter your query...");
|
||||
console.log("Response:", res.data);
|
||||
const data = res.data;
|
||||
updateData(data);
|
||||
} catch (err) {
|
||||
console.error("Error:", err);
|
||||
setError("Failed to process query.");
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{ type: "error", content: "Error: Unable to get a response." },
|
||||
]);
|
||||
} finally {
|
||||
console.log("Query completed");
|
||||
setIsLoading(false);
|
||||
setQuery("");
|
||||
}
|
||||
};
|
||||
|
||||
const handleGetScreenshot = async () => {
|
||||
try {
|
||||
setCurrentView("screenshot");
|
||||
} catch (err) {
|
||||
setError("Browser not in use");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<header className="header">
|
||||
<div className="header-brand">
|
||||
<div className="logo-container">
|
||||
<img src={faviconPng} alt="AgenticSeek" className="logo-icon" />
|
||||
</div>
|
||||
<div className="brand-text">
|
||||
<h1>AgenticSeek</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className="header-status">
|
||||
<div
|
||||
className={`status-indicator ${isOnline ? "online" : "offline"}`}
|
||||
>
|
||||
<div className="status-dot"></div>
|
||||
<span className="status-text">
|
||||
{isOnline ? "Online" : "Offline"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="header-actions">
|
||||
<a
|
||||
href="https://github.com/Fosowl/agenticSeek"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="action-button github-link"
|
||||
aria-label="View on GitHub"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||
</svg>
|
||||
<span className="action-text">GitHub</span>
|
||||
</a>
|
||||
<div>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main className="main">
|
||||
<ResizableLayout initialLeftWidth={50}>
|
||||
<div className="chat-section">
|
||||
<h2>Chat Interface</h2>
|
||||
<div className="messages">
|
||||
{messages.length === 0 ? (
|
||||
<p className="placeholder">
|
||||
No messages yet. Type below to start!
|
||||
</p>
|
||||
) : (
|
||||
messages.map((msg, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`message ${
|
||||
msg.type === "user"
|
||||
? "user-message"
|
||||
: msg.type === "agent"
|
||||
? "agent-message"
|
||||
: "error-message"
|
||||
}`}
|
||||
>
|
||||
<div className="message-header">
|
||||
{msg.type === "agent" && (
|
||||
<span className="agent-name">{msg.agentName}</span>
|
||||
)}
|
||||
{msg.type === "agent" &&
|
||||
msg.reasoning &&
|
||||
expandedReasoning.has(index) && (
|
||||
<div className="reasoning-content">
|
||||
<ReactMarkdown>{msg.reasoning}</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
{msg.type === "agent" && (
|
||||
<button
|
||||
className="reasoning-toggle"
|
||||
onClick={() => toggleReasoning(index)}
|
||||
title={
|
||||
expandedReasoning.has(index)
|
||||
? "Hide reasoning"
|
||||
: "Show reasoning"
|
||||
}
|
||||
>
|
||||
{expandedReasoning.has(index) ? "▼" : "▶"} Reasoning
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="message-content">
|
||||
<ReactMarkdown>{msg.content}</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
{isOnline && <div className="loading-animation">{status}</div>}
|
||||
{!isLoading && !isOnline && (
|
||||
<p className="loading-animation">
|
||||
System offline. Deploy backend first.
|
||||
</p>
|
||||
)}
|
||||
<form onSubmit={handleSubmit} className="input-form">
|
||||
<input
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Type your query..."
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<div className="action-buttons">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="icon-button"
|
||||
aria-label="Send message"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
|
||||
<path
|
||||
d="M22 2L11 13M22 2L15 22L11 13M22 2L2 9L11 13"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleStop}
|
||||
className="icon-button stop-button"
|
||||
aria-label="Stop processing"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<rect
|
||||
x="6"
|
||||
y="6"
|
||||
width="12"
|
||||
height="12"
|
||||
fill="currentColor"
|
||||
rx="2"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="computer-section">
|
||||
<h2>Computer View</h2>
|
||||
<div className="view-selector">
|
||||
<button
|
||||
className={currentView === "blocks" ? "active" : ""}
|
||||
onClick={() => setCurrentView("blocks")}
|
||||
>
|
||||
Editor View
|
||||
</button>
|
||||
<button
|
||||
className={currentView === "screenshot" ? "active" : ""}
|
||||
onClick={
|
||||
responseData?.screenshot
|
||||
? () => setCurrentView("screenshot")
|
||||
: handleGetScreenshot
|
||||
}
|
||||
>
|
||||
Browser View
|
||||
</button>
|
||||
</div>
|
||||
<div className="content">
|
||||
{error && <p className="error">{error}</p>}
|
||||
{currentView === "blocks" ? (
|
||||
<div className="blocks">
|
||||
{responseData &&
|
||||
responseData.blocks &&
|
||||
Object.values(responseData.blocks).length > 0 ? (
|
||||
Object.values(responseData.blocks).map((block, index) => (
|
||||
<div key={index} className="block">
|
||||
<p className="block-tool">Tool: {block.tool_type}</p>
|
||||
<pre>{block.block}</pre>
|
||||
<p className="block-feedback">
|
||||
Feedback: {block.feedback}
|
||||
</p>
|
||||
{block.success ? (
|
||||
<p className="block-success">Success</p>
|
||||
) : (
|
||||
<p className="block-failure">Failure</p>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="block">
|
||||
<p className="block-tool">Tool: No tool in use</p>
|
||||
<pre>No file opened</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="screenshot">
|
||||
<img
|
||||
src={responseData?.screenshot || "placeholder.png"}
|
||||
alt="Screenshot"
|
||||
onError={(e) => {
|
||||
e.target.src = "placeholder.png";
|
||||
console.error("Failed to load screenshot");
|
||||
}}
|
||||
key={responseData?.screenshotTimestamp || "default"}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ResizableLayout>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App;
|
||||
|
||||
Reference in New Issue
Block a user