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 = process.env.REACT_APP_BACKEND_URL || 'http://localhost:7777';
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 fetchLatestAnswer = useCallback(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);
}
}, [messages]);
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",
}
);
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 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 (
{isOnline ? "Online" : "Offline"}
Chat Interface
{messages.length === 0 ? (
No messages yet. Type below to start!
) : (
messages.map((msg, index) => (
{msg.type === "agent" && (
{msg.agentName}
)}
{msg.type === "agent" &&
msg.reasoning &&
expandedReasoning.has(index) && (
{msg.reasoning}
)}
{msg.type === "agent" && (
)}
{msg.content}
))
)}
{isOnline &&
{status}
}
{!isLoading && !isOnline && (
System offline. Deploy backend first.
)}
Computer View
{error &&
{error}
}
{currentView === "blocks" ? (
{responseData &&
responseData.blocks &&
Object.values(responseData.blocks).length > 0 ? (
Object.values(responseData.blocks).map((block, index) => (
Tool: {block.tool_type}
{block.block}
Feedback: {block.feedback}
{block.success ? (
Success
) : (
Failure
)}
))
) : (
Tool: No tool in use
No file opened
)}
) : (

{
e.target.src = "placeholder.png";
console.error("Failed to load screenshot");
}}
key={responseData?.screenshotTimestamp || "default"}
/>
)}
);
}
export default App;