Andrew Moffat
tip
3dbba32
from datetime import date
from typing import cast
import openai
import streamlit as st
import gcal
import google_oauth
st.title(":calendar: Creative Calendar")
st.subheader("Illustrate a month from your Google calendar")
client: openai.OpenAI | None = None
month_date: date | None = None
name: str | None = None
gender: str | None = None
genre: str | None = None
submitted = False
creds = google_oauth.load_creds()
# no Google credentials? we need to authenticate
if creds is None:
flow = google_oauth.get_flow()
url = google_oauth.get_auth_url(flow)
st.link_button("Authenticate with Google", url=url)
auth_code = st.text_input("Google auth code", type="password")
if auth_code:
creds = google_oauth.get_creds(flow, auth_code)
st.rerun()
# we have Google credentials, make sure we have the other things
else:
st.checkbox("Authenticated with Google", value=True, disabled=True)
openapi_key = st.text_input("OpenAI API key", type="password")
if openapi_key:
client = openai.OpenAI(api_key=openapi_key)
with st.form("my_form"):
name = st.text_input("Your name")
gender = st.radio("Your gender", ["Male", "Female"]) or "Male"
genre = st.text_input("Illustration genre", value="fantasy")
month_date = cast(date, st.date_input("Select month"))
submitted = st.form_submit_button("Submit")
def compose_story_prompt(
*,
name: str,
gender: str,
events: list[gcal.Event],
genre: str,
) -> str:
prompt_events = []
for ev in events:
dt = ev.start.strftime("%B %d, %Y")
line = f"{dt}: {ev.title}"
if ev.description:
line += f" ({ev.description})"
prompt_events.append(line)
event_str = "\n".join(["-" + ev for ev in prompt_events])
prompt = f"""Consider the following events from my Google calendar:
{event_str}
Write a description for a short {genre} story that captures the major events
from my calendar. The description should cover only the major themes from my
calendar. Do not write the story itself, just a description of the story. Do
not include any references to a calendar or specific dates, just the major
themes from my calendar.
The main character is a {gender} named {name}.
"""
return prompt
def compose_picture_prompt(story: str, genre: str) -> str:
prompt = f"""For the following short {genre} story description, create a single
{genre}-style image that captures the major events in the story. Do not
include any text in the illustration.
-----
{story}
"""
return prompt
@st.cache_data(show_spinner="Writing the story...")
def write_story(story_prompt):
completion = client.chat.completions.create(
model="gpt-4",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": story_prompt},
],
}
],
)
content = completion.choices[0].message.content
return content
@st.cache_data(show_spinner="Illustrating the cover...")
def draw_picture(picture_prompt: str) -> str:
response = client.images.generate(
model="dall-e-3",
prompt=picture_prompt,
size="1024x1024",
quality="standard",
n=1,
)
image_url = response.data[0].url
return cast(str, image_url)
if client and creds and submitted:
cal_service = gcal.build_calendar_api(creds)
calendars = gcal.get_calendars(cal_service)
month_start, month_end = gcal.get_start_and_end(cast(date, month_date))
events = gcal.get_events(cal_service, month_start, month_end, calendars)
st.write(
f":white_check_mark: Fetched {len(events)} events from "
f"{len(calendars)} calendars"
)
story_prompt = compose_story_prompt(
name=cast(str, name),
gender=cast(str, gender),
events=events,
genre=cast(str, genre),
)
story = write_story(story_prompt)
with st.expander("See the story"):
st.write(story)
st.write(
"Tip: The story and image quality depends on the quality"
" of your calendar events."
)
picture_prompt = compose_picture_prompt(story, genre)
image_url = draw_picture(picture_prompt)
st.image(image_url)