Skip to content

Commit

Permalink
web api
Browse files Browse the repository at this point in the history
  • Loading branch information
vyokky committed Jul 5, 2024
1 parent ba4c7c3 commit 1e423d4
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 80 deletions.
16 changes: 5 additions & 11 deletions documents/docs/creating_app_agent/warpping_app_native_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,24 +279,18 @@ EXCEL_API_PROMPT: "ufo/prompts/apps/excel/api.yaml"

4. **Register the Prompt Address in APIPromptLoader:**

- Register the prompt address in the `APIPromptLoader.load_com_api_prompt` method in the `ufo/prompter/agent_prompter.py` file.
- Register the prompt address in the `APIPromptLoader.load_api_prompt` method in the `ufo/prompter/agent_prompter.py` file.

Example:
```python
def load_com_api_prompt(self) -> Dict[str, str]:
def load_api_prompt(self) -> Dict[str, str]:
"""
Load the prompt template for COM APIs.
:return: The prompt template for COM APIs.
"""
app2configkey_mapper = {
"WINWORD.EXE": "WORD_API_PROMPT",
"EXCEL.EXE": "EXCEL_API_PROMPT",
"POWERPNT.EXE": "POWERPOINT_API_PROMPT",
"olk.exe": "OUTLOOK_API_PROMPT",
}
config_key = app2configkey_mapper.get(self.root_name, None)
prompt_address = configs.get(config_key, None)
prompt_address = configs["APP_API_PROMPT_ADDRESS"].get(self.root_name, None)
print(prompt_address)
if prompt_address:
return AppAgentPrompter.load_prompt_template(prompt_address, None)
Expand Down
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ psutil==5.9.8
beautifulsoup4==4.12.3
sentence-transformers==2.5.1
pandas==1.4.3
html2text==2024.2.26
##For Qwen
#dashscope==1.15.0
##For removing stopwords
#nltk==3.8.1
##For Gemini
#google-generativeai==0.7.0
#google-generativeai==0.7.0

2 changes: 1 addition & 1 deletion ufo/agents/states/host_agent_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def create_app_agent(self, agent: "HostAgent", context: Context) -> AppAgent:

# Create the COM receiver for the app agent.
if configs.get("USE_APIS", False):
app_agent.Puppeteer.receiver_manager.create_com_receiver(
app_agent.Puppeteer.receiver_manager.create_api_receiver(
application_root_name, application_window_name
)

Expand Down
5 changes: 4 additions & 1 deletion ufo/automator/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Licensed under the MIT License.

from .ui_control import controller
from .app_apis import factory
66 changes: 63 additions & 3 deletions ufo/automator/app_apis/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,29 @@
from typing import Type

from ufo.automator.app_apis.basic import WinCOMReceiverBasic
from ufo.automator.app_apis.word.wordclient import WordWinCOMReceiver
from ufo.automator.app_apis.excel.excelclient import ExcelWinCOMReceiver
from ufo.automator.basic import ReceiverFactory
from ufo.automator.app_apis.word.wordclient import WordWinCOMReceiver
from ufo.automator.app_apis.web.webclient import WebReceiver
from ufo.automator.basic import ReceiverBasic, ReceiverFactory
from ufo.automator.puppeteer import ReceiverManager
from ufo.utils import print_with_color


class COMReceiverFactory(ReceiverFactory):
class APIReceiverFactory(ReceiverFactory):
"""
The factory class for the API receiver.
"""

@classmethod
def is_api(cls) -> bool:
"""
Check if the receiver is API.
"""
return True


@ReceiverManager.register
class COMReceiverFactory(APIReceiverFactory):
"""
The factory class for the COM receiver.
"""
Expand Down Expand Up @@ -65,3 +81,47 @@ def __app_root_mappping(self, app_root_name: str) -> str:
}

return win_com_map.get(app_root_name, None)

@classmethod
def name(cls) -> str:
"""
The name of the factory.
"""
return "COM"


@ReceiverManager.register
class WebReceiverFactory(APIReceiverFactory):
"""
The factory class for the COM receiver.
"""

def create_receiver(self, app_root_name: str, *args, **kwargs) -> ReceiverBasic:
"""
Create the web receiver.
:param app_root_name: The app root name.
:param process_name: The process name.
:return: The receiver.
"""

if app_root_name not in self.supported_app_roots:
return None

web_receiver = WebReceiver()
print_with_color(f"Web receiver created for {app_root_name}.", "green")

return web_receiver

@property
def supported_app_roots(self):
"""
Get the supported app roots.
"""
return ["msedge.exe", "chrome.exe"]

@classmethod
def name(cls) -> str:
"""
The name of the factory.
"""
return "Web"
2 changes: 2 additions & 0 deletions ufo/automator/app_apis/web/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
108 changes: 108 additions & 0 deletions ufo/automator/app_apis/web/webclient.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

from __future__ import annotations

from typing import Any, Dict, Type

import html2text
import requests

from ufo.automator.basic import CommandBasic, ReceiverBasic


class WebReceiver(ReceiverBasic):
"""
The base class for Web COM client using crawl4ai.
"""

_command_registry: Dict[str, Type[WebCommand]] = {}

def __init__(self) -> None:
"""
Initialize the Web COM client.
"""
self._headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
}

def web_crawler(self, url: str, ignore_link: bool) -> str:
"""
Run the crawler with various options.
:param url: The URL of the webpage.
:param ignore_link: Whether to ignore the links.
:return: The result markdown content.
"""

try:
# Get the HTML content of the webpage
response = requests.get(url, headers=self._headers)
response.raise_for_status()

html_content = response.text

# Convert the HTML content to markdown
h = html2text.HTML2Text()
h.ignore_links = ignore_link
markdown_content = h.handle(html_content)

return markdown_content

except requests.RequestException as e:
print(f"Error fetching the URL: {e}")

return f"Error fetching the URL: {e}"

@property
def type_name(self):
return "WEB"

@property
def xml_format_code(self) -> int:
return 0 # This might not be applicable for web, adjust accordingly


class WebCommand(CommandBasic):
"""
The base class for Web commands.
"""

def __init__(self, receiver: WebReceiver, params: Dict[str, Any]) -> None:
"""
Initialize the Web command.
:param receiver: The receiver of the command.
:param params: The parameters of the command.
"""
super().__init__(receiver, params)
self.receiver = receiver

@classmethod
def name(cls) -> str:
"""
The name of the command.
"""
return "web"


@WebReceiver.register
class WebCrawlerCommand(WebCommand):
"""
The command to run the crawler with various options.
"""

def execute(self):
"""
Execute the command to run the crawler.
:return: The result content.
"""
return self.receiver.web_crawler(
url=self.params.get("url"),
ignore_link=self.params.get("ignore_link", False),
)

@classmethod
def name(cls) -> str:
"""
The name of the command.
"""
return "web_crawler"
19 changes: 16 additions & 3 deletions ufo/automator/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any, Dict, List, Type

from ufo.utils import print_with_color
from typing import Dict, List, Type


class ReceiverBasic(ABC):
Expand Down Expand Up @@ -50,6 +48,7 @@ def register(cls, command_class: Type[CommandBasic]) -> Type[CommandBasic]:
"""
Decorator to register the state class to the state manager.
:param command_class: The state class to be registered.
:return: The state class.
"""
cls._command_registry[command_class.name()] = command_class
return command_class
Expand Down Expand Up @@ -106,3 +105,17 @@ class ReceiverFactory(ABC):
@abstractmethod
def create_receiver(self, *args, **kwargs):
pass

@classmethod
def name(cls) -> str:
"""
The name of the receiver factory.
"""
return cls.__class__.__name__

@classmethod
def is_api(cls) -> bool:
"""
Check if the receiver factory is to create an API receiver.
"""
return False
Loading

0 comments on commit 1e423d4

Please sign in to comment.