diff --git a/.gitignore b/.gitignore index de9ba73..474fee5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ test.ipynb func_text.ipynb func_test +gemini/init_creator.py +pyproject.toml # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/gemini/__init__.py b/gemini/__init__.py index 5dee7e7..07bf962 100644 --- a/gemini/__init__.py +++ b/gemini/__init__.py @@ -1,28 +1,15 @@ # Copyright 2024 Daniel Park, Antonio Cheang, MIT License from os import environ -from gemini.core import Gemini -from gemini.constants import ( - HEADERS, - REPLIT_SUPPORT_PROGRAM_LANGUAGES, - Tool, -) -from gemini.client import GeminiClient -from gemini.utils import max_token, max_sentence, build_replit_data, extract_code +from .client import GeminiClient +from .core import Gemini +from .src.misc import * +from .src.parser import * +from .src.tools import * +from .src.tools.google import * gemini_api_key = environ.get("GEMINI_COOKIES") -__all__ = [ - "GeminiClient", - "Gemini", - "max_token", - "max_sentence", - "HEADERS", - "REPLIT_SUPPORT_PROGRAM_LANGUAGES", - "Tool", - "build_replit_data", - "extract_code", -] -__version__ = "1.0.3" +__version__ = "1.0.4" __author__ = ( "daniel park , antonio cheang " ) diff --git a/gemini/client.py b/gemini/client.py index 59c4f52..a8305da 100644 --- a/gemini/client.py +++ b/gemini/client.py @@ -11,7 +11,7 @@ import requests from typing import Optional, Any, List -from .constants import ( +from gemini.src.misc.constants import ( HEADERS, SUPPORTED_BROWSERS, BOT_SERVER, diff --git a/gemini/core.py b/gemini/core.py index bba27aa..553ecae 100644 --- a/gemini/core.py +++ b/gemini/core.py @@ -10,9 +10,16 @@ from typing import Optional, Tuple, Dict from requests.exceptions import ConnectionError, RequestException -from .models.parser.custom_parser import ParseMethod1, ParseMethod2 -from .models.parser.response_parser import ResponseParser -from .constants import HEADERS, HOST, BOT_SERVER, POST_ENDPOINT, SUPPORTED_BROWSERS +from .src.parser.custom_parser import ParseMethod1, ParseMethod2 +from .src.parser.response_parser import ResponseParser +from .src.model.output import GeminiCandidate, GeminiModelOutput +from .src.misc.constants import ( + HEADERS, + HOST, + BOT_SERVER, + POST_ENDPOINT, + SUPPORTED_BROWSERS, +) class Gemini: @@ -340,6 +347,30 @@ def generate_content(self, prompt) -> dict: ) return parsed_json + def generate_content(self, prompt: str) -> GeminiModelOutput: + """Generates content based on the prompt and returns a GeminiModelOutput object.""" + response_text, response_status_code = self.send_request(prompt) + if response_status_code != 200: + raise ValueError(f"Response status: {response_status_code}") + else: + parser = ResponseParser(cookies=self.cookies) + parsed_data = parser.parse(response_text) + + return parsed_data + # return self._create_model_output(parsed_data, parsed_json=parsed_data) + + def _create_model_output( + self, parsed_data: dict, parsed_json: dict + ) -> GeminiModelOutput: + candidates = [ + GeminiCandidate(**details) for details in parsed_data["candidates"].values() + ] + return GeminiModelOutput( + metadata=parsed_data.get("metadata", []), + candidates=candidates, + response_dict=parsed_json, + ) + def generate_custom_content(self, prompt: str, *custom_parsers) -> str: """Generates content based on the prompt, attempting to parse with ParseMethod1, ParseMethod2, and any additional parsers provided.""" response_text, response_status_code = self.send_request(prompt) diff --git a/gemini/models/__init__.py b/gemini/src/__init__.py similarity index 100% rename from gemini/models/__init__.py rename to gemini/src/__init__.py diff --git a/gemini/models/tools/__init__.py b/gemini/src/misc/__init__.py similarity index 100% rename from gemini/models/tools/__init__.py rename to gemini/src/misc/__init__.py diff --git a/gemini/constants.py b/gemini/src/misc/constants.py similarity index 100% rename from gemini/constants.py rename to gemini/src/misc/constants.py diff --git a/gemini/utils.py b/gemini/src/misc/utils.py similarity index 100% rename from gemini/utils.py rename to gemini/src/misc/utils.py diff --git a/gemini/models/tools/gdocs.py b/gemini/src/model/__init__.py similarity index 100% rename from gemini/models/tools/gdocs.py rename to gemini/src/model/__init__.py diff --git a/gemini/src/model/output.py b/gemini/src/model/output.py new file mode 100644 index 0000000..ced876e --- /dev/null +++ b/gemini/src/model/output.py @@ -0,0 +1,71 @@ +import httpx +from pathlib import Path +from loguru import logger +from typing import List, Optional, Dict +from pydantic import BaseModel, HttpUrl + + +class GeminiImage(BaseModel): + url: HttpUrl + title: str = "[Image]" + alt: str = "" + + def __str__(self): + return f"{self.title}({self.url}) - {self.alt}" + + async def save( + self, + path: str = "temp", + filename: Optional[str] = None, + cookies: Optional[dict] = None, + ) -> Optional[Path]: + filename = filename or Path(self.url).name + save_path = Path(path) / filename + save_path.parent.mkdir(parents=True, exist_ok=True) + + async with httpx.AsyncClient(follow_redirects=True, cookies=cookies) as client: + try: + response = await client.get(self.url) + response.raise_for_status() + save_path.write_bytes(response.content) + return save_path + except httpx.HTTPStatusError as e: + logger.error( + f"Error downloading image: {e.response.status_code} {e.response.reason_phrase}" + ) + return None + + +class GeminiCandidate(BaseModel): + rcid: str + text: str + web_images: List[GeminiImage] = [] + generated_images: List[GeminiImage] = [] + raw: Dict + + +class GeminiModelOutput(BaseModel): + metadata: List[str] + candidates: List[GeminiCandidate] + chosen: int = 0 + response_dict: Optional[dict] = None + + @property + def rcid(self) -> str: + return self.candidates[self.chosen].rcid + + @property + def text(self) -> str: + return self.candidates[self.chosen].text + + @property + def web_images(self) -> List[GeminiImage]: + return self.candidates[self.chosen].web_images + + @property + def generated_images(self) -> List[GeminiImage]: + return self.candidates[self.chosen].generated_images + + @property + def raw(self) -> Optional[Dict]: + return self.response_dict diff --git a/gemini/src/parser/__init__.py b/gemini/src/parser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gemini/models/parser/base.py b/gemini/src/parser/base.py similarity index 100% rename from gemini/models/parser/base.py rename to gemini/src/parser/base.py diff --git a/gemini/models/parser/custom_parser.py b/gemini/src/parser/custom_parser.py similarity index 99% rename from gemini/models/parser/custom_parser.py rename to gemini/src/parser/custom_parser.py index 41fc8b7..17555cb 100644 --- a/gemini/models/parser/custom_parser.py +++ b/gemini/src/parser/custom_parser.py @@ -1,4 +1,4 @@ -from gemini.models.parser.base import BaesParser +from gemini.src.parser.base import BaesParser from typing import Dict, Any diff --git a/gemini/models/parser/response_parser.py b/gemini/src/parser/response_parser.py similarity index 95% rename from gemini/models/parser/response_parser.py rename to gemini/src/parser/response_parser.py index a268988..0f298df 100644 --- a/gemini/models/parser/response_parser.py +++ b/gemini/src/parser/response_parser.py @@ -1,4 +1,4 @@ -from gemini.models.parser.base import BaesParser +from gemini.src.parser.base import BaesParser import json @@ -47,7 +47,6 @@ def _extract_body(self, response_text): ) def _parse_candidates(self, candidates_data): - candidates = [] for candidate_data in candidates_data: web_images = self._parse_web_images(candidate_data[4]) generated_images = self._parse_generated_images(candidate_data[12]) @@ -57,8 +56,7 @@ def _parse_candidates(self, candidates_data): "web_images": web_images, "generated_images": generated_images, } - candidates.append(candidate_dict) - return candidates + return candidate_dict def _parse_web_images(self, images_data): if not images_data: diff --git a/gemini/src/tools/__init__.py b/gemini/src/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gemini/models/citation.py b/gemini/src/tools/citation.py similarity index 97% rename from gemini/models/citation.py rename to gemini/src/tools/citation.py index c02525c..5c0758e 100644 --- a/gemini/models/citation.py +++ b/gemini/src/tools/citation.py @@ -1,4 +1,3 @@ -# Legacy code from Bard, not used in Gemini. class DraftCitation: """Github source [[909, 1095, ['', 'edelahoz/Introduction-to-Python ', '', '', '', None, None, '', False, '', '', '', '', '', '', ''], 1, 100, None, [1]], diff --git a/gemini/models/decorator.py b/gemini/src/tools/decorator.py similarity index 100% rename from gemini/models/decorator.py rename to gemini/src/tools/decorator.py diff --git a/gemini/models/draft.py b/gemini/src/tools/draft.py similarity index 85% rename from gemini/models/draft.py rename to gemini/src/tools/draft.py index 6ed44d5..cc67882 100644 --- a/gemini/models/draft.py +++ b/gemini/src/tools/draft.py @@ -1,18 +1,17 @@ -# Legacy code from Bard, not used in Gemini. -from typing import List, Optional, Union, Dict, Tuple - -from gemini.models.citation import DraftCitation -from gemini.models.tools.code import CodeContent -from gemini.models.tools.flight import BardFlightContent -from gemini.models.tools.gworkspace import GoogleWorkspaceContent -from gemini.models.image import BardImageContent -from gemini.models.tools.hotel import BardHotelContent -from gemini.models.tools.json import JsonContent -from gemini.models.tools.link import BardLink -from gemini.models.tools.map import BardMapContent -from gemini.models.tools.tool_declaimer import BardToolDeclaimer -from gemini.models.user_content import UserContent -from gemini.models.tools.youtube import BardYoutubeContent +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: diff --git a/gemini/models/exceptions.py b/gemini/src/tools/exceptions.py similarity index 100% rename from gemini/models/exceptions.py rename to gemini/src/tools/exceptions.py diff --git a/gemini/src/tools/google/__init__.py b/gemini/src/tools/google/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gemini/models/tools/code.py b/gemini/src/tools/google/code.py similarity index 100% rename from gemini/models/tools/code.py rename to gemini/src/tools/google/code.py diff --git a/gemini/models/tools/flight.py b/gemini/src/tools/google/flight.py similarity index 98% rename from gemini/models/tools/flight.py rename to gemini/src/tools/google/flight.py index 10273f1..8937529 100644 --- a/gemini/models/tools/flight.py +++ b/gemini/src/tools/google/flight.py @@ -1,4 +1,4 @@ -from gemini.models.user_content import UserContent +from gemini.src.tools.user_content import UserContent from typing import List diff --git a/gemini/src/tools/google/gdocs.py b/gemini/src/tools/google/gdocs.py new file mode 100644 index 0000000..e69de29 diff --git a/gemini/models/tools/gworkspace.py b/gemini/src/tools/google/gworkspace.py similarity index 97% rename from gemini/models/tools/gworkspace.py rename to gemini/src/tools/google/gworkspace.py index 681132d..9ef9d98 100644 --- a/gemini/models/tools/gworkspace.py +++ b/gemini/src/tools/google/gworkspace.py @@ -1,4 +1,5 @@ from enum import Enum +from typing import List class GoogleWorkspaceContentKind(Enum): @@ -61,7 +62,7 @@ def author(self) -> str: return self._input_list[5] @property - def timestamp_seconds(self) -> [int]: + def timestamp_seconds(self) -> List[int]: return self._input_list[6] def __str__(self) -> str: diff --git a/gemini/models/tools/hotel.py b/gemini/src/tools/google/hotel.py similarity index 97% rename from gemini/models/tools/hotel.py rename to gemini/src/tools/google/hotel.py index 801fa0b..c36cbb3 100644 --- a/gemini/models/tools/hotel.py +++ b/gemini/src/tools/google/hotel.py @@ -1,4 +1,4 @@ -from gemini.models.user_content import UserContent +from gemini.src.tools.user_content import UserContent from typing import List diff --git a/gemini/models/tools/json.py b/gemini/src/tools/google/json.py similarity index 90% rename from gemini/models/tools/json.py rename to gemini/src/tools/google/json.py index bae98e6..3ded6ce 100644 --- a/gemini/models/tools/json.py +++ b/gemini/src/tools/google/json.py @@ -1,6 +1,6 @@ import json -from gemini.models.user_content import UserContent +from gemini.src.tools.user_content import UserContent class JsonContent(UserContent): diff --git a/gemini/models/tools/link.py b/gemini/src/tools/google/link.py similarity index 88% rename from gemini/models/tools/link.py rename to gemini/src/tools/google/link.py index a01e9f7..1423557 100644 --- a/gemini/models/tools/link.py +++ b/gemini/src/tools/google/link.py @@ -1,4 +1,4 @@ -from gemini.models.user_content import UserContent +from gemini.src.tools.user_content import UserContent class BardLink(UserContent): diff --git a/gemini/models/tools/map.py b/gemini/src/tools/google/map.py similarity index 94% rename from gemini/models/tools/map.py rename to gemini/src/tools/google/map.py index d2709dc..e743e75 100644 --- a/gemini/models/tools/map.py +++ b/gemini/src/tools/google/map.py @@ -1,7 +1,7 @@ from enum import Enum from typing import List, Optional, Union, Dict, Tuple -from gemini.models.user_content import UserContent +from gemini.src.tools.user_content import UserContent class BardMapsPoint: @@ -122,21 +122,21 @@ def instructions(self) -> list: return self._input_list[0] @property - def duration(self) -> [int, str]: + def duration(self) -> List[int, str]: # [16873, '4 hours 41 mins'] return self._input_list[1] @property - def distance(self) -> [int, str]: + def distance(self) -> List[int, str]: # [313054, '313 km'] return self._input_list[2] @property - def start_point(self) -> [float, float]: + def start_point(self) -> List[float, float]: return self._input_list[5] @property - def end_point(self) -> [float, float]: + def end_point(self) -> List[float, float]: return self._input_list[6] @property @@ -172,7 +172,7 @@ def sections(self) -> List[BardMapsRoadSection]: return [BardMapsRoadSection(s) for s in self._map[1]] @property - def geo_position(self) -> [[float, float], [float, float]]: + def geo_position(self) -> Tuple[Tuple[float, float], Tuple[float, float]]: return self._map[6] def __str__(self): diff --git a/gemini/models/tools/tool.py b/gemini/src/tools/google/tool.py similarity index 100% rename from gemini/models/tools/tool.py rename to gemini/src/tools/google/tool.py diff --git a/gemini/models/tools/tool_declaimer.py b/gemini/src/tools/google/tool_declaimer.py similarity index 90% rename from gemini/models/tools/tool_declaimer.py rename to gemini/src/tools/google/tool_declaimer.py index eb9bcb9..cf1834f 100644 --- a/gemini/models/tools/tool_declaimer.py +++ b/gemini/src/tools/google/tool_declaimer.py @@ -1,4 +1,4 @@ -from gemini.models.user_content import UserContent +from gemini.src.tools.user_content import UserContent class BardToolDeclaimer(UserContent): diff --git a/gemini/models/tools/youtube.py b/gemini/src/tools/google/youtube.py similarity index 92% rename from gemini/models/tools/youtube.py rename to gemini/src/tools/google/youtube.py index 0568a18..d74018a 100644 --- a/gemini/models/tools/youtube.py +++ b/gemini/src/tools/google/youtube.py @@ -1,6 +1,6 @@ -from typing import List, Optional, Union, Dict, Tuple +from typing import List -from gemini.models.user_content import UserContent +from gemini.src.tools.user_content import UserContent class BardYoutubeVideo: @@ -60,7 +60,7 @@ def __len__(self): return len(self._input_list[4][0]) @property - def videos(self) -> list[BardYoutubeVideo]: + def videos(self) -> List[BardYoutubeVideo]: return ( [BardYoutubeVideo(video) for video in self._input_list[4][0]] if self._input_list[4] diff --git a/gemini/models/image.py b/gemini/src/tools/image.py similarity index 94% rename from gemini/models/image.py rename to gemini/src/tools/image.py index 03670e1..3393f0c 100644 --- a/gemini/models/image.py +++ b/gemini/src/tools/image.py @@ -1,4 +1,3 @@ -# Legacy code from Bard, not used in Gemini. from typing import Optional @@ -14,7 +13,7 @@ def __str__(self) -> str: return f"{self.urls[0]} ({self.width}x{self.height})" -class BardImageContent: +class GeminiImageContent: def __init__(self, input_list: list): self._input_list = input_list diff --git a/gemini/models/result.py b/gemini/src/tools/result.py similarity index 84% rename from gemini/models/result.py rename to gemini/src/tools/result.py index dc79919..f183b66 100644 --- a/gemini/models/result.py +++ b/gemini/src/tools/result.py @@ -1,8 +1,7 @@ -# Legacy code from Bard, not used in Gemini. -from typing import List, Optional, Union, Dict, Tuple +from typing import List, Optional -from gemini.models.draft import GeminiDraft -from gemini.models.tools.tool import GeminiTool +from gemini.src.tools.draft import GeminiDraft +from gemini.src.tools.google.tool import GeminiTool class GeminiUserLocation: @@ -36,7 +35,7 @@ def __init__(self, input_list: list): self.response_id = self._input_list[1][1] @property - def search_queries(self) -> list[str, int]: + def search_queries(self) -> List[str, int]: return self._input_list[2] @property @@ -44,7 +43,7 @@ def factuality_queries(self) -> Optional[list]: return self._input_list[3] @property - def drafts(self) -> list[GeminiDraft]: + def drafts(self) -> List[GeminiDraft]: return ( [GeminiDraft(c) for c in self._input_list[4]] if self._input_list[4] else [] ) @@ -68,7 +67,7 @@ def topic(self) -> Optional[str]: return self._input_list[10][0] @property - def tools_applied(self) -> list[GeminiTool]: + def tools_applied(self) -> List[GeminiTool]: if len(self._input_list) < 12: return [] return ( diff --git a/gemini/models/user_content.py b/gemini/src/tools/user_content.py similarity index 55% rename from gemini/models/user_content.py rename to gemini/src/tools/user_content.py index 0de5d3f..ada3719 100644 --- a/gemini/models/user_content.py +++ b/gemini/src/tools/user_content.py @@ -1,5 +1,4 @@ -# Legacy code from Bard, not used in Gemini. -from abc import ABC, abstractmethod, abstractproperty +from abc import ABC, abstractmethod class UserContent(ABC): @@ -8,7 +7,7 @@ class UserContent(ABC): def key(self) -> str: pass - # markdown representation as a long string + # Markdown representation as a long string @property @abstractmethod def markdown_text(self) -> str: diff --git a/requirements.txt b/requirements.txt index c37fad9..172df5b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,6 @@ httpx[http2]>=0.20.0 langdetect deep_translator google-cloud-translate -browser_cookie3 \ No newline at end of file +browser_cookie3 +loguru +pydantic \ No newline at end of file diff --git a/setup.py b/setup.py index c792eaa..6a6fc03 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ def read(file_path, version=False): }, keywords="Python, API, Gemini, Google Gemini, Large Language Model, Chatbot API, Google API, Chatbot", classifiers=[ - "Development Status :: 3 - Alpha", + "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Science/Research", "Natural Language :: English", "Programming Language :: Python",