File size: 4,373 Bytes
88aba82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3dbba32
 
 
 
88aba82
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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)