File size: 13,645 Bytes
dfb55ea
 
dcabda1
 
 
 
 
 
a37d179
dfb55ea
 
dcabda1
 
 
 
 
dfb55ea
 
dcabda1
dfb55ea
 
 
 
dcabda1
 
 
 
 
 
 
 
 
dfb55ea
 
 
 
 
 
 
 
 
 
dcabda1
 
 
dfb55ea
 
 
 
 
 
dcabda1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dfb55ea
 
 
dcabda1
 
dfb55ea
 
 
 
 
 
 
dcabda1
 
 
 
 
dfb55ea
 
 
dcabda1
dfb55ea
dcabda1
dfb55ea
 
 
 
 
 
 
 
dcabda1
 
 
 
 
 
 
 
 
 
 
 
 
dfb55ea
dcabda1
dfb55ea
 
 
 
 
dcabda1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dfb55ea
 
 
 
 
 
 
 
 
 
 
 
dcabda1
dfb55ea
 
 
dcabda1
 
 
 
 
 
dfb55ea
 
 
 
 
 
 
 
dcabda1
 
 
 
 
 
 
dfb55ea
 
 
dcabda1
 
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
from openai import OpenAI
import gradio as gr
import requests
import matplotlib.pyplot as plt
import io
from PIL import Image, UnidentifiedImageError
import random
import time
import os


#------------------------------------------------------------------------------------------------


api_key = os.environ["genai_stories"]
# define LLM model for story creation, with OpenAI's format
client = OpenAI(
            base_url="https://api-inference.huggingface.co/v1/",
	        api_key=api_key
            )
# the model to utilize
model = "mistralai/Mixtral-8x7B-Instruct-v0.1"

# define model for image creation
# API_URL = "https://api-inference.huggingface.co/models/black-forest-labs/FLUX.1-dev"
# API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
# API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-3.5-large"
API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-3.5-large-turbo"

bearer_key = "Bearer " + api_key
headers = {"Authorization": bearer_key}


#------------------------------------------------------------------------------------------------


# connect with the LLM and create responses
def chat_with_llm(model, messages):
    completion = client.chat.completions.create(
        model=model,
        messages=messages,
        max_tokens=2048,
        temperature=0.7,
        top_p=0.98,
        seed=random.randint(0, 100))
    return completion.choices[0].message.content


#------------------------------------------------------------------------------------------------


# connect with image model and create images
def request_to_image_model(story):
	response = requests.post(API_URL, headers=headers, json=story)
	return response.content

# create the final image, adjusting the model's hyperparameters and converting to RGB 
def create_image(prompt):
    try:
        image_bytes = request_to_image_model({
            "inputs": prompt, 
            "parameters": {
                # "num_inference_steps": 50,  # Optional: controls generation quality
                # "guidance_scale": 7.5,      # Optional: controls adherence to the prompt
                "seed": random.randint(0, 100)                 # Set the seed to a random number
                }
            })
    except (UnidentifiedImageError) as e:
        time.sleep(60)
        image_bytes = request_to_image_model({
            "inputs": prompt, 
            "parameters": {
                # "num_inference_steps": 50,  # Optional: controls generation quality
                # "guidance_scale": 7.5,      # Optional: controls adherence to the prompt
                "seed": random.randint(0, 100)                 # Set the seed to a random number
                }
            })

    image = Image.open(io.BytesIO(image_bytes))
    # Convert the image to RGB if it's not
    if image.mode != 'RGB':
        image = image.convert('RGB')

    return image


#------------------------------------------------------------------------------------------------


# create a prompt for the image model based on the character
def create_character_prompt(story_type):
    main_character = story_type.split(' ')[2]
    if main_character == 'king':
        character_prompt = " The " + main_character + " has a strong jawline and piercing eyes, dark hair, shining silver armor with simple decorations, red cape falling behind him, large sword."
    elif main_character == 'alien':
        character_prompt = " The " + main_character + " has a round, shiny head, big eyes glow bright blue, small and green with tiny arms, silver spacesuit covers its body."
    elif main_character == 'rabbit':
        character_prompt = " The " + main_character + " has soft white fur, big ears stand up tall, wears a tiny red bow tie, fluffy tail bounces as it hops."
    elif main_character == 'princess':
        character_prompt = " The " + main_character + " has long brown hair, sparkling blue eyes shine brightly, wears a flowing pink gown, small jeweled crown rests on her head."
    return character_prompt


#------------------------------------------------------------------------------------------------


# Initialize the story based on the story_type selected by the player
def generate_story(story_type):
    global model, messages
    messages = []
    messages.append({"role": "system", "content": f"You are a structured storytelling assistant. Your purpose is to generate stories, questions, and answers in a specific format. Always adhere strictly to the rules provided by the user. This is the topic that you must create a story about: story_type = {story_type}"})
    messages.append({"role": "user", "content": f"You must create and return one story part, one question and four answers. To do that, you must explicitly follow these steps: Step 1) Create the initial part of the story, according to the story_type from your system content, within 50 and 60 words, without switching to new line. Then you must switch line by adding this: '\n\n'. Step 2) Create a question, within 10 and 20 words, on how to proceed the story from step 1. Then you must switch line by adding this: '\n\n'. Step 3) Create the 4 potential answers for the question in step 2. The answers of the question must be given in the format: '1:... | 2:... | 3:... | 4:...'. Do not change this format and do not add any new lines between the answers. Every answer must be maximum 20 words. All answers must be separated from each other with '|'. Now some general guidelines for your response: 1) Don't explicitly specify 'Story', 'Question', or 'Answer'. 2) You must ALWAYS reply in this format: '[story from step 1]\n\n[question from step 2]\n\n[answers from step 3]'. 3) Do not return any other stuff in your response. 4) Always change lines with '\n\n' between ther story part and question and between the question and the answers."})
    messages = [messages[0]] + [messages[-1]]
    response = chat_with_llm(model, messages)
    lines    = response.split("\n\n")
    story    = lines[0]  # Everything before the last two lines is the story
    question = lines[1]  # The second to last line is the question
    answers  = [i.strip() for i in lines[2].split('|')]  # The last line contains answers separated by '|'
    messages.append({"role": "assistant", "content": "I am waiting for next command."})
    # main_character = story_type.split(' ')[2]
    character_prompt = create_character_prompt(story_type)
    image_prompt = "Cartoon image, with bright colors and simple shapes, that describes this story: " + story + character_prompt
    image = create_image(image_prompt)
    return story, question, gr.Radio(choices=answers, interactive=True), gr.Radio(choices=[story_type], interactive=False), gr.Button(interactive=False), image


# Continue the story based on what happened so far and the player's latest answer
def continue_story(previous_story, selected_option, story_type):
    global model, messages
    messages.append({"role": "user", "content": f"You must create and return one story part, one question and four answers. To do that, you must explicitly follow these steps: Step 1) Based on this story so far: '{previous_story} {selected_option}', continue the story and create the next part of the story within 50 and 60 words, without changing lines. You must provide ONLY the new part that you created. Then you must switch line by adding this: '\n\n'. Step 2) Create a question, within 10 and 20 words, on how to proceed the story from step 1. Then you must switch line by adding this: '\n\n'. Step 3) Create the 4 potential answers for the question in step 2. The answers of the question must be given in the format: '1:... | 2:... | 3:... | 4:...'. Do not change this format and do not add any new lines between the answers. Every answer must be maximum 20 words. All answers must be separated from each other with '|'. Now some general guidelines for your response: 1) Don't explicitly specify 'Story', 'Question', or 'Answer'. 2) You must ALWAYS reply in this format: '[story from step 1]\n\n[question from step 2]\n\n[answers from step 3]'. 3) Do not return any other stuff in your response. 4) Always change lines with '\n\n' between story part and question that you generate. 5) Always change lines with '\n\n' between question and the answers that you generate"})
    messages = [messages[0]] + [messages[-1]]
    response = chat_with_llm(model, messages)
    lines    = response.split("\n\n")
    next_story = lines[0]  # Everything before the last two lines is the story
    question   = lines[1]  # The second to last line is the question
    answers    = [i.strip() for i in lines[2].split('|')]  # The last line contains answers separated by '|'
    messages.append({"role": "assistant", "content": "I am waiting for next command."})
    story = previous_story + '\n\n' + next_story
    # image_prompt = "Cartoon image of: " + next_story + ". The image has bright colors and simple shapes, intended for children story."
    main_character = story_type.split(' ')[2].lower()
    character_prompt = create_character_prompt(story_type)
    if main_character in next_story.lower():
        image_prompt = "Cartoon image, with bright colors and simple shapes, that describes this story: " + story + character_prompt
    else:
        image_prompt = "Cartoon image, with bright colors and simple shapes, that describes this story: " + story
    image = create_image(image_prompt)
    return story, question, gr.Radio(choices=answers, interactive=True), image


# End the story based on what happened so far and the player's latest answer
def end_story(previous_story, selected_option, story_type):
    global model, messages
    messages.append({"role": "user", "content": f"You must create an ending for this story: '{previous_story}' and also considering the latest answer: '{selected_option}'. You must provide only the ending of the story in an exciting way. Do not return any other stuff in your response."})
    end_story = chat_with_llm(model, messages)
    # lines    = response.split("\n\n")
    # end_story = lines[0]  # Everything before the last two lines is the story
    messages.append({"role": "assistant", "content": "I ended the story successfully. Now I am not waiting for any more responses from the player."})
    story = previous_story + '\n\n' + end_story
    # image_prompt = "Cartoon image of " + end_story + ". The image has bright colors and simple shapes, intended for children story."
    main_character = story_type.split(' ')[2].lower()
    character_prompt = create_character_prompt(story_type)
    if main_character in end_story.lower():
        image_prompt = "Cartoon image, with bright colors and simple shapes, that describes this story: " + story + character_prompt
    else:
        image_prompt = "Cartoon image, with bright colors and simple shapes, that describes this story: " + story
    image = create_image(image_prompt)
    return story, image


# Function to handle resetting the app
# def reset_app():
#     # Return empty values for all outputs
#     # global messages
#     # messages = []
#     return "", "", "", gr.Radio(choices=["A knight who leads an army attacking a castle",
#                                                                     "A kid alien who lands on earth and explores around", 
#                                                                     "Animals who participate in song contest", 
#                                                                     "A little princess who is saved by a prince"], interactive=True), gr.Button(interactive=True) 


# initialize messages 
messages = []


# design the UI
with gr.Blocks() as game_ui:
    with gr.Row():
        # Left column for the story
        with gr.Column(scale=1):
            story = gr.Textbox(label="Story", interactive=False, lines=12)
            story_image = gr.Image()  # height=256, width=256

        # Right column for the question, answers, and buttons
        with gr.Column(scale=1):
            story_type = gr.Radio(label="What story to create?", choices=["A fearless king who leads an army attacking a castle.",
                                                                          "A kid alien who lands on earth and explores around.", 
                                                                          "A joyful rabbit who participates in song contest.", 
                                                                          "A beautiful princess who tries to find her way home."
                                                                          ])
                
            start_button = gr.Button("Start Game!")

            question = gr.Textbox(label="Question", interactive=False)
            answers = gr.Radio(label="Choose an answer", choices=[])

            submit_button = gr.Button("Submit your answer")
            end_button = gr.Button("End the story")

            # reset_button = gr.Button("Reset and play again")

    # what the buttons do
    start_button.click(fn=generate_story,  inputs=[story_type],                 outputs=[story, question, answers, story_type, start_button, story_image])
    submit_button.click(fn=continue_story, inputs=[story, answers, story_type], outputs=[story, question, answers, story_image])
    end_button.click(fn=end_story,         inputs=[story, answers, story_type], outputs=[story, story_image])
    # reset_button.click(fn=reset_app,       inputs=[], outputs=[story, question, answers, story_type, start_button])


# Launch the Gradio interface
game_ui.launch()
# game_ui.launch(share=True)