Merge pull request #126 from Fosowl/dev

Update readme and slight frontend fix attempt
This commit is contained in:
Martin 2025-04-21 15:08:56 +02:00 committed by GitHub
commit 50142b48f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 34 additions and 36 deletions

View File

@ -15,6 +15,7 @@ English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Franç
> 🛠️ **Work in Progress** Looking for contributors! > 🛠️ **Work in Progress** Looking for contributors!
https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469 https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469

View File

@ -9,7 +9,7 @@
**Manus AI 的本地替代品**,它是一个具有语音功能的大语言模型秘书,可以 Coding、访问你的电脑文件、浏览网页并自动修正错误与反省最重要的是不会向云端传送任何资料。采用 DeepSeek R1 等推理模型构建,完全在本地硬体上运行,进而保证资料的隐私。 **Manus AI 的本地替代品**,它是一个具有语音功能的大语言模型秘书,可以 Coding、访问你的电脑文件、浏览网页并自动修正错误与反省最重要的是不会向云端传送任何资料。采用 DeepSeek R1 等推理模型构建,完全在本地硬体上运行,进而保证资料的隐私。
[![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/4Ub2D6Fj) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460) [![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/XSTKZ8nP) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460)
> 🛠️ **目前还在开发阶段** 欢迎任何贡献者加入我们! > 🛠️ **目前还在开发阶段** 欢迎任何贡献者加入我们!

View File

@ -10,7 +10,7 @@
**Manus AI 的本地替代品**,它是一個具有語音功能的大語言模型秘書,可以 Coding、訪問你的電腦文件、瀏覽網頁並自動修正錯誤與反省最重要的是不會向雲端傳送任何資料。採用 DeepSeek R1 等推理模型構建,完全在本地硬體上運行,進而保證資料的隱私。 **Manus AI 的本地替代品**,它是一個具有語音功能的大語言模型秘書,可以 Coding、訪問你的電腦文件、瀏覽網頁並自動修正錯誤與反省最重要的是不會向雲端傳送任何資料。採用 DeepSeek R1 等推理模型構建,完全在本地硬體上運行,進而保證資料的隱私。
[![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/4Ub2D6Fj) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460) [![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/XSTKZ8nP) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460)
> 🛠️ **目前還在開發階段** 歡迎任何貢獻者加入我們! > 🛠️ **目前還在開發階段** 歡迎任何貢獻者加入我們!

12
api.py
View File

@ -12,6 +12,7 @@ from fastapi.responses import JSONResponse
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
import uuid
from sources.llm_provider import Provider from sources.llm_provider import Provider
from sources.interaction import Interaction from sources.interaction import Interaction
@ -21,6 +22,7 @@ from sources.utility import pretty_print
from sources.logger import Logger from sources.logger import Logger
from sources.schemas import QueryRequest, QueryResponse from sources.schemas import QueryRequest, QueryResponse
from celery import Celery from celery import Celery
api = FastAPI(title="AgenticSeek API", version="0.1.0") api = FastAPI(title="AgenticSeek API", version="0.1.0")
@ -131,7 +133,8 @@ async def get_latest_answer():
global query_resp_history global query_resp_history
if interaction.current_agent is None: if interaction.current_agent is None:
return JSONResponse(status_code=404, content={"error": "No agent available"}) return JSONResponse(status_code=404, content={"error": "No agent available"})
if interaction.current_agent.last_answer not in [q["answer"] for q in query_resp_history]: uid = str(uuid.uuid4())
if not any(q["answer"] == interaction.current_agent.last_answer for q in query_resp_history):
query_resp = { query_resp = {
"done": "false", "done": "false",
"answer": interaction.current_agent.last_answer, "answer": interaction.current_agent.last_answer,
@ -139,8 +142,9 @@ async def get_latest_answer():
"success": interaction.current_agent.success, "success": interaction.current_agent.success,
"blocks": {f'{i}': block.jsonify() for i, block in enumerate(interaction.current_agent.get_blocks_result())} if interaction.current_agent else {}, "blocks": {f'{i}': block.jsonify() for i, block in enumerate(interaction.current_agent.get_blocks_result())} if interaction.current_agent else {},
"status": interaction.current_agent.get_status_message if interaction.current_agent else "No status available", "status": interaction.current_agent.get_status_message if interaction.current_agent else "No status available",
"timestamp": str(time.time()) "uid": uid
} }
interaction.current_agent.last_answer = ""
query_resp_history.append(query_resp) query_resp_history.append(query_resp)
return JSONResponse(status_code=200, content=query_resp) return JSONResponse(status_code=200, content=query_resp)
if query_resp_history: if query_resp_history:
@ -176,7 +180,7 @@ async def process_query(request: QueryRequest):
success="false", success="false",
blocks={}, blocks={},
status="Ready", status="Ready",
timestamp=str(time.time()) uid=str(uuid.uuid4())
) )
if is_generating: if is_generating:
logger.warning("Another query is being processed, please wait.") logger.warning("Another query is being processed, please wait.")
@ -215,7 +219,7 @@ async def process_query(request: QueryRequest):
"success": query_resp.success, "success": query_resp.success,
"blocks": query_resp.blocks, "blocks": query_resp.blocks,
"status": query_resp.status, "status": query_resp.status,
"timestamp": query_resp.timestamp "uid": query_resp.uid
} }
query_resp_history.append(query_resp_dict) query_resp_history.append(query_resp_dict)

View File

@ -18,7 +18,7 @@ function App() {
checkHealth(); checkHealth();
fetchLatestAnswer(); fetchLatestAnswer();
fetchScreenshot(); fetchScreenshot();
}, 1500); }, 3000);
return () => clearInterval(intervalId); return () => clearInterval(intervalId);
}, [messages]); }, [messages]);
@ -61,7 +61,13 @@ function App() {
} }
}; };
const normalizeAnswer = (answer) => answer.trim().toLowerCase(); const normalizeAnswer = (answer) => {
return answer
.trim()
.toLowerCase()
.replace(/\s+/g, ' ')
.replace(/[.,!?]/g, '')
};
const scrollToBottom = () => { const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
@ -75,12 +81,10 @@ function App() {
if (!data.answer || data.answer.trim() === '') { if (!data.answer || data.answer.trim() === '') {
return; return;
} }
const normalizedNewAnswer = normalizeAnswer(data.answer);
const answerExists = messages.some( const answerExists = messages.some(
(msg) => (msg) => normalizeAnswer(msg.content) === normalizedNewAnswer
msg.timestamp === data.timestamp &&
normalizeAnswer(msg.content) === normalizeAnswer(data.answer)
); );
console.log('Fetched latest answer:', data.answer);
if (!answerExists) { if (!answerExists) {
setMessages((prev) => [ setMessages((prev) => [
...prev, ...prev,
@ -89,11 +93,13 @@ function App() {
content: data.answer, content: data.answer,
agentName: data.agent_name, agentName: data.agent_name,
status: data.status, status: data.status,
timestamp: data.timestamp, uid: data.uid,
}, },
]); ]);
setStatus(data.status); setStatus(data.status);
scrollToBottom(); scrollToBottom();
} else {
console.log('Duplicate answer detected, skipping:', data.answer);
} }
} catch (error) { } catch (error) {
console.error('Error fetching latest answer:', error); console.error('Error fetching latest answer:', error);

View File

@ -115,7 +115,7 @@ class Agent():
def extract_reasoning_text(self, text: str) -> None: def extract_reasoning_text(self, text: str) -> None:
""" """
Extract the reasoning block of a easoning model like deepseek. Extract the reasoning block of a reasoning model like deepseek.
""" """
start_tag = "<think>" start_tag = "<think>"
end_tag = "</think>" end_tag = "</think>"

View File

@ -244,7 +244,7 @@ class Browser:
def is_link_valid(self, url:str) -> bool: def is_link_valid(self, url:str) -> bool:
"""Check if a URL is a valid link (page, not related to icon or metadata).""" """Check if a URL is a valid link (page, not related to icon or metadata)."""
if len(url) > 64: if len(url) > 72:
self.logger.warning(f"URL too long: {url}") self.logger.warning(f"URL too long: {url}")
return False return False
parsed_url = urlparse(url) parsed_url = urlparse(url)

View File

@ -22,7 +22,7 @@ class Provider:
self.provider_name = provider_name.lower() self.provider_name = provider_name.lower()
self.model = model self.model = model
self.is_local = is_local self.is_local = is_local
self.server_ip = self.check_address_format(server_address) self.server_ip = server_address
self.available_providers = { self.available_providers = {
"ollama": self.ollama_fn, "ollama": self.ollama_fn,
"server": self.server_fn, "server": self.server_fn,
@ -44,7 +44,6 @@ class Provider:
self.api_key = self.get_api_key(self.provider_name) self.api_key = self.get_api_key(self.provider_name)
elif self.provider_name != "ollama": elif self.provider_name != "ollama":
pretty_print(f"Provider: {provider_name} initialized at {self.server_ip}", color="success") pretty_print(f"Provider: {provider_name} initialized at {self.server_ip}", color="success")
self.check_address_format(self.server_ip)
if not self.is_ip_online(self.server_ip.split(':')[0]): if not self.is_ip_online(self.server_ip.split(':')[0]):
raise Exception(f"Server at {self.server_ip} is offline.") raise Exception(f"Server at {self.server_ip} is offline.")
@ -57,21 +56,6 @@ class Provider:
exit(1) exit(1)
return api_key return api_key
def check_address_format(self, address):
"""
Validate if the address is valid IP.
"""
try:
address = address.replace('http://', '')
ip, port = address.rsplit(":", 1)
if all(c.lower() in ".:abcdef0123456789" for c in ip):
ipaddress.ip_address(ip)
if not port.isdigit() or not (0 <= int(port) <= 65535):
raise ValueError("Port must be a number between 0 and 65535.")
except ValueError as e:
raise Exception(f"Invalid address format: {e}. Is port specified?")
return address
def respond(self, history, verbose = True): def respond(self, history, verbose = True):
""" """
Use the choosen provider to generate text. Use the choosen provider to generate text.

View File

@ -178,6 +178,7 @@ class AgentRouter:
("Find a public API for movie data and build a web app to display movie ratings", "HIGH"), ("Find a public API for movie data and build a web app to display movie ratings", "HIGH"),
("Create a Node.js server that queries a public API for traffic data and displays it", "HIGH"), ("Create a Node.js server that queries a public API for traffic data and displays it", "HIGH"),
("can you find api and build a python web app with it ?", "HIGH"), ("can you find api and build a python web app with it ?", "HIGH"),
("do a deep search of current AI player for 2025 and make me a report in a file", "HIGH"),
("Find a public API for recipe data and build a web app to display recipes", "HIGH"), ("Find a public API for recipe data and build a web app to display recipes", "HIGH"),
("Search the web for recent space mission updates and build a Flask app", "HIGH"), ("Search the web for recent space mission updates and build a Flask app", "HIGH"),
("Create a Python script to scrape a website and save data to a database", "HIGH"), ("Create a Python script to scrape a website and save data to a database", "HIGH"),

View File

@ -23,10 +23,10 @@ class QueryResponse(BaseModel):
success: str success: str
blocks: dict blocks: dict
status: str status: str
timestamp: str uid: str
def __str__(self): def __str__(self):
return f"Done: {self.done}, Answer: {self.answer}, Agent Name: {self.agent_name}, Success: {self.success}, Blocks: {self.blocks}" return f"Done: {self.done}, Answer: {self.answer}, Agent Name: {self.agent_name}, Success: {self.success}, Blocks: {self.blocks}, Status: {self.status}, UID: {self.uid}"
def jsonify(self): def jsonify(self):
return { return {
@ -34,7 +34,9 @@ class QueryResponse(BaseModel):
"answer": self.answer, "answer": self.answer,
"agent_name": self.agent_name, "agent_name": self.agent_name,
"success": self.success, "success": self.success,
"blocks": self.blocks "blocks": self.blocks,
"status": self.status,
"uid": self.uid
} }
class executorResult: class executorResult: