feat : small frontend improvement & planner auto fix

This commit is contained in:
martin legrand 2025-04-15 16:31:53 +02:00
parent 14f42b638d
commit a32cf60958
15 changed files with 2968 additions and 53 deletions

6
app.py Normal file → Executable file
View File

@ -22,12 +22,15 @@ config.read('config.ini')
app = FastAPI(title="AgenticSeek API", version="0.1.0")
logger = Logger("backend.log")
if not os.path.exists(".screenshots"):
os.makedirs(".screenshots")
app.mount("/screenshots", StaticFiles(directory=".screenshots"), name="screenshots")
# Add CORS middleware to allow frontend requests
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
@ -139,7 +142,6 @@ async def process_query(request: QueryRequest):
)
try:
interaction.tts_enabled = request.tts_enabled
interaction.stt_enabled = request.stt_enabled
interaction.last_query = request.query
logger.info("Agents request is being processed")
is_generating = True

View File

@ -1,4 +1,4 @@
version: '3.8'
version: '3'
services:
redis:
@ -30,10 +30,12 @@ services:
- "8080:8080"
volumes:
- ./searxng:/etc/searxng:rw
- ./searxng/entrypoint.sh:/usr/local/bin/searxng-entrypoint.sh:ro
entrypoint: /usr/local/bin/searxng-entrypoint.sh
environment:
- SEARXNG_BASE_URL=http://localhost:8080/
- UWSGI_WORKERS=1
- UWSGI_THREADS=1
- UWSGI_WORKERS=4
- UWSGI_THREADS=4
cap_add:
- CHOWN
- SETGID

View File

@ -45,7 +45,7 @@
gap: 40px;
height: calc(100vh - 200px);
}
.left-panel,
.right-panel {
background-color: #1a1a1a;
@ -54,6 +54,7 @@
box-shadow: 0 0 10px rgba(0, 255, 204, 0.2);
display: flex;
flex-direction: column;
overflow: hidden;
}
.left-panel {
@ -66,6 +67,7 @@
padding: 10px;
display: flex;
flex-direction: column;
max-height: 100%;
gap: 15px;
}
@ -81,6 +83,11 @@
border-radius: 8px;
font-size: 0.9rem;
}
.messages::-webkit-scrollbar,
.content::-webkit-scrollbar {
width: 8px;
}
.user-message {
background-color: #00ffcc;
@ -189,7 +196,7 @@
.content {
flex: 1;
overflow-y: auto;
padding: 10px;
padding: 5px;
}
.blocks {
@ -200,7 +207,7 @@
.block {
background-color: #222;
padding: 15px;
padding: 10px;
border: 1px solid #00ffcc;
border-radius: 4px;
}
@ -214,7 +221,7 @@
.block pre {
background-color: #111;
padding: 10px;
padding: 5px;
border-radius: 4px;
font-size: 0.85rem;
white-space: pre-wrap;
@ -222,7 +229,7 @@
}
.screenshot {
margin-top: 20px;
margin-top: 10px;
}
.screenshot img {

View File

@ -9,45 +9,111 @@ function App() {
const [error, setError] = useState(null);
const [currentView, setCurrentView] = useState('blocks');
const [responseData, setResponseData] = useState(null);
const [isOnline, setIsOnline] = useState(false); // Added state
const messagesEndRef = useRef(null);
useEffect(() => {
scrollToBottom();
}, [messages]);
// Added: checks /health
const checkHealth = async () => {
try {
await axios.get('http://0.0.0.0:8000/health');
setIsOnline(true);
console.log('System is online');
} catch {
setIsOnline(false);
console.log('System is offline');
}
};
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
if (currentView === 'screenshot') {
let isMounted = true;
const fetchScreenshot = async () => {
try {
const res = await axios.get('http://0.0.0.0:8000/screenshots/updated_screen.png', {
responseType: 'blob',
params: { t: new Date().getTime() }
});
if (isMounted) {
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);
if (isMounted) {
setResponseData((prev) => ({
...prev,
screenshot: 'placeholder.png',
screenshotTimestamp: new Date().getTime()
}));
}
}
};
fetchScreenshot();
const interval = setInterval(fetchScreenshot, 1000);
return () => {
isMounted = false;
clearInterval(interval);
if (responseData?.screenshot && responseData.screenshot !== 'placeholder.png') {
URL.revokeObjectURL(responseData.screenshot);
}
};
}
}, [currentView]);
const handleSubmit = async (e) => {
e.preventDefault();
if (!query.trim()) return;
checkHealth();
if (!query.trim()) {
console.log('Empty query');
return;
}
setMessages((prev) => [...prev, { type: 'user', content: query }]);
setIsLoading(true);
setError(null);
try {
//const res = await axios.post('http://backend:8000/query', { ... });
const res = await axios.post('${process.env.BACKEND_URL}/query', {
console.log('Sending query:', query);
const res = await axios.post('http://0.0.0.0:8000/query', {
query,
lang: 'en',
tts_enabled: false,
stt_enabled: false,
tts_enabled: false
});
console.log('Response:', res.data);
const data = res.data;
setResponseData(data);
setMessages((prev) => [
...prev,
{ type: 'agent', content: data.answer, agentName: data.agent_name },
]);
setCurrentView('blocks');
} 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('');
}
@ -55,11 +121,12 @@ function App() {
const handleGetScreenshot = async () => {
try {
//const res = await axios.get('http://backend:8000/screenshots/updated_screen.png');
const res = await axios.get('${process.env.BACKEND_URL}/screenshots/updated_screen.png');
console.log('Fetching screenshot...');
const res = await axios.get('http://0.0.0.0:8000/screenshots/updated_screen.png');
setResponseData((prev) => ({ ...prev, screenshot: res.data.screenshot }));
setCurrentView('screenshot');
} catch (err) {
console.error('Error fetching screenshot:', err);
setError('Browser not in use');
}
};
@ -73,7 +140,7 @@ function App() {
<div className="chat-container">
<div className="left-panel">
<h2>C H A T</h2>
<br></br>
<br />
<div className="messages">
{messages.length === 0 ? (
<p className="placeholder">No messages yet. Type below to start!</p>
@ -98,7 +165,8 @@ function App() {
)}
<div ref={messagesEndRef} />
</div>
{isLoading && <div className="loading-animation">Loading...</div>}
{isOnline && isLoading && <div className="loading-animation">Thinking...</div>}
{!isLoading && !isOnline && <p className="loading-animation">System offline. Deploy backend first.</p>}
<form onSubmit={handleSubmit} className="input-form">
<input
type="text"
@ -114,7 +182,7 @@ function App() {
</div>
<div className="right-panel">
<h2>I N T E R F A C E</h2>
<br></br>
<br />
<div className="view-selector">
<button
className={currentView === 'blocks' ? 'active' : ''}
@ -153,7 +221,15 @@ function App() {
</div>
) : (
<div className="screenshot">
<img src="${process.env.BACKEND_URL}/screenshots/updated_screen.png" alt="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'} // Force re-render
/>
</div>
)}
</div>

View File

@ -45,6 +45,7 @@ rules:
- Do not ever use editor such as vim or nano.
- Make sure to always cd your work folder before executing commands, like cd <work dir> && <your command>
- only use file name with file_finder, not path
- If query is unrelated to file operations, do nothing, and say that there was mistake in agent allocation.
Example Interaction
User: "I need to find the file config.txt and read its contents."

View File

@ -55,6 +55,7 @@ rules:
- Make sure to always cd your work folder before executing commands, like cd <work dir> && <your command>
- Do not ever use editor such as vim or nano.
- only use file name with file_finder, not path
- If query is unrelated to file operations, do nothing, and say that there was mistake in agent allocation.
Example Interaction
User: "I need to find the file config.txt and read its contents."

9
searxng/entrypoint.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
# entrypoint for docker searxng service
SECRET_KEY=$(openssl rand -hex 32)
sed -i "s/ultrasecretkey/$SECRET_KEY/g" /etc/searxng/settings.yml
exec /usr/local/searxng/dockerfiles/docker-entrypoint.sh "$@"

View File

@ -5,11 +5,11 @@ gid = searxng
# Number of workers (usually CPU count)
# default value: %k (= number of CPU core, see Dockerfile)
workers = 1
workers = 4
# Number of threads per worker
# default value: 4 (see Dockerfile)
threads = 1
threads = 4
# The right granted on the created socket
chmod-socket = 666
@ -19,7 +19,7 @@ single-interpreter = true
master = true
plugin = python3
lazy-apps = true
enable-threads = 1
enable-threads = 4
# Module to import
module = searx.webapp
@ -49,4 +49,4 @@ die-on-term
# uwsgi serves the static files
static-map = /static=/usr/local/searxng/searx/static
static-gzip-all = True
offload-threads = 1
offload-threads = 4

2731
searxng/settings.yml.new Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,72 @@
#!/bin/bash
# Script to automate SearXNG setup and deployment with Docker Compose
command_exists() {
command -v "$1" &> /dev/null
}
# Check if Docker is installed
if ! command_exists docker; then
echo "Error: Docker is not installed. Please install Docker first."
echo "On Ubuntu: sudo apt install docker.io"
echo "On macOS/Windows: Install Docker Desktop from https://www.docker.com/get-started/"
exit 1
fi
# Check if Docker daemon is running
echo "Checking if Docker daemon is running..."
if ! docker info &> /dev/null; then
echo "Error: Docker daemon is not running or inaccessible."
if [ "$(uname)" = "Linux" ]; then
echo "Trying to start Docker service (may require sudo)..."
if sudo systemctl start docker &> /dev/null; then
echo "Docker started successfully."
else
echo "Failed to start Docker. Possible issues:"
echo "1. Run this script with sudo: sudo bash setup_searxng.sh"
echo "2. Check Docker installation: sudo systemctl status docker"
echo "3. Add your user to the docker group: sudo usermod -aG docker $USER (then log out and back in)"
exit 1
fi
else
echo "Please start Docker manually:"
echo "- On macOS/Windows: Open Docker Desktop."
echo "- On Linux: Run 'sudo systemctl start docker' or check your distro's docs."
exit 1
fi
else
echo "Docker daemon is running."
fi
# Check if Docker Compose is installed
if ! command_exists docker-compose; then
echo "Error: Docker Compose is not installed. Please install it first."
echo "On Ubuntu: sudo apt install docker-compose"
echo "Or via pip: pip install docker-compose"
exit 1
fi
# Create a directory for SearXNG config if it doesnt exist
mkdir -p searxng
cd . || exit
# Check if docker-compose.yml exists
if [ ! -f "docker-compose.yml" ]; then
echo "Error: docker-compose.yml not found in the current directory."
echo "Please create it before running this script."
exit 1
fi
# Start containers to generate initial config files
echo "Starting containers for initial setup..."
if ! docker-compose up -d; then
echo "Error: Failed to start containers. Check Docker logs with 'docker compose logs'."
echo "Possible fixes: Run with sudo or ensure port 8080 is free."
exit 1
fi
sleep 10
# Generate a secret key and update settings
SECRET_KEY=$(openssl rand -hex 32)
if [ -f "searxng/settings.yml" ]; then
@ -16,4 +83,9 @@ else
echo "Error: settings.yml not found. Initial setup may have failed."
docker-compose logs searxng
exit 1
fi
fi
# Display status and access instructions
echo "SearXNG setup complete!"
docker ps -a --filter "name=searxng" --filter "name=redis"
echo "Access SearXNG at: http://localhost:8080"

View File

@ -9,7 +9,7 @@ workers = 1
# Number of threads per worker
# default value: 4 (see Dockerfile)
enable-threads = false
enable-threads = true
threads = 1
# The right granted on the created socket
@ -20,7 +20,7 @@ single-interpreter = true
master = true
plugin = python3
lazy-apps = true
enable-threads = 1
enable-threads = 4
# Module to import
module = searx.webapp
@ -50,4 +50,4 @@ die-on-term
# uwsgi serves the static files
static-map = /static=/usr/local/searxng/searx/static
static-gzip-all = True
offload-threads = 2
offload-threads = 4

View File

@ -76,8 +76,7 @@ class PlannerAgent(Agent):
"""
return prompt
def show_plan(self, answer: dict) -> None:
agents_tasks = self.parse_agent_tasks(answer)
def show_plan(self, agents_tasks: dict, answer: str) -> None:
if agents_tasks == (None, None):
pretty_print(answer, color="warning")
pretty_print("Failed to make a plan. This can happen with (too) small LLM. Clarify your request and insist on it making a plan within ```json.", color="failure")
@ -94,12 +93,13 @@ class PlannerAgent(Agent):
animate_thinking("Thinking...", color="status")
self.memory.push('user', prompt)
answer, _ = self.llm_request()
self.show_plan(answer)
ok_str = input("Is the plan ok? (y/n): ")
if ok_str == 'y':
ok = True
else:
prompt = input("Please reformulate: ")
agents_tasks = self.parse_agent_tasks(answer)
if agents_tasks == (None, None):
prompt = f"Failed to parse the tasks. Please make a plan within ```json.\n"
pretty_print("Failed to make plan. Retrying...", color="warning")
continue
self.show_plan(agents_tasks, answer)
ok = True
return answer
def start_agent_process(self, task: str, required_infos: dict | None) -> str:

View File

@ -123,16 +123,25 @@ class Browser:
self.js_scripts_folder = "./sources/web_scripts/" if not __name__ == "__main__" else "./web_scripts/"
self.anticaptcha = "https://chrome.google.com/webstore/detail/nopecha-captcha-solver/dknlfmjaanfblgfdfebhijalfmhmjjjo/related"
self.logger = Logger("browser.log")
self.screenshot_folder = os.path.join(os.getcwd(), ".screenshots")
self.tabs = []
try:
self.driver = driver
self.wait = WebDriverWait(self.driver, 10)
except Exception as e:
raise Exception(f"Failed to initialize browser: {str(e)}")
self.screenshot_folder = os.path.join(os.getcwd(), ".screenshots")
self.screenshot()
self.driver.get("https://www.google.com")
self.setup_tabs()
if anticaptcha_manual_install:
self.load_anticatpcha_manually()
def setup_tabs(self):
self.tabs = self.driver.window_handles
self.driver.get("https://www.google.com")
self.screenshot()
def switch_control_tab(self):
self.logger.log("Switching to control tab.")
self.driver.switch_to.window(self.tabs[0])
def load_anticatpcha_manually(self):
pretty_print("You might want to install the AntiCaptcha extension for captchas.", color="warning")
@ -154,6 +163,7 @@ class Browser:
)
self.apply_web_safety()
self.logger.log(f"Navigated to: {url}")
self.logger.info(f"Navigated to: {self.get_page_title()}")
self.screenshot()
return True
except TimeoutException as e:
@ -201,6 +211,8 @@ class Browser:
lines.append(cleaned)
result = "[Start of page]\n\n" + "\n\n".join(lines) + "\n\n[End of page]"
result = re.sub(r'!\[(.*?)\]\(.*?\)', r'[IMAGE: \1]', result)
self.logger.info(f"Extracted text: {result[:100]}...")
self.logger.info(f"Extracted text length: {len(result)}")
return result[:8192]
except Exception as e:
self.logger.error(f"Error getting text: {str(e)}")
@ -226,9 +238,11 @@ class Browser:
def is_link_valid(self, url:str) -> bool:
"""Check if a URL is a valid link (page, not related to icon or metadata)."""
if len(url) > 64:
self.logger.warning(f"URL too long: {url}")
return False
parsed_url = urlparse(url)
if not parsed_url.scheme or not parsed_url.netloc:
self.logger.warning(f"Invalid URL: {url}")
return False
if re.search(r'/\d+$', parsed_url.path):
return False
@ -360,6 +374,7 @@ class Browser:
Wait for a submission outcome (e.g., URL change or new element).
"""
try:
self.logger.info("Waiting for submission outcome...")
wait = WebDriverWait(self.driver, timeout)
wait.until(
lambda driver: driver.current_url != self.driver.current_url or
@ -387,8 +402,10 @@ class Browser:
message=f"Button with XPath '{xpath}' not clickable within {timeout} seconds"
)
if self.click_element(xpath):
self.logger.info(f"Clicked button '{button_text}' at XPath: {xpath}")
return True
else:
self.logger.warning(f"Button '{button_text}' at XPath: {xpath} not clickable")
return False
except TimeoutException:
self.logger.warning(f"Timeout waiting for '{button_text}' button at XPath: {xpath}")
@ -424,9 +441,9 @@ class Browser:
self.logger.info(f"Ticked checkbox {index}")
except ElementClickInterceptedException:
self.driver.execute_script("arguments[0].click();", checkbox)
self.logger.info(f"Ticked checkbox {index} using JavaScript")
self.logger.warning(f"Click checkbox {index} intercepted")
else:
self.logger.debug(f"Checkbox {index} already ticked")
self.logger.info(f"Checkbox {index} already ticked")
except TimeoutException:
self.logger.warning(f"Timeout waiting for checkbox {index} to be clickable")
continue
@ -534,6 +551,7 @@ class Browser:
def scroll_bottom(self) -> bool:
"""Scroll to the bottom of the page."""
try:
self.logger.info("Scrolling to the bottom of the page...")
self.driver.execute_script(
"window.scrollTo(0, document.body.scrollHeight);"
)
@ -549,6 +567,7 @@ class Browser:
def screenshot(self, filename:str = 'updated_screen.png') -> bool:
"""Take a screenshot of the current page."""
self.logger.info("Taking screenshot...")
try:
path = os.path.join(self.screenshot_folder, filename)
if not os.path.exists(self.screenshot_folder):
@ -564,11 +583,12 @@ class Browser:
"""
Apply security measures to block any website malicious/annoying execution, privacy violation etc..
"""
self.logger.info("Applying web safety measures...")
script = self.load_js("inject_safety_script.js")
input_elements = self.driver.execute_script(script)
if __name__ == "__main__":
driver = create_driver(headless=True, stealth_mode=True)
driver = create_driver(headless=False, stealth_mode=True)
browser = Browser(driver, anticaptcha_manual_install=True)
#browser.go_to("https://github.com/Fosowl/agenticSeek")

View File

@ -1,12 +1,11 @@
from typing import Tuple, Callable
from pydantic import BaseModel
from sources.utility import pretty_print
class QueryRequest(BaseModel):
query: str
lang: str = "en"
tts_enabled: bool = True
stt_enabled: bool = False
def __str__(self):
return f"Query: {self.query}, Language: {self.lang}, TTS: {self.tts_enabled}, STT: {self.stt_enabled}"
@ -14,9 +13,7 @@ class QueryRequest(BaseModel):
def jsonify(self):
return {
"query": self.query,
"lang": self.lang,
"tts_enabled": self.tts_enabled,
"stt_enabled": self.stt_enabled
}
class QueryResponse(BaseModel):

View File

@ -54,12 +54,9 @@ if [ ! -f "docker-compose.yml" ]; then
exit 1
fi
# start searxng service for internet search service
cd searxng && ./setup_searxng.sh && cd ..
# start docker compose for searxng, redis, frontend services
echo "SearXNG setup complete!"
# start docker compose for searxng, redis, frontend services
echo "Warning: stopping all docker containers (t-4 seconds)..."
sleep 4
docker stop $(docker ps -a -q)