Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ChatGPT fix, test coverage for claude and copilot, local server switch #63

Merged
merged 11 commits into from
Feb 28, 2024
28 changes: 21 additions & 7 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
version: 2.1
orbs:
python: circleci/python@2.0.3
browser-tools: circleci/browser-tools@1.4.6
browser-tools: circleci/browser-tools@1.4.8
codecov: codecov/codecov@4.0.1
jobs:
build-and-test:
docker:
- image: cimg/python:3.10.4
resource_class: talkingheads/localservers
docker:
- image: ugorsahin/talkingheads_test:v1
auth:
username: $DOCKER_UNAME
password: $DOCKER_PAT
working_directory: ~/project
steps:
- checkout
- browser-tools/install-chrome
Expand All @@ -18,13 +23,22 @@ jobs:
name: Install talkingheads and pytest
command: |
python -m pip install .
python -m pip install pytest
python -m pip install pytest psutil pytest-cov
- run:
name: Run tests
command: python -m pytest
command: |
mkdir test-results artifacts
python -m pytest --junitxml=test-results/junit.xml --cov=talkingheads --cov-report=xml:artifacts/coverage.xml
- store_test_results:
path: talkinghead-results
path: test-results
- store_artifacts:
path: artifacts
- codecov/upload:
file: artifacts/coverage.xml
token: CODECOV_TOKEN
workflows:
base-tests:
jobs:
- build-and-test
- build-and-test:
context:
- docker-hub-creds
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ MANIFEST
*.ipynb

# Sphinx
_build
_build

# CircleCI
artifacts
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
<a href='https://github.com/ugorsahin/TalkingHeads/actions/workflows/codeql.yml'>
<img src='https://github.com/ugorsahin/TalkingHeads/actions/workflows/codeql.yml/badge.svg' alt='CodeQL Status' />
</a>
<a href="https://codecov.io/gh/ugorsahin/TalkingHeads" >
<img src="https://codecov.io/gh/ugorsahin/TalkingHeads/graph/badge.svg?token=YPXKNXSZAD"/>
</a>
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
</p>

Welcome to TalkingHeads! 🤖🚀
Welcome to TalkingHeads! 💬

TalkingHeads is a versatile Python library that serves as an interface for seamless communication with ChatGPT, Claude, Copilot, Gemini, HuggingChat, and Pi 🤖💬
TalkingHeads is a versatile Python library that serves as an interface for seamless communication with ChatGPT, Claude, Copilot, Gemini, HuggingChat, and Pi 🤖

By leveraging the power of browser automation, this library enables users to effortlessly interact with online LLM tools, providing a streamlined and automated approach to generate responses. 🚀✨

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ name = "talkingheads"
description = "A library to communicate with ChatGPT, Claude, Copilot, Gemini, HuggingChat, and Pi"
readme = "README.md"
requires-python = ">=3.8"
version = "0.4.1"
version = "0.4.2"
classifiers = [
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
Expand Down
34 changes: 10 additions & 24 deletions src/talkingheads/base_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,17 @@ class BaseBrowser:
url (str): The URL to be used as an entrypoint.
uname_var (str): The username environment variable, to enable multiple agents.
pwd_var (str): The password environment variable, to enable multiple agents.
username (str, optional): The username to auth. Deprecated. Use environment variables instead.
password (str, optional): The password to auth. Deprecated. Use environment variables instead.
headless (bool, optional): A boolean to enable/disable headless mode in driver. Default: True.
username (str, optional): Deprecated. Use environment variables instead.
password (str, optional): Deprecated. Use environment variables instead.
headless (bool, optional): Enables/disables headless mode. Default: True.
cold_start (bool, optional): If set, it will return after opening the browser. Default: False.
incognito (bool, optional): A boolean to set incognito mode. Default: True.
driver_arguments (list, optional): A list of additional arguments to be passed to the driver. Default: None.
driver_version (int, optional): The version of the chromedriver. Default: None.
auto_save (bool, optional): A boolean to enable/disable automatic saving. Default: False.
save_path (str, optional): The file path to save chat logs. Default: None.
verbose (bool, optional): A boolean to enable/disable logging. Default: False.
credential_check (bool, optional): A boolean to enable/disable credential check. Default: True.
credential_check (bool, optional): Enables/disables credential check. Default: True.
skip_login (bool, optional): If True, skips the login procedure. Default: False.
user_data_dir (str, optional): The directory path to user profile. Default: None.
uc_params (dict, optional): Parameters for uc.Chrome().
Expand Down Expand Up @@ -147,6 +147,7 @@ def __init__(
self.set_save_path(save_path)

def __del__(self):
self.browser.close()
self.browser.quit()
if self.auto_save:
self.save()
Expand Down Expand Up @@ -317,38 +318,23 @@ def log_chat(

def preload_custom_func(self) -> None:
"""
A function to implement specific instructions before loading the webpage
A function to implement custom instructions before loading the webpage
"""
logging.info(
"""
The preload behavior is not implemented,
which could be considered normal if verification is not required.
"""
)
logging.info("The preload behavior is not implemented")

def postload_custom_func(self) -> None:
"""
A function to implement specific instructions after loading the webpage
A function to implement custom instructions after loading the webpage
"""
logging.info(
"""
The postload behavior is not implemented,
which could be considered normal if verification is not required.
"""
)
logging.info("The postload behavior is not implemented")

def pass_verification(self) -> bool:
"""
Performs the verification process on the page if challenge is present.
Returns:
None
"""
logging.info(
"""
Pass verification function is not implemented,
which could be considered normal if verification is not required.
"""
)
logging.info("The pass verification function is not implemented")
return True

@abc.abstractmethod
Expand Down
19 changes: 9 additions & 10 deletions src/talkingheads/model_library/chatgpt.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,14 @@ def pass_verification(self, max_trial: int = 10, wait_time: int = 1) -> bool:
Returns: None
"""
for _ in range(max_trial):
if not self.check_login_page():
break
verify_button = self.browser.find_elements(By.ID, "challenge-stage")
if len(verify_button):
try:
verify_button[0].click()
logging.info("Clicked verification button")
except Exceptions.ElementNotInteractableException:
logging.info("Verification button is not present or clickable")
if not verify_button:
break
try:
verify_button[0].click()
logging.info("Clicked verification button")
except Exceptions.ElementNotInteractableException:
logging.info("Verification button is not present or clickable")
time.sleep(wait_time)
else:
logging.error("It is not possible to pass verification")
Expand Down Expand Up @@ -77,7 +76,7 @@ def login(self, username: str, password: str):
time.sleep(1)

# Find email textbox, enter e-mail
email_box = self.wait_until_appear(By.ID, "username")
email_box = self.wait_until_appear(By.CLASS_NAME, self.markers.email_cq)
email_box.send_keys(username)
logging.info("Filled email box")

Expand All @@ -88,7 +87,7 @@ def login(self, username: str, password: str):
logging.info("Clicked continue button")

# Find password textbox, enter password
pass_box = self.wait_until_appear(By.ID, "password")
pass_box = self.wait_until_appear(By.ID, self.markers.pwd_iq)
pass_box.send_keys(password)
logging.info("Filled password box")
# Click continue
Expand Down
14 changes: 8 additions & 6 deletions src/talkingheads/model_library/claude.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Class definition for Claude client"""
import logging
import time

from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
Expand All @@ -24,7 +25,7 @@ def __init__(self, **kwargs):
**kwargs,
)

def login(self, _1: str = None, _2: str = None):
def login(self, _1: str = None, _2: str = None) -> bool:
"""
It is only possible to login Claude manually.

Expand Down Expand Up @@ -72,7 +73,7 @@ def is_ready_to_prompt(self) -> bool:
text_area.send_keys(Keys.CONTROL + "a", Keys.DELETE)
return True

def interact(self, prompt: str):
def interact(self, prompt: str) -> str:
"""Sends a prompt and retrieves the answer from the ChatGPT system.

This function interacts with the Claude.
Expand All @@ -86,7 +87,7 @@ def interact(self, prompt: str):
prompt (str): The interaction text.

Returns:
Dict[str]: The generated answer and references.
str: The generated answer.
"""

text_area = self.find_or_fail(By.CLASS_NAME, self.markers.textarea_cq)
Expand Down Expand Up @@ -126,17 +127,18 @@ def reset_thread(self) -> bool:
"""
text_area = self.find_or_fail(By.CLASS_NAME, self.markers.textarea_cq)
text_area.send_keys(Keys.CONTROL + "K")
start_button = self.find_or_fail(By.XPATH, self.markers.start_button_xq)
time.sleep(0.5)
start_button = self.wait_until_appear(By.XPATH, self.markers.start_button_xq)
if not start_button:
return False
start_button.click()

return True

def switch_model(self, _: str):
def switch_model(self, _: str) -> None:
raise NotImplementedError("Claude only has one model")

def regenerate_response(self):
def regenerate_response(self) -> str:
regen_button = self.find_or_fail(By.XPATH, self.markers.regen_xq)
if not regen_button:
return ""
Expand Down
Loading
Loading