import logging import os import random import re from typing import Union import openai from dotenv import load_dotenv from flask import Flask, redirect, request from html5lib import HTMLParser from htmls import index_html, prompt_html_template load_dotenv() app = Flask(__name__) openai.api_key = os.getenv("OPENAI_KEY") logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") def format_message(message: str, role: str) -> list: """ Formats the message to send to chatgpt Args: message: The message to send to chatgpt Returns: The formatted message in a list Raises: None """ return [ {"role": "system", "content": role}, {"role": "user", "content": message}, ] def extract_html_from_chatgpt_response(returned_string: str) -> Union[str, None]: """ Extracts the HTML from the chatgpt response Args: returned_string: The string returned by chatgpt Returns: The HTML if it is valid, None otherwise Raises: None """ validator = HTMLParser(strict=True) try: validator.parse(returned_string) logging.info(f"Valid HTML Found! {returned_string}") return returned_string except Exception: match = returned_string.split("") if len(match) > 1: logging.info("No valid HTML found, trying to parse...") html = "" + match[1] html_match = html.split("") possible_html = html_match[0] + "" if "(slugified_related_topic_name)" not in possible_html: return possible_html def get_random_topic() -> str: """ Gets a random topic from chatgpt Args: None Returns: A random topic Raises: None """ try: completion = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=format_message( "Give me a random list of 10 topics to talk about in a comma separated list. Each topic should be less " "than 5 words long and should not contain commas. Be as creative as possible and choose topics from " "multiple genres and industries.", "You are a very knowledgeable person with random knowledge about everything the humanity has done", ), ) topics = completion.choices[0].message.content.split(",") if len(topics) < 5: logging.info(f"The topics is too short: {topics}. Retrying...") return get_random_topic() logging.info(f"The topics list is: {topics}") topic = random.choice(topics) return topic.strip().capitalize() except openai.error.RateLimitError: return get_random_topic() def generate_topic_image(topic: str) -> str: """ Generates an image for the topic Args: topic: The topic to generate an image for Returns: The URL of the image Raises: None """ try: completion = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=format_message( f"Generate a prompt for dall-e about {topic}. Use less than 10 words and only return the prompt", "You are a dall-e expert", ), ) dalle_prompt = completion.choices[0].message.content logging.info(f"The Dall-E prompt is: {dalle_prompt}") image = openai.Image.create(prompt=dalle_prompt, n=1, size="256x256") return image["data"][0]["url"] except Exception: generate_topic_image(topic) def get_chatgpt_html_response(message: str, count: int = 0, html: str = None) -> str: """ Gets the response from chatgpt Args: message: The message to send to chatgpt count: The number of times this function has been called Returns: The HTML response from chatgpt Raises: None """ count += 1 if count >= 5: return f"Error! ChatGPT is having problems with this topic: {message}. Try a new one." logging.info(f"The topic to generate is {message}") try: completion = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=format_message( f""" You are going to add information to this: {html}. - Add 5 parragraphs talking about {message} in the "information-container" div. - Add 5 links for related topics in the "related-topics-container" div. The href should be "/infinite?topic=(slugified_related_topic_name)" and the text should be "(related_topic_name). - Add 5 links for new random topics not related to {message} in the "new-topics-container" div. The href should be the same as before. - Only respond with the HTML code """, "You are a very skilled web developer with expertise in HTML.", ), ) except openai.error.RateLimitError: return get_chatgpt_html_response(message, count) response = completion.choices[0].message.content html = extract_html_from_chatgpt_response(response) if not html: logging.info(f"No HTML was found. The response was {response}. Retrying...") return get_chatgpt_html_response(message, count) pattern = r'src="([^"]*)"' image = generate_topic_image(message) logging.info(f"The image is {image}") complete_html = re.sub(pattern, f'src="{image}"', html) return_html = complete_html.split("")[0] return_html += ( "" ) return return_html @app.route("/") def index(): return index_html @app.route("/start") def start(): random_topic = get_random_topic() html = prompt_html_template.replace("(topic)", random_topic) return get_chatgpt_html_response(random_topic, html=html) @app.route("/infinite") def topic(): topic = request.args.get("topic", None) or request.args.get("topics", None) if not topic: return redirect("/start") unslugified_topic = topic.replace("-", " ").replace("_", " ").replace("%20", " ").capitalize() if len(unslugified_topic.split()) > 5: logging.info(f"The topic is too long: {unslugified_topic}") return "Error! Your topic should be < 5 words" html = prompt_html_template.replace("(topic)", unslugified_topic) return get_chatgpt_html_response(unslugified_topic, html=html)