From ea6b0bb9bcbbf8ff1d1f46be0b2184c61e7c7a24 Mon Sep 17 00:00:00 2001 From: dsdanielpark Date: Sat, 16 Mar 2024 16:52:40 +0900 Subject: [PATCH] refactor: delete legacy and clean package import using autogen-init --- .github/ISSUE_TEMPLATE/bug_report.md | 10 +- .github/ISSUE_TEMPLATE/custom.md | 10 +- .github/pull_request_template.md | 4 +- documents/README_DEV.md | 6 +- gemini/__init__.py | 24 +- gemini/async_core.py | 1 + gemini/core.py | 4 +- gemini/src/extension/__init__.py | 1 + gemini/src/extension/replit.py | 25 ++ gemini/src/misc/__init__.py | 4 + gemini/src/{model => misc}/decorator.py | 0 gemini/src/{model => misc}/exceptions.py | 0 gemini/src/misc/utils.py | 27 +-- gemini/src/model/__init__.py | 2 + gemini/src/model/parser/__init__.py | 3 + gemini/src/{ => model}/parser/base.py | 0 .../src/{ => model}/parser/custom_parser.py | 2 +- .../src/{ => model}/parser/response_parser.py | 2 +- gemini/src/module/voice/__init__.py | 2 + gemini/src/parser/__init__.py | 0 gemini/src/tools/__init__.py | 1 - gemini/src/tools/citation.py | 38 --- gemini/src/tools/draft.py | 169 ------------- gemini/src/tools/google/__init__.py | 1 - gemini/src/tools/google/code.py | 26 -- gemini/src/tools/google/flight.py | 127 ---------- gemini/src/tools/google/gdocs.py | 0 gemini/src/tools/google/gworkspace.py | 70 ------ gemini/src/tools/google/hotel.py | 97 -------- gemini/src/tools/google/json.py | 28 --- gemini/src/tools/google/link.py | 22 -- gemini/src/tools/google/map.py | 227 ------------------ gemini/src/tools/google/tool.py | 29 --- gemini/src/tools/google/tool_declaimer.py | 21 -- gemini/src/tools/google/youtube.py | 89 ------- gemini/src/tools/image.py | 48 ---- gemini/src/tools/result.py | 78 ------ gemini/src/tools/user_content.py | 15 -- save/temp.wav | Bin 0 -> 11712 bytes setup.py | 7 + 40 files changed, 84 insertions(+), 1136 deletions(-) create mode 100644 gemini/src/extension/__init__.py create mode 100644 gemini/src/extension/replit.py rename gemini/src/{model => misc}/decorator.py (100%) rename gemini/src/{model => misc}/exceptions.py (100%) create mode 100644 gemini/src/model/parser/__init__.py rename gemini/src/{ => model}/parser/base.py (100%) rename gemini/src/{ => model}/parser/custom_parser.py (99%) rename gemini/src/{ => model}/parser/response_parser.py (99%) create mode 100644 gemini/src/module/voice/__init__.py delete mode 100644 gemini/src/parser/__init__.py delete mode 100644 gemini/src/tools/__init__.py delete mode 100644 gemini/src/tools/citation.py delete mode 100644 gemini/src/tools/draft.py delete mode 100644 gemini/src/tools/google/__init__.py delete mode 100644 gemini/src/tools/google/code.py delete mode 100644 gemini/src/tools/google/flight.py delete mode 100644 gemini/src/tools/google/gdocs.py delete mode 100644 gemini/src/tools/google/gworkspace.py delete mode 100644 gemini/src/tools/google/hotel.py delete mode 100644 gemini/src/tools/google/json.py delete mode 100644 gemini/src/tools/google/link.py delete mode 100644 gemini/src/tools/google/map.py delete mode 100644 gemini/src/tools/google/tool.py delete mode 100644 gemini/src/tools/google/tool_declaimer.py delete mode 100644 gemini/src/tools/google/youtube.py delete mode 100644 gemini/src/tools/image.py delete mode 100644 gemini/src/tools/result.py delete mode 100644 gemini/src/tools/user_content.py create mode 100644 save/temp.wav diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index a86cc03..2a4c5e7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -18,14 +18,14 @@ assignees: dsdanielpark Example ``` -pip uninstall bardapi # and restart kernel -pip install bardapi==0.1.29 # check proper version -pip install -u bardapi==0.1.29 # upgrade version +pip uninstall python-gemini-api # and restart kernel +pip install python-gemini-api==2.0.0 # check proper version +pip install -u python-gemini-api==2.0.0 # upgrade version ``` ```python -import bardapi -bardapi.__version__ +import gemini +gemini.__version__ ``` ----------Please delete the content above this line, including this line.------------- diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md index 633def1..b299731 100644 --- a/.github/ISSUE_TEMPLATE/custom.md +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -18,14 +18,14 @@ Please make sure to check for more efficient package management. *Please priorit Example ``` -pip uninstall bardapi # and restart kernel -pip install bardapi==0.1.29 # check proper version -pip install -u bardapi==0.1.29 # upgrade version +pip uninstall python-gemini-api # and restart kernel +pip install python-gemini-api==2.0.0 # check proper version +pip install -u python-gemini-api==2.0.0 # upgrade version ``` ```python -import bardapi -bardapi.__version__ +import gemini +gemini.__version__ ``` ----------Please delete the content above this line, including this line.------------- diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2fbb8f2..d4e0898 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,7 @@ -I welcome the overall code refactoring and new features. However, please make sure to conduct QA. Before making a PR, please check the entire functionality by adding it to https://github.com/dsdanielpark/Bard-API/blob/main/func_test.ipynb. Due to various reasons, I couldn't implement test mocking code. I also welcome anyone who can provide test mocking and refactoring. But please keep the existing method names and examples. While I aim to accept PRs as quickly as possible, they may be rejected if QA is not correct. +I welcome the overall code refactoring and new features. However, please make sure to conduct QA. Before making a PR, please check the entire functionality by adding it to https://github.com/dsdanielpark/Gemini-API/blob/main/func_test.ipynb. Due to various reasons, I couldn't implement test mocking code. I also welcome anyone who can provide test mocking and refactoring. But please keep the existing method names and examples. While I aim to accept PRs as quickly as possible, they may be rejected if QA is not correct. -# BARD-API Pull Request Template +# Gemini-API Pull Request Template Please provide the necessary information as succinctly as possible. I appreciate it if you write it in an easy-to-read format as I don't have much time to dedicate to maintaining this package. ## Description diff --git a/documents/README_DEV.md b/documents/README_DEV.md index 26a5824..79cadc3 100644 --- a/documents/README_DEV.md +++ b/documents/README_DEV.md @@ -6,7 +6,7 @@ Development Status :: 3 - Alpha pip install git+https://github.com/dsdanielpark/Gemini-API.git ``` -This section aims to implement functionalities of the Bard API. While there are many readily portable codes, updates may be delayed. +This section aims to implement functionalities of the Gemini API. While there are many readily portable codes, updates may be delayed. # Contents @@ -31,7 +31,7 @@ This section aims to implement functionalities of the Bard API. While there are ### Multi-language Gemini -For commercial use cases, please refrain from using the unofficial Google Translate package included in bardapi for non-commercial purposes. Instead, kindly visit the official Google Cloud Translation website. Please use it responsibly, taking full responsibility for your actions, as bardapi package does not assume any implicit or explicit liability. +For commercial use cases, please refrain from using the unofficial Google Translate package included in `python-gemini-api` for non-commercial purposes. Instead, kindly visit the official Google Cloud Translation website. Please use it responsibly, taking full responsibility for your actions, as `python-gemini-api` package does not assume any implicit or explicit liability. > Official Google Translation API - Support Languages: https://cloud.google.com/translate/docs/languages?hl=ko > Unofficial Google Trnaslator for non-profit purposes (such as feature testing) @@ -90,7 +90,7 @@ GeminiAsync is not using requests library instead it is using httpx library and ### Translation to Another Programming Language Please check the translation results in [this folder](https://github.com/dsdanielpark/Gemini-API/tree/main/translate_to). -- Copy the code of [Core.py](https://github.com/dsdanielpark/Gemini-API/blob/17d5e948d4afc535317de3964232ab82fe223521/bardapi/core.py). +- Copy the code of [Core.py](https://github.com/dsdanielpark/Gemini-API/blob/17d5e948d4afc535317de3964232ab82fe223521/`python-gemini-api`/core.py). - Ask ChatGPT to translate like "Translate to Swift." - Ask ChatGPT to optimize the code or provide any desired instructions until you're satisfied.
diff --git a/gemini/__init__.py b/gemini/__init__.py index 5b8ea3d..944b19b 100644 --- a/gemini/__init__.py +++ b/gemini/__init__.py @@ -1,12 +1,26 @@ from os import environ -from .async_core import GeminiClient + from .core import Gemini +from .async_core import GeminiClient + from .src.model.image import GeminiImage from .src.model.output import GeminiCandidate, GeminiModelOutput -from .src.misc import * -from .src.parser import * -from .src.tools import * -from .src.tools.google import * +from .src.model.parser.base import BaesParser +from .src.model.parser.custom_parser import ParseMethod1, ParseMethod2 +from .src.model.parser.response_parser import ResponseParser, _parse_code + +from .src.misc.constants import Tool +from .src.misc.decorator import retry, log_method, time_execution, handle_errors +from .src.misc.exceptions import PackageError, GeminiAPIError, TimeoutError +from .src.misc.utils import extract_code, upload_image, max_token, max_sentence + +from .src.extension.replit import prepare_replit_data + +try: + from .src.module.voice.google import google_tts, google_stt + from .src.module.voice.openai import openai_tts, openai_stt +except ImportError as e: + pass gemini_api_key = environ.get("GEMINI_COOKIES") diff --git a/gemini/async_core.py b/gemini/async_core.py index 93bc1e0..3884287 100644 --- a/gemini/async_core.py +++ b/gemini/async_core.py @@ -1,3 +1,4 @@ +# TO-DO: Current logic contains traces of development attempts, so need to add asynchronous processing based on core.py and document it. import os import re import json diff --git a/gemini/core.py b/gemini/core.py index d49c099..e030df6 100644 --- a/gemini/core.py +++ b/gemini/core.py @@ -9,8 +9,8 @@ from typing import Optional, Tuple, Dict, Union from requests.exceptions import ConnectionError, RequestException -from .src.parser.custom_parser import ParseMethod1, ParseMethod2 -from .src.parser.response_parser import ResponseParser +from .src.model.parser.custom_parser import ParseMethod1, ParseMethod2 +from .src.model.parser.response_parser import ResponseParser from .src.model.output import GeminiCandidate, GeminiModelOutput from .src.misc.utils import upload_image from .src.misc.constants import ( diff --git a/gemini/src/extension/__init__.py b/gemini/src/extension/__init__.py new file mode 100644 index 0000000..64d53ad --- /dev/null +++ b/gemini/src/extension/__init__.py @@ -0,0 +1 @@ +from .replit import prepare_replit_data diff --git a/gemini/src/extension/replit.py b/gemini/src/extension/replit.py new file mode 100644 index 0000000..6b20184 --- /dev/null +++ b/gemini/src/extension/replit.py @@ -0,0 +1,25 @@ +import json + + +def prepare_replit_data(instructions: str, code: str, filename: str) -> list: + """ + Creates and returns the input image data structure based on provided parameters. + + Args: + instructions (str): The instruction text. + code (str): The code. + filename (str): The filename. + + Returns: + list: The input image data structure. + """ + return [ + [ + [ + "qACoKe", + json.dumps([instructions, 5, code, [[filename, code]]]), + None, + "generic", + ] + ] + ] diff --git a/gemini/src/misc/__init__.py b/gemini/src/misc/__init__.py index e69de29..072f918 100644 --- a/gemini/src/misc/__init__.py +++ b/gemini/src/misc/__init__.py @@ -0,0 +1,4 @@ +from .constants import Tool +from .decorator import retry, log_method, time_execution, handle_errors +from .exceptions import PackageError, GeminiAPIError, TimeoutError +from .utils import extract_code, upload_image, max_token, max_sentence diff --git a/gemini/src/model/decorator.py b/gemini/src/misc/decorator.py similarity index 100% rename from gemini/src/model/decorator.py rename to gemini/src/misc/decorator.py diff --git a/gemini/src/model/exceptions.py b/gemini/src/misc/exceptions.py similarity index 100% rename from gemini/src/model/exceptions.py rename to gemini/src/misc/exceptions.py diff --git a/gemini/src/misc/utils.py b/gemini/src/misc/utils.py index 593def3..0df052c 100644 --- a/gemini/src/misc/utils.py +++ b/gemini/src/misc/utils.py @@ -1,7 +1,6 @@ -import json import requests from typing import Union -from .constants import IMAGE_PUSH_ID +from gemini.src.misc.constants import IMAGE_PUSH_ID def extract_code(text: str) -> str: @@ -68,30 +67,6 @@ def upload_image(file: Union[bytes, str]) -> str: return response.text -def prepare_replit_data(instructions: str, code: str, filename: str) -> list: - """ - Creates and returns the input image data structure based on provided parameters. - - Args: - instructions (str): The instruction text. - code (str): The code. - filename (str): The filename. - - Returns: - list: The input image data structure. - """ - return [ - [ - [ - "qACoKe", - json.dumps([instructions, 5, code, [[filename, code]]]), - None, - "generic", - ] - ] - ] - - def max_token(text: str, n: int) -> str: """ Return the first 'n' tokens (words) of the given text. diff --git a/gemini/src/model/__init__.py b/gemini/src/model/__init__.py index e69de29..1f06a4e 100644 --- a/gemini/src/model/__init__.py +++ b/gemini/src/model/__init__.py @@ -0,0 +1,2 @@ +from .image import GeminiImage +from .output import GeminiCandidate, GeminiModelOutput diff --git a/gemini/src/model/parser/__init__.py b/gemini/src/model/parser/__init__.py new file mode 100644 index 0000000..cbd1a52 --- /dev/null +++ b/gemini/src/model/parser/__init__.py @@ -0,0 +1,3 @@ +from .base import BaesParser +from .custom_parser import ParseMethod1, ParseMethod2 +from .response_parser import ResponseParser, _parse_code diff --git a/gemini/src/parser/base.py b/gemini/src/model/parser/base.py similarity index 100% rename from gemini/src/parser/base.py rename to gemini/src/model/parser/base.py diff --git a/gemini/src/parser/custom_parser.py b/gemini/src/model/parser/custom_parser.py similarity index 99% rename from gemini/src/parser/custom_parser.py rename to gemini/src/model/parser/custom_parser.py index 17555cb..e5bc9d1 100644 --- a/gemini/src/parser/custom_parser.py +++ b/gemini/src/model/parser/custom_parser.py @@ -1,4 +1,4 @@ -from gemini.src.parser.base import BaesParser +from gemini.src.model.parser.base import BaesParser from typing import Dict, Any diff --git a/gemini/src/parser/response_parser.py b/gemini/src/model/parser/response_parser.py similarity index 99% rename from gemini/src/parser/response_parser.py rename to gemini/src/model/parser/response_parser.py index a7e2bf6..0065d09 100644 --- a/gemini/src/parser/response_parser.py +++ b/gemini/src/model/parser/response_parser.py @@ -1,6 +1,6 @@ import json from typing import Dict -from gemini.src.parser.base import BaesParser +from gemini.src.model.parser.base import BaesParser from gemini.src.misc.utils import extract_code diff --git a/gemini/src/module/voice/__init__.py b/gemini/src/module/voice/__init__.py new file mode 100644 index 0000000..18a2e74 --- /dev/null +++ b/gemini/src/module/voice/__init__.py @@ -0,0 +1,2 @@ +from .google import google_tts, google_stt +from .openai import openai_tts, openai_stt diff --git a/gemini/src/parser/__init__.py b/gemini/src/parser/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/gemini/src/tools/__init__.py b/gemini/src/tools/__init__.py deleted file mode 100644 index 3db80bf..0000000 --- a/gemini/src/tools/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Legacy diff --git a/gemini/src/tools/citation.py b/gemini/src/tools/citation.py deleted file mode 100644 index 42cf036..0000000 --- a/gemini/src/tools/citation.py +++ /dev/null @@ -1,38 +0,0 @@ -# Legacy -class DraftCitation: - """Github source - [[909, 1095, ['', 'edelahoz/Introduction-to-Python ', '', '', '', None, None, '', False, '', '', '', '', '', '', ''], 1, 100, None, [1]], - [940, 1139, ['', 'Engr-Asad-Hussain/oop ', '', '', '', None, None, '', False, '', '', '', '', '', '', ''], 1, 100, None, [1]], - [953, 1166, ['', 'TeknikhogskolanGothenburg/PGBPYH21_Programmering', '', '', '', None, None, '', False, '', '', '', '', '', '', ''], 1, 100, None, [1]]] - - Wiki source - [[284, 425, ['http://en.wikipedia.org/wiki/Jill_Biden', ' ', '', None, '', None, None, '', False, '', '', '', '', '', '', ''], 1, 1 , None, [1, 'normal_citation_datasets']], - [196, 411, ['https://en.wikipedia.org/wiki/Jill_Biden#:~:text=Jill%20Tracy%20Jacobs%20was%20born,II%20who%20used%20the%20G.I.', '', '', '', '', None, None, '', False, '', '', '', '', '', '', ''], 2, 2, None, [1, 'normal_citation_datasets']]]] - - [None, None, ['https://en.wikipedia.org/wiki/Te%27omim_Cave'], 3] - """ - - def __init__(self, input_list: list, text: str): - self._input_list = input_list - self.span: tuple[int, int] = (self._input_list[0], self._input_list[1]) - if self.span[0] is None or self.span[1] is None: - self.text = "" - else: - self.text: str = text[self.span[0] : self.span[1]] - - @property - def source_path(self) -> str: - return self._input_list[2][1] if len(self._input_list[2]) > 1 else "" - - @property - def source_full(self) -> str: - return self._input_list[2][0] - - @property - def source_dataset(self) -> list: - if len(self._input_list) < 5: - return [] - return self._input_list[6] - - def __str__(self) -> str: - return self.source_full if self.source_full else self.source_path diff --git a/gemini/src/tools/draft.py b/gemini/src/tools/draft.py deleted file mode 100644 index 242c77f..0000000 --- a/gemini/src/tools/draft.py +++ /dev/null @@ -1,169 +0,0 @@ -# Legacy -from typing import List, Optional, Dict - -from gemini.src.tools.citation import DraftCitation -from gemini.src.tools.google.code import CodeContent -from gemini.src.tools.google.flight import BardFlightContent -from gemini.src.tools.google.gworkspace import GoogleWorkspaceContent -from gemini.src.tools.image import BardImageContent -from gemini.src.tools.google.hotel import BardHotelContent -from gemini.src.tools.google.json import JsonContent -from gemini.src.tools.google.link import BardLink -from gemini.src.tools.google.map import BardMapContent -from gemini.src.tools.google.tool_declaimer import BardToolDeclaimer -from gemini.src.tools.user_content import UserContent -from gemini.src.tools.google.youtube import BardYoutubeContent - - -class GeminiDraft: - def __init__(self, input_list: list): - self._input_list = input_list - self.id = self._input_list[0] - - @property - def text(self) -> str: - return self._input_list[1][0] - - @property - def text_with_user_content(self) -> str: - text = self.text - for uc in self.user_content.values(): - key = uc.key - mk = uc.markdown_text - text = text.replace(key, "\n" + mk) - return text - - @property - def citations(self) -> List[DraftCitation]: - text = self.text - return ( - [DraftCitation(c, text) for c in self._input_list[2][0]] - if self._input_list[2] - else [] - ) - - @property - def images(self) -> List[BardImageContent]: - # also in self._attachments[1] - return ( - [BardImageContent(img) for img in self._input_list[4]] - if self._input_list[4] - else [] - ) - - @property - def language(self) -> str: - # en - return self._input_list[9] - - @property - def _attachments(self) -> Optional[list]: - return self._input_list[12] - - @property - def map_content(self) -> List[BardMapContent]: - if not self._attachments: - return [] - return ( - [BardMapContent(a) for a in self._attachments[3]] - if self._attachments[3] - else [] - ) - - @property - def json_content(self) -> List[JsonContent]: - if not self._attachments: - return [] - return ( - [JsonContent(a) for a in self._attachments[10]] - if self._attachments[10] - else [] - ) - - @property - def gworkspace(self) -> List[GoogleWorkspaceContent]: - if not self._attachments or len(self._attachments) < 13: - return [] - return ( - [GoogleWorkspaceContent(a) for a in self._attachments[12][0][2]] - if self._attachments[12] - else [] - ) - - @property - def youtube(self) -> List[BardYoutubeContent]: - if not self._attachments: - return [] - return ( - [BardYoutubeContent(a) for a in self._attachments[4]] - if self._attachments[4] - else [] - ) - - @property - def python_code(self) -> List[CodeContent]: - # Google has a dedicated Python model that can also run code. - # The text model uses the output of the Python model to generate answers, - # including answers in other languages. - # - # The code snippet is the same for all drafts! - if not self._attachments: - return [] - return ( - [CodeContent(a) for a in self._attachments[5]] - if self._attachments[5] and self._attachments[5][0][3] - else [] - ) - - @property - def links(self) -> List[BardLink]: - if not self._attachments: - return [] - return ( - [BardLink(a) for a in self._attachments[8]] if self._attachments[8] else [] - ) - - @property - def flights(self) -> List[BardFlightContent]: - if not self._attachments or len(self._attachments) < 17: - return [] - return ( - [BardFlightContent(a) for a in self._attachments[16]] - if self._attachments[16] - else [] - ) - - @property - def hotels(self) -> List[BardHotelContent]: - if not self._attachments or len(self._attachments) < 19: - return [] - return ( - [BardHotelContent(a) for a in self._attachments[17]] - if self._attachments[17] - else [] - ) - - @property - def tool_disclaimers(self) -> List[BardToolDeclaimer]: - if not self._attachments or len(self._attachments) < 23: - return [] - - return ( - [BardToolDeclaimer(a) for a in self._attachments[22]] - if self._attachments[22] - else [] - ) - - @property - def user_content(self) -> Dict[str, UserContent]: - d = {v.key: v for v in self.youtube} - d.update({v.key: v for v in self.map_content}) - d.update({v.key: v for v in self.flights}) - d.update({v.key: v for v in self.links}) - d.update({v.key: v for v in self.tool_disclaimers}) - d.update({v.key: v for v in self.json_content}) - d.update({v.key: v for v in self.hotels}) - return d - - def __str__(self) -> str: - return self.text diff --git a/gemini/src/tools/google/__init__.py b/gemini/src/tools/google/__init__.py deleted file mode 100644 index 3db80bf..0000000 --- a/gemini/src/tools/google/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Legacy diff --git a/gemini/src/tools/google/code.py b/gemini/src/tools/google/code.py deleted file mode 100644 index db21098..0000000 --- a/gemini/src/tools/google/code.py +++ /dev/null @@ -1,26 +0,0 @@ -# Legacy -class CodeContent: - # 32 items - def __init__(self, input_list: list): - self._input_list = input_list - - @property - def request(self) -> str: - return self._input_list[3] - - @property - def output(self) -> str: - return self._input_list[1] - - @property - def output_image(self) -> str: - # Don't know how to extract this, but it's - # [['[image-tag: code_execution_image_1_1695640907.0254648.png]']] - return self._input_list[27] - - @property - def code(self) -> str: - return self._input_list[8] - - def __str__(self) -> str: - return self.code diff --git a/gemini/src/tools/google/flight.py b/gemini/src/tools/google/flight.py deleted file mode 100644 index 1a7c22c..0000000 --- a/gemini/src/tools/google/flight.py +++ /dev/null @@ -1,127 +0,0 @@ -# Legacy -from gemini.src.tools.user_content import UserContent -from typing import List - - -class BardFlight: - def __init__(self, input_list: List): - self._input_list = input_list - - @property - def url(self) -> str: - if len(self._input_list) > 2: - return self._input_list[2] - else: - return "" - - @property - def price(self) -> str: - if len(self._input_list) > 3: - return self._input_list[3] - else: - return "" - - @property - def airlines(self) -> List[str]: - if len(self._input_list) > 0 and isinstance(self._input_list[0], list): - return self._input_list[0] - else: - return [] - - @property - def airline_logo(self) -> str: - return self._input_list[0][1] - - @property - def departure_airport(self) -> str: - return self._input_list[0][2] - - @property - def arrival_airport(self) -> str: - return self._input_list[0][3] - - @property - def departure_time(self) -> str: - return self._input_list[0][7] - - @property - def arrival_time(self) -> str: - return self._input_list[0][8] - - @property - def duration(self) -> str: - return self._input_list[0][9] - - @property - def stops(self) -> str: - return self._input_list[0][6] - - def __str__(self) -> str: - return f'{",".join(self.airlines)} - {self.departure_airport} to {self.arrival_airport} - {self.departure_time} to {self.arrival_time} - {self.price}' - - -class BardFlightContent(UserContent): - """http://googleusercontent.com/flight_content/""" - - def __init__(self, input_list: list): - self._input_list = input_list - - @property - def key(self) -> str: - return self._input_list[3][0] - - @property - def title(self) -> str: - return self._input_list[3][2] - - @property - def search_url(self) -> str: - return self._input_list[2] - - @property - def flights(self) -> List[BardFlight]: - return ( - [BardFlight(flight) for flight in self._input_list[1]] - if self._input_list[1] - else [] - ) - - @property - def from_airport(self) -> str: - # 'OSL' - return self._input_list[4] - - @property - def to_airport(self) -> str: - # 'MAD' - return self._input_list[5] - - @property - def from_date(self) -> str: - # Jan 22, 2023 - return self._input_list[6] - - @property - def to_date(self) -> str: - # Jan 28, 2023 - return self._input_list[7] - - @property - def who(self) -> str: - # '1 adult' - return self._input_list[8] - - def __getitem__(self, item) -> BardFlight: - return self.flights[item] - - def __len__(self): - return len(self.flights) - - def __str__(self) -> str: - return self.title - - @property - def markdown_text(self) -> str: - return f"#### {self.title}\n\n" + "\n".join( - [" * " + str(flight) for flight in self.flights] - ) diff --git a/gemini/src/tools/google/gdocs.py b/gemini/src/tools/google/gdocs.py deleted file mode 100644 index e69de29..0000000 diff --git a/gemini/src/tools/google/gworkspace.py b/gemini/src/tools/google/gworkspace.py deleted file mode 100644 index 3fc9a06..0000000 --- a/gemini/src/tools/google/gworkspace.py +++ /dev/null @@ -1,70 +0,0 @@ -# Legacy -from enum import Enum -from typing import List - - -class GoogleWorkspaceContentKind(Enum): - UNKNOWN = 0 - EMAIL = 1 - FILE = 2 - DOCUMENT = 3 - PDF = 4 - SPREADSHEET = 5 - - -class GoogleWorkspaceContent: - # [[1], 'https://mail.google.com/mail/u/0/#all/18ad29e935cf6dc3', - # 'Start your journey to becoming an AI expert today!', ['18ad29e935cf6dc3'], - # 'Tuesday, 26 September 2023 19:52 CEST', 'Stanford University', [1695750721]] - - # [[3], 'https://docs.google.com/document/d/1pS5RTC2PSVn02lGOOAtaEg5U9M4_Ss_eoqkb2msO-Yo', - # 'CV (Andrew Matuk)', ['1pS5RTC2PSVn02lGOOAtaEg5U9M4_Ss_eoqkb2msO-Yo', 147325], - # 'Friday, 28 July 2023 11:29 CEST', 'Andrew Me', [1690536542, 363000000]] - def __init__(self, input_list: list): - self._input_list = input_list - - @property - def kind(self) -> GoogleWorkspaceContentKind: - return ( - GoogleWorkspaceContentKind(self._input_list[0][0]) - if self._input_list[0] < 5 - else GoogleWorkspaceContentKind.UNKNOWN - ) - - def icon(self) -> str: - di = { - GoogleWorkspaceContentKind.UNKNOWN: "https://drive-thirdparty.googleusercontent.com/64/type/application/vnd.google-apps.unknown", - GoogleWorkspaceContentKind.EMAIL: "https://www.gstatic.com/lamda/images/default_email_avatar_7fc85bbd3d2f35d5bd091.svg", - GoogleWorkspaceContentKind.FILE: "https://drive-thirdparty.googleusercontent.com/64/type/application/vnd.google-apps.file", - GoogleWorkspaceContentKind.DOCUMENT: "https://drive-thirdparty.googleusercontent.com/64/type/application/vnd.google-apps.document", - GoogleWorkspaceContentKind.PDF: "https://drive-thirdparty.googleusercontent.com/64/type/application/pdf", - GoogleWorkspaceContentKind.SPREADSHEET: "https://drive-thirdparty.googleusercontent.com/64/type/application/vnd.google-apps.spreadsheet", - } - return di[self.kind] - - @property - def url(self) -> str: - return self._input_list[1] - - @property - def title(self) -> str: - return self._input_list[2] - - @property - def id(self) -> str: - return self._input_list[3][0] - - @property - def date(self) -> str: - return self._input_list[4] - - @property - def author(self) -> str: - return self._input_list[5] - - @property - def timestamp_seconds(self) -> List[int]: - return self._input_list[6] - - def __str__(self) -> str: - return self.title diff --git a/gemini/src/tools/google/hotel.py b/gemini/src/tools/google/hotel.py deleted file mode 100644 index 6135c57..0000000 --- a/gemini/src/tools/google/hotel.py +++ /dev/null @@ -1,97 +0,0 @@ -# Legacy -from gemini.src.tools.user_content import UserContent -from typing import List - - -class BardHotel: - def __init__(self, input_list: list): - self._input_list = input_list - - @property - def name(self) -> str: - return self._input_list[0] - - @property - def images(self) -> List[str]: - return self._input_list[1] - - @property - def stars(self) -> int: - return self._input_list[2] - - @property - def rating_count(self) -> int: - return self._input_list[3] - - @property - def stars_text(self) -> int: - return self._input_list[4] - - @property - def description(self) -> int: - return self._input_list[5] - - @property - def url(self) -> int: - return self._input_list[7] - - @property - def price(self) -> int: - return self._input_list[8] - - def __str__(self): - return f"{self.name} {self.stars}★({self.rating_count})\n{self.description} {self.price}" - - def markdown_text(self): - return ( - f"**{self.name}** {self.stars}★({self.rating_count}) - {self.price}\n - ![]({self.images[0]})" - f"\n - {self.description}" - ) - - -class BardHotelContent(UserContent): - def __init__(self, input_list: list): - self._input_list = input_list - - @property - def hotels(self) -> List[BardHotel]: - return ( - [BardHotel(h) for h in self._input_list[0]] if self._input_list[0] else [] - ) - - @property - def key(self) -> str: - return self._input_list[2][0] - - @property - def full_title(self) -> str: - return self._input_list[2][2] - - @property - def tool_name(self): - return self._input_list[2][6][1] - - @property - def title(self) -> str: - return self._input_list[3] - - @property - def from_date(self) -> str: - return self._input_list[4] - - @property - def to_date(self) -> str: - return self._input_list[5] - - @property - def who(self) -> str: - return self._input_list[8] - - def __str__(self): - return self.full_title - - @property - def markdown_text(self) -> str: - return f"#### {self.full_title}\n\n" + "\n".join( - ["1. " + flight.markdown_text() for flight in self.hotels] - ) diff --git a/gemini/src/tools/google/json.py b/gemini/src/tools/google/json.py deleted file mode 100644 index daf6595..0000000 --- a/gemini/src/tools/google/json.py +++ /dev/null @@ -1,28 +0,0 @@ -# Legacy -import json - -from gemini.src.tools.user_content import UserContent - - -class JsonContent(UserContent): - def __init__(self, input_list: list): - self._input_list = input_list - - @property - def object(self) -> str: - return json.loads(self.json_text) - - @property - def json_text(self) -> str: - return self._input_list[1] - - @property - def key(self) -> str: - return self._input_list[0] - - @property - def markdown_text(self) -> str: - return "{json}" - - def __str__(self) -> str: - return self.json_text[:20] diff --git a/gemini/src/tools/google/link.py b/gemini/src/tools/google/link.py deleted file mode 100644 index a92a5c6..0000000 --- a/gemini/src/tools/google/link.py +++ /dev/null @@ -1,22 +0,0 @@ -# Legacy -from gemini.src.tools.user_content import UserContent - - -class BardLink(UserContent): - def __init__(self, input_list: list): - self._input_list = input_list - - @property - def key(self) -> str: - return self._input_list[0][0] - - @property - def url(self) -> str: - return self._input_list[1] - - def __str__(self) -> str: - return self.url - - @property - def markdown_text(self) -> str: - return f"[{self.url}]({self.url})" diff --git a/gemini/src/tools/google/map.py b/gemini/src/tools/google/map.py deleted file mode 100644 index 773c107..0000000 --- a/gemini/src/tools/google/map.py +++ /dev/null @@ -1,227 +0,0 @@ -# Legacy -from enum import Enum -from typing import List, Optional, Union, Dict, Tuple - -from gemini.src.tools.user_content import UserContent - - -class BardMapsPoint: - def __init__(self, input_list: list): - self._input_list = input_list - - self.geo_position = self._input_list[11] - self.lat: float = self.geo_position[0] - self.lng: float = self.geo_position[1] - - @property - def id(self) -> str: - return self._input_list[1] - - @property - def address(self) -> str: - # '12170 Dornans Rd, Moose, WY 83012, USA' - return self._input_list[8] - - @property - def address_short(self) -> str: - # 'Dornans, 12170 Dornans Rd, Moose' - return self._input_list[50] - - @property - def geo_position_rect(self) -> list: - return self._input_list[12] - - @property - def rating(self) -> float: - return self._input_list[13] - - @property - def rating_count(self) -> int: - return self._input_list[27] - - @property - def gmaps_url(self) -> str: - return self._input_list[14] - - @property - def website_url(self) -> str: - return self._input_list[15] - - @property - def schedule(self) -> dict: - v = self._input_list[20] - return { - "open": v[0], - "value": v[1], - "human": v[2], - } - - @property - def title(self) -> Tuple[str, str]: - # Albertsons, "en" - return self._input_list[30] - - @property - def place_type_and_lang(self) -> Optional[Tuple[str, str]]: - # ['Grocery store', 'en'] - return self._input_list[31] - - @property - def place_type(self) -> str: - # grocery_store - return self._input_list[49] # same [31], "en" - - def description(self) -> Optional[Tuple[str, str]]: - # ['Gourmet groceries, cheeses & baked goods are available at this casual deli in a resort setting.', 'en'] - return self._input_list[51] - - @property - def images(self) -> list: - return ( - [{"url": img[0], "author": img[3]} for img in self._input_list[53]] - if self._input_list[53] - else [] - ) - - def __str__(self) -> str: - place_type = self.place_type_and_lang - if place_type: - place_type = " - " + place_type[0] - else: - place_type = "" - return f"{self.title[0]}{place_type}" - - def markdown(self) -> str: - description = self.description() - if description: - description = "\n" + description[0] - else: - description = "" - - place_type = self.place_type_and_lang - if place_type: - place_type = place_type[0] - else: - place_type = "" - return f"{self.title[0]}\n{place_type} {self.rating}★({self.rating_count}){description}" - - -class TravelMode(Enum): - DRIVING = 0 - WALKING = 1 - TRANSIT = 2 - BICYCLING = 3 - UNKNOWN = 4 - - -class BardMapsRoadSection: - def __init__(self, input_list: list): - self._input_list = input_list - - @property - def instructions(self) -> list: - return self._input_list[0] - - @property - def duration(self) -> List[int, str]: - # [16873, '4 hours 41 mins'] - return self._input_list[1] - - @property - def distance(self) -> List[int, str]: - # [313054, '313 km'] - return self._input_list[2] - - @property - def start_point(self) -> List[float, float]: - return self._input_list[5] - - @property - def end_point(self) -> List[float, float]: - return self._input_list[6] - - @property - def start_location(self) -> str: - return self._input_list[7] - - @property - def end_location(self) -> str: - return self._input_list[8] - - def __str__(self): - return f"{self.start_location} to {self.end_location} - {self.duration[1]}({self.distance[1]})" - - -class BardMapsDirections: - def __init__(self, input_list: list): - self._input_list = input_list - - @property - def _map(self) -> list: - return self._input_list[0][1][0] - - @property - def url(self) -> str: - return self._input_list[1] - - @property - def road_name(self) -> str: - return self._map[0] - - @property - def sections(self) -> List[BardMapsRoadSection]: - return [BardMapsRoadSection(s) for s in self._map[1]] - - @property - def geo_position(self) -> Tuple[Tuple[float, float], Tuple[float, float]]: - return self._map[6] - - def __str__(self): - return "via " + self.road_name - - -class BardMapContent(UserContent): - """http://googleusercontent.com/map_content/""" - - def __init__(self, input_list: list): - self._input_list = input_list - - @property - def key(self) -> str: - return self._input_list[2][0] - - @property - def title(self) -> str: - # Places - return self._input_list[2][2] - - @property - def tool_human_name(self) -> str: - # Google Maps - return self._input_list[2][6][0] - - @property - def points(self) -> List[BardMapsPoint]: - return ( - [BardMapsPoint(point) for point in self._input_list[0][1]] - if self._input_list[0] - else [] - ) - - @property - def directions(self) -> Optional[BardMapsDirections]: - return BardMapsDirections(self._input_list[1]) if self._input_list[1] else None - - def __str__(self) -> str: - return self.title - - @property - def markdown_text(self) -> str: - res = f"# {self.title}\n\n" - if self.directions: - res += f"## {self.directions}\n\n" - - if self.points: - res += "\n\n".join([f"## {p.markdown()}" for p in self.points]) - - return res diff --git a/gemini/src/tools/google/tool.py b/gemini/src/tools/google/tool.py deleted file mode 100644 index 665a093..0000000 --- a/gemini/src/tools/google/tool.py +++ /dev/null @@ -1,29 +0,0 @@ -# Legacy -from typing import Optional - - -class GeminiTool: - def __init__(self, input_list: list): - self._input_list = input_list - - @property - def step(self) -> Optional[str]: - # Finding your documents - return self._input_list[0] - - @property - def name(self) -> str: - # google_map_tool - return self._input_list[1][0] - - @property - def human_name(self) -> str: - # Google Maps - return self._input_list[1][1][2] - - @property - def logo(self) -> str: - return self._input_list[1][1][3] - - def __str__(self) -> str: - return f'{self.human_name} - {self.step or ""}' diff --git a/gemini/src/tools/google/tool_declaimer.py b/gemini/src/tools/google/tool_declaimer.py deleted file mode 100644 index 838c4e8..0000000 --- a/gemini/src/tools/google/tool_declaimer.py +++ /dev/null @@ -1,21 +0,0 @@ -# Legacy -from gemini.src.tools.user_content import UserContent - - -class BardToolDeclaimer(UserContent): - """http://googleusercontent.com/tool_disclaimer_content/""" - - def __init__(self, input_list: list): - self._input_list = input_list - self.text = self._input_list[1] - - @property - def key(self) -> str: - return self._input_list[0][0] - - @property - def markdown_text(self) -> str: - return "\n".join(["> " + line for line in self.text.split("\n")]) - - def __str__(self) -> str: - return self.text[:20] diff --git a/gemini/src/tools/google/youtube.py b/gemini/src/tools/google/youtube.py deleted file mode 100644 index a37f81f..0000000 --- a/gemini/src/tools/google/youtube.py +++ /dev/null @@ -1,89 +0,0 @@ -# Legacy -from typing import List - -from gemini.src.tools.user_content import UserContent - - -class BardYoutubeVideo: - def __init__(self, input_list: list): - self._input_list = input_list - - @property - def id(self) -> str: - return self._input_list[1] - - @property - def title(self) -> str: - return self._input_list[0] - - @property - def url(self) -> str: - return self._input_list[2] - - @property - def author(self) -> str: - return self._input_list[3] - - @property - def channel_logo(self) -> str: - return self._input_list[4] - - @property - def text(self) -> str: - return self._input_list[5][0] if self._input_list[5] else "" - - def __str__(self) -> str: - return self.title - - -class BardYoutubeContent(UserContent): - """http://googleusercontent.com/youtube_content/""" - - def __init__(self, input_list: list): - self._input_list = input_list - - @property - def key(self) -> str: - return self._input_list[0][0] - - @property - def search_query(self) -> str: - return self._input_list[0][2] - - @property - def search_url(self) -> str: - return self._input_list[7] - - def __getitem__(self, item): - return self.videos[item] - - def __len__(self): - return len(self._input_list[4][0]) - - @property - def videos(self) -> List[BardYoutubeVideo]: - return ( - [BardYoutubeVideo(video) for video in self._input_list[4][0]] - if self._input_list[4] - else [] - ) - - def __str__(self) -> str: - return self.search_query - - @property - def markdown_text(self) -> str: - videos = [ - ( - f"1. [{video.title}]({video.url}) by {video.author}\n\n - {video.text}" - if video.text - else f"1. [{video.title}]({video.url}) by {video.author}" - ) - for video in self.videos - ] - - return ( - f"#### {self.search_query}\n\n" - + "\n".join(videos) - + f"\n\n _View related videos on_ [YouTube]({self.search_url})" - ) diff --git a/gemini/src/tools/image.py b/gemini/src/tools/image.py deleted file mode 100644 index 2c83b4b..0000000 --- a/gemini/src/tools/image.py +++ /dev/null @@ -1,48 +0,0 @@ -# Legacy -from typing import Optional - - -class GeminiImage: - def __init__(self, input_list: list): - self._input_list = input_list - self.urls = input_list[0] - self.width = input_list[2] - self.height = input_list[3] - self.alt: Optional[str] = input_list[4] if len(input_list) > 4 else None - - def __str__(self) -> str: - return f"{self.urls[0]} ({self.width}x{self.height})" - - -class GeminiImageContent: - def __init__(self, input_list: list): - self._input_list = input_list - - @property - def original(self) -> GeminiImage: - return GeminiImage(self._input_list[0]) - - @property - def source(self) -> dict: - v = self._input_list[1] - return {"pages": v[0], "domain": v[1], "fav_icon": v[3]} - - @property - def key(self) -> str: - """use this to replace the image in the markdown, several images can have the same key""" - return self._input_list[2] - - @property - def thumbnail(self) -> GeminiImage: - return GeminiImage(self._input_list[3]) - - @property - def markdown(self) -> list: - return self._input_list[7] - - @property - def alt(self) -> str: - return self.markdown[2] - - def __str__(self) -> str: - return f"[{self.alt}]({self.original.urls[0]})" diff --git a/gemini/src/tools/result.py b/gemini/src/tools/result.py deleted file mode 100644 index d4099a5..0000000 --- a/gemini/src/tools/result.py +++ /dev/null @@ -1,78 +0,0 @@ -# Legacy -from typing import List, Optional - -from gemini.src.tools.draft import GeminiDraft -from gemini.src.tools.google.tool import GeminiTool - - -class GeminiUserLocation: - def __init__(self, input_list: list): - self._input_list = input_list - - @property - def location_str(self) -> str: - return self._input_list[0] - - @property - def description(self) -> str: - return self._input_list[1] - - @property - def geo_position(self) -> list: - return self._input_list[3][0][0][3] - - @property - def image_url(self) -> str: - return "https:" + self._input_list[4] - - def __str__(self) -> str: - return self.location_str - - -class GeminiResult: - def __init__(self, input_list: list): - self._input_list = input_list - self.conversation_id = self._input_list[1][0] - self.response_id = self._input_list[1][1] - - @property - def search_queries(self) -> List[str, int]: - return self._input_list[2] - - @property - def factuality_queries(self) -> Optional[list]: - return self._input_list[3] - - @property - def drafts(self) -> List[GeminiDraft]: - return ( - [GeminiDraft(c) for c in self._input_list[4]] if self._input_list[4] else [] - ) - - @property - def location(self) -> GeminiUserLocation: - return GeminiUserLocation(self._input_list[5]) - - @property - def progress_tool(self) -> GeminiTool: - return GeminiTool(self._input_list[6]) if self._input_list[6] else None - - @property - def country(self) -> str: - return self._input_list[8] - - @property - def topic(self) -> Optional[str]: - if len(self._input_list) < 11 or not self._input_list[10]: - return None - return self._input_list[10][0] - - @property - def tools_applied(self) -> List[GeminiTool]: - if len(self._input_list) < 12: - return [] - return ( - [GeminiTool(tool) for tool in self._input_list[11]] - if self._input_list[11] - else [] - ) diff --git a/gemini/src/tools/user_content.py b/gemini/src/tools/user_content.py deleted file mode 100644 index 4a94a50..0000000 --- a/gemini/src/tools/user_content.py +++ /dev/null @@ -1,15 +0,0 @@ -# Legacy -from abc import ABC, abstractmethod - - -class UserContent(ABC): - @property - @abstractmethod - def key(self) -> str: - pass - - # Markdown representation as a long string - @property - @abstractmethod - def markdown_text(self) -> str: - pass diff --git a/save/temp.wav b/save/temp.wav new file mode 100644 index 0000000000000000000000000000000000000000..43b7361d05f14cbeb720107521907c6c9f6e681d GIT binary patch literal 11712 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