From 357cba36e4b486544840dd3f01bc0ea55a1d4497 Mon Sep 17 00:00:00 2001 From: ajaysi Date: Mon, 22 Apr 2024 10:09:07 +0530 Subject: [PATCH] Fixed issue with Gemini API --- README.md | 21 +++- alwrity.py | 48 ++++----- .../image_to_text_gen/gemini_image_details.py | 79 --------------- .../openai_vision_image_details.py} | 0 .../text_generation/ai_essay_writer.py | 2 +- .../text_generation/gemini_pro_text.py | 2 +- .../gen_dali2_images.py | 0 .../gen_dali3_images.py | 0 .../gen_stabl_diff_img.py | 41 ++++++++ .../gen_variation_img.py | 0 .../generate_image_from_prompt.py | 23 ++--- .../text_to_image_generation}/save_image.py | 0 lib/text_to_image/stabl_diff_img2html.py | 66 ------------- .../.alwrity_utils.py.swp} | Bin 12288 -> 16384 bytes lib/utils/alwrity_utils.py | 92 ++++++++++++++++++ 15 files changed, 188 insertions(+), 186 deletions(-) delete mode 100644 lib/gpt_providers/image_to_text_gen/gemini_image_details.py rename lib/{image_to_text/gpt_vision_image_details.py => gpt_providers/image_to_text_gen/openai_vision_image_details.py} (100%) rename lib/gpt_providers/{image_generation => text_to_image_generation}/gen_dali2_images.py (100%) rename lib/gpt_providers/{image_generation => text_to_image_generation}/gen_dali3_images.py (100%) create mode 100644 lib/gpt_providers/text_to_image_generation/gen_stabl_diff_img.py rename lib/gpt_providers/{image_generation => text_to_image_generation}/gen_variation_img.py (100%) rename lib/{text_to_image => gpt_providers/text_to_image_generation}/generate_image_from_prompt.py (80%) rename lib/{blog_postprocessing => gpt_providers/text_to_image_generation}/save_image.py (100%) delete mode 100644 lib/text_to_image/stabl_diff_img2html.py rename lib/{gpt_providers/text_generation/.gemini_pro_text.py.swp => utils/.alwrity_utils.py.swp} (50%) diff --git a/README.md b/README.md index 511942b5..298ed9a8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,28 @@ -# AI Content Generation Toolkit - Alwrity -![](https://github.com/AJaySi/AI-Blog-Writer/blob/main/workspace/keyword_blog.gif) +# How to Use AI Content Generation Toolkit - Alwrity + +1). Visit alwrity.com, Under tools section, you will find some AI content writing tools, which are Free & No-Signup. +**Note:** Although, this is limited, as is our wallet & Resources. + +2). For complete AI content toolkit, alwrity offers a commandline App. Its a BYOK model(Bring Your Own Key). +**Note:** (πŸ—―οΈ commandline, byok and shit) ... Now, before you run away πŸƒπŸ’¨ + +If you have πŸ’» Laptop + πŸ›œ Internet + 20 minutes, you will be generating blogs, articles etc with just few words. + +**[Getting Started for Absolute Begginers]**(https://www.alwrity.com/post/getting-started-with-alwrity-ai-writer) + +Getting started for pretentious Developers : Continue Reading..... + +**If you still Get stuck, Open a issue here & say pretty please** :: https://github.com/AJaySi/AI-Writer/issues + ## Introduction Alwrity automates and enhances the process of blog creation, optimization, and management. Leveraging AI technologies, it assists content creators and digital marketers in generating, formatting, and uploading blog content efficiently. The toolkit integrates advanced AI models for text generation, image creation, and data analysis, streamlining the content creation pipeline. +# AI Content Generation Toolkit - Alwrity +![](https://github.com/AJaySi/AI-Blog-Writer/blob/main/workspace/keyword_blog.gif) + --- ## Getting Started πŸš€ 🀞🀞🀞 diff --git a/alwrity.py b/alwrity.py index 462e403f..90fff985 100644 --- a/alwrity.py +++ b/alwrity.py @@ -5,6 +5,7 @@ from datetime import datetime import typer from prompt_toolkit.shortcuts import checkboxlist_dialog, message_dialog, input_dialog +from prompt_toolkit.shortcuts import radiolist_dialog from prompt_toolkit import prompt from prompt_toolkit.styles import Style from prompt_toolkit.shortcuts import radiolist_dialog @@ -20,7 +21,8 @@ load_dotenv(Path('.env')) app = typer.Typer() -from lib.utils.alwrity_utils import blog_from_audio, blog_from_keyword, do_web_research, do_web_research, write_story, essay_writer, blog_tools, competitor_analysis +from lib.utils.alwrity_utils import blog_from_audio, blog_from_keyword, do_web_research, do_web_research +from lib.utils.alwrity_utils import write_story, essay_writer, blog_tools, competitor_analysis, image_to_text_writer, image_generator def prompt_for_time_range(): @@ -61,10 +63,11 @@ def start_interactive_mode(): ("AI Blog Writer", "AI Blog Writer"), ("AI Story Writer", "AI Story Writer"), ("AI Essay Writer", "AI Essay Writer"), + ("AI Image to Text Writer", "AI Image to Text Writer"), ("Online Blog Tools/Apps", "Online Blog Tools/Apps"), ("Do keyword Research", "Do keyword Research"), ("Competitor Analysis", "Competitor Analysis"), - ("Create Blog Images(TBD)", "Create Blog Images(TBD)"), + ("Create Blog Images", "Create Blog Images"), ("AI Social Media(TBD)", "AI Social Media(TBD)"), ("Quit", "Quit") ] @@ -76,10 +79,12 @@ def start_interactive_mode(): write_story() elif mode == 'AI Essay Writer': essay_writer() + elif mode == 'AI Image to Text Writer': + image_to_text_writer() elif mode == 'Do keyword Research': do_web_research() - elif mode == 'Create Blog Images(TBD)': - faq_generator() + elif mode == 'Create Blog Images': + image_generator() elif mode == 'Competitor Analysis': competitor_analysis() elif mode == 'Online Blog Tools/Apps': @@ -105,8 +110,7 @@ def check_search_apis(): """ # Use rich.print for styling and hyperlinking - print("\n\nπŸ™‹β™‚οΈ πŸ™‹β™‚οΈ Before doing web research, ensure the following API keys are available:") - print("Blogen uses Basic, Semantic, Neural web search using above APIs for contextual blog generation.\n") + print("Alwrity uses Basic, Semantic, Neural web search using above APIs for contextual blog generation.\n") api_keys = { "METAPHOR_API_KEY": "Metaphor AI Key (Get it here: [link=https://dashboard.exa.ai/login]Metaphor API[/link])", @@ -121,7 +125,6 @@ def check_search_apis(): if os.getenv(key) is None: # Use rich.print for styling and hyperlinking print(f"[bold red]βœ– 🚫 {key} is missing:[/bold red] [blue underline]Get {key} API Key[/blue underline]") - typer.echo(f"[bold red]βœ– 🚫 {key} is missing:[/bold red] [link={key}]Get {key} API Key[/link]") missing_keys.append((key, description)) if missing_keys: @@ -142,10 +145,12 @@ def get_api_key(api_key: str, api_description: str): api_key (str): The name of the API key variable. api_description (str): The description of the API key. """ - user_input = typer.prompt(f"\n\nπŸ™†πŸ’©πŸ’©πŸ™† - Please enter {api_key} API Key:") + print("\n\n") + print(f"[bold green] πŸ™‹ Attention Here: πŸ™‹ -- {api_description}") + user_input = typer.prompt(f"πŸ’© -**Please Enter(copy/paste) {api_key} API Key** - HereπŸ™‹:") with open(".env", "a") as env_file: env_file.write(f"{api_key}={user_input}\n") - print(f"βœ… {api_description} API Key added to .env file.") + print(f"βœ… API Key added to .env file.") def write_blog(): @@ -172,22 +177,20 @@ def check_llm_environs(): """ Function to check which LLM api is given. """ # Load .env file load_dotenv(Path('.env')) - # Check if GPT_PROVIDER is defined in .env file gpt_provider = os.getenv("GPT_PROVIDER") # Disable unsupported GPT providers supported_providers = ['google', 'openai', 'mistralai'] if gpt_provider is None or gpt_provider.lower() not in map(str.lower, supported_providers): # Prompt user to select a provider - selected_provider = radiolistbox( - msg='Select your preferred GPT provider:', - title='GPT Provider Selection', - choices=["Google", "OpenAI", "MistralAI/WIP", "Ollama (TBD)"], - default_choice="Google" - ) - gpt_provider = selected_provider + gpt_provider = radiolist_dialog( + title="Select your GPT Provider(llm) from 'google', 'openai', 'mistralai'", + values=[("google", "Google Gemini Pro"), ("openai", "OpenAI- ChatGPT"), ("mistralai", "MistralAI/WIP")]).run() # Update .env file os.environ["GPT_PROVIDER"] = gpt_provider + with open(".env", "a") as env_file: + env_file.write(f"GPT_PROVIDER=gpt_provider\n") + print(f"βœ… API Key added to .env file.") if gpt_provider.lower() == "google": api_key_var = "GEMINI_API_KEY" @@ -198,9 +201,9 @@ def check_llm_environs(): elif gpt_provider.lower() == "mistralai": api_key_var = "MISTRAL_API_KEY" missing_api_msg = "To get your MistralAI API key, please visit: https://mistralai.com/api" - else: - print("Unrecognised/Unsupported GPT provider. Check your main_config and environs.") - exit(1) + + if api_key_var not in os.environ: + get_api_key(api_key_var, missing_api_msg) def check_internet(): @@ -233,13 +236,10 @@ def create_env_file(): if __name__ == "__main__": - print("Checking Internet, lets get the basics right.") check_internet() - print("Create .env file, if not Present working directory") create_env_file() - print("Check Metaphor, Tavily, YOU.com Search API keys.") + os.system("clear" if os.name == "posix" else "cls") check_search_apis() - print("Check LLM details & AI Model to use.") check_llm_environs() os.environ["SEARCH_SAVE_FILE"] = os.path.join(os.getcwd(), "workspace", "web_research_report" + "_" + datetime.now().strftime("%Y-%m-%d_%H-%M-%S")) diff --git a/lib/gpt_providers/image_to_text_gen/gemini_image_details.py b/lib/gpt_providers/image_to_text_gen/gemini_image_details.py deleted file mode 100644 index 3ad28e19..00000000 --- a/lib/gpt_providers/image_to_text_gen/gemini_image_details.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -""" -import os -import logging -from pathlib import Path - -import google.generativeai as genai -logging.basicConfig(level=logging.INFO, format='%(asctime)s-%(levelname)s-%(module)s-%(lineno)d-%(message)s') -from dotenv import load_dotenv -load_dotenv(Path('../../.env')) - -from tenacity import ( - retry, - stop_after_attempt, - wait_random_exponential, -) # for exponential backoff - - -@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6)) -def gemini_get_img_info(prompt, img_path): - """ Get image details from arxiv papers. """ - logging.info(f"Get image details from Gemini Pro.") - try: - genai.configure(api_key=os.getenv("GEMINI_API_KEY")) - except Exception as e: - logging.error(f"Could not load gemini API key: {e}") - raise e - - # Set up the model - generation_config = { - "temperature": 0.9, - "top_p": 1, - "top_k": 1, - "max_output_tokens": 1096, - } - - safety_settings = [{ - "category": "HARM_CATEGORY_HARASSMENT", - "threshold": "BLOCK_NONE" - }, - { - "category": "HARM_CATEGORY_HATE_SPEECH", - "threshold": "BLOCK_NONE" - }, - { - "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", - "threshold": "BLOCK_NONE" - }, - { - "category": "HARM_CATEGORY_DANGEROUS_CONTENT", - "threshold": "BLOCK_NONE" - },] - - try: - model = genai.GenerativeModel(model_name="gemini-pro-vision", - generation_config=generation_config, - safety_settings=safety_settings) - except Exception as e: - logging.error(f"Could not create GenerativeModel: {e}") - raise e - - # Validate that an image is present - if not (img := Path(img_path)).exists(): - raise FileNotFoundError(f"Could not find image: {img}") - - image_parts = [{ - "mime_type": "image/png", - "data": Path(img_path).read_bytes() - },] - - prompt_parts = [f"{prompt}", image_parts[0],] - - try: - response = model.generate_content(prompt_parts) - return response.text - except Exception as e: - logging.error(f"Gemini is blocking this request: {response.prompt_feedback.block_reason}") - logging.error(f"Gemini Vision, Failed to give image Details: {e}\n{response.prompt_feedback}") - raise e diff --git a/lib/image_to_text/gpt_vision_image_details.py b/lib/gpt_providers/image_to_text_gen/openai_vision_image_details.py similarity index 100% rename from lib/image_to_text/gpt_vision_image_details.py rename to lib/gpt_providers/image_to_text_gen/openai_vision_image_details.py diff --git a/lib/gpt_providers/text_generation/ai_essay_writer.py b/lib/gpt_providers/text_generation/ai_essay_writer.py index 0f679d2c..bd7c36fe 100644 --- a/lib/gpt_providers/text_generation/ai_essay_writer.py +++ b/lib/gpt_providers/text_generation/ai_essay_writer.py @@ -132,7 +132,7 @@ def ai_essay_generator(essay_title, selected_essay_type, selected_education_leve load_dotenv(Path('../.env')) genai.configure(api_key=os.getenv('GEMINI_API_KEY')) # Initialize the generative model - model = genai.GenerativeModel('gemini-1.0-pro') + model = genai.GenerativeModel('gemini-pro') # Generate prompts try: diff --git a/lib/gpt_providers/text_generation/gemini_pro_text.py b/lib/gpt_providers/text_generation/gemini_pro_text.py index 8e158de6..16da3ac3 100644 --- a/lib/gpt_providers/text_generation/gemini_pro_text.py +++ b/lib/gpt_providers/text_generation/gemini_pro_text.py @@ -19,7 +19,7 @@ from tenacity import ( ) -#@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6)) +@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6)) def gemini_text_response(prompt, temperature, top_p, n, max_tokens): """ Common functiont to get response from gemini pro Text. """ #FIXME: Include : https://github.com/google-gemini/cookbook/blob/main/quickstarts/rest/System_instructions_REST.ipynb diff --git a/lib/gpt_providers/image_generation/gen_dali2_images.py b/lib/gpt_providers/text_to_image_generation/gen_dali2_images.py similarity index 100% rename from lib/gpt_providers/image_generation/gen_dali2_images.py rename to lib/gpt_providers/text_to_image_generation/gen_dali2_images.py diff --git a/lib/gpt_providers/image_generation/gen_dali3_images.py b/lib/gpt_providers/text_to_image_generation/gen_dali3_images.py similarity index 100% rename from lib/gpt_providers/image_generation/gen_dali3_images.py rename to lib/gpt_providers/text_to_image_generation/gen_dali3_images.py diff --git a/lib/gpt_providers/text_to_image_generation/gen_stabl_diff_img.py b/lib/gpt_providers/text_to_image_generation/gen_stabl_diff_img.py new file mode 100644 index 00000000..ff4c5a51 --- /dev/null +++ b/lib/gpt_providers/text_to_image_generation/gen_stabl_diff_img.py @@ -0,0 +1,41 @@ +from PIL import Image +import requests + +# Ensure you sign up for an account to obtain an API key: +# https://platform.stability.ai/ +# Your API key can be found here after account creation: +# https://platform.stability.ai/account/keys + + +def generate_stable_diffusion_image(prompt): + """ + Generate images using Stable Diffusion API based on a given prompt. + + Args: + prompt (str): The prompt to generate the image. + image_dir (str): The directory where the image will be saved. + + Raises: + Warning: If the adult content classifier is triggered. + Exception: For any issues during image generation or saving. + """ + api_key = os.getenv('STABILITY_API_KEY') + + response = requests.post( + f"https://api.stability.ai/v2beta/stable-image/generate/sd3", + headers={ + "authorization": f"Bearer {api_key}", + "accept": "image/*" + }, + files={"none": ''}, + data={ + "prompt": prompt, + "output_format": "webp", + }, + ) + + if response.status_code == 200: + with open("./dog-wearing-glasses.jpeg", 'wb') as file: + file.write(response.content) + else: + raise Exception(str(response.json())) diff --git a/lib/gpt_providers/image_generation/gen_variation_img.py b/lib/gpt_providers/text_to_image_generation/gen_variation_img.py similarity index 100% rename from lib/gpt_providers/image_generation/gen_variation_img.py rename to lib/gpt_providers/text_to_image_generation/gen_variation_img.py diff --git a/lib/text_to_image/generate_image_from_prompt.py b/lib/gpt_providers/text_to_image_generation/generate_image_from_prompt.py similarity index 80% rename from lib/text_to_image/generate_image_from_prompt.py rename to lib/gpt_providers/text_to_image_generation/generate_image_from_prompt.py index 5f56698c..b2c8646c 100644 --- a/lib/text_to_image/generate_image_from_prompt.py +++ b/lib/gpt_providers/text_to_image_generation/generate_image_from_prompt.py @@ -20,11 +20,12 @@ logger.add(sys.stdout, format="{level}|{file}:{line}:{function}| {message}" ) -from .gpt_providers.openai_gpt_provider import generate_dalle2_images, generate_dalle3_images, openai_chatgpt -from .stabl_diff_img2html import generate_stable_diffusion_image +#from .gen_dali2_images +from .gen_dali3_images import generate_dalle3_images +from .gen_stabl_diff_img import generate_stable_diffusion_image -def generate_image(user_prompt, image_dir, image_engine="dalle3"): +def generate_image(user_prompt, image_engine="dalle3"): """ The generation API endpoint creates an image based on a text prompt. @@ -40,18 +41,14 @@ def generate_image(user_prompt, image_dir, image_engine="dalle3"): Must be one of "url" or "b64_json". Defaults to "url". --> user (str): A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse. """ - logger.info(f"Generated blog images will be stored at: {image_dir=}") - img_prompt = generate_img_prompt(user_prompt) # call the OpenAI API to generate image from prompt. - logger.info(f"Calling openai.image.generate with prompt: {img_prompt}") + logger.info(f"Calling image.generate with prompt: {img_prompt}") - if 'dalle2' in image_engine: - image_stored_at = generate_dalle2_images(img_prompt, image_dir) - elif 'dalle3' in image_engine: - image_stored_at = generate_dalle3_images(img_prompt, image_dir) - elif 'stable_diffusion' in image_engine: - image_stored_at = generate_stable_diffusion_image(img_prompt, image_dir) + if 'Dalle3' in image_engine: + image_stored_at = generate_dalle3_images(img_prompt) + elif 'Stable Diffusion' in image_engine: + image_stored_at = generate_stable_diffusion_image(img_prompt) return image_stored_at @@ -72,5 +69,5 @@ def generate_img_prompt(user_prompt): Advice for creating prompt for image from the given text(no more than 150 words). Reply with only one answer and no descrition. Generate image prompt for the below text. Text: {user_prompt}""" - response = openai_chatgpt(prompt) + response = (prompt) return response diff --git a/lib/blog_postprocessing/save_image.py b/lib/gpt_providers/text_to_image_generation/save_image.py similarity index 100% rename from lib/blog_postprocessing/save_image.py rename to lib/gpt_providers/text_to_image_generation/save_image.py diff --git a/lib/text_to_image/stabl_diff_img2html.py b/lib/text_to_image/stabl_diff_img2html.py deleted file mode 100644 index 4d2a39d4..00000000 --- a/lib/text_to_image/stabl_diff_img2html.py +++ /dev/null @@ -1,66 +0,0 @@ -import os -import io -import warnings -from PIL import Image -from stability_sdk import client -import stability_sdk.interfaces.gooseai.generation.generation_pb2 as generation - -# Set the host URL environment variable. Ensure it doesn't have 'https' or a trailing slash. -os.environ['STABILITY_HOST'] = 'grpc.stability.ai:443' - -# Ensure you sign up for an account to obtain an API key: -# https://platform.stability.ai/ -# Your API key can be found here after account creation: -# https://platform.stability.ai/account/keys - - -def generate_stable_diffusion_image(prompt, image_dir): - """ - Generate images using Stable Diffusion API based on a given prompt. - - Args: - prompt (str): The prompt to generate the image. - image_dir (str): The directory where the image will be saved. - - Raises: - Warning: If the adult content classifier is triggered. - Exception: For any issues during image generation or saving. - """ - try: - # Initialize the StabilityInference client with the API key and other settings. - stability_api = client.StabilityInference( - key=os.environ['STABILITY_KEY'], # Reference to the API key. - verbose=True, # Enable verbose mode for debug messages. - engine="stable-diffusion-xl-1024-v1-0", # Engine used for generation. - ) - - # Generating the image with specified parameters. - answers = stability_api.generate( - prompt=prompt, - seed=4253978046, # Deterministic seed for reproducible results. - steps=50, # Number of inference steps. - cfg_scale=7.0, # Strength of prompt matching. - width=1024, height=1024, # Image dimensions. - samples=1, # Number of images to generate. - sampler=generation.SAMPLER_K_DPMPP_2M # Denoising sampler selection. - ) - - # Process responses and save images. - for resp in answers: - for artifact in resp.artifacts: - if artifact.finish_reason == generation.FILTER: - warnings.warn( - "Request activated safety filters. Modify the prompt and retry." - ) - if artifact.type == generation.ARTIFACT_IMAGE: - img = Image.open(io.BytesIO(artifact.binary)) - img_name = os.path.join(image_dir, f"{artifact.seed}.png") - img.show() - img.save(img_name) # Save the image with the seed in the filename. - - except Exception as e: - raise Exception(f"Error during image generation or saving: {e}") - -# Example usage: -# generate_stable_diffusion_image("A futuristic cityscape", "/path/to/save/images/") - diff --git a/lib/gpt_providers/text_generation/.gemini_pro_text.py.swp b/lib/utils/.alwrity_utils.py.swp similarity index 50% rename from lib/gpt_providers/text_generation/.gemini_pro_text.py.swp rename to lib/utils/.alwrity_utils.py.swp index a62ca028c88a54647606d6df55c047eb64fdc18d..715fe2b1334d520def21580ed829f44afe70e0fc 100644 GIT binary patch literal 16384 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 OZ78E|=P~mD literal 12288 zcmeHN&2Jnv6nD#oueOLvTyZx@*@(?{BvrH?7u?@yP|~^Kk9m1y)CQQI@6u~KncTDD-^v} zkQgsfawL4Nbj$EH;{`nCiWwo}R=^_>3#;JSs%A2NlrL3%#X!ZtLl{WSVD`-enjWF;Fp3F;Fp3F;Fp3F;Fp3G4Ow7z$E*~Cm7uRe1IqN`@UWG zyja~T1}X+B1}X+B1}X+B1}X+B1}X+B1}X+B1}X-gLI#*m$m`D%a_BiYkN^J{-~aC) zBIH-#XW)C_JK!_mDzF5+0lWqr0RBEm$XCEEU<=T|CEzqL2fPKefj1(twQz$?Ig`2Qnt7x)5L2QCA4 zTnB+$&k*uC@GkHZe(cx+J_e2#KYToebNAo4O!F(0LBLg$D;+$bD*x>TUNu zG$joRrM)${p^EE7#+uWCk`Y~SM?%_k108QR>3MF{Sj0iIHbV+)^vRjyYki&=I`3{c zkwTfK=(s)UOL3DH{XV&FbBj;T;A4$L#S^ZZ_PEVtkk*|mQxmm z2@#LDHW5hNX{}JO?INi-HA5EDE2%bCBFw!TUuw#FGBq_?i`hs7IO!D!wz9_==x|R^ z4|A!V8J$`~I3>nyZOCF@_gUf!2Fnh*@oh!+jHi1yZY#HSo6o2){Gn_d->qBVPk62ICBOQgwgcF)DmqiYD8Gb zb;fx)K$sUgo;Hpn=MyBc%*vD%l1wM!iicy$;zHb)3bCY(jCmpIDd7i`(zT&XlFV(# zgTbsJB4bPhhL5firdT9XC!h@oKmznZgOP=fCBqU^N)j=WhJ}SfIgy2AWhgYwoZdAC z8w*(kptGV!t`SDuP8XQ!nrTf-3S6)_U!5OC4?~8bYwe0xWQxMLsNx|XF*D&HV(XLV zBwb$*CnZFbx;Eq?UJA5Dq!d@R@)k_cYUO>5mA7E47O^!Ci(8T!&&XAdCAN))dMz(B z$VAb|#9KvWyi1lqX6useavmji$G|dZYs*rm^T6(bEosB`G(|5Ufl3ps!ujsu#Cdev zwHle*3hW`D+GcjMK^NKDit`^erT?f{k;$^vwl1Q$gy(p3Tk^H%o0DyIb|v4D!sSgL znV?{h*kdO~>MRl7Dj#>G#5qFNchMki~XKY?1laBrH*Z?Vj$BgaD)K7w2LNb2$u$6`8gKaap^QA#tMvuv3 zePPB_M2S>})*WYM9%hpkW;(Swtm-^Gz2R<~bFHj+eJ)U($EP<2`2Mlk-oP$xZw1fL z%j&Nqj^fN$1Z2NYLdB6$$&++}_ZlE*Id{kk6OrZzra<%wAF2>V5SCi5D)sY(l< zvz9Qxe9xCI9G96H)~B1zR{n<&wIS(-oT-$JC#^`%K^BHZ!iuzn3H#N