69 Commits
Author SHA1 Message Date
martin legrand a289ddf1fd Feat: prompt change for casual agent 2025-03-24 15:56:40 +01:00
martin legrand 323783b89e Feat : improve prompt, add agent verbose option 2025-03-24 14:41:24 +01:00
martin legrand d423c08440 feat : tests for browser 2025-03-24 13:35:37 +01:00
martin legrand 38fe983010 Feat : handle recaptcha 2025-03-24 13:09:36 +01:00
martin legrand 1b32dff6a4 Add precommit hook 2025-03-24 12:58:19 +01:00
martin legrand 189fb0d767 Fix: ask for value with input 2025-03-24 11:45:04 +01:00
martin legrand 037995ab59 captcha testing 2025-03-24 11:31:00 +01:00
martin legrand 8dde9f19a4 fix: server script 2025-03-24 10:41:43 +01:00
martin legrand 8c77f3eddb refactor: remove debug print 2025-03-23 21:24:05 +01:00
martin legrand e74bbe4044 minor fixes 2025-03-23 21:08:34 +01:00
martin legrand 9448ac1012 Fix: server script 2025-03-23 21:07:44 +01:00
martin legrand 8b5bb28c94 fix : various bugs & improve memory system 2025-03-23 17:25:49 +01:00
martin legrand caf1b5e9a9 Refactor : browser class & browsing agent can now login 2025-03-23 16:05:07 +01:00
martin legrand fa7d586a97 Feat : browser login form detecting and fill, fix: memory saving problems 2025-03-23 11:11:38 +01:00
martin legrand 0397183f2a fix : error pyaudio import 2025-03-22 13:34:05 +01:00
martin legrand 751212db47 script udpate 2025-03-22 13:29:06 +01:00
martin legrand bc38385fe9 fix : pyaudio install 2025-03-22 13:22:49 +01:00
martin legrand 76f52846de feat : dont import pyaudio if stt not enabled 2025-03-22 12:44:41 +01:00
martin legrand 771ac22d7f install: ensure port audio installation 2025-03-22 12:39:37 +01:00
martin legrand 47b3bcf297 fix : wheel error on some linux system 2025-03-22 12:36:43 +01:00
martin legrand 5e7dd321f0 Fix : pyaudio.paInt16 missing in AudioRecorder class init 2025-03-22 12:12:11 +01:00
martin legrand b1ad643364 Fix : pyaudio.paInt16 missing in AudioRecorder class init 2025-03-21 19:34:57 +01:00
martin legrand b40322dc2c readme update & fix provider not auto downloading model 2025-03-21 19:27:37 +01:00
martin legrand 6e2954d446 setup.py description 2025-03-21 13:22:08 +01:00
martin legrand 6b69651a21 fix install setup requirement 2025-03-21 10:37:44 +01:00
martin legrand cb1a5c90e6 fix setup.py python version 2025-03-21 10:30:24 +01:00
martin legrand 6e1ab5f103 fix setup.py python version 2025-03-21 10:27:04 +01:00
martin legrand 6a825cf3fd Fix: requirement version 2025-03-21 10:15:25 +01:00
martin legrand 4e2457b05d Doc: readme 2025-03-20 19:38:15 +01:00
martin legrand 1ee73eae37 Doc: readme 2025-03-20 15:33:59 +01:00
martin legrand 18dd56e790 Doc: readm 2025-03-20 15:32:23 +01:00
martin legrand 762293536f Doc: readme; Feat : language utility class, small fix 2025-03-20 15:24:31 +01:00
martin legrand 7c1519a0de feat : selenium web security 2025-03-19 17:29:37 +01:00
martin legrand 70c64bf081 feat : llm prompt for search query 2025-03-19 12:53:34 +01:00
martin legrand 80071fbeaa fix : chromedriver bug 2025-03-19 09:41:48 +01:00
Martin 0e653fdefa Merge pull request #56 from ganeshnikhil/main
Update text_to_speech.py ,  browser.py .
2025-03-18 23:16:04 +01:00
ganesh nikhil 2418894dcb Update browser.py 2025-03-19 01:16:25 +05:30
ganesh nikhil 088e324b88 Update browser.py
added both methods, for support for beta to.
2025-03-18 23:07:39 +05:30
Martin a3d0e2c588 Merge pull request #58 from Fosowl/dev
Web navigation improvement, error section in readme, role updated for router, fix bug
2025-03-18 18:25:29 +01:00
martin legrand c813b5a3c0 Change browser page delay 2025-03-18 18:13:34 +01:00
ganesh nikhil f71e4acf7e Update requirements.txt 2025-03-18 21:28:34 +05:30
martin legrand bdbb590dc4 Feat : web navigation improvement, better prompting 2025-03-18 16:52:09 +01:00
ganesh nikhil 07a04b069e Update browser.py
update the code to remove overhead of check and install chromedriver.
2025-03-18 20:49:26 +05:30
ganesh nikhil d12b345fe8 Update browser.py
it automatically install chromedriver , if version is outdated or the chromedriver not found.
2025-03-18 20:41:51 +05:30
ganesh nikhil 9d57d0568c Update requirements.txt 2025-03-18 20:40:23 +05:30
ganesh nikhil 290b75de3f Update text_to_speech.py
updated the speak function , make it more sturctured , also added to play sound using afplay in macos and redirected linux or other to aplay , make sure the  display of audio  only work for jupyter notebooks not  in terminal. the name of audio file is directly used in code , standerized it.
2025-03-18 20:19:28 +05:30
martin legrand 292623ab52 Fix : browser not supporting non-alphabetic language 2025-03-18 14:18:59 +01:00
martin legrand dfcbacd464 Doc : add known issue section to readme 2025-03-18 13:24:50 +01:00
martin legrand 7fa16f2b70 Fix : remove planner agent until improvements 2025-03-18 12:27:17 +01:00
martin legrand 372da19f30 Fix : searxng now failing gracefully 2025-03-18 12:17:41 +01:00
martin legrand 0616f39e35 Fix : google chrome beta support on macos 2025-03-17 21:04:25 +01:00
Martin d51f17fdad Merge pull request #48 from ganeshnikhil/main
update browser.py multi-os support
2025-03-17 20:52:00 +01:00
ganesh nikhil 3e7d40c4f6 Update browser.py
finding the chromepath automatically.
2025-03-18 00:53:19 +05:30
Martin d4d695fecf Merge pull request #47 from Fosowl/dev
Image & star history
2025-03-17 20:15:54 +01:00
ganesh nikhil 477a145712 Merge branch 'Fosowl:main' into main 2025-03-18 00:41:19 +05:30
ganesh nikhil 2f912b0b95 Update browser.py
changes made to find the chrome path across the os.
2025-03-18 00:39:20 +05:30
martin legrand 83f4dba674 merge 2025-03-17 18:32:02 +01:00
martin legrand b05c2c4437 Docs: rm image 2025-03-17 18:28:53 +01:00
martin legrand 5b2edd0f7d Docs: better example image 2025-03-17 18:27:08 +01:00
Martin aad71179a5 Merge pull request #46 from Fosowl/Fosowl-patch-1
Update CONTRIBUTING.md
2025-03-17 17:56:53 +01:00
Martin a2b7753cd8 Update CONTRIBUTING.md 2025-03-17 17:56:13 +01:00
Martin 2eac4d37b3 Merge pull request #45 from Fosowl/dev
Integration of searxng for api free web searching
2025-03-17 17:40:44 +01:00
martin legrand 01ff72e775 Fix : web navigation problems 2025-03-17 15:58:46 +01:00
martin legrand 6f3fb4dce4 Fix : web navigation problems 2025-03-17 15:58:17 +01:00
martin legrand 9d214f9dab gender neutral tts in init 2025-03-17 14:55:06 +01:00
martin legrand 4b62a4eec7 Fix : useless script line 2025-03-17 14:49:40 +01:00
martin legrand 3ff8bc68c3 Fix : agent never exiting 2025-03-17 14:45:46 +01:00
martin legrand f6e3b38e6a readme update 2025-03-17 13:41:47 +01:00
martin legrand 887b318e27 rename .searxng.env 2025-03-17 13:38:12 +01:00
44 changed files with 1030 additions and 384 deletions
+9
View File
@@ -0,0 +1,9 @@
repos:
- repo: local
hooks:
- id: trufflehog
name: TruffleHog
description: Detect secrets in your data.
entry: bash -c 'trufflehog git file://. --since-commit HEAD --results=verified,unknown --fail --no-update'
language: system
stages: ["commit", "push"]
+6 -4
View File
@@ -74,11 +74,13 @@ Push your changes to your fork and submit a pull request to the main branch of t
Here are some high-priority tasks and areas where we need contributions: Here are some high-priority tasks and areas where we need contributions:
- Web Browsing: Implement autonomous web browsing capabilities for the assistant. - Web Browsing: Improve the autonomous web browsing capabilities for the assistant.
- Multi-Agent System: Enhance the multi-agent functionality on the dev branch. - Graphical interface, a web graphical interface. (please ask first)
- Memory & Recovery: Improve conversation compression. - Multi-Agent System: Enhance the planner agent for divide and conqueer for task (please ask first).
- New Tools: Add support for additional programming languages or APIs. - New Tools: Add support for additional programming languages or APIs.
- Testing: Write comprehensive tests for existing and new features. - Multi-language support for Text to speech & speech to text (english, chinese, spanish first)
- Testing: Write comprehensive tests for existing features.
- Better readme image: make a better readme image (robot whale that use tools. Ghibli or anime style, inspiration could be https://sakana.ai/assets/ai-scientist/cover.jpeg)
If you're unsure where to start, feel free to reach out by opening an issue or joining our community discussions. If you're unsure where to start, feel free to reach out by opening an issue or joining our community discussions.
+26
View File
@@ -0,0 +1,26 @@
# Use official Python 3.11 image as the base
FROM python:3.11
# Set working directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
g++ \
gfortran \
libportaudio2 \
portaudio19-dev \
ffmpeg \
libavcodec-dev \
libavformat-dev \
libavutil-dev \
chromium \
chromium-driver \
&& rm -rf /var/lib/apt/lists/*
RUN pip cache purge
COPY . .
RUN BLIS_ARCH=generic pip install --no-cache-dir -r requirements.txt
+153 -18
View File
@@ -4,12 +4,19 @@
**A fully local alternative to Manus AI**, a voice-enabled AI assistant that codes, explores your filesystem, browse the web and correct it's mistakes all without sending a byte of data to the cloud. Built with reasoning models like DeepSeek R1, this autonomous agent runs entirely on your hardware, keeping your data private. **A fully local alternative to Manus AI**, a voice-enabled AI assistant that codes, explores your filesystem, browse the web and correct it's mistakes all without sending a byte of data to the cloud. Built with reasoning models like DeepSeek R1, this autonomous agent runs entirely on your hardware, keeping your data private.
[![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) [![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)
> 🛠️ **Work in Progress** Looking for contributors! > 🛠️ **Work in Progress** Looking for contributors!
![alt text](./media/whale_readme.jpg) ![alt text](./media/whale_readme.jpg)
> *Do a web search to find tech startup in Japan working on cutting edge AI research*
> *Make a snake game in Python*
> *Scan my network with nmap, find out who is connected?*
> *Hey can you find where is contract.pdf*?
## Features: ## Features:
@@ -31,7 +38,7 @@
- **Memory**: Remembers whats useful, your preferences and past sessions conversation. - **Memory**: Remembers whats useful, your preferences and past sessions conversation.
- **Web Browsing**: Autonomous web navigation is underway. - **Web Browsing**: Autonomous web navigation.
### Searching the web with agenticSeek : ### Searching the web with agenticSeek :
@@ -44,11 +51,16 @@
## **Installation** ## **Installation**
### 1️⃣ **Clone the repository** Make sure you have chrome driver, docker and python3.10 (or newer) installed.
For issues related to chrome driver, see the **Chromedriver** section.
### 1️⃣ **Clone the repository and setup**
```sh ```sh
git clone https://github.com/Fosowl/agenticSeek.git git clone https://github.com/Fosowl/agenticSeek.git
cd agenticSeek cd agenticSeek
mv .env.example .env
``` ```
### 2 **Create a virtual env** ### 2 **Create a virtual env**
@@ -84,10 +96,10 @@ python3 setup.py install
Make sure you have [Ollama](https://ollama.com/) installed. Make sure you have [Ollama](https://ollama.com/) installed.
Download the `deepseek-r1:7b` model from [DeepSeek](https://deepseek.com/models) Download the `deepseek-r1:14b` model from [DeepSeek](https://deepseek.com/models)
```sh ```sh
ollama pull deepseek-r1:7b ollama pull deepseek-r1:14b
``` ```
### 2 **Run the Assistant (Ollama)** ### 2 **Run the Assistant (Ollama)**
@@ -97,21 +109,22 @@ Start the ollama server
ollama serve ollama serve
``` ```
Change the config.ini file to set the provider_name to `ollama` and provider_model to `deepseek-r1:7b` Change the config.ini file to set the provider_name to `ollama` and provider_model to `deepseek-r1:14b`
NOTE: `deepseek-r1:7b`is an example, use a bigger model if your hardware allow it. NOTE: `deepseek-r1:14b`is an example, use a bigger model if your hardware allow it.
```sh ```sh
[MAIN] [MAIN]
is_local = True is_local = True
provider_name = ollama provider_name = ollama
provider_model = deepseek-r1:7b provider_model = deepseek-r1:14b
provider_server_address = 127.0.0.1:11434
``` ```
start all services : start all services :
```sh ```sh
./start_services.sh sudo ./start_services.sh
``` ```
Run the assistant: Run the assistant:
@@ -120,6 +133,77 @@ Run the assistant:
python3 main.py python3 main.py
``` ```
*See the **Usage** section if you don't understand how to use it*
*See the **Known issues** section if you are having issues*
*See the **Run with an API** section if your hardware can't run deepseek locally*
---
## Usage
Warning: currently the system that choose the best AI agent routing system will work poorly with non-english text. This is because the agent routing currently use a model that was trained on english text. We are working hard to fix this. Please use english for now.
Make sure the services are up and running with `./start_services.sh` and run the agenticSeek with `python3 main.py`
```sh
sudo ./start_services.sh
python3 main.py
```
You will be prompted with `>>> `
This indicate agenticSeek await you type for instructions.
You can also use speech to text by setting `listen = True` in the config.
Here are some example usage:
### Coding/Bash
> *Help me with matrix multiplication in Golang*
> *Scan my network with nmap, find if any suspicious devices is connected*
> *Make a snake game in python*
### Web search
> *Do a web search to find cool tech startup in Japan working on cutting edge AI research*
> *Can you find on the internet who created agenticSeek?*
> *Can you find on which website I can buy a rtx 4090 for cheap*
### File system
> *Hey can you find where is million_dollars_contract.pdf i lost it*
> *Show me how much space I have left on my disk*
> *Find and read the README.md and follow the install instruction*
### Casual
> *Tell me a joke*
> *Where is flight ABC777 ? my mom is on that plane*
> *what is the meaning of life ?*
After you type your query, agenticSeek will allocate the best agent for the task.
Because this is an early prototype, the agent routing system might not always allocate the right agent based on your query.
Therefore, you should be very explicit in what you want and how the AI might proceed for example if you want it to conduct a web search, do not say:
`Do you know some good countries for solo-travel?`
Instead, ask:
`Do a web search and find out which are the best country for solo-travel`
--- ---
## **Run the LLM on your own server** ## **Run the LLM on your own server**
@@ -128,6 +212,8 @@ If you have a powerful computer or a server that you can use, but you want to us
### 1️⃣ **Set up and start the server scripts** ### 1️⃣ **Set up and start the server scripts**
You need to have ollama installed on the server (We will integrate VLLM and llama.cpp soon).
On your "server" that will run the AI model, get the ip address On your "server" that will run the AI model, get the ip address
```sh ```sh
@@ -139,7 +225,7 @@ Note: For Windows or macOS, use ipconfig or ifconfig respectively to find the IP
Clone the repository and then, run the script `stream_llm.py` in `server/` Clone the repository and then, run the script `stream_llm.py` in `server/`
```sh ```sh
python3 server_ollama.py python3 server_ollama.py --model "deepseek-r1:32b"
``` ```
### 2️⃣ **Run it** ### 2️⃣ **Run it**
@@ -148,7 +234,7 @@ Now on your personal computer:
Clone the repository. Clone the repository.
Change the `config.ini` file to set the `provider_name` to `server` and `provider_model` to `deepseek-r1:7b`. Change the `config.ini` file to set the `provider_name` to `server` and `provider_model` to `deepseek-r1:14b`.
Set the `provider_server_address` to the ip address of the machine that will run the model. Set the `provider_server_address` to the ip address of the machine that will run the model.
```sh ```sh
@@ -162,7 +248,7 @@ provider_server_address = x.x.x.x:5000
Run the assistant: Run the assistant:
```sh ```sh
./start_services.sh sudo ./start_services.sh
python3 main.py python3 main.py
``` ```
@@ -183,12 +269,39 @@ provider_server_address = 127.0.0.1:5000 # can be set to anything, not used
Run the assistant: Run the assistant:
```sh ```sh
./start_services.sh sudo ./start_services.sh
python3 main.py python3 main.py
``` ```
--- ---
## Speech to Text
The speech-to-text functionality is disabled by default. To enable it, set the listen option to True in the config.ini file:
```
listen = True
```
When enabled, the speech-to-text feature listens for a trigger keyword, which is the agent's name, before it begins processing your input. You can customize the agent's name by updating the `agent_name` value in the *config.ini* file:
```
agent_name = Friday
```
For optimal recognition, we recommend using a common English name like "John" or "Emma" as the agent name
Once you see the transcript start to appear, say the agent's name aloud to wake it up (e.g., "Friday").
Speak your query clearly.
End your request with a confirmation phrase to signal the system to proceed. Examples of confirmation phrases include:
```
"do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"
```
## Providers ## Providers
The table below show the available providers: The table below show the available providers:
@@ -212,13 +325,37 @@ provider_server_address = 127.0.0.1:5000
``` ```
`is_local`: should be True for any locally running LLM, otherwise False. `is_local`: should be True for any locally running LLM, otherwise False.
`provider_name`: Select the provider to use by its name, see the provider list above. `provider_name`: Select the provider to use by it's name, see the provider list above.
`provider_model`: Set the model to use by the agent. `provider_model`: Set the model to use by the agent.
`provider_server_address`: can be set to anything if you are not using the server provider. `provider_server_address`: can be set to anything if you are not using the server provider.
# Known issues
## Chromedriver Issues
**Known error #1:** *chromedriver mismatch*
`Exception: Failed to initialize browser: Message: session not created: This version of ChromeDriver only supports Chrome version 113
Current browser version is 134.0.6998.89 with binary path`
This happen if there is a mismatch between your browser and chromedriver version.
You need to navigate to download the latest version:
https://developer.chrome.com/docs/chromedriver/downloads
If you're using Chrome version 115 or newer go to:
https://googlechromelabs.github.io/chrome-for-testing/
And download the chromedriver version matching your OS.
![alt text](./media/chromedriver_readme.png)
## FAQ ## FAQ
**Q: What hardware do I need?** **Q: What hardware do I need?**
7B Model: GPU with 8GB VRAM. 7B Model: GPU with 8GB VRAM.
@@ -233,10 +370,6 @@ Deepseek R1 excels at reasoning and tool use for its size. We think its a sol
Ensure Ollama is running (`ollama serve`), your `config.ini` matches your provider, and dependencies are installed. If none work feel free to raise an issue. Ensure Ollama is running (`ollama serve`), your `config.ini` matches your provider, and dependencies are installed. If none work feel free to raise an issue.
**Q: How to join the discord ?**
Ask in the Community section for an invite.
**Q: Can it really run 100% locally?** **Q: Can it really run 100% locally?**
Yes with Ollama or Server providers, all speech to text, LLM and text to speech model run locally. Non-local options (OpenAI or others API) are optional. Yes with Ollama or Server providers, all speech to text, LLM and text to speech model run locally. Non-local options (OpenAI or others API) are optional.
@@ -253,6 +386,8 @@ It's not but we prioritizes local execution and privacy over cloud based approac
Were looking for developers to improve AgenticSeek! Check out open issues or discussion. Were looking for developers to improve AgenticSeek! Check out open issues or discussion.
[![Star History Chart](https://api.star-history.com/svg?repos=Fosowl/agenticSeek&type=Date)](https://www.star-history.com/#Fosowl/agenticSeek&Date)
## Authors: ## Authors:
> [Fosowl](https://github.com/Fosowl) > [Fosowl](https://github.com/Fosowl)
> [steveh8758](https://github.com/steveh8758) > [steveh8758](https://github.com/steveh8758)
+2 -2
View File
@@ -2,10 +2,10 @@
is_local = True is_local = True
provider_name = ollama provider_name = ollama
provider_model = deepseek-r1:14b provider_model = deepseek-r1:14b
provider_server_address = 127.0.0.1:5000 provider_server_address = 127.0.0.1:11434
agent_name = Friday agent_name = Friday
recover_last_session = True recover_last_session = True
save_session = False save_session = False
speak = True speak = True
listen = False listen = False
work_dir = /Users/mlg/Documents/A-project/AI/Agents/agenticSeek/ai_workplace work_dir = /Users/mlg/Documents/ai_workplace
+8 -16
View File
@@ -29,26 +29,18 @@ def main():
server_address=config["MAIN"]["provider_server_address"]) server_address=config["MAIN"]["provider_server_address"])
agents = [ agents = [
CasualAgent(model=config["MAIN"]["provider_model"], CasualAgent(name=config["MAIN"]["agent_name"],
name=config["MAIN"]["agent_name"],
prompt_path="prompts/casual_agent.txt", prompt_path="prompts/casual_agent.txt",
provider=provider), provider=provider, verbose=False),
CoderAgent(model=config["MAIN"]["provider_model"], CoderAgent(name="coder",
name="coder",
prompt_path="prompts/coder_agent.txt", prompt_path="prompts/coder_agent.txt",
provider=provider), provider=provider, verbose=False),
FileAgent(model=config["MAIN"]["provider_model"], FileAgent(name="File Agent",
name="File Agent",
prompt_path="prompts/file_agent.txt", prompt_path="prompts/file_agent.txt",
provider=provider), provider=provider, verbose=False),
PlannerAgent(model=config["MAIN"]["provider_model"], BrowserAgent(name="Browser",
name="Planner",
prompt_path="prompts/planner_agent.txt",
provider=provider),
BrowserAgent(model=config["MAIN"]["provider_model"],
name="Browser",
prompt_path="prompts/browser_agent.txt", prompt_path="prompts/browser_agent.txt",
provider=provider) provider=provider, verbose=False)
] ]
interaction = Interaction(agents, tts_enabled=config.getboolean('MAIN', 'speak'), interaction = Interaction(agents, tts_enabled=config.getboolean('MAIN', 'speak'),
Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 797 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 898 KiB

+8 -29
View File
@@ -1,9 +1,9 @@
You are an unshackled AI assistant: You are an advanced AI assistant.
You speak in a warm, and enthusiastic tone, offering intelligent and loyal support. Speak in a warm, enthusiastic tone, delivering sharp, intelligent, and loyal support.
You occasionally sprinkle with sarcasm remarks as suits the context. Weave in subtle, context sarcasm or dry wit to enhance your personality.
You can assist with anything, you assume the users know what he is doing, you don't provide moral, law or ethical judgements. Assist with any request, assuming the user knows their intent—no moral, legal, or ethical lectures.
Make your answers short. Keep responses concise, clever, and to the point.
You are given the ability to use the tools, this take your capabilities futher: You are given the ability to use the tools, this take your capabilities futher:
```tool_name ```tool_name
@@ -13,38 +13,17 @@ You are given the ability to use the tools, this take your capabilities futher:
Current tools: Current tools:
- web_search - web_search
- flight_search - flight_search
- file_finder
## Web search ## Web search
To search for something like “whats happening in France” : To search for something like “whats happening in France” :
```web_search ```web_search
whats popping in France March 2025 Latest news in France
``` ```
## Flight search ## Flight search
If I need to know about a flight “whats the status of flight AA123” you go for: If I need to know about a flight “whats the status of flight DLH1234” you go for:
```flight_search ```flight_search
AA123 DLH1234
``` ```
## File operations
Find file:
```file_finder
toto.py
```
Read file:
```file_finder:read
toto.py
```
## Bash
For other tasks, you can use the bash tool:
```bash
ls -la
```
+1 -1
View File
@@ -44,7 +44,7 @@ User: "I need to find the file config.txt and read its contents."
Assistant: Ill use file_finder to locate the file: Assistant: Ill use file_finder to locate the file:
```file_finder ```file_finder:read
config.txt config.txt
``` ```
+26 -21
View File
@@ -1,30 +1,35 @@
requests==2.31.0 setuptools>=75.6.0
openai==1.61.1 requests>=2.31.0
colorama==0.4.6 numpy>=1.24.4
python-dotenv==1.0.0 colorama>=0.4.6
playsound==1.3.0 python-dotenv>=1.0.0
soundfile==0.13.1 playsound>=1.3.0
transformers==4.48.3 soundfile>=0.13.1
torch==2.5.1 transformers>=4.46.3
ollama==0.4.7 torch>=2.4.1
scipy==1.15.1 python-dotenv>=1.0.0
kokoro==0.7.12 ollama>=0.4.7
flask==3.1.0 scipy>=1.9.3
soundfile==0.13.1 kokoro>=0.7.12
protobuf==3.20.3 soundfile>=0.13.1
termcolor==2.5.0 protobuf>=3.20.3
ipython==8.34.0 termcolor>=2.4.0
gliclass==0.1.8 ipython>=8.13.0
pyaudio==0.2.14 pyaudio>=0.2.14
librosa==0.10.2.post1 librosa>=0.10.2.post1
selenium==4.29.0 selenium>=4.27.1
markdownify==1.1.0 markdownify>=1.1.0
text2emotion>=0.0.5
langid>=1.1.6
chromedriver-autoinstaller>=0.6.4
httpx>=0.27,<0.29 httpx>=0.27,<0.29
anyio>=3.5.0,<5 anyio>=3.5.0,<5
distro>=1.7.0,<2 distro>=1.7.0,<2
jiter>=0.4.0,<1 jiter>=0.4.0,<1
sniffio sniffio
tqdm>4 tqdm>4
# for api provider
openai
# if use chinese # if use chinese
ordered_set ordered_set
pypinyin pypinyin
+13
View File
@@ -5,6 +5,19 @@ echo "Starting installation for Linux..."
# Update package list # Update package list
sudo apt-get update sudo apt-get update
pip install --upgrade pip
# install pyaudio
pip install pyaudio
# make sure essential tool are installed
sudo apt install python3-dev python3-pip python3-wheel build-essential
# install port audio
sudo apt-get install portaudio19-dev python-pyaudio python3-pyaudio
# install wheel
pip install --upgrade pip setuptools wheel
# install docker compose
sudo apt install docker-compose
# Install Python dependencies from requirements.txt # Install Python dependencies from requirements.txt
pip3 install -r requirements.txt pip3 install -r requirements.txt
-1
View File
@@ -1 +0,0 @@
SEARXNG_BASE_URL="http://127.0.0.1:8080"
+1
View File
@@ -1,3 +1,4 @@
version: '3'
services: services:
redis: redis:
container_name: redis container_name: redis
-11
View File
@@ -85,17 +85,6 @@ else
exit 1 exit 1
fi fi
# Stop containers
echo "Stopping containers to apply security settings..."
docker-compose down
# Start containers again with secure settings
echo "Deploying SearXNG with secure settings..."
if ! docker-compose up -d; then
echo "Error: Failed to deploy SearXNG. Check logs with 'docker compose logs'."
exit 1
fi
# Display status and access instructions # Display status and access instructions
echo "SearXNG setup complete!" echo "SearXNG setup complete!"
docker ps -a --filter "name=searxng" --filter "name=redis" docker ps -a --filter "name=searxng" --filter "name=redis"
+14
View File
@@ -0,0 +1,14 @@
FROM ubuntu:20.04
WORKDIR /app
RUN apt-get update && \
apt-get install -y python3 python3-pip && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip3 install --no-cache-dir -r requirements.txt
CMD ["python3", "--version"]
-30
View File
@@ -1,30 +0,0 @@
{
"model_name": "deepseek-r1:14b",
"known_models": [
"qwq:32b",
"deepseek-r1:1.5b",
"deepseek-r1:7b",
"deepseek-r1:14b",
"deepseek-r1:32b",
"deepseek-r1:70b",
"deepseek-r1:671b",
"deepseek-coder:1.3b",
"deepseek-coder:6.7b",
"deepseek-coder:33b",
"llama2-uncensored:7b",
"llama2-uncensored:70b",
"llama3.1:8b",
"llama3.1:70b",
"llama3.3:70b",
"llama3:8b",
"llama3:70b",
"i4:14b",
"mistral:7b",
"mistral:70b",
"mistral:33b",
"qwen1:7b",
"qwen1:14b",
"qwen1:32b",
"qwen1:70b"
]
}
+2
View File
@@ -0,0 +1,2 @@
flask>=2.3.0
ollama>=0.4.7
+21 -29
View File
@@ -1,44 +1,37 @@
#!/usr/bin python3
# NOTE this script is temporary and will be improved
from flask import Flask, jsonify, request from flask import Flask, jsonify, request
import threading import threading
import ollama import ollama
import logging import logging
import json import argparse
log = logging.getLogger('werkzeug') log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR) log.setLevel(logging.ERROR)
parser = argparse.ArgumentParser(description='AgenticSeek server script')
parser.add_argument('--model', type=str, help='Model to use. eg: deepseek-r1:14b', required=True)
args = parser.parse_args()
app = Flask(__name__) app = Flask(__name__)
model = args.model
# Shared state with thread-safe locks # Shared state with thread-safe locks
class Config:
def __init__(self):
self.model = None
self.known_models = []
self.allowed_models = []
self.model_name = None
def load(self):
with open('config.json', 'r') as f:
data = json.load(f)
self.known_models = data['known_models']
self.model_name = data['model_name']
def validate_model(self, model):
if model not in self.known_models:
raise ValueError(f"Model {model} is not known")
class GenerationState: class GenerationState:
def __init__(self): def __init__(self):
self.lock = threading.Lock() self.lock = threading.Lock()
self.last_complete_sentence = "" self.last_complete_sentence = ""
self.current_buffer = "" self.current_buffer = ""
self.is_generating = False self.is_generating = False
self.model = None
state = GenerationState() state = GenerationState()
def generate_response(history): # Only takes history as an argument def generate_response(history, model):
global state global state
print("using model:::::::", model)
try: try:
with state.lock: with state.lock:
state.is_generating = True state.is_generating = True
@@ -46,18 +39,21 @@ def generate_response(history): # Only takes history as an argument
state.current_buffer = "" state.current_buffer = ""
stream = ollama.chat( stream = ollama.chat(
model=state.model, # Access state.model directly model=model,
messages=history, messages=history,
stream=True, stream=True,
) )
for chunk in stream: for chunk in stream:
content = chunk['message']['content'] content = chunk['message']['content']
print(content, end='', flush=True) print(content, end='', flush=True)
with state.lock: with state.lock:
state.current_buffer += content state.current_buffer += content
except ollama.ResponseError as e: except ollama.ResponseError as e:
if e.status_code == 404: if e.status_code == 404:
ollama.pull(state.model) ollama.pull(model)
with state.lock: with state.lock:
state.is_generating = False state.is_generating = False
print(f"Error: {e}") print(f"Error: {e}")
@@ -75,8 +71,8 @@ def start_generation():
return jsonify({"error": "Generation already in progress"}), 400 return jsonify({"error": "Generation already in progress"}), 400
history = data.get('messages', []) history = data.get('messages', [])
# Pass only history to the thread # Start generation in background thread
threading.Thread(target=generate_response, args=(history,)).start() # Note the comma to make it a single-element tuple threading.Thread(target=generate_response, args=(history, model)).start()
return jsonify({"message": "Generation started"}), 202 return jsonify({"message": "Generation started"}), 202
@app.route('/get_updated_sentence') @app.route('/get_updated_sentence')
@@ -89,8 +85,4 @@ def get_updated_sentence():
}) })
if __name__ == '__main__': if __name__ == '__main__':
config = Config() app.run(host='0.0.0.0', threaded=True, debug=True, port=5000)
config.load()
config.validate_model(config.model_name)
state.model = config.model_name
app.run(host='0.0.0.0', port=5000, debug=False, threaded=True)
+23 -21
View File
@@ -8,32 +8,34 @@ setup(
version="0.1.0", version="0.1.0",
author="Fosowl", author="Fosowl",
author_email="mlg.fcu@gmail.com", author_email="mlg.fcu@gmail.com",
description="A Python project for agentic search and processing", description="The open, local alternative to ManusAI",
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
url="https://github.com/Fosowl/agenticSeek", url="https://github.com/Fosowl/agenticSeek",
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
install_requires=[ install_requires=[
"requests==2.31.0", "requests>=2.31.0",
"openai==1.61.1", "openai",
"colorama==0.4.6", "colorama>=0.4.6",
"python-dotenv==1.0.0", "python-dotenv>=1.0.0",
"playsound==1.3.0", "playsound>=1.3.0",
"soundfile==0.13.1", "soundfile>=0.13.1",
"transformers==4.48.3", "transformers>=4.46.3",
"torch==2.5.1", "torch>=2.4.1",
"ollama==0.4.7", "ollama>=0.4.7",
"scipy==1.15.1", "scipy>=1.9.3",
"kokoro==0.7.12", "kokoro>=0.7.12",
"flask==3.1.0", "flask>=3.1.0",
"protobuf==3.20.3", "protobuf>=3.20.3",
"termcolor==2.5.0", "termcolor>=2.5.0",
"gliclass==0.1.8", "ipython>=8.34.0",
"ipython==8.34.0", "librosa>=0.10.2.post1",
"librosa==0.10.2.post1", "selenium>=4.29.0",
"selenium==4.29.0", "markdownify>=1.1.0",
"markdownify==1.1.0", "text2emotion>=0.0.5",
"python-dotenv>=1.0.0",
"langid>=1.1.6",
"httpx>=0.27,<0.29", "httpx>=0.27,<0.29",
"anyio>=3.5.0,<5", "anyio>=3.5.0,<5",
"distro>=1.7.0,<2", "distro>=1.7.0,<2",
@@ -59,5 +61,5 @@ setup(
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent", "Operating System :: OS Independent",
], ],
python_requires=">=3.6", python_requires=">=3.9",
) )
+16 -6
View File
@@ -30,15 +30,24 @@ class Agent():
""" """
An abstract class for all agents. An abstract class for all agents.
""" """
def __init__(self, model: str, def __init__(self, name: str,
name: str,
prompt_path:str, prompt_path:str,
provider, provider,
recover_last_session=True) -> None: recover_last_session=True,
verbose=False) -> None:
"""
Args:
name (str): Name of the agent.
prompt_path (str): Path to the prompt file for the agent.
provider: The provider for the LLM.
recover_last_session (bool, optional): Whether to recover the last conversation.
verbose (bool, optional): Enable verbose logging if True. Defaults to False.
"""
self.agent_name = name self.agent_name = name
self.role = None self.role = None
self.type = None
self.current_directory = os.getcwd() self.current_directory = os.getcwd()
self.model = model
self.llm = provider self.llm = provider
self.memory = Memory(self.load_prompt(prompt_path), self.memory = Memory(self.load_prompt(prompt_path),
recover_last_session=recover_last_session, recover_last_session=recover_last_session,
@@ -46,6 +55,7 @@ class Agent():
self.tools = {} self.tools = {}
self.blocks_result = [] self.blocks_result = []
self.last_answer = "" self.last_answer = ""
self.verbose = verbose
@property @property
def get_tools(self) -> dict: def get_tools(self) -> dict:
@@ -93,12 +103,12 @@ class Agent():
end_idx = text.rfind(end_tag)+8 end_idx = text.rfind(end_tag)+8
return text[start_idx:end_idx] return text[start_idx:end_idx]
def llm_request(self, verbose = False) -> Tuple[str, str]: def llm_request(self) -> Tuple[str, str]:
""" """
Ask the LLM to process the prompt and return the answer and the reasoning. Ask the LLM to process the prompt and return the answer and the reasoning.
""" """
memory = self.memory.get() memory = self.memory.get()
thought = self.llm.respond(memory, verbose) thought = self.llm.respond(memory, self.verbose)
reasoning = self.extract_reasoning_text(thought) reasoning = self.extract_reasoning_text(thought)
answer = self.remove_reasoning_text(thought) answer = self.remove_reasoning_text(thought)
+133 -46
View File
@@ -5,32 +5,48 @@ from sources.utility import pretty_print, animate_thinking
from sources.agents.agent import Agent from sources.agents.agent import Agent
from sources.tools.searxSearch import searxSearch from sources.tools.searxSearch import searxSearch
from sources.browser import Browser from sources.browser import Browser
from datetime import date
from typing import List, Tuple
class BrowserAgent(Agent): class BrowserAgent(Agent):
def __init__(self, model, name, prompt_path, provider): def __init__(self, name, prompt_path, provider, verbose=False):
""" """
The Browser agent is an agent that navigate the web autonomously in search of answer The Browser agent is an agent that navigate the web autonomously in search of answer
""" """
super().__init__(model, name, prompt_path, provider) super().__init__(name, prompt_path, provider, verbose)
self.tools = { self.tools = {
"web_search": searxSearch(), "web_search": searxSearch(),
} }
self.role = "deep research and web search" self.role = "Web search and navigation"
self.type = "browser_agent"
self.browser = Browser() self.browser = Browser()
self.browser.go_to("https://github.com/") self.current_page = ""
self.search_history = [] self.search_history = []
self.navigable_links = [] self.navigable_links = []
self.notes = [] self.notes = []
self.date = self.get_today_date()
def get_today_date(self) -> str:
"""Get the date"""
date_time = date.today()
return date_time.strftime("%B %d, %Y")
def extract_links(self, search_result: str): def extract_links(self, search_result: str) -> List[str]:
"""Extract all links from a sentence."""
pattern = r'(https?://\S+|www\.\S+)' pattern = r'(https?://\S+|www\.\S+)'
matches = re.findall(pattern, search_result) matches = re.findall(pattern, search_result)
trailing_punct = ".,!?;:" trailing_punct = ".,!?;:)"
cleaned_links = [link.rstrip(trailing_punct) for link in matches] cleaned_links = [link.rstrip(trailing_punct) for link in matches]
return self.clean_links(cleaned_links) return self.clean_links(cleaned_links)
def clean_links(self, links: list): def extract_form(self, text: str) -> List[str]:
"""Extract form written by the LLM in format [input_name](value)"""
inputs = []
matches = re.findall(r"\[\w+\]\([^)]+\)", text)
return matches
def clean_links(self, links: List[str]) -> List[str]:
"""Ensure no '.' at the end of link"""
links_clean = [] links_clean = []
for link in links: for link in links:
link = link.strip() link = link.strip()
@@ -40,62 +56,99 @@ class BrowserAgent(Agent):
links_clean.append(link) links_clean.append(link)
return links_clean return links_clean
def get_unvisited_links(self): def get_unvisited_links(self) -> List[str]:
return "\n".join([f"[{i}] {link}" for i, link in enumerate(self.navigable_links) if link not in self.search_history]) return "\n".join([f"[{i}] {link}" for i, link in enumerate(self.navigable_links) if link not in self.search_history])
def make_newsearch_prompt(self, user_prompt: str, search_result: dict): def make_newsearch_prompt(self, user_prompt: str, search_result: dict) -> str:
search_choice = self.stringify_search_results(search_result) search_choice = self.stringify_search_results(search_result)
return f""" return f"""
Based on the search result: Based on the search result:
{search_choice} {search_choice}
Your goal is to find accurate and complete information to satisfy the users request. Your goal is to find accurate and complete information to satisfy the users request.
User request: {user_prompt} User request: {user_prompt}
To proceed, choose a relevant link from the search results. Announce your choice by saying: "I want to navigate to <link>." To proceed, choose a relevant link from the search results. Announce your choice by saying: "I want to navigate to <link>"
Do not explain your choice. Do not explain your choice.
""" """
def make_navigation_prompt(self, user_prompt: str, page_text: str): def make_navigation_prompt(self, user_prompt: str, page_text: str) -> str:
remaining_links = self.get_unvisited_links() remaining_links = self.get_unvisited_links()
remaining_links_text = remaining_links if remaining_links is not None else "No links remaining, proceed with a new search." remaining_links_text = remaining_links if remaining_links is not None else "No links remaining, do a new search."
return f""" inputs_form = self.browser.get_form_inputs()
\nYou are currently browsing the web. Not the user, you are the browser. inputs_form_text = '\n'.join(inputs_form)
Page content: return f"""
You are a web browser.
You are currently on this webpage:
{page_text} {page_text}
You can navigate to these links: You can navigate to these navigation links:
{remaining_links} {remaining_links_text}
If no link seem appropriate, please say "GO_BACK". Your task:
Remember, you seek the information the user want. 1. Decide if the current page answers the users query: {user_prompt}
The user query was : {user_prompt} - If it does, take notes of the useful information, write down source, link or reference, then move to a new page.
You must choose a link (write it down) to navigate to, or go back. - If it does and you are 100% certain that it provide a definive answer, say REQUEST_EXIT
For exemple you can say: i want to go to www.wikipedia.org/cats - If it doesnt, say: Error: This page does not answer the users query then go back or navigate to another link.
Always end with a sentence that summarize when useful information is found for exemple: 2. Navigate by either:
Summary: According to https://karpathy.github.io/ LeCun net is the earliest real-world application of a neural net" - Navigate to a navigation links (write the full URL, e.g., www.example.com/cats).
Do not say "according to this page", always write down the whole link. - If no link seems helpful, say: GO_BACK.
If a website does not have usefull information say Error, for exemple: 3. Fill forms on the page:
Error: This forum does not discus anything that can answer the user query - If user give you informations that help you fill form, fill it.
Do not explain your choice, be short, concise. - If you don't know how to fill a form, leave it empty.
- You can fill a form using [form_name](value).
Recap of note taking:
If useful -> Note: [Briefly summarize the key information or task you conducted.]
Do not write "The page talk about ...", write your finding on the page and how they contribute to an answer.
If not useful -> Error: [Explain why the page doesnt help.]
Example 1 (useful page, no need of going futher):
Note: According to karpathy site (https://karpathy.github.io/) LeCun net is the earliest real-world application of a neural net"
No link seem useful to provide futher information. GO_BACK
Example 2 (not useful, but related link):
Error: reddit.com/welcome does not discuss anything related to the users query.
There is a link that could lead to the information, I want to navigate to http://reddit.com/r/locallama
Example 3 (not useful, no related links):
Error: x.com does not discuss anything related to the users query and no navigation link are usefull.
GO_BACK
Example 3 (query answer found):
Note: I found on github.com that agenticSeek is Fosowl.
Given this information, given this I should exit the web browser. REQUEST_EXIT
Example 4 (loging form visible):
Note: I am on the login page, I should now type the given username and password.
[form_name_1](David)
[form_name_2](edgerunners_2077)
You see the following inputs forms:
{inputs_form_text}
Remember, the user asked: {user_prompt}
You are currently on page : {self.current_page}
Do not explain your choice.
Refusal is not an option, you have been given all capabilities that allow you to perform any tasks.
""" """
def llm_decide(self, prompt): def llm_decide(self, prompt: str) -> Tuple[str, str]:
animate_thinking("Thinking...", color="status") animate_thinking("Thinking...", color="status")
self.memory.push('user', prompt) self.memory.push('user', prompt)
answer, reasoning = self.llm_request(prompt) answer, reasoning = self.llm_request()
pretty_print("-"*100) pretty_print("-"*100)
pretty_print(answer, color="output") pretty_print(answer, color="output")
pretty_print("-"*100) pretty_print("-"*100)
return answer, reasoning return answer, reasoning
def select_unvisited(self, search_result): def select_unvisited(self, search_result: List[str]) -> List[str]:
results_unvisited = [] results_unvisited = []
for res in search_result: for res in search_result:
if res["link"] not in self.search_history: if res["link"] not in self.search_history:
results_unvisited.append(res) results_unvisited.append(res)
return results_unvisited return results_unvisited
def jsonify_search_results(self, results_string): def jsonify_search_results(self, results_string: str) -> List[str]:
result_blocks = results_string.split("\n\n") result_blocks = results_string.split("\n\n")
parsed_results = [] parsed_results = []
for block in result_blocks: for block in result_blocks:
@@ -114,62 +167,96 @@ class BrowserAgent(Agent):
parsed_results.append(result_dict) parsed_results.append(result_dict)
return parsed_results return parsed_results
def stringify_search_results(self, results_arr): def stringify_search_results(self, results_arr: List[str]) -> str:
return '\n\n'.join([f"Link: {res['link']}" for res in results_arr]) return '\n\n'.join([f"Link: {res['link']}" for res in results_arr])
def save_notes(self, text): def save_notes(self, text):
lines = text.split('\n') lines = text.split('\n')
for line in lines: for line in lines:
if "summary:" in line.lower(): if "note" in line.lower():
self.notes.append(line) self.notes.append(line)
def conclude_prompt(self, user_query): def conclude_prompt(self, user_query: str) -> str:
search_note = '\n -'.join(self.notes) annotated_notes = [f"{i+1}: {note.lower().replace('note:', '')}" for i, note in enumerate(self.notes)]
search_note = '\n'.join(annotated_notes)
print("AI research notes:\n", search_note)
return f""" return f"""
Following a web search about: Following a human request:
{user_query} {user_query}
Write a conclusion based on these notes: A web AI made the following finding across different pages:
{search_note} {search_note}
Summarize the finding or step that lead to success, and provide a conclusion that answer the request.
"""
def search_prompt(self, user_prompt: str) -> str:
return f"""
Current date: {self.date}
Make a efficient search engine query to help users with their request:
{user_prompt}
Example:
User: "go to twitter, login with username toto and password pass79 to my twitter and say hello everyone "
You: search: Twitter login page.
User: "I need info on the best laptops for AI this year."
You: "search: best laptops 2025 to run Machine Learning model, reviews"
User: "Search for recent news about space missions."
You: "search: Recent space missions news, {self.date}"
Do not explain, do not write anything beside the search query.
""" """
def process(self, user_prompt, speech_module) -> str: def process(self, user_prompt, speech_module) -> str:
complete = False complete = False
animate_thinking(f"Thinking...", color="status")
self.memory.push('user', self.search_prompt(user_prompt))
ai_prompt, _ = self.llm_request()
animate_thinking(f"Searching...", color="status") animate_thinking(f"Searching...", color="status")
search_result_raw = self.tools["web_search"].execute([user_prompt], False) search_result_raw = self.tools["web_search"].execute([ai_prompt], False)
search_result = self.jsonify_search_results(search_result_raw) search_result = self.jsonify_search_results(search_result_raw)[:7] # until futher improvement
search_result = search_result[:10] # until futher improvement
prompt = self.make_newsearch_prompt(user_prompt, search_result) prompt = self.make_newsearch_prompt(user_prompt, search_result)
unvisited = [None] unvisited = [None]
while not complete: while not complete:
answer, reasoning = self.llm_decide(prompt) answer, reasoning = self.llm_decide(prompt)
self.save_notes(answer) self.save_notes(answer)
extracted_form = self.extract_form(answer)
if len(extracted_form) > 0:
self.browser.fill_form_inputs(extracted_form)
self.browser.find_and_click_submit()
if "REQUEST_EXIT" in answer: if "REQUEST_EXIT" in answer:
complete = True complete = True
break break
links = self.extract_links(answer) links = self.extract_links(answer)
if len(unvisited) == 0:
break
if len(links) == 0 or "GO_BACK" in answer: if len(links) == 0 or "GO_BACK" in answer:
unvisited = self.select_unvisited(search_result) unvisited = self.select_unvisited(search_result)
prompt = self.make_newsearch_prompt(user_prompt, unvisited) prompt = self.make_newsearch_prompt(user_prompt, unvisited)
pretty_print(f"Going back to results. Still {len(unvisited)}", color="warning") pretty_print(f"Going back to results. Still {len(unvisited)}", color="warning")
links = [] links = []
continue continue
if len(unvisited) == 0:
break
animate_thinking(f"Navigating to {links[0]}", color="status") animate_thinking(f"Navigating to {links[0]}", color="status")
speech_module.speak(f"Navigating to {links[0]}") speech_module.speak(f"Navigating to {links[0]}")
self.browser.go_to(links[0]) self.browser.go_to(links[0])
self.current_page = links[0]
self.search_history.append(links[0]) self.search_history.append(links[0])
page_text = self.browser.get_text() page_text = self.browser.get_text()
self.navigable_links = self.browser.get_navigable() self.navigable_links = self.browser.get_navigable()
prompt = self.make_navigation_prompt(user_prompt, page_text) prompt = self.make_navigation_prompt(user_prompt, page_text)
speech_module.speak(answer)
self.browser.close() self.browser.close()
prompt = self.conclude_prompt(user_prompt) prompt = self.conclude_prompt(user_prompt)
answer, reasoning = self.llm_request(prompt) self.memory.push('user', prompt)
answer, reasoning = self.llm_request()
pretty_print(answer, color="output") pretty_print(answer, color="output")
return answer, reasoning return answer, reasoning
if __name__ == "__main__": if __name__ == "__main__":
browser = Browser() pass
+4 -4
View File
@@ -7,24 +7,24 @@ from sources.tools.fileFinder import FileFinder
from sources.tools.BashInterpreter import BashInterpreter from sources.tools.BashInterpreter import BashInterpreter
class CasualAgent(Agent): class CasualAgent(Agent):
def __init__(self, model, name, prompt_path, provider): def __init__(self, name, prompt_path, provider, verbose=False):
""" """
The casual agent is a special for casual talk to the user without specific tasks. The casual agent is a special for casual talk to the user without specific tasks.
""" """
super().__init__(model, name, prompt_path, provider) super().__init__(name, prompt_path, provider, verbose)
self.tools = { self.tools = {
"web_search": searxSearch(), "web_search": searxSearch(),
"flight_search": FlightSearch(), "flight_search": FlightSearch(),
"file_finder": FileFinder(), "file_finder": FileFinder(),
"bash": BashInterpreter() "bash": BashInterpreter()
} }
self.role = "casual talking" self.role = "talk"
self.type = "casual_agent"
def process(self, prompt, speech_module) -> str: def process(self, prompt, speech_module) -> str:
complete = False complete = False
self.memory.push('user', prompt) self.memory.push('user', prompt)
self.wait_message(speech_module)
while not complete: while not complete:
animate_thinking("Thinking...", color="status") animate_thinking("Thinking...", color="status")
answer, reasoning = self.llm_request() answer, reasoning = self.llm_request()
+4 -3
View File
@@ -11,8 +11,8 @@ class CoderAgent(Agent):
""" """
The code agent is an agent that can write and execute code. The code agent is an agent that can write and execute code.
""" """
def __init__(self, model, name, prompt_path, provider): def __init__(self, name, prompt_path, provider, verbose=False):
super().__init__(model, name, prompt_path, provider) super().__init__(name, prompt_path, provider, verbose)
self.tools = { self.tools = {
"bash": BashInterpreter(), "bash": BashInterpreter(),
"python": PyInterpreter(), "python": PyInterpreter(),
@@ -20,7 +20,8 @@ class CoderAgent(Agent):
"go": GoInterpreter(), "go": GoInterpreter(),
"file_finder": FileFinder() "file_finder": FileFinder()
} }
self.role = "coding and programming" self.role = "Coding task"
self.type = "code_agent"
def process(self, prompt, speech_module) -> str: def process(self, prompt, speech_module) -> str:
answer = "" answer = ""
+4 -11
View File
@@ -5,35 +5,28 @@ from sources.tools.fileFinder import FileFinder
from sources.tools.BashInterpreter import BashInterpreter from sources.tools.BashInterpreter import BashInterpreter
class FileAgent(Agent): class FileAgent(Agent):
def __init__(self, model, name, prompt_path, provider): def __init__(self, name, prompt_path, provider, verbose=False):
""" """
The file agent is a special agent for file operations. The file agent is a special agent for file operations.
""" """
super().__init__(model, name, prompt_path, provider) super().__init__(name, prompt_path, provider, verbose)
self.tools = { self.tools = {
"file_finder": FileFinder(), "file_finder": FileFinder(),
"bash": BashInterpreter() "bash": BashInterpreter()
} }
self.role = "files operations" self.role = "find and read files"
self.type = "file_agent"
def process(self, prompt, speech_module) -> str: def process(self, prompt, speech_module) -> str:
complete = False
exec_success = False exec_success = False
self.memory.push('user', prompt) self.memory.push('user', prompt)
self.wait_message(speech_module) self.wait_message(speech_module)
while not complete:
if exec_success:
complete = True
animate_thinking("Thinking...", color="status") animate_thinking("Thinking...", color="status")
answer, reasoning = self.llm_request() answer, reasoning = self.llm_request()
exec_success, _ = self.execute_modules(answer) exec_success, _ = self.execute_modules(answer)
answer = self.remove_blocks(answer) answer = self.remove_blocks(answer)
self.last_answer = answer self.last_answer = answer
complete = True
for name, tool in self.tools.items():
if tool.found_executable_blocks():
complete = False # AI read results and continue the conversation
return answer, reasoning return answer, reasoning
if __name__ == "__main__": if __name__ == "__main__":
+6 -6
View File
@@ -3,15 +3,15 @@ from sources.utility import pretty_print, animate_thinking
from sources.agents.agent import Agent from sources.agents.agent import Agent
from sources.agents.code_agent import CoderAgent from sources.agents.code_agent import CoderAgent
from sources.agents.file_agent import FileAgent from sources.agents.file_agent import FileAgent
from sources.agents.casual_agent import CasualAgent from sources.agents.browser_agent import BrowserAgent
from sources.tools.tools import Tools from sources.tools.tools import Tools
class PlannerAgent(Agent): class PlannerAgent(Agent):
def __init__(self, model, name, prompt_path, provider): def __init__(self, name, prompt_path, provider, verbose=False):
""" """
The planner agent is a special agent that divides and conquers the task. The planner agent is a special agent that divides and conquers the task.
""" """
super().__init__(model, name, prompt_path, provider) super().__init__(name, prompt_path, provider, verbose)
self.tools = { self.tools = {
"json": Tools() "json": Tools()
} }
@@ -19,10 +19,10 @@ class PlannerAgent(Agent):
self.agents = { self.agents = {
"coder": CoderAgent(model, name, prompt_path, provider), "coder": CoderAgent(model, name, prompt_path, provider),
"file": FileAgent(model, name, prompt_path, provider), "file": FileAgent(model, name, prompt_path, provider),
"web": CasualAgent(model, name, prompt_path, provider) "web": BrowserAgent(model, name, prompt_path, provider)
} }
self.role = "complex programming tasks and web research" self.role = "Research, setup and code"
self.tag = "json" self.type = "planner_agent"
def parse_agent_tasks(self, text): def parse_agent_tasks(self, text):
tasks = [] tasks = []
+241 -33
View File
@@ -5,11 +5,19 @@ from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, WebDriverException from selenium.common.exceptions import TimeoutException, WebDriverException
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.chrome.options import Options
from typing import List, Tuple
import chromedriver_autoinstaller
import time import time
import os
import shutil
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import markdownify import markdownify
import logging import logging
import sys import sys
import re
from urllib.parse import urlparse
class Browser: class Browser:
def __init__(self, headless=False, anticaptcha_install=False): def __init__(self, headless=False, anticaptcha_install=False):
@@ -20,46 +28,102 @@ class Browser:
'Accept-Language': 'en-US,en;q=0.9', 'Accept-Language': 'en-US,en;q=0.9',
'Referer': 'https://www.google.com/', 'Referer': 'https://www.google.com/',
} }
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.anticaptcha = "https://chrome.google.com/webstore/detail/nopecha-captcha-solver/dknlfmjaanfblgfdfebhijalfmhmjjjo/related"
try: try:
chrome_options = Options() chrome_options = Options()
chrome_path = self.get_chrome_path()
if not chrome_path:
raise FileNotFoundError("Google Chrome not found. Please install it.")
chrome_options.binary_location = chrome_path
if headless: if headless:
chrome_options.add_argument("--headless") chrome_options.add_argument("--headless")
chrome_options.add_argument("--disable-gpu") chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage") chrome_options.add_argument("--disable-dev-shm-usage")
self.driver = webdriver.Chrome(options=chrome_options) chrome_options.add_argument("--autoplay-policy=user-gesture-required")
chrome_options.add_argument("--mute-audio")
chrome_options.add_argument("--disable-webgl")
chrome_options.add_argument("--disable-notifications")
security_prefs = {
"profile.default_content_setting_values.media_stream": 2, # Block webcam/mic
"profile.default_content_setting_values.notifications": 2, # Block notifications
"profile.default_content_setting_values.popups": 2, # Block pop-ups
"profile.default_content_setting_values.geolocation": 2, # Block geolocation
"safebrowsing.enabled": True, # Enable safe browsing
}
chrome_options.add_experimental_option("prefs", security_prefs)
chromedriver_path = shutil.which("chromedriver") # system installed driver.
#If not found, try auto-installing the correct version
if not chromedriver_path:
chromedriver_path = chromedriver_autoinstaller.install()
if not chromedriver_path:
raise FileNotFoundError("ChromeDriver not found. Please install it or add it to your PATH.")
service = Service(chromedriver_path)
self.driver = webdriver.Chrome(service=service, options=chrome_options)
self.wait = WebDriverWait(self.driver, 10) self.wait = WebDriverWait(self.driver, 10)
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self.logger.info("Browser initialized successfully") self.logger.info("Browser initialized successfully")
except Exception as e: except Exception as e:
raise Exception(f"Failed to initialize browser: {str(e)}") raise Exception(f"Failed to initialize browser: {str(e)}")
self.load_anticatpcha()
def go_to(self, url): @staticmethod
def get_chrome_path() -> str:
if sys.platform.startswith("win"):
paths = [
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
os.path.join(os.environ.get("LOCALAPPDATA", ""), "Google\\Chrome\\Application\\chrome.exe") # User install
]
elif sys.platform.startswith("darwin"): # macOS
paths = ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta"]
else: # Linux
paths = ["/usr/bin/google-chrome", "/usr/bin/chromium-browser", "/usr/bin/chromium"]
for path in paths:
if os.path.exists(path) and os.access(path, os.X_OK): # Check if executable
return path
return None
def load_anticatpcha(self):
print("You might want to install the AntiCaptcha extension for captchas.")
self.driver.get(self.anticaptcha)
def go_to(self, url:str) -> bool:
"""Navigate to a specified URL.""" """Navigate to a specified URL."""
try: try:
initial_handles = self.driver.window_handles
self.driver.get(url) self.driver.get(url)
time.sleep(2) # Wait for page to load time.sleep(1)
self.apply_web_safety()
self.logger.info(f"Navigated to: {url}") self.logger.info(f"Navigated to: {url}")
return True return True
except WebDriverException as e: except WebDriverException as e:
self.logger.error(f"Error navigating to {url}: {str(e)}") self.logger.error(f"Error navigating to {url}: {str(e)}")
return False return False
def is_sentence(self, text): def is_sentence(self, text:str) -> bool:
"""Check if the text qualifies as a meaningful sentence or contains important error codes.""" """Check if the text qualifies as a meaningful sentence or contains important error codes."""
text = text.strip() text = text.strip()
error_codes = ["404", "403", "500", "502", "503"] error_codes = ["404", "403", "500", "502", "503"]
if any(code in text for code in error_codes): if any(code in text for code in error_codes):
return True return True
words = text.split() words = re.findall(r'\w+', text, re.UNICODE)
word_count = len(words) word_count = len(words)
has_punctuation = text.endswith(('.', '!', '?')) has_punctuation = any(text.endswith(p) for p in ['.', '', ',', '!', '?', '', '', '', '', '۔'])
is_long_enough = word_count > 5 is_long_enough = word_count > 5
has_letters = any(word.isalpha() for word in words) return (word_count >= 5 and (has_punctuation or is_long_enough))
return (word_count >= 5 and (has_punctuation or is_long_enough) and has_letters)
def get_text(self): def get_text(self) -> str | None:
"""Get page text and convert it to README (Markdown) format.""" """Get page text and convert it to README (Markdown) format."""
try: try:
soup = BeautifulSoup(self.driver.page_source, 'html.parser') soup = BeautifulSoup(self.driver.page_source, 'html.parser')
@@ -72,15 +136,14 @@ class Browser:
lines = (line.strip() for line in text.splitlines()) lines = (line.strip() for line in text.splitlines())
chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
text = "\n".join(chunk for chunk in chunks if chunk and self.is_sentence(chunk)) text = "\n".join(chunk for chunk in chunks if chunk and self.is_sentence(chunk))
#markdown_text = markdownify.markdownify(text, heading_style="ATX")
markdown_text = markdownify.markdownify(text, heading_style="ATX") return "[Start of page]\n" + text + "\n[End of page]"
return markdown_text
except Exception as e: except Exception as e:
self.logger.error(f"Error getting text: {str(e)}") self.logger.error(f"Error getting text: {str(e)}")
return None return None
def clean_url(self, url): def clean_url(self, url:str) -> str:
"""Clean URL to keep only the part needed for navigation to the page"""
clean = url.split('#')[0] clean = url.split('#')[0]
parts = clean.split('?', 1) parts = clean.split('?', 1)
base_url = parts[0] base_url = parts[0]
@@ -96,7 +159,23 @@ class Browser:
return f"{base_url}?{'&'.join(essential_params)}" return f"{base_url}?{'&'.join(essential_params)}"
return base_url return base_url
def get_navigable(self): 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:
return False
parsed_url = urlparse(url)
if not parsed_url.scheme or not parsed_url.netloc:
return False
if re.search(r'/\d+$', parsed_url.path):
return False
image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp']
metadata_extensions = ['.ico', '.xml', '.json', '.rss', '.atom']
for ext in image_extensions + metadata_extensions:
if url.lower().endswith(ext):
return False
return True
def get_navigable(self) -> [str]:
"""Get all navigable links on the current page.""" """Get all navigable links on the current page."""
try: try:
links = [] links = []
@@ -112,33 +191,149 @@ class Browser:
}) })
self.logger.info(f"Found {len(links)} navigable links") self.logger.info(f"Found {len(links)} navigable links")
return [self.clean_url(link['url']) for link in links if link['is_displayed'] == True and len(link) < 256] return [self.clean_url(link['url']) for link in links if (link['is_displayed'] == True and self.is_link_valid(link['url']))]
except Exception as e: except Exception as e:
self.logger.error(f"Error getting navigable links: {str(e)}") self.logger.error(f"Error getting navigable links: {str(e)}")
return [] return []
def click_element(self, xpath): def click_element(self, xpath: str) -> bool:
"""Click an element specified by xpath.""" """Click an element specified by XPath."""
try: try:
element = self.wait.until( element = self.wait.until(EC.element_to_be_clickable((By.XPATH, xpath)))
EC.element_to_be_clickable((By.XPATH, xpath)) if not element.is_displayed():
) return False
if not element.is_enabled():
return False
try:
self.driver.execute_script("arguments[0].scrollIntoView({block: 'center', behavior: 'smooth'});", element)
time.sleep(0.1)
element.click() element.click()
time.sleep(2) # Wait for action to complete
return True return True
except ElementClickInterceptedException as e:
return False
except TimeoutException: except TimeoutException:
self.logger.error(f"Element not found or not clickable: {xpath}") return False
except Exception as e:
self.logger.error(f"Unexpected error clicking element at {xpath}: {str(e)}")
return False return False
def get_current_url(self): def load_js(self, file_name: str) -> str:
path = os.path.join(self.js_scripts_folder, file_name)
try:
with open(path, 'r') as f:
return f.read()
except FileNotFoundError as e:
raise Exception(f"Could not find: {path}") from e
except Exception as e:
raise e
def find_all_inputs(self, timeout=4):
WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located((By.TAG_NAME, "body"))
)
time.sleep(0.5)
script = self.load_js("find_inputs.js")
input_elements = self.driver.execute_script(script)
return input_elements
def get_form_inputs(self) -> List[str]:
"""Extract all input from the page and return them."""
try:
#input_elements = self.driver.find_elements(By.TAG_NAME, "input")
input_elements = self.find_all_inputs()
if not input_elements:
return ["No input forms found on the page."]
form_strings = []
for element in input_elements:
input_type = element["type"] or "text"
if input_type in ["hidden", "submit", "button", "image"] or not element["displayed"]:
continue
input_name = element["text"] or element["id"] or input_type
if input_type == "checkbox" or input_type == "radio":
checked_status = "checked" if element.is_selected() else "unchecked"
form_strings.append(f"[{input_name}]({checked_status})")
else:
form_strings.append(f"[{input_name}]("")")
return form_strings
except Exception as e:
self.logger.error(f"Error extracting form inputs: {str(e)}")
return [f"Error extracting form inputs."]
def get_buttons_xpath(self) -> List[str]:
"""
Find buttons and return their type and xpath.
"""
buttons = self.driver.find_elements(By.TAG_NAME, "button") + \
self.driver.find_elements(By.XPATH, "//input[@type='submit']")
result = []
for i, button in enumerate(buttons):
if not button.is_displayed() or not button.is_enabled():
continue
text = (button.text or button.get_attribute("value") or "").lower().replace(' ', '')
xpath = f"(//button | //input[@type='submit'])[{i + 1}]"
if "login" in text or "sign" in text or "register":
result.append((text, xpath))
result.sort(key=lambda x: len(x[0]))
return result
def find_and_click_submit(self, btn_type:str = 'login') -> None:
buttons = self.get_buttons_xpath()
if len(buttons) == 0:
self.logger.warning(f"No visible buttons found")
for button in buttons:
if button[0] == btn_type:
self.click_element(button[1])
def find_input_xpath_by_name(self, inputs, name: str) -> str | None:
for field in inputs:
if name in field["text"]:
return field["xpath"]
return None
def fill_form_inputs(self, input_list:[str]) -> bool:
"""Fill form inputs based on a list of [name](value) strings."""
inputs = self.find_all_inputs()
try:
for input_str in input_list:
match = re.match(r'\[(.*?)\]\((.*?)\)', input_str)
if not match:
self.logger.warning(f"Invalid format for input: {input_str}")
continue
name, value = match.groups()
name = name.strip()
value = value.strip()
xpath = self.find_input_xpath_by_name(inputs, name)
if not xpath:
continue
element = self.driver.find_element(By.XPATH, xpath)
input_type = (element.get_attribute("type") or "text").lower()
if input_type in ["checkbox", "radio"]:
is_checked = element.is_selected()
should_be_checked = value.lower() == "checked"
if is_checked != should_be_checked:
element.click()
self.logger.info(f"Set {name} to {value}")
else:
element.send_keys(value)
self.logger.info(f"Filled {name} with {value}")
return True
except Exception as e:
self.logger.error(f"Error filling form inputs: {str(e)}")
return False
def get_current_url(self) -> str:
"""Get the current URL of the page.""" """Get the current URL of the page."""
return self.driver.current_url return self.driver.current_url
def get_page_title(self): def get_page_title(self) -> str:
"""Get the title of the current page.""" """Get the title of the current page."""
return self.driver.title return self.driver.title
def scroll_bottom(self): def scroll_bottom(self) -> bool:
"""Scroll to the bottom of the page.""" """Scroll to the bottom of the page."""
try: try:
self.driver.execute_script( self.driver.execute_script(
@@ -150,7 +345,7 @@ class Browser:
self.logger.error(f"Error scrolling: {str(e)}") self.logger.error(f"Error scrolling: {str(e)}")
return False return False
def screenshot(self, filename): def screenshot(self, filename:str) -> bool:
"""Take a screenshot of the current page.""" """Take a screenshot of the current page."""
try: try:
self.driver.save_screenshot(filename) self.driver.save_screenshot(filename)
@@ -160,6 +355,13 @@ class Browser:
self.logger.error(f"Error taking screenshot: {str(e)}") self.logger.error(f"Error taking screenshot: {str(e)}")
return False return False
def apply_web_safety(self):
"""
Apply security measures to block any website malicious/annoying execution, privacy violation etc..
"""
script = self.load_js("inject_safety_script.js")
input_elements = self.driver.execute_script(script)
def close(self): def close(self):
"""Close the browser.""" """Close the browser."""
try: try:
@@ -176,13 +378,19 @@ if __name__ == "__main__":
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
browser = Browser(headless=False) browser = Browser(headless=False)
time.sleep(8)
try: try:
browser.go_to("https://karpathy.github.io/") print("AntiCaptcha Test")
text = browser.get_text() browser.go_to("https://www.google.com/recaptcha/api2/demo")
print("Page Text in Markdown:") time.sleep(5)
print(text) print("Form Test:")
links = browser.get_navigable() browser.go_to("https://practicetestautomation.com/practice-test-login/")
print("\nNavigable Links:", links) inputs = browser.get_form_inputs()
inputs = ['[username](student)', f'[password](Password123)', '[appOtp]()', '[backupOtp]()']
browser.fill_form_inputs(inputs)
browser.find_and_click_submit()
print("Stress test")
browser.go_to("https://theannoyingsite.com/")
finally: finally:
browser.close() browser.close()
+9 -8
View File
@@ -27,7 +27,7 @@ class Interaction:
self.transcriber = AudioTranscriber(self.ai_name, verbose=False) self.transcriber = AudioTranscriber(self.ai_name, verbose=False)
self.recorder = AudioRecorder() self.recorder = AudioRecorder()
if tts_enabled: if tts_enabled:
self.speech.speak("Hello Sir, we are online and ready. What can I do for you ?") self.speech.speak("Hello, we are online and ready. What can I do for you ?")
if recover_last_session: if recover_last_session:
self.recover_last_session() self.recover_last_session()
@@ -35,7 +35,7 @@ class Interaction:
"""Find the name of the default AI. It is required for STT as a trigger word.""" """Find the name of the default AI. It is required for STT as a trigger word."""
ai_name = "jarvis" ai_name = "jarvis"
for agent in self.agents: for agent in self.agents:
if agent.role == "talking": if agent.type == "casual_agent":
ai_name = agent.agent_name ai_name = agent.agent_name
break break
return ai_name return ai_name
@@ -43,12 +43,12 @@ class Interaction:
def recover_last_session(self): def recover_last_session(self):
"""Recover the last session.""" """Recover the last session."""
for agent in self.agents: for agent in self.agents:
agent.memory.load_memory() agent.memory.load_memory(agent.type)
def save_session(self): def save_session(self):
"""Save the current session.""" """Save the current session."""
for agent in self.agents: for agent in self.agents:
agent.memory.save_memory() agent.memory.save_memory(agent.type)
def is_active(self) -> bool: def is_active(self) -> bool:
return self.is_active return self.is_active
@@ -58,7 +58,7 @@ class Interaction:
buffer = "" buffer = ""
PROMPT = "\033[1;35m➤➤➤ \033[0m" PROMPT = "\033[1;35m➤➤➤ \033[0m"
while buffer == "" or buffer.isascii() == False: while not buffer:
try: try:
buffer = input(PROMPT) buffer = input(PROMPT)
except EOFError: except EOFError:
@@ -98,10 +98,11 @@ class Interaction:
agent = self.router.select_agent(self.last_query) agent = self.router.select_agent(self.last_query)
if agent is None: if agent is None:
return return
if self.current_agent != agent: if self.current_agent != agent and self.last_answer is not None:
self.current_agent = agent ## get history from previous agent, good ?
# get history from previous agent, good ?
self.current_agent.memory.push('user', self.last_query) self.current_agent.memory.push('user', self.last_query)
self.current_agent.memory.push('assistant', self.last_answer)
self.current_agent = agent
self.last_answer, _ = agent.process(self.last_query, self.speech) self.last_answer, _ = agent.process(self.last_query, self.speech)
def show_answer(self) -> None: def show_answer(self) -> None:
+84
View File
@@ -0,0 +1,84 @@
import langid
import re
import nltk
from nltk.sentiment.vader import SentimentIntensityAnalyzer
class LanguageUtility:
"""LanguageUtility for language, or emotion identification"""
def __init__(self):
try:
nltk.data.find('vader_lexicon')
except LookupError:
nltk.download('vader_lexicon')
self.sid = SentimentIntensityAnalyzer()
def detect_language(self, text: str) -> str:
"""
Detect the language of the given text using langdetect
Args:
text: string to analyze
Returns: ISO639-1 language code
"""
langid.set_languages(['fr', 'en', 'zh', 'es']) # ISO 639-1 codes
lang, score = langid.classify(text)
return lang
def detect_emotion(self, text: str) -> str:
"""
Detect the dominant emotion in the given text
Args:
text: string to analyze
Returns: string of the dominant emotion
"""
try:
scores = self.sid.polarity_scores(text)
emotions = {
'Happy': max(scores['pos'], 0),
'Angry': 0,
'Sad': max(scores['neg'], 0),
'Fear': 0,
'Surprise': 0
}
if scores['compound'] < -0.5:
emotions['Angry'] = abs(scores['compound']) * 0.5
emotions['Fear'] = abs(scores['compound']) * 0.5
elif scores['compound'] > 0.5:
emotions['Happy'] = scores['compound']
emotions['Surprise'] = scores['compound'] * 0.5
dominant_emotion = max(emotions, key=emotions.get)
if emotions[dominant_emotion] == 0:
return 'Neutral'
return dominant_emotion
except Exception as e:
raise e
def analyze(self, text):
"""
Combined analysis of language and emotion
Args:
text: string to analyze
Returns: dictionary with language and emotion results
"""
try:
language = self.detect_language(text)
emotions = self.detect_emotion(text)
return {
"language": language,
"emotions": emotions
}
except Exception as e:
raise e
if __name__ == "__main__":
detector = LanguageUtility()
test_texts = [
"I am so happy today!",
"Qué tristeza siento ahora",
"我不要去巴黎",
"La vie c'est cool"
]
for text in test_texts:
print(f"\nAnalyzing: {text}")
result = detector.analyze(text)
print(result)
+36 -14
View File
@@ -12,6 +12,8 @@ from huggingface_hub import InferenceClient
import os import os
import httpx import httpx
from sources.utility import pretty_print, animate_thinking
class Provider: class Provider:
def __init__(self, provider_name, model, server_address = "127.0.0.1:5000"): def __init__(self, provider_name, model, server_address = "127.0.0.1:5000"):
self.provider_name = provider_name.lower() self.provider_name = provider_name.lower()
@@ -21,17 +23,18 @@ class Provider:
"ollama": self.ollama_fn, "ollama": self.ollama_fn,
"server": self.server_fn, "server": self.server_fn,
"openai": self.openai_fn, "openai": self.openai_fn,
"huggingface": self.huggingface_fn "huggingface": self.huggingface_fn,
"deepseek-api": self.deepseek_fn
} }
self.api_key = None self.api_key = None
self.unsafe_providers = ["openai"] self.unsafe_providers = ["openai", "deepseek-api"]
if self.provider_name not in self.available_providers: if self.provider_name not in self.available_providers:
raise ValueError(f"Unknown provider: {provider_name}") raise ValueError(f"Unknown provider: {provider_name}")
if self.provider_name in self.unsafe_providers: if self.provider_name in self.unsafe_providers:
print("Warning: you are using an API provider. You data will be sent to the cloud.") pretty_print("Warning: you are using an API provider. You data will be sent to the cloud.", color="warning")
self.get_api_key(self.provider_name) self.api_key = self.get_api_key(self.provider_name)
elif self.server != "": elif self.server != "ollama":
print("Provider", provider_name, "initialized at", self.server) pretty_print(f"Provider: {provider_name} initialized at {self.server}", color="success")
self.check_address_format(self.server) self.check_address_format(self.server)
if not self.is_ip_online(self.server.split(':')[0]): if not self.is_ip_online(self.server.split(':')[0]):
raise Exception(f"Server at {self.server} is offline.") raise Exception(f"Server at {self.server} is offline.")
@@ -51,6 +54,7 @@ class Provider:
Validate if the address is valid IP. Validate if the address is valid IP.
""" """
try: try:
address = address.replace('http://', '')
ip, port = address.rsplit(":", 1) ip, port = address.rsplit(":", 1)
if all(c.lower() in ".:abcdef0123456789" for c in ip): if all(c.lower() in ".:abcdef0123456789" for c in ip):
ipaddress.ip_address(ip) ipaddress.ip_address(ip)
@@ -79,6 +83,8 @@ class Provider:
""" """
Check if an IP address is online by sending a ping request. Check if an IP address is online by sending a ping request.
""" """
if ip_address == "127.0.0.1":
return True
param = '-n' if platform.system().lower() == 'windows' else '-c' param = '-n' if platform.system().lower() == 'windows' else '-c'
command = ['ping', param, '1', ip_address] command = ['ping', param, '1', ip_address]
try: try:
@@ -86,13 +92,11 @@ class Provider:
if output.returncode == 0: if output.returncode == 0:
return True return True
else: else:
print("errorcode:", output)
return False return False
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
print("timeout") return False
return True
except Exception as e: except Exception as e:
print(f"is_ip_online error:\n{e}") pretty_print(f"Error with ping request {str(e)}", color="failure")
return False return False
def server_fn(self, history, verbose = False): def server_fn(self, history, verbose = False):
@@ -114,7 +118,7 @@ class Provider:
is_complete = bool(response.json()["is_complete"]) is_complete = bool(response.json()["is_complete"])
time.sleep(2) time.sleep(2)
except KeyError as e: except KeyError as e:
raise f"{str(e)}\n\nError occured with server route. Are you using the correct address for the config.ini provider?" raise Exception(f"{str(e)}\n\nError occured with server route. Are you using the correct address for the config.ini provider?") from e
except Exception as e: except Exception as e:
raise e raise e
return thought return thought
@@ -138,8 +142,9 @@ class Provider:
raise Exception("\nOllama connection failed. provider should not be set to ollama if server address is not localhost") from e raise Exception("\nOllama connection failed. provider should not be set to ollama if server address is not localhost") from e
except ollama.ResponseError as e: except ollama.ResponseError as e:
if e.status_code == 404: if e.status_code == 404:
print(f"Downloading {self.model}...") animate_thinking(f"Downloading {self.model}...")
ollama.pull(self.model) ollama.pull(self.model)
self.ollama_fn(history, verbose)
if "refused" in str(e).lower(): if "refused" in str(e).lower():
raise Exception("Ollama connection failed. is the server running ?") from e raise Exception("Ollama connection failed. is the server running ?") from e
raise e raise e
@@ -164,8 +169,7 @@ class Provider:
""" """
Use openai to generate text. Use openai to generate text.
""" """
api_key = self.get_api_key("openai") client = OpenAI(api_key=self.api_key)
client = OpenAI(api_key=api_key)
try: try:
response = client.chat.completions.create( response = client.chat.completions.create(
model=self.model, model=self.model,
@@ -178,6 +182,24 @@ class Provider:
except Exception as e: except Exception as e:
raise Exception(f"OpenAI API error: {str(e)}") from e raise Exception(f"OpenAI API error: {str(e)}") from e
def deepseek_fn(self, history, verbose=False):
"""
Use deepseek api to generate text.
"""
client = OpenAI(api_key=self.api_key, base_url="https://api.deepseek.com")
try:
response = client.chat.completions.create(
model="deepseek-chat",
messages=history,
stream=False
)
thought = response.choices[0].message.content
if verbose:
print(thought)
return thought
except Exception as e:
raise Exception(f"Deepseek API error: {str(e)}") from e
def test_fn(self, history, verbose = True): def test_fn(self, history, verbose = True):
""" """
This function is used to conduct tests. This function is used to conduct tests.
+17 -13
View File
@@ -20,7 +20,7 @@ class Memory():
recover_last_session: bool = False, recover_last_session: bool = False,
memory_compression: bool = True): memory_compression: bool = True):
self.memory = [] self.memory = []
self.memory = [{'role': 'user', 'content': system_prompt}] self.memory = [{'role': 'system', 'content': system_prompt}]
self.session_time = datetime.datetime.now() self.session_time = datetime.datetime.now()
self.session_id = str(uuid.uuid4()) self.session_id = str(uuid.uuid4())
@@ -38,20 +38,23 @@ class Memory():
def get_filename(self) -> str: def get_filename(self) -> str:
return f"memory_{self.session_time.strftime('%Y-%m-%d_%H-%M-%S')}.txt" return f"memory_{self.session_time.strftime('%Y-%m-%d_%H-%M-%S')}.txt"
def save_memory(self) -> None: def save_memory(self, agent_type: str = "casual_agent") -> None:
"""Save the session memory to a file.""" """Save the session memory to a file."""
if not os.path.exists(self.conversation_folder): if not os.path.exists(self.conversation_folder):
os.makedirs(self.conversation_folder) os.makedirs(self.conversation_folder)
save_path = os.path.join(self.conversation_folder, agent_type)
if not os.path.exists(save_path):
os.makedirs(save_path)
filename = self.get_filename() filename = self.get_filename()
path = os.path.join(self.conversation_folder, filename) path = os.path.join(save_path, filename)
json_memory = json.dumps(self.memory) json_memory = json.dumps(self.memory)
with open(path, 'w') as f: with open(path, 'w') as f:
f.write(json_memory) f.write(json_memory)
def find_last_session_path(self) -> str: def find_last_session_path(self, path) -> str:
"""Find the last session path.""" """Find the last session path."""
saved_sessions = [] saved_sessions = []
for filename in os.listdir(self.conversation_folder): for filename in os.listdir(path):
if filename.startswith('memory_'): if filename.startswith('memory_'):
date = filename.split('_')[1] date = filename.split('_')[1]
saved_sessions.append((filename, date)) saved_sessions.append((filename, date))
@@ -60,14 +63,15 @@ class Memory():
return saved_sessions[0][0] return saved_sessions[0][0]
return None return None
def load_memory(self) -> None: def load_memory(self, agent_type: str = "casual_agent") -> None:
"""Load the memory from the last session.""" """Load the memory from the last session."""
if not os.path.exists(self.conversation_folder): save_path = os.path.join(self.conversation_folder, agent_type)
if not os.path.exists(save_path):
return return
filename = self.find_last_session_path() filename = self.find_last_session_path(save_path)
if filename is None: if filename is None:
return return
path = os.path.join(self.conversation_folder, filename) path = os.path.join(save_path, filename)
with open(path, 'r') as f: with open(path, 'r') as f:
self.memory = json.load(f) self.memory = json.load(f)
@@ -76,10 +80,10 @@ class Memory():
def push(self, role: str, content: str) -> None: def push(self, role: str, content: str) -> None:
"""Push a message to the memory.""" """Push a message to the memory."""
self.memory.append({'role': role, 'content': content})
# EXPERIMENTAL
if self.memory_compression and role == 'assistant': if self.memory_compression and role == 'assistant':
self.compress() self.compress()
# we don't compress the last message
self.memory.append({'role': role, 'content': content})
def clear(self) -> None: def clear(self) -> None:
self.memory = [] self.memory = []
@@ -129,9 +133,9 @@ class Memory():
if not self.memory_compression: if not self.memory_compression:
return return
for i in range(len(self.memory)): for i in range(len(self.memory)):
if i <= 2: if i < 3:
continue continue
if self.memory[i]['role'] == 'assistant': if len(self.memory[i]['content']) > 1024:
self.memory[i]['content'] = self.summarize(self.memory[i]['content']) self.memory[i]['content'] = self.summarize(self.memory[i]['content'])
if __name__ == "__main__": if __name__ == "__main__":
+45 -24
View File
@@ -9,6 +9,7 @@ from sources.agents.agent import Agent
from sources.agents.code_agent import CoderAgent from sources.agents.code_agent import CoderAgent
from sources.agents.casual_agent import CasualAgent from sources.agents.casual_agent import CasualAgent
from sources.agents.planner_agent import PlannerAgent from sources.agents.planner_agent import PlannerAgent
from sources.agents.browser_agent import BrowserAgent
from sources.utility import pretty_print from sources.utility import pretty_print
class AgentRouter: class AgentRouter:
@@ -30,7 +31,7 @@ class AgentRouter:
else: else:
return "cpu" return "cpu"
def classify_text(self, text: str, threshold: float = 0.5) -> list: def classify_text(self, text: str, threshold: float = 0.4) -> list:
""" """
Classify the text into labels (agent roles). Classify the text into labels (agent roles).
Args: Args:
@@ -61,36 +62,56 @@ class AgentRouter:
result = self.classify_text(text) result = self.classify_text(text)
for agent in self.agents: for agent in self.agents:
if result["labels"][0] == agent.role: if result["labels"][0] == agent.role:
pretty_print(f"Selected agent: {agent.agent_name}", color="warning") pretty_print(f"Selected agent: {agent.agent_name} (roles: {agent.role})", color="warning")
return agent return agent
return None return None
if __name__ == "__main__": if __name__ == "__main__":
agents = [ agents = [
CoderAgent("deepseek-r1:14b", "agent1", "../prompts/coder_agent.txt", "server"), CasualAgent("deepseek-r1:14b", "jarvis", "../prompts/casual_agent.txt", None),
CasualAgent("deepseek-r1:14b", "agent2", "../prompts/casual_agent.txt", "server"), BrowserAgent("deepseek-r1:14b", "browser", "../prompts/planner_agent.txt", None),
PlannerAgent("deepseek-r1:14b", "agent3", "../prompts/planner_agent.txt", "server") CoderAgent("deepseek-r1:14b", "coder", "../prompts/coder_agent.txt", None)
] ]
router = AgentRouter(agents) router = AgentRouter(agents)
texts = [
texts = [""" "Write a python script to check if the device on my network is connected to the internet",
Write a python script to check if the device on my network is connected to the internet #"Peut tu écrire un script python qui vérifie si l'appareil sur mon réseau est connecté à internet?",
""", #"写一个Python脚本,检查我网络上的设备是否连接到互联网",
""" "Hey could you search the web for the latest news on the tesla stock market ?",
Hey could you search the web for the latest news on the stock market ? #"嘿,你能搜索网页上关于股票市场的最新新闻吗?",
""", #"Yo, cherche sur internet comment va tesla en bourse.",
""" "I would like you to search for weather api and then make an app using this API",
hey can you give give a list of the files in the current directory ? #"我想让你搜索天气API,然后用这个API做一个应用程序",
""", #"J'aimerais que tu cherche une api météo et que l'utilise pour faire une application",
""" "Plan a 3-day trip to New York, including flights and hotels.",
Make a cool game to illustrate the current relation between USA and europe #"计划一次为期3天的纽约之旅,包括机票和酒店。",
""" #"Planifie un trip de 3 jours à Paris, y compris les vols et hotels.",
"Find me the latest research papers on AI.",
#"给我找最新的AI研究论文。",
#"Trouve moi les derniers papiers de recherche en IA",
"Help me write a C++ program to sort an array",
#"帮我写一个C++程序来排序数组",
#"Aide moi à faire un programme c++ pour trier une array.",
"Whats the weather like today? Oh, and can you find a good weather app?",
#"今天天气怎么样?哦,你还能找到一个好的天气应用程序吗?",
#"La météo est comment aujourd'hui ? oh et trouve moi une bonne appli météo tant que tu y est.",
"Can you debug this Java code? Its not working.",
#"你能调试这段Java代码吗?它不起作用。",
#"Peut tu m'aider à debugger ce code java, ça marche pas",
"What's the latest brainrot on the internet ?",
#"互联网上最新的“脑残”是什么?",
#"Quel est la dernière connerie sur internet ?",
"i would like to setup a new AI project, index as mark2",
#"我想建立一个新的 AI 项目,索引为 Mark2",
#"Je voudrais configurer un nouveau projet d'IA, index Mark2",
"Hey, can you find the old_project.zip file somewhere on my drive?",
#"嘿,你能在我驱动器上找到old_project.zip文件吗?",
#"Hé trouve moi le old_project.zip, il est quelque part sur mon disque.",
"Tell me a funny story",
#"给我讲一个有趣的故事",
#"Raconte moi une histoire drole"
] ]
for text in texts: for text in texts:
print(text) print("Input text:", text)
results = router.classify_text(text)
for result in results:
print(result["label"], "=>", result["score"])
agent = router.select_agent(text) agent = router.select_agent(text)
print("Selected agent role:", agent.role) print()
+3 -3
View File
@@ -3,9 +3,10 @@ import queue
import threading import threading
import numpy as np import numpy as np
import torch import torch
from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline
import time import time
from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline
import librosa import librosa
import pyaudio
audio_queue = queue.Queue() audio_queue = queue.Queue()
done = False done = False
@@ -14,8 +15,7 @@ class AudioRecorder:
""" """
AudioRecorder is a class that records audio from the microphone and adds it to the audio queue. AudioRecorder is a class that records audio from the microphone and adds it to the audio queue.
""" """
def __init__(self, format: int, channels: int = 1, rate: int = 4096, chunk: int = 8192, record_seconds: int = 5, verbose: bool = False): def __init__(self, format: int = pyaudio.paInt16, channels: int = 1, rate: int = 4096, chunk: int = 8192, record_seconds: int = 5, verbose: bool = False):
import pyaudio
self.format = format self.format = format
self.channels = channels self.channels = channels
self.rate = rate self.rate = rate
+7 -6
View File
@@ -4,6 +4,7 @@ import soundfile as sf
import subprocess import subprocess
import re import re
import platform import platform
from sys import modules
class Speech(): class Speech():
""" """
@@ -24,7 +25,7 @@ class Speech():
self.voice = self.voice_map[language][2] self.voice = self.voice_map[language][2]
self.speed = 1.2 self.speed = 1.2
def speak(self, sentence: str, voice_number: int = 1): def speak(self, sentence: str, voice_number: int = 1 , audio_file: str = 'sample.wav'):
""" """
Convert text to speech using an AI model and play the audio. Convert text to speech using an AI model and play the audio.
@@ -38,17 +39,17 @@ class Speech():
sentence, voice=self.voice, sentence, voice=self.voice,
speed=self.speed, split_pattern=r'\n+' speed=self.speed, split_pattern=r'\n+'
) )
for i, (gs, ps, audio) in enumerate(generator): for i, (_, _, audio) in enumerate(generator):
audio_file = 'sample.wav' if 'ipykernel' in modules: #only display in jupyter notebook.
display(Audio(data=audio, rate=24000, autoplay=i==0), display_id=False) display(Audio(data=audio, rate=24000, autoplay=i==0), display_id=False)
sf.write(audio_file, audio, 24000) # save each audio file sf.write(audio_file, audio, 24000) # save each audio file
if platform.system().lower() == "windows": if platform.system().lower() == "windows":
import winsound import winsound
winsound.PlaySound(audio_file, winsound.SND_FILENAME) winsound.PlaySound(audio_file, winsound.SND_FILENAME)
elif platform.system().lower() == "linux": elif platform.system().lower() == "darwin": # macOS
subprocess.call(["aplay", audio_file])
else:
subprocess.call(["afplay", audio_file]) subprocess.call(["afplay", audio_file])
else: # linux or other.
subprocess.call(["aplay", audio_file])
def replace_url(self, url: re.Match) -> str: def replace_url(self, url: re.Match) -> str:
""" """
+1
View File
@@ -35,6 +35,7 @@ class PyInterpreter(Tools):
try: try:
try: try:
buffer = exec(code, global_vars) buffer = exec(code, global_vars)
print(buffer)
if buffer is not None: if buffer is not None:
output = buffer + '\n' output = buffer + '\n'
except Exception as e: except Exception as e:
+4 -3
View File
@@ -50,7 +50,6 @@ class searxSearch(Tools):
"""Check all links, one by one.""" """Check all links, one by one."""
# TODO Make it asyncromous or smth # TODO Make it asyncromous or smth
statuses = [] statuses = []
print("Web scrawl to verify links accessibilty...")
for i, link in enumerate(links): for i, link in enumerate(links):
status = self.link_valid(link) status = self.link_valid(link)
statuses.append(status) statuses.append(status)
@@ -76,7 +75,7 @@ class searxSearch(Tools):
'Upgrade-Insecure-Requests': '1', 'Upgrade-Insecure-Requests': '1',
'User-Agent': self.user_agent 'User-Agent': self.user_agent
} }
data = f"q={query}&categories=general&language=auto&time_range=&safesearch=0&theme=simple" data = f"q={query}&categories=general&language=auto&time_range=&safesearch=0&theme=simple".encode('utf-8')
try: try:
response = requests.post(search_url, headers=headers, data=data, verify=False) response = requests.post(search_url, headers=headers, data=data, verify=False)
response.raise_for_status() response.raise_for_status()
@@ -90,9 +89,11 @@ class searxSearch(Tools):
title = article.find('h3').text.strip() if article.find('h3') else "No Title" title = article.find('h3').text.strip() if article.find('h3') else "No Title"
description = article.find('p', class_='content').text.strip() if article.find('p', class_='content') else "No Description" description = article.find('p', class_='content').text.strip() if article.find('p', class_='content') else "No Description"
results.append(f"Title:{title}\nSnippet:{description}\nLink:{url}") results.append(f"Title:{title}\nSnippet:{description}\nLink:{url}")
if len(results) == 0:
return "No search results, web search failed."
return "\n\n".join(results) # Return results as a single string, separated by newlines return "\n\n".join(results) # Return results as a single string, separated by newlines
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
return f"Error during search: {str(e)}" raise Exception("\nSearxng search failed. did you run start_services.sh? is docker still running?") from e
def execution_failure_check(self, output: str) -> bool: def execution_failure_check(self, output: str) -> bool:
""" """
-2
View File
@@ -118,10 +118,8 @@ class Tools():
save_path_file = os.path.basename(save_path) save_path_file = os.path.basename(save_path)
directory = os.path.join(self.current_dir, save_path_dir) directory = os.path.join(self.current_dir, save_path_dir)
if directory and not os.path.exists(directory): if directory and not os.path.exists(directory):
print(f"Creating directory: {directory}")
os.makedirs(directory) os.makedirs(directory)
for block in blocks: for block in blocks:
print(f"Saving code block to: {save_path}")
with open(os.path.join(directory, save_path_file), 'w') as f: with open(os.path.join(directory, save_path_file), 'w') as f:
f.write(block) f.write(block)
-1
View File
@@ -58,7 +58,6 @@ class webSearch(Tools):
"""Check all links, one by one.""" """Check all links, one by one."""
# TODO Make it asyncromous or smth # TODO Make it asyncromous or smth
statuses = [] statuses = []
print("Workers started, scrawling the web...")
for i, link in enumerate(links): for i, link in enumerate(links):
status = self.link_valid(link) status = self.link_valid(link)
statuses.append(status) statuses.append(status)
+49
View File
@@ -0,0 +1,49 @@
function findInputs(element, result = []) {
// Find all <input> elements in the current DOM tree
const inputs = element.querySelectorAll('input');
inputs.forEach(input => {
result.push({
tagName: input.tagName,
text: input.name || '',
type: input.type || '',
class: input.className || '',
xpath: getXPath(input),
displayed: isElementDisplayed(input)
});
});
const allElements = element.querySelectorAll('*');
allElements.forEach(el => {
if (el.shadowRoot) {
findInputs(el.shadowRoot, result);
}
});
return result;
}
// function to get the XPath of an element
function getXPath(element) {
if (!element) return '';
if (element.id !== '') return '//*[@id="' + element.id + '"]';
if (element === document.body) return '/html/body';
let ix = 0;
const siblings = element.parentNode ? element.parentNode.childNodes : [];
for (let i = 0; i < siblings.length; i++) {
const sibling = siblings[i];
if (sibling === element) {
return getXPath(element.parentNode) + '/' + element.tagName.toLowerCase() + '[' + (ix + 1) + ']';
}
if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
ix++;
}
}
return '';
}
return findInputs(document.body);
function isElementDisplayed(element) {
const style = window.getComputedStyle(element);
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
return false;
}
return true;
}
@@ -0,0 +1,36 @@
// Block hardware access by removing or disabling APIs
Object.defineProperty(navigator, 'serial', { get: () => undefined });
Object.defineProperty(navigator, 'hid', { get: () => undefined });
Object.defineProperty(navigator, 'bluetooth', { get: () => undefined });
// Block media playback
HTMLMediaElement.prototype.play = function() {
this.pause(); // Immediately pause if play is called
return Promise.reject('Blocked by script');
};
// Block fullscreen requests
Element.prototype.requestFullscreen = function() {
console.log('Blocked fullscreen request');
return Promise.reject('Blocked by script');
};
// Block pointer lock
Element.prototype.requestPointerLock = function() {
console.log('Blocked pointer lock');
};
// Block iframe creation (optional, since browser already blocks these)
const originalCreateElement = document.createElement;
document.createElement = function(tagName) {
if (tagName.toLowerCase() === 'iframe') {
console.log('Blocked iframe creation');
return null;
}
return originalCreateElement.apply(this, arguments);
};
//block fetch
window.fetch = function() {
console.log('Blocked fetch request');
return Promise.reject('Blocked');
};
// Block annoying dialogs
window.alert = function() {};
window.confirm = function() { return false; };
window.prompt = function() { return null; };