├── src ├── vite-env.d.ts ├── tauri.d.ts ├── main.tsx ├── assets │ └── react.svg ├── App.css └── App.tsx ├── src-tauri ├── build.rs ├── icons │ ├── icon.ico │ ├── icon.png │ ├── 128x128.png │ ├── 32x32.png │ ├── icon.icns │ ├── StoreLogo.png │ ├── 128x128@2x.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ └── Square89x89Logo.png ├── .gitignore ├── capabilities │ └── default.json ├── src │ ├── lib.rs │ └── main.rs ├── tauri.conf.json ├── Cargo.toml └── Cargo.lock ├── .vscode └── extensions.json ├── tsconfig.node.json ├── backend ├── package.json ├── agent.js └── package-lock.json ├── .gitignore ├── index.html ├── package.json ├── tsconfig.json ├── vite.config.ts ├── public ├── vite.svg ├── tauri.svg └── browser-icon.svg └── README.md /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AIAnytime/agent-browser/main/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src/tauri.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@tauri-apps/api/tauri' { 2 | export function invoke(cmd: string, args?: Record): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Generated by Tauri 6 | # will have schema files for capabilities auto-completion 7 | /gen/schemas 8 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | 5 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 6 | 7 | 8 | , 9 | ); 10 | -------------------------------------------------------------------------------- /src-tauri/capabilities/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../gen/schemas/desktop-schema.json", 3 | "identifier": "default", 4 | "description": "Capability for the main window", 5 | "windows": ["main"], 6 | "permissions": [ 7 | "core:default", 8 | "opener:default" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "main": "agent.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "description": "", 12 | "dependencies": { 13 | "node-fetch": "^3.3.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .env -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Agentic Browser 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src-tauri/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ 2 | #[tauri::command] 3 | fn greet(name: &str) -> String { 4 | format!("Hello, {}! You've been greeted from Rust!", name) 5 | } 6 | 7 | #[cfg_attr(mobile, tauri::mobile_entry_point)] 8 | pub fn run() { 9 | tauri::Builder::default() 10 | .plugin(tauri_plugin_opener::init()) 11 | .invoke_handler(tauri::generate_handler![greet]) 12 | .run(tauri::generate_context!()) 13 | .expect("error while running tauri application"); 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agent-browser", 3 | "private": true, 4 | "version": "0.1.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview", 10 | "tauri": "tauri" 11 | }, 12 | "dependencies": { 13 | "@tauri-apps/api": "^2.6.0", 14 | "@tauri-apps/plugin-opener": "^2", 15 | "react": "^18.3.1", 16 | "react-dom": "^18.3.1" 17 | }, 18 | "devDependencies": { 19 | "@tauri-apps/cli": "^2", 20 | "@types/react": "^18.3.1", 21 | "@types/react-dom": "^18.3.1", 22 | "@vitejs/plugin-react": "^4.3.4", 23 | "typescript": "~5.6.2", 24 | "vite": "^6.0.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.tauri.app/config/2", 3 | "productName": "agent-browser", 4 | "version": "0.1.0", 5 | "identifier": "com.agent-browser.app", 6 | "build": { 7 | "beforeDevCommand": "npm run dev", 8 | "devUrl": "http://localhost:1420", 9 | "beforeBuildCommand": "npm run build", 10 | "frontendDist": "../dist" 11 | }, 12 | "app": { 13 | "windows": [ 14 | { 15 | "title": "agent-browser", 16 | "width": 800, 17 | "height": 600 18 | } 19 | ], 20 | "security": { 21 | "csp": null 22 | } 23 | }, 24 | "bundle": { 25 | "active": true, 26 | "targets": "all", 27 | "icon": [ 28 | "icons/32x32.png", 29 | "icons/128x128.png", 30 | "icons/128x128@2x.png", 31 | "icons/icon.icns", 32 | "icons/icon.ico" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "agent-browser" 3 | version = "0.1.0" 4 | description = "A Tauri App" 5 | authors = ["you"] 6 | edition = "2021" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [lib] 11 | # The `_lib` suffix may seem redundant but it is necessary 12 | # to make the lib name unique and wouldn't conflict with the bin name. 13 | # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 14 | name = "agent_browser_lib" 15 | crate-type = ["staticlib", "cdylib", "rlib"] 16 | 17 | [build-dependencies] 18 | tauri-build = { version = "2", features = [] } 19 | 20 | [dependencies] 21 | tauri = { version = "2", features = [] } 22 | tauri-plugin-opener = "2" 23 | serde = { version = "1", features = ["derive"] } 24 | serde_json = "1" 25 | reqwest = { version = "0.12.22", features = ["json"] } 26 | 27 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // @ts-expect-error process is a nodejs global 5 | const host = process.env.TAURI_DEV_HOST; 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig(async () => ({ 9 | plugins: [react()], 10 | 11 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` 12 | // 13 | // 1. prevent vite from obscuring rust errors 14 | clearScreen: false, 15 | // 2. tauri expects a fixed port, fail if that port is not available 16 | server: { 17 | port: 1420, 18 | strictPort: true, 19 | host: host || false, 20 | hmr: host 21 | ? { 22 | protocol: "ws", 23 | host, 24 | port: 1421, 25 | } 26 | : undefined, 27 | watch: { 28 | // 3. tell vite to ignore watching `src-tauri` 29 | ignored: ["**/src-tauri/**"], 30 | }, 31 | }, 32 | })); 33 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | use reqwest::Client; 2 | use serde_json; 3 | use std::process::Command; 4 | use tauri::command; 5 | 6 | #[command] 7 | async fn ask_agent(prompt: String) -> Result { 8 | let client = Client::new(); 9 | let api_key = std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY not set"); 10 | 11 | let res = client.post("https://api.openai.com/v1/chat/completions") 12 | .bearer_auth(api_key) 13 | .json(&serde_json::json!({ 14 | "model": "gpt-4o", 15 | "messages": [ 16 | { "role": "system", "content": "You are a helpful browser assistant." }, 17 | { "role": "user", "content": prompt } 18 | ] 19 | })) 20 | .send() 21 | .await 22 | .map_err(|e| e.to_string())?; 23 | 24 | let json: serde_json::Value = res.json().await.map_err(|e| e.to_string())?; 25 | Ok(json["choices"][0]["message"]["content"] 26 | .as_str() 27 | .unwrap_or("No response") 28 | .to_string()) 29 | } 30 | 31 | #[command] 32 | fn run_agent(prompt: String, url: String) -> Result { 33 | let api_key = std::env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY not set"); 34 | 35 | let output = Command::new("node") 36 | .arg("/Users/sonukumar/Desktop/YT/agent-browser/backend/agent.js") 37 | .arg(prompt) 38 | .env("OPENAI_API_KEY", api_key) 39 | .env("CURRENT_URL", url) 40 | .output() 41 | .map_err(|e| e.to_string())?; 42 | 43 | Ok(String::from_utf8_lossy(&output.stdout).to_string()) 44 | } 45 | 46 | #[command] 47 | fn run_tool_and_wait(tool_name: String, arg: Option, _app: tauri::AppHandle) -> Result { 48 | match tool_name.as_str() { 49 | "click_button" => Ok("Clicked button successfully".to_string()), 50 | "search_dom" => Ok(format!("Found matches for '{}' in the DOM", arg.unwrap_or_default())), 51 | "scrape_table" => Ok("Table data extracted: Column1 | Column2 | Column3\nValue1 | Value2 | Value3".to_string()), 52 | "extract_prices" => Ok("Prices found: $19.99, $29.99, $49.99".to_string()), 53 | _ => Ok(format!("Unknown tool: {}", tool_name)) 54 | } 55 | } 56 | 57 | fn main() { 58 | tauri::Builder::default() 59 | .invoke_handler(tauri::generate_handler![ask_agent, run_agent, run_tool_and_wait]) 60 | .run(tauri::generate_context!()) 61 | .expect("error while running tauri application"); 62 | } -------------------------------------------------------------------------------- /public/tauri.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /backend/agent.js: -------------------------------------------------------------------------------- 1 | // backend/agent.js 2 | const fetch = require('node-fetch'); 3 | 4 | const tools = { 5 | extract_prices: async () => { 6 | // Note: The DOM content will be accessed directly through frontend tools 7 | return 'Price extraction requested - waiting for frontend execution'; 8 | }, 9 | search_dom: async (keyword) => { 10 | // Note: The DOM content will be accessed directly through frontend tools 11 | return `DOM search for "${keyword}" requested - waiting for frontend execution`; 12 | }, 13 | click_button: async () => { 14 | return 'Button click requested - waiting for frontend execution'; 15 | }, 16 | scrape_table: async () => { 17 | return 'Table scrape requested - waiting for frontend execution'; 18 | } 19 | }; 20 | 21 | async function askAgentReAct(prompt) { 22 | const messages = [ 23 | { 24 | role: 'system', 25 | content: `You are an intelligent browser assistant embedded in a Tauri browser app. 26 | You can reason and use tools to help users with tasks on webpages. 27 | 28 | CURRENT URL: ${process.env.CURRENT_URL || 'unknown'} 29 | 30 | AVAILABLE TOOLS: 31 | - extract_prices: Extract all prices from the current page 32 | - search_dom(keyword): Find text matching a pattern and return context 33 | - click_button: Click the first visible button on the page 34 | - scrape_table: Extract and return table data 35 | 36 | RESPONSE FORMAT: 37 | Always use this format: 38 | Thought: [your reasoning] 39 | Action: [tool name] OR Action: [tool_name]([argument]) for tools with args 40 | [Wait for observation, then continue] 41 | Thought: [your reasoning based on observation] 42 | Final Answer: [your conclusive answer to the user's query]` 43 | }, 44 | { role: 'user', content: prompt } 45 | ]; 46 | 47 | for (let i = 0; i < 4; i++) { 48 | const res = await fetch('https://api.openai.com/v1/chat/completions', { 49 | method: 'POST', 50 | headers: { 51 | 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`, 52 | 'Content-Type': 'application/json' 53 | }, 54 | body: JSON.stringify({ 55 | model: 'gpt-4o', 56 | messages, 57 | temperature: 0.3 58 | }) 59 | }); 60 | 61 | const data = await res.json(); 62 | if (!data.choices || !data.choices[0]) { 63 | return 'Error: Failed to get response from AI service'; 64 | } 65 | 66 | const reply = data.choices[0].message.content; 67 | messages.push({ role: 'assistant', content: reply }); 68 | 69 | if (reply.includes('Final Answer:')) break; 70 | 71 | // Extract tool and argument 72 | const actionMatch = reply.match(/Action: (\w+)(?:\((.+?)\))?/); 73 | if (actionMatch) { 74 | const toolName = actionMatch[1]; 75 | const arg = actionMatch[2] || ''; 76 | 77 | let observation; 78 | if (tools[toolName]) { 79 | if (arg) { 80 | observation = await tools[toolName](arg); 81 | } else { 82 | observation = await tools[toolName](); 83 | } 84 | } else { 85 | observation = `Error: Tool "${toolName}" not found`; 86 | } 87 | 88 | messages.push({ role: 'user', content: `Observation: ${observation}` }); 89 | } 90 | } 91 | 92 | return messages.map((m) => m.content).join('\n\n'); 93 | } 94 | 95 | (async () => { 96 | const prompt = process.argv[2]; 97 | const result = await askAgentReAct(prompt); 98 | console.log(result); 99 | })(); 100 | -------------------------------------------------------------------------------- /backend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "backend", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "node-fetch": "^3.3.2" 13 | } 14 | }, 15 | "node_modules/data-uri-to-buffer": { 16 | "version": "4.0.1", 17 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", 18 | "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", 19 | "license": "MIT", 20 | "engines": { 21 | "node": ">= 12" 22 | } 23 | }, 24 | "node_modules/fetch-blob": { 25 | "version": "3.2.0", 26 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", 27 | "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", 28 | "funding": [ 29 | { 30 | "type": "github", 31 | "url": "https://github.com/sponsors/jimmywarting" 32 | }, 33 | { 34 | "type": "paypal", 35 | "url": "https://paypal.me/jimmywarting" 36 | } 37 | ], 38 | "license": "MIT", 39 | "dependencies": { 40 | "node-domexception": "^1.0.0", 41 | "web-streams-polyfill": "^3.0.3" 42 | }, 43 | "engines": { 44 | "node": "^12.20 || >= 14.13" 45 | } 46 | }, 47 | "node_modules/formdata-polyfill": { 48 | "version": "4.0.10", 49 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", 50 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", 51 | "license": "MIT", 52 | "dependencies": { 53 | "fetch-blob": "^3.1.2" 54 | }, 55 | "engines": { 56 | "node": ">=12.20.0" 57 | } 58 | }, 59 | "node_modules/node-domexception": { 60 | "version": "1.0.0", 61 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 62 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 63 | "deprecated": "Use your platform's native DOMException instead", 64 | "funding": [ 65 | { 66 | "type": "github", 67 | "url": "https://github.com/sponsors/jimmywarting" 68 | }, 69 | { 70 | "type": "github", 71 | "url": "https://paypal.me/jimmywarting" 72 | } 73 | ], 74 | "license": "MIT", 75 | "engines": { 76 | "node": ">=10.5.0" 77 | } 78 | }, 79 | "node_modules/node-fetch": { 80 | "version": "3.3.2", 81 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", 82 | "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", 83 | "license": "MIT", 84 | "dependencies": { 85 | "data-uri-to-buffer": "^4.0.0", 86 | "fetch-blob": "^3.1.4", 87 | "formdata-polyfill": "^4.0.10" 88 | }, 89 | "engines": { 90 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 91 | }, 92 | "funding": { 93 | "type": "opencollective", 94 | "url": "https://opencollective.com/node-fetch" 95 | } 96 | }, 97 | "node_modules/web-streams-polyfill": { 98 | "version": "3.3.3", 99 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", 100 | "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", 101 | "license": "MIT", 102 | "engines": { 103 | "node": ">= 8" 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/browser-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Agentic Browser 2 | 3 | A powerful browser assistant with embedded AI capabilities. This project combines a React frontend, Tauri Rust backend, and Node.js agent to create an intelligent browser experience that can answer questions about web pages, interact with page content, and extract information. 4 | 5 | ## Features 6 | 7 | - Embedded browser with navigation controls 8 | - AI assistant that answers questions about web content 9 | - DOM interaction tools (click buttons, search content, extract data) 10 | - Chat interface with syntax highlighting for agent responses 11 | - Responsive UI with collapsible sidebar 12 | - ReAct agent pattern for step-by-step reasoning 13 | 14 | ## Prerequisites 15 | 16 | Before you begin, ensure you have the following installed: 17 | 18 | - [Node.js](https://nodejs.org/) (v16 or later) 19 | - [Rust](https://www.rust-lang.org/tools/install) 20 | - [Tauri CLI](https://tauri.app/v1/guides/getting-started/prerequisites/) (`npm install -g @tauri-apps/cli`) 21 | - An OpenAI API Key (for the agent functionality) 22 | 23 | ## Setup Instructions 24 | 25 | ### 1. Clone the Repository 26 | 27 | ```bash 28 | git clone https://github.com/AIAnytime/agent-browser.git 29 | cd agent-browser 30 | ``` 31 | 32 | ### 2. Install Dependencies 33 | 34 | ```bash 35 | # Install frontend dependencies 36 | npm install 37 | 38 | # Install backend dependencies 39 | cd backend 40 | npm install 41 | cd .. 42 | ``` 43 | 44 | ### 3. Set Environment Variables 45 | 46 | Create a `.env` file in the project root with the following content: 47 | 48 | ``` 49 | OPENAI_API_KEY=your_openai_api_key_here 50 | ``` 51 | 52 | ### 4. Run the Application 53 | 54 | From the project root directory: 55 | 56 | ```bash 57 | # Development mode with hot-reloading 58 | npm run tauri dev 59 | 60 | # Or build for production 61 | npm run tauri build 62 | ``` 63 | 64 | ## Usage Guide 65 | 66 | ### Navigation 67 | 68 | 1. When the application starts, you'll see an embedded browser and a sidebar. 69 | 2. Use the navigation bar at the top to enter URLs and browse the web. 70 | 3. The sidebar contains the AI assistant chat interface. 71 | 72 | ### Using the AI Assistant 73 | 74 | 1. While browsing, type your question in the chat input at the bottom of the sidebar. 75 | 2. The AI will analyze the current webpage and respond with relevant information. 76 | 3. For specific content, you can also right-click on selected text to ask about it directly. 77 | 78 | ### Available Tools 79 | 80 | The AI assistant can use several tools to interact with web pages: 81 | 82 | - **search_dom**: Search the page for specific content 83 | - **click_button**: Click buttons or links on the page 84 | - **scrape_table**: Extract table data from the page 85 | - **extract_prices**: Find price information on the page 86 | - **navigate_to**: Navigate to a different URL 87 | 88 | ## Project Structure 89 | 90 | ``` 91 | agent-browser/ 92 | ├── src/ # React frontend code 93 | │ ├── App.tsx # Main application component 94 | │ └── App.css # Styles 95 | ├── src-tauri/ # Rust backend code 96 | │ └── src/ 97 | │ └── main.rs # Tauri application entry point 98 | ├── backend/ # Node.js agent code 99 | │ ├── agent.js # ReAct agent implementation 100 | │ └── package.json # Node dependencies 101 | └── package.json # Frontend dependencies 102 | ``` 103 | 104 | ## Troubleshooting 105 | 106 | - **Agent not responding**: Ensure your OpenAI API key is set correctly in the `.env` file. 107 | - **DOM interaction issues**: Check the browser console for errors related to DOM interaction tools. 108 | - **Build errors**: Verify that you have the correct versions of Node.js, Rust, and Tauri CLI installed. 109 | 110 | ## How It Works 111 | 112 | ### ReAct Agent Pattern 113 | 114 | This browser uses the ReAct (Reasoning + Acting) pattern for AI agents: 115 | 116 | 1. **Thought**: The AI analyzes the current context and formulates a plan 117 | 2. **Action**: The AI executes a tool to gather information or modify the page 118 | 3. **Observation**: The AI receives feedback from the executed tool 119 | 4. **Final Answer**: After sufficient reasoning, the AI provides a comprehensive answer 120 | 121 | Example interaction flow: 122 | 123 | ``` 124 | User: Find the cheapest flight on this page. 125 | 126 | AI Thought: I need to read all prices on the page. 127 | Action: extract_prices() 128 | Observation: $99, $105, $129 129 | Thought: $99 is cheapest. I will return it. 130 | Final Answer: The cheapest flight is $99. 131 | ``` 132 | 133 | ## Architecture 134 | 135 | | Layer | Technology | Role | 136 | | ------------------- | --------------------------------------- | ----------------------------------------- | 137 | | UI Shell | **Tauri + React** | Browser window and interface | 138 | | Backend | **Rust** | IPC coordination, command execution | 139 | | Agent | **Node.js + OpenAI API** | ReAct agent implementation | 140 | | Web View | **Tauri WebView** | Embedded browser functionality | 141 | | Communication | **Tauri IPC Events** | Between React UI, Rust backend, and agent | 142 | 143 | ## Contributing 144 | 145 | Contributions are welcome! Please feel free to submit a Pull Request. 146 | 147 | 1. Fork the repository 148 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 149 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 150 | 4. Push to the branch (`git push origin feature/amazing-feature`) 151 | 5. Open a Pull Request 152 | 153 | ## License 154 | 155 | This project is licensed under the MIT License - see the LICENSE file for details. 156 | 157 | ## Acknowledgments 158 | 159 | - OpenAI for the GPT models powering the agent 160 | - Tauri for the cross-platform windowing solution 161 | - React for the frontend framework 162 | 163 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | /* Modern UI for Agent Browser */ 2 | 3 | :root { 4 | --primary-color: #4a6cf7; 5 | --secondary-color: #6c757d; 6 | --success-color: #28a745; 7 | --danger-color: #dc3545; 8 | --light-color: #f8f9fa; 9 | --dark-color: #343a40; 10 | --bg-color: #f5f7ff; 11 | --sidebar-width: 380px; 12 | --sidebar-bg: #ffffff; 13 | --header-height: 60px; 14 | --border-radius: 8px; 15 | --box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); 16 | --transition: all 0.3s ease; 17 | } 18 | 19 | * { 20 | margin: 0; 21 | padding: 0; 22 | box-sizing: border-box; 23 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; 24 | } 25 | 26 | body { 27 | background-color: var(--bg-color); 28 | color: var(--dark-color); 29 | } 30 | 31 | .app-container { 32 | display: flex; 33 | flex-direction: column; 34 | height: 100vh; 35 | overflow: hidden; 36 | } 37 | 38 | /* Navigation Bar */ 39 | .navbar { 40 | display: flex; 41 | justify-content: space-between; 42 | align-items: center; 43 | height: var(--header-height); 44 | padding: 0 20px; 45 | background-color: #fff; 46 | box-shadow: var(--box-shadow); 47 | z-index: 10; 48 | } 49 | 50 | .url-bar { 51 | flex: 1; 52 | display: flex; 53 | max-width: calc(100% - 150px); 54 | } 55 | 56 | .url-bar input { 57 | flex: 1; 58 | padding: 8px 12px; 59 | border: 1px solid #ddd; 60 | border-radius: var(--border-radius) 0 0 var(--border-radius); 61 | font-size: 14px; 62 | outline: none; 63 | transition: border-color 0.2s; 64 | } 65 | 66 | .url-bar input:focus { 67 | border-color: var(--primary-color); 68 | } 69 | 70 | .url-bar button { 71 | padding: 8px 16px; 72 | background-color: var(--primary-color); 73 | color: white; 74 | border: none; 75 | border-radius: 0 var(--border-radius) var(--border-radius) 0; 76 | cursor: pointer; 77 | transition: background-color 0.2s; 78 | } 79 | 80 | .url-bar button:hover { 81 | background-color: #395dd4; 82 | } 83 | 84 | .nav-controls button { 85 | padding: 8px 16px; 86 | background-color: var(--light-color); 87 | border: 1px solid #ddd; 88 | border-radius: var(--border-radius); 89 | color: var(--dark-color); 90 | cursor: pointer; 91 | transition: var(--transition); 92 | } 93 | 94 | .nav-controls button:hover { 95 | background-color: #e9ecef; 96 | } 97 | 98 | /* Main Content Area */ 99 | .content-area { 100 | display: flex; 101 | height: calc(100vh - var(--header-height)); 102 | position: relative; 103 | } 104 | 105 | /* Browser iframe */ 106 | .browser-iframe { 107 | flex: 1; 108 | border: none; 109 | height: 100%; 110 | background-color: #fff; 111 | } 112 | 113 | /* Agent Sidebar */ 114 | .agent-sidebar { 115 | width: var(--sidebar-width); 116 | height: 100%; 117 | background-color: var(--sidebar-bg); 118 | box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1); 119 | display: flex; 120 | flex-direction: column; 121 | transition: transform 0.3s ease; 122 | position: absolute; 123 | top: 0; 124 | right: 0; 125 | z-index: 5; 126 | } 127 | 128 | .agent-sidebar.closed { 129 | transform: translateX(100%); 130 | } 131 | 132 | .agent-sidebar.open { 133 | transform: translateX(0); 134 | } 135 | 136 | .sidebar-header { 137 | padding: 16px; 138 | border-bottom: 1px solid #eee; 139 | display: flex; 140 | justify-content: space-between; 141 | align-items: center; 142 | } 143 | 144 | .sidebar-header h2 { 145 | font-size: 18px; 146 | color: var(--primary-color); 147 | font-weight: 600; 148 | } 149 | 150 | .close-btn { 151 | background: none; 152 | border: none; 153 | font-size: 24px; 154 | color: var(--secondary-color); 155 | cursor: pointer; 156 | transition: color 0.2s; 157 | } 158 | 159 | .close-btn:hover { 160 | color: var(--danger-color); 161 | } 162 | 163 | /* Agent Chat */ 164 | .agent-chat { 165 | flex: 1; 166 | overflow-y: auto; 167 | padding: 16px; 168 | display: flex; 169 | flex-direction: column; 170 | } 171 | 172 | .welcome-message { 173 | background-color: rgba(74, 108, 247, 0.05); 174 | padding: 20px; 175 | border-radius: var(--border-radius); 176 | margin-bottom: 20px; 177 | } 178 | 179 | .welcome-message h3 { 180 | color: var(--primary-color); 181 | margin-bottom: 12px; 182 | } 183 | 184 | .welcome-message ul { 185 | margin-left: 20px; 186 | margin-top: 10px; 187 | } 188 | 189 | .welcome-message li { 190 | margin-bottom: 8px; 191 | color: #555; 192 | } 193 | 194 | .messages { 195 | display: flex; 196 | flex-direction: column; 197 | gap: 16px; 198 | } 199 | 200 | .message { 201 | max-width: 100%; 202 | } 203 | 204 | .user-message, .assistant-message { 205 | display: flex; 206 | gap: 12px; 207 | margin-bottom: 16px; 208 | } 209 | 210 | .user-icon, .assistant-icon { 211 | width: 36px; 212 | height: 36px; 213 | display: flex; 214 | align-items: center; 215 | justify-content: center; 216 | border-radius: 50%; 217 | flex-shrink: 0; 218 | font-size: 20px; 219 | } 220 | 221 | .user-icon { 222 | background-color: #e6f0ff; 223 | } 224 | 225 | .assistant-icon { 226 | background-color: #e9f5f9; 227 | } 228 | 229 | .message-content { 230 | flex: 1; 231 | padding: 12px; 232 | border-radius: var(--border-radius); 233 | font-size: 14px; 234 | line-height: 1.5; 235 | overflow-wrap: break-word; 236 | } 237 | 238 | .user .message-content { 239 | background-color: #f5f7ff; 240 | border: 1px solid #e6e9fd; 241 | } 242 | 243 | .assistant .message-content { 244 | background-color: #ffffff; 245 | border: 1px solid #eaeaea; 246 | } 247 | 248 | /* Styling for Agent Response Parts */ 249 | .thought { 250 | color: #6d4c9f; 251 | margin-bottom: 10px; 252 | } 253 | 254 | .action { 255 | color: #2c7be5; 256 | margin-bottom: 10px; 257 | } 258 | 259 | .observation { 260 | color: #666; 261 | background-color: #f8f9fa; 262 | padding: 8px; 263 | border-left: 3px solid #ddd; 264 | margin-bottom: 10px; 265 | font-family: monospace; 266 | white-space: pre-wrap; 267 | font-size: 13px; 268 | } 269 | 270 | .final-answer { 271 | color: #28a745; 272 | margin-top: 10px; 273 | font-weight: 500; 274 | } 275 | 276 | /* Loading Indicator */ 277 | .loading-indicator { 278 | display: flex; 279 | justify-content: center; 280 | padding: 20px 0; 281 | } 282 | 283 | .loading-dots { 284 | display: flex; 285 | align-items: center; 286 | justify-content: center; 287 | gap: 6px; 288 | } 289 | 290 | .loading-dots span { 291 | width: 8px; 292 | height: 8px; 293 | border-radius: 50%; 294 | background-color: var(--primary-color); 295 | animation: bounce 1.4s infinite ease-in-out both; 296 | } 297 | 298 | .loading-dots span:nth-child(1) { 299 | animation-delay: -0.32s; 300 | } 301 | 302 | .loading-dots span:nth-child(2) { 303 | animation-delay: -0.16s; 304 | } 305 | 306 | @keyframes bounce { 307 | 0%, 80%, 100% { 308 | transform: scale(0); 309 | } 40% { 310 | transform: scale(1.0); 311 | } 312 | } 313 | 314 | /* Agent Input */ 315 | .agent-input { 316 | display: flex; 317 | padding: 16px; 318 | border-top: 1px solid #eee; 319 | background-color: white; 320 | } 321 | 322 | .agent-input input { 323 | flex: 1; 324 | padding: 12px; 325 | border: 1px solid #ddd; 326 | border-radius: var(--border-radius) 0 0 var(--border-radius); 327 | outline: none; 328 | font-size: 14px; 329 | transition: border-color 0.2s; 330 | } 331 | 332 | .agent-input input:focus { 333 | border-color: var(--primary-color); 334 | } 335 | 336 | .agent-input button { 337 | padding: 12px 20px; 338 | background-color: var(--primary-color); 339 | color: white; 340 | border: none; 341 | border-radius: 0 var(--border-radius) var(--border-radius) 0; 342 | cursor: pointer; 343 | transition: background-color 0.2s; 344 | } 345 | 346 | .agent-input button:hover:not(:disabled) { 347 | background-color: #395dd4; 348 | } 349 | 350 | .agent-input button:disabled { 351 | background-color: #a0aec0; 352 | cursor: not-allowed; 353 | } 354 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useRef } from 'react'; 2 | import './App.css'; 3 | 4 | // Access Tauri API through the global window object with type safety 5 | declare global { 6 | interface Window { 7 | __TAURI__: { 8 | invoke(command: string, args?: Record): Promise; 9 | event: { 10 | listen(event: string, callback: (event: any) => void): Promise<() => void>; 11 | emit(event: string, payload?: any): Promise; 12 | }; 13 | }; 14 | } 15 | } 16 | 17 | function App() { 18 | const [isLoading, setIsLoading] = useState(false); 19 | const [userQuery, setUserQuery] = useState(''); 20 | const [agentResponses, setAgentResponses] = useState<{role: string, content: string}[]>([]); 21 | const [isSidebarOpen, setIsSidebarOpen] = useState(false); 22 | const [currentUrl, setCurrentUrl] = useState('https://www.google.com'); 23 | const iframeRef = useRef(null); 24 | 25 | // Register agent tools in the window object 26 | useEffect(() => { 27 | // Check if running in Tauri context 28 | const isTauri = !!window.__TAURI__; 29 | console.log('Running in Tauri context:', isTauri); 30 | 31 | if (!isTauri) { 32 | console.warn('Not running in Tauri context. Some features may not work.'); 33 | } 34 | 35 | // Define agent tools that can be called from backend 36 | (window as any).agentTools = { 37 | click_button: (selector?: string) => { 38 | try { 39 | const iframe = iframeRef.current; 40 | if (!iframe || !iframe.contentDocument) return 'No iframe content available'; 41 | 42 | const targetSelector = selector || 'button'; 43 | const btn = iframe.contentDocument.querySelector(targetSelector); 44 | if (btn) { 45 | (btn as HTMLElement).click(); 46 | return `Clicked element matching selector: ${targetSelector}`; 47 | } 48 | return `No element found matching selector: ${targetSelector}`; 49 | } catch (error) { 50 | console.error('Error in click_button:', error); 51 | return `Error executing click: ${error}`; 52 | } 53 | }, 54 | 55 | search_dom: (keyword: string) => { 56 | try { 57 | const iframe = iframeRef.current; 58 | if (!iframe || !iframe.contentDocument) return 'No iframe content available'; 59 | 60 | const text = iframe.contentDocument.body.innerText; 61 | const pattern = new RegExp(`.{0,30}${keyword}.{0,30}`, 'gi'); 62 | const found = text.match(pattern); 63 | return found ? found.join('\n') : `No match for "${keyword}"`; 64 | } catch (error) { 65 | console.error('Error in search_dom:', error); 66 | return `Error searching DOM: ${error}`; 67 | } 68 | }, 69 | 70 | scrape_table: () => { 71 | try { 72 | const iframe = iframeRef.current; 73 | if (!iframe || !iframe.contentDocument) return 'No iframe content available'; 74 | 75 | const table = iframe.contentDocument.querySelector('table'); 76 | if (!table) return 'No table found.'; 77 | 78 | const rows = Array.from(table.querySelectorAll('tr')).map(row => 79 | Array.from(row.querySelectorAll('td, th')) 80 | .map(cell => cell.textContent?.trim()) 81 | .join(' | ') 82 | ); 83 | 84 | return rows.join('\n'); 85 | } catch (error) { 86 | console.error('Error in scrape_table:', error); 87 | return `Error scraping table: ${error}`; 88 | } 89 | }, 90 | 91 | extract_prices: () => { 92 | try { 93 | const iframe = iframeRef.current; 94 | if (!iframe || !iframe.contentDocument) return 'No iframe content available'; 95 | 96 | const text = iframe.contentDocument.body.innerText; 97 | const matches = text.match(/\$\d+(\.\d+)?/g); 98 | return matches ? matches.join(', ') : 'No prices found'; 99 | } catch (error) { 100 | console.error('Error in extract_prices:', error); 101 | return `Error extracting prices: ${error}`; 102 | } 103 | }, 104 | 105 | navigate_to: (url: string) => { 106 | try { 107 | setCurrentUrl(url); 108 | return `Navigating to ${url}`; 109 | } catch (error) { 110 | return `Error navigating: ${error}`; 111 | } 112 | } 113 | }; 114 | 115 | // Listen for tool execution requests from backend only if running in Tauri context 116 | const setupToolListener = async () => { 117 | if (!window.__TAURI__) { 118 | console.warn('Tauri API not available. Tool execution will not work.'); 119 | return; 120 | } 121 | 122 | try { 123 | await window.__TAURI__.event.listen('agent-tool', async (event) => { 124 | const { tool, arg, responseEvent } = event.payload; 125 | console.log('Received tool request:', tool, arg, 'Response event:', responseEvent); 126 | 127 | const tools = (window as any).agentTools; 128 | if (tools && tools[tool]) { 129 | try { 130 | const result = await tools[tool](arg); 131 | console.log('Tool result:', result); 132 | // Send back result to Rust backend with the specific response event 133 | await window.__TAURI__.event.emit(responseEvent, result); 134 | } catch (error) { 135 | console.error('Error executing tool:', error); 136 | await window.__TAURI__.event.emit(responseEvent, `Error: ${error}`); 137 | } 138 | } else { 139 | await window.__TAURI__.event.emit(responseEvent, `Error: Tool '${tool}' not found`); 140 | } 141 | }); 142 | console.log('Tool listener set up successfully'); 143 | } catch (error) { 144 | console.error('Error setting up tool listener:', error); 145 | } 146 | }; 147 | 148 | setupToolListener(); 149 | }, []); 150 | 151 | // Handle URL changes in the iframe 152 | const handleIframeLoad = () => { 153 | if (iframeRef.current && iframeRef.current.contentWindow) { 154 | try { 155 | // Try to get the current URL from the iframe 156 | const iframeSrc = iframeRef.current.contentWindow.location.href; 157 | if (iframeSrc !== 'about:blank') { 158 | setCurrentUrl(iframeSrc); 159 | } 160 | } catch (e) { 161 | console.error('Could not access iframe location:', e); 162 | } 163 | } 164 | }; 165 | 166 | // Function to simulate agent response in development mode 167 | const simulateAgentResponse = async (prompt: string): Promise => { 168 | // Add a small delay to simulate API processing 169 | await new Promise(resolve => setTimeout(resolve, 1000)); 170 | 171 | // Generate a mock response based on the prompt 172 | if (prompt.toLowerCase().includes('about') || prompt.toLowerCase().includes('what is')) { 173 | return `Thought: I need to analyze what this website is about. 174 | 175 | Action: search_dom(heading) 176 | 177 | Observation: Found matches for 'AI That Builds Competitive Advantage' 178 | 179 | Thought: I can see this is a company website related to AI solutions. 180 | 181 | Final Answer: This website appears to be for Kodryx, a company focused on practical AI solutions for businesses. Their tagline is "AI That Builds Competitive Advantage" and they seem to offer AI services that help businesses gain a competitive edge. They emphasize real-world AI applications with measurable impact.`; 182 | } 183 | 184 | if (prompt.toLowerCase().includes('extract') || prompt.toLowerCase().includes('price')) { 185 | return `Thought: I'll look for any prices mentioned on this page. 186 | 187 | Action: extract_prices 188 | 189 | Observation: Prices found: $19.99, $29.99, $49.99 190 | 191 | Thought: I found some pricing information on the page. 192 | 193 | Final Answer: I found the following prices on this page: $19.99, $29.99, and $49.99. These might be for different subscription tiers or products offered by Kodryx.`; 194 | } 195 | 196 | // Default response for other queries 197 | return `Thought: I need to understand the query "${prompt}" in the context of this website. 198 | 199 | Action: search_dom(kodryx) 200 | 201 | Observation: Found matches for 'Kodryx AI Agent' and 'Workflow Automation' 202 | 203 | Thought: This gives me some context about the website's focus. 204 | 205 | Final Answer: Based on the content I can see, this website is for Kodryx, a company specializing in AI agents and workflow automation. They appear to offer enterprise AI solutions that help businesses improve efficiency and gain competitive advantages through practical AI implementation.`; 206 | }; 207 | 208 | // Function to run the agent with more capability 209 | const runAgent = async (prompt: string) => { 210 | if (isLoading || !prompt.trim()) return; 211 | 212 | setIsLoading(true); 213 | 214 | // Add the user query to the responses 215 | setAgentResponses(prev => [...prev, { role: 'user', content: prompt }]); 216 | 217 | try { 218 | let response: string; 219 | 220 | // Check if Tauri API is available 221 | if (!window.__TAURI__) { 222 | console.log('Running in development mode - using simulated agent responses'); 223 | response = await simulateAgentResponse(prompt); 224 | } else { 225 | // Call the run_agent command to start the full agent process 226 | response = await window.__TAURI__.invoke('run_agent', { 227 | prompt, 228 | url: currentUrl, 229 | }); 230 | } 231 | 232 | console.log('Agent response:', response); 233 | 234 | // Add the response to the chat 235 | if (response && typeof response === 'string') { 236 | setAgentResponses(prev => [...prev, { role: 'assistant', content: response }]); 237 | } 238 | } catch (error) { 239 | console.error('Error running agent:', error); 240 | setAgentResponses(prev => [...prev, { 241 | role: 'assistant', 242 | content: `Error: ${error}` 243 | }]); 244 | } finally { 245 | setIsLoading(false); 246 | setUserQuery(''); 247 | // Open sidebar if it's closed 248 | if (!isSidebarOpen) setIsSidebarOpen(true); 249 | } 250 | }; 251 | 252 | // Add a contextmenu handler to allow asking about selected text 253 | useEffect(() => { 254 | const handleContextMenu = async (e: MouseEvent) => { 255 | e.preventDefault(); 256 | 257 | const selection = window.getSelection()?.toString() || ''; 258 | if (selection) { 259 | await runAgent(`Help me understand: ${selection}`); 260 | } 261 | }; 262 | 263 | // Only add context menu handler if running in Tauri context 264 | if (window.__TAURI__) { 265 | document.addEventListener('contextmenu', handleContextMenu); 266 | 267 | return () => { 268 | document.removeEventListener('contextmenu', handleContextMenu); 269 | }; 270 | } 271 | 272 | return undefined; 273 | }, []); 274 | 275 | // Handle form submission 276 | const handleSubmit = (e: React.FormEvent) => { 277 | e.preventDefault(); 278 | runAgent(userQuery); 279 | }; 280 | 281 | // Format agent responses with syntax highlighting for different parts 282 | const formatResponse = (content: string) => { 283 | return content.split('\n\n').map((paragraph, i) => { 284 | if (paragraph.startsWith('Thought:')) { 285 | return ( 286 |
287 | 🤔 {paragraph} 288 |
289 | ); 290 | } else if (paragraph.startsWith('Action:')) { 291 | return ( 292 |
293 | 🔧 {paragraph} 294 |
295 | ); 296 | } else if (paragraph.startsWith('Observation:')) { 297 | return ( 298 |
299 | 👁️ {paragraph} 300 |
301 | ); 302 | } else if (paragraph.startsWith('Final Answer:')) { 303 | return ( 304 |
305 | ✅ {paragraph} 306 |
307 | ); 308 | } else { 309 | return

{paragraph}

; 310 | } 311 | }); 312 | }; 313 | 314 | return ( 315 |
316 | 332 | 333 | {/* Main content area */} 334 |
335 | {/* Browser iframe */} 336 |