Multiple stories, sleep/wake, saving/loading, fonts
This commit is contained in:
parent
bd7c3cb8ab
commit
23b2a4f779
6 changed files with 557 additions and 563 deletions
|
|
@ -1,407 +0,0 @@
|
|||
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import math
|
||||
from enum import Enum
|
||||
import pygame
|
||||
|
||||
# Image Names
|
||||
WELCOME_IMAGE = "welcome.png"
|
||||
BACKGROUND_IMAGE = "paper_background.png"
|
||||
LOADING_IMAGE = "loading.png"
|
||||
BUTTON_BACK_IMAGE = "button_back.png"
|
||||
BUTTON_NEXT_IMAGE = "button_next.png"
|
||||
|
||||
# Asset Paths
|
||||
IMAGES_PATH = os.path.dirname(sys.argv[0]) + "images/"
|
||||
FONTS_PATH = os.path.dirname(sys.argv[0]) + "fonts/"
|
||||
|
||||
# Font Path, Size
|
||||
TITLE_FONT = (FONTS_PATH + "lucida_black.ttf", 48)
|
||||
TITLE_COLOR = (0, 0, 0)
|
||||
TEXT_FONT = (FONTS_PATH + "times new roman.ttf", 24)
|
||||
TEXT_COLOR = (0, 0, 0)
|
||||
|
||||
# Delays to control the speed of the text
|
||||
# Default
|
||||
WORD_DELAY = 0.1
|
||||
TITLE_FADE_TIME = 0.05
|
||||
TITLE_FADE_STEPS = 25
|
||||
TEXT_FADE_TIME = 0.25
|
||||
TEXT_FADE_STEPS = 51
|
||||
BUTTON_FADE_TIME = 0.10
|
||||
BUTTON_FADE_STEPS = 11
|
||||
|
||||
# Whitespace Settings in Pixels
|
||||
PAGE_TOP_MARGIN = 20
|
||||
PAGE_SIDE_MARGIN = 20
|
||||
PAGE_BOTTOM_MARGIN = 0
|
||||
PAGE_NAV_HEIGHT = 100
|
||||
EXTRA_LINE_SPACING = 0
|
||||
PARAGRAPH_SPACING = 30
|
||||
|
||||
|
||||
class Position(Enum):
|
||||
TOP = 0
|
||||
CENTER = 1
|
||||
BOTTOM = 2
|
||||
LEFT = 3
|
||||
RIGHT = 4
|
||||
|
||||
|
||||
class Button:
|
||||
def __init__(self, x, y, image, action, draw_function):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.image = image
|
||||
self.action = action
|
||||
self._width = self.image.get_width()
|
||||
self._height = self.image.get_height()
|
||||
self._visible = False
|
||||
self._draw_function = draw_function
|
||||
|
||||
def is_in_bounds(self, position):
|
||||
x, y = position
|
||||
return (
|
||||
self.x <= x <= self.x + self.width and self.y <= y <= self.y + self.height
|
||||
)
|
||||
|
||||
def show(self):
|
||||
self._draw_function(
|
||||
self.image, self.x, self.y, BUTTON_FADE_TIME, BUTTON_FADE_STEPS
|
||||
)
|
||||
self._visible = True
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._width
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self._height
|
||||
|
||||
@property
|
||||
def visible(self):
|
||||
return self._visible
|
||||
|
||||
|
||||
class Textarea:
|
||||
def __init__(self, x, y, width, height):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return {"width": self.width, "height": self.height}
|
||||
|
||||
|
||||
class Book:
|
||||
def __init__(self, rotation=0):
|
||||
self.paragraph_number = 0
|
||||
self.page = 0
|
||||
self.title = ""
|
||||
self.paragraphs = []
|
||||
self.pages = []
|
||||
self.rotation = rotation
|
||||
self.images = {}
|
||||
self.fonts = {}
|
||||
self.width = 0
|
||||
self.height = 0
|
||||
self.back_button = None
|
||||
self.next_button = None
|
||||
self.textarea = None
|
||||
self.screen = None
|
||||
# Use a cursor to keep track of where we are in the text area
|
||||
self.cursor = {"x": 0, "y": 0}
|
||||
|
||||
def init(self):
|
||||
# Output to the LCD instead of the console
|
||||
os.putenv("DISPLAY", ":0")
|
||||
|
||||
# Initialize the display
|
||||
pygame.init()
|
||||
self.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
|
||||
self.width = self.screen.get_height()
|
||||
self.height = self.screen.get_width()
|
||||
|
||||
# Preload images
|
||||
self.load_image("welcome", WELCOME_IMAGE)
|
||||
self.load_image("background", BACKGROUND_IMAGE)
|
||||
self.load_image("loading", LOADING_IMAGE)
|
||||
|
||||
# Preload fonts
|
||||
self.load_font("title", TITLE_FONT)
|
||||
self.load_font("text", TEXT_FONT)
|
||||
|
||||
# Add buttons
|
||||
back_button_image = pygame.image.load(IMAGES_PATH + BUTTON_BACK_IMAGE)
|
||||
next_button_image = pygame.image.load(IMAGES_PATH + BUTTON_NEXT_IMAGE)
|
||||
button_spacing = (
|
||||
self.width - (back_button_image.get_width() + next_button_image.get_width())
|
||||
) // 3
|
||||
button_ypos = (
|
||||
self.height
|
||||
- PAGE_NAV_HEIGHT
|
||||
+ (PAGE_NAV_HEIGHT - next_button_image.get_height()) // 2
|
||||
)
|
||||
self.back_button = Button(
|
||||
button_spacing,
|
||||
button_ypos,
|
||||
back_button_image,
|
||||
self.previous_page,
|
||||
self.fade_in_surface,
|
||||
)
|
||||
self.next_button = Button(
|
||||
self.width - button_spacing - next_button_image.get_width(),
|
||||
button_ypos,
|
||||
next_button_image,
|
||||
self.next_page,
|
||||
self.fade_in_surface,
|
||||
)
|
||||
|
||||
# Add Text Area
|
||||
self.textarea = Textarea(
|
||||
PAGE_SIDE_MARGIN,
|
||||
PAGE_TOP_MARGIN,
|
||||
self.width - PAGE_SIDE_MARGIN * 2,
|
||||
self.height - PAGE_NAV_HEIGHT - PAGE_TOP_MARGIN - PAGE_BOTTOM_MARGIN,
|
||||
)
|
||||
|
||||
pygame.mouse.set_visible(False)
|
||||
self.screen.fill((255, 255, 255))
|
||||
|
||||
def handle_events(self):
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
raise SystemExit
|
||||
if event.type == pygame.MOUSEBUTTONDOWN:
|
||||
if event.button == 1:
|
||||
# If button pressed while visible, trigger action
|
||||
coords = self.rotate_mouse_pos(event.pos)
|
||||
for button in [self.back_button, self.next_button]:
|
||||
if button.visible and button.is_in_bounds(coords):
|
||||
button.action()
|
||||
|
||||
def rotate_mouse_pos(self, point):
|
||||
# Recalculate the mouse position based on the rotation of the screen
|
||||
# So that we have the coordinates relative to the upper left corner of the screen
|
||||
angle = 360 - self.rotation
|
||||
y, x = point
|
||||
x -= self.width // 2
|
||||
y -= self.height // 2
|
||||
x, y = x * math.sin(math.radians(angle)) + y * math.cos(
|
||||
math.radians(angle)
|
||||
), x * math.cos(math.radians(angle)) - y * math.sin(math.radians(angle))
|
||||
x += self.width // 2
|
||||
y += self.height // 2
|
||||
return (round(x), round(y))
|
||||
|
||||
def load_image(self, name, filename):
|
||||
try:
|
||||
image = pygame.image.load(IMAGES_PATH + filename)
|
||||
self.images[name] = image
|
||||
except pygame.error:
|
||||
pass
|
||||
|
||||
def load_font(self, name, details):
|
||||
self.fonts[name] = pygame.font.Font(details[0], details[1])
|
||||
|
||||
def get_position(self, obj, x, y):
|
||||
if x == Position.CENTER:
|
||||
x = (self.width - obj.get_width()) // 2
|
||||
elif x == Position.RIGHT:
|
||||
x = self.width - obj.get_width()
|
||||
elif x == Position.LEFT:
|
||||
x = 0
|
||||
elif not isinstance(x, int):
|
||||
raise ValueError("Invalid x position")
|
||||
|
||||
if y == Position.CENTER:
|
||||
y = (self.height - obj.get_height()) // 2
|
||||
elif y == Position.BOTTOM:
|
||||
y = self.height - obj.get_height()
|
||||
elif y == Position.TOP:
|
||||
y = 0
|
||||
elif not isinstance(y, int):
|
||||
raise ValueError("Invalid y position")
|
||||
|
||||
return (x, y)
|
||||
|
||||
# Display a surface either positionally or with a specific x,y coordinate
|
||||
def display_surface(
|
||||
self, surface, x=Position.CENTER, y=Position.CENTER, target_surface=None
|
||||
):
|
||||
buffer = self.create_transparent_buffer((self.width, self.height))
|
||||
buffer.blit(surface, self.get_position(surface, x, y))
|
||||
if target_surface is None:
|
||||
buffer = pygame.transform.rotate(buffer, self.rotation)
|
||||
self.screen.blit(buffer, (0, 0))
|
||||
else:
|
||||
target_surface.blit(buffer, (0, 0))
|
||||
|
||||
def fade_in_surface(self, surface, x, y, fade_time, fade_steps=50):
|
||||
background = self.create_transparent_buffer((self.width, self.height))
|
||||
self.display_surface(
|
||||
self.images["background"], Position.CENTER, Position.CENTER, background
|
||||
)
|
||||
|
||||
buffer = self.create_transparent_buffer(surface.get_size())
|
||||
fade_delay = round(
|
||||
fade_time / fade_steps * 1000
|
||||
) # Time to delay in ms between each fade step
|
||||
|
||||
for alpha in range(0, 255, round(255 / fade_steps)):
|
||||
buffer.blit(background, (-x, -y))
|
||||
surface.set_alpha(alpha)
|
||||
buffer.blit(surface, (0, 0))
|
||||
self.display_surface(buffer, x, y)
|
||||
pygame.display.update()
|
||||
pygame.time.wait(fade_delay)
|
||||
|
||||
def display_current_page(self):
|
||||
self.display_surface(
|
||||
self.images["background"], Position.CENTER, Position.CENTER
|
||||
)
|
||||
pygame.display.update()
|
||||
|
||||
page_data = self.pages[self.page]
|
||||
|
||||
# Display the title
|
||||
if page_data["title"]:
|
||||
self.display_title()
|
||||
|
||||
self.fade_in_surface(
|
||||
page_data["buffer"],
|
||||
self.textarea.x,
|
||||
page_data["text_position"],
|
||||
TEXT_FADE_TIME,
|
||||
TEXT_FADE_STEPS,
|
||||
)
|
||||
|
||||
# Display the navigation buttons
|
||||
if self.page > 0:
|
||||
self.back_button.show()
|
||||
|
||||
if self.page < len(self.pages) - 1:
|
||||
self.next_button.show()
|
||||
pygame.display.update()
|
||||
|
||||
@staticmethod
|
||||
def create_transparent_buffer(size):
|
||||
if isinstance(size, (tuple, list)):
|
||||
(width, height) = size
|
||||
elif isinstance(size, dict):
|
||||
width = size["width"]
|
||||
height = size["height"]
|
||||
else:
|
||||
raise ValueError(f"Invalid size {size}. Should be tuple, list, or dict.")
|
||||
buffer = pygame.Surface((width, height), pygame.SRCALPHA, 32)
|
||||
buffer = buffer.convert_alpha()
|
||||
return buffer
|
||||
|
||||
def display_title(self):
|
||||
# Render the title as multiple lines if too big
|
||||
lines = self.wrap_text(self.title, self.fonts["title"], self.textarea.width)
|
||||
self.cursor["y"] = 0
|
||||
for line in lines:
|
||||
words = line.split(" ")
|
||||
self.cursor["x"] = (
|
||||
self.textarea.width // 2 - self.fonts["title"].size(line)[0] // 2
|
||||
)
|
||||
for word in words:
|
||||
text = self.fonts["title"].render(word + " ", True, TITLE_COLOR)
|
||||
self.fade_in_surface(
|
||||
text,
|
||||
self.cursor["x"] + self.textarea.x,
|
||||
self.cursor["y"] + self.textarea.y,
|
||||
TITLE_FADE_TIME,
|
||||
TITLE_FADE_STEPS,
|
||||
)
|
||||
pygame.display.update()
|
||||
self.cursor["x"] += text.get_width()
|
||||
time.sleep(WORD_DELAY)
|
||||
self.cursor["y"] += self.fonts["title"].size(line)[1]
|
||||
|
||||
def title_height(self):
|
||||
lines = self.wrap_text(self.title, self.fonts["title"], self.textarea.width)
|
||||
height = 0
|
||||
for line in lines:
|
||||
height += self.fonts["title"].size(line)[1]
|
||||
return height
|
||||
|
||||
@staticmethod
|
||||
def wrap_text(text, font, width):
|
||||
lines = []
|
||||
line = ""
|
||||
for word in text.split(" "):
|
||||
if font.size(line + word)[0] < width:
|
||||
line += word + " "
|
||||
else:
|
||||
lines.append(line)
|
||||
line = word + " "
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
def previous_page(self):
|
||||
if self.page > 0:
|
||||
self.page -= 1
|
||||
self.display_current_page()
|
||||
|
||||
def next_page(self):
|
||||
if self.page < len(self.pages) - 1:
|
||||
self.page += 1
|
||||
self.display_current_page()
|
||||
|
||||
def display_loading(self):
|
||||
self.display_surface(self.images["loading"], Position.CENTER, Position.CENTER)
|
||||
pygame.display.update()
|
||||
|
||||
def display_welcome(self):
|
||||
self.display_surface(self.images["welcome"], Position.CENTER, Position.CENTER)
|
||||
pygame.display.update()
|
||||
|
||||
# Parse out the title and story and separage into pages
|
||||
def parse_story(self, story):
|
||||
self.title = story.split("Title: ")[1].split("\n\n")[0]
|
||||
paragraphs = story.split("\n\n")[1:]
|
||||
page = self.add_page()
|
||||
for paragraph in paragraphs:
|
||||
lines = self.wrap_text(paragraph, self.fonts["text"], self.textarea.width)
|
||||
for line in lines:
|
||||
self.cursor["x"] = 0
|
||||
text = self.fonts["text"].render(line, True, TEXT_COLOR)
|
||||
self.display_surface(
|
||||
text, self.cursor["x"], self.cursor["y"], page["buffer"]
|
||||
)
|
||||
self.cursor["y"] += self.fonts["text"].size(line)[1]
|
||||
if (
|
||||
self.cursor["y"] + self.fonts["text"].get_height()
|
||||
> page["buffer"].get_height()
|
||||
):
|
||||
page = self.add_page()
|
||||
if self.cursor["y"] > 0:
|
||||
self.cursor["y"] += PARAGRAPH_SPACING
|
||||
|
||||
def add_page(self):
|
||||
page = {
|
||||
"title": False,
|
||||
"text_position": 0,
|
||||
}
|
||||
if len(self.pages) == 0:
|
||||
page["title"] = True
|
||||
page["text_position"] = self.title_height() + PARAGRAPH_SPACING
|
||||
page["buffer"] = self.create_transparent_buffer(
|
||||
(self.textarea.width, self.textarea.height - page["text_position"])
|
||||
)
|
||||
self.cursor["y"] = 0
|
||||
self.pages.append(page)
|
||||
return page
|
||||
|
||||
# save settings?
|
||||
# load settings?
|
||||
BIN
Magic_AI_Storybook/fonts/Desdemona Black Regular.otf
Normal file
BIN
Magic_AI_Storybook/fonts/Desdemona Black Regular.otf
Normal file
Binary file not shown.
BIN
Magic_AI_Storybook/fonts/Desdemona.ttf
Normal file
BIN
Magic_AI_Storybook/fonts/Desdemona.ttf
Normal file
Binary file not shown.
|
|
@ -1,33 +0,0 @@
|
|||
Title: Luna and the Fairy in the Oak Tree
|
||||
|
||||
In a small town hidden deep in the woods, there was an old oak tree that had been standing tall for centuries. It was said that a fairy lived in the oak tree and protected the forest creatures from harm. The locals had always respected the tree and never harmed any of the animals in the woods.
|
||||
|
||||
One day, a little girl named Luna moved to the town with her parents. She was a curious and adventurous girl who loved exploring the outdoors. She had heard tales of the fairy in the oak tree, and she was determined to meet her.
|
||||
|
||||
One sunny afternoon, Luna set out on her adventure. She packed some snacks and her favorite toys and headed towards the woods. She walked for a while until she reached the base of the old oak tree. She looked up high and saw a small light shining in the branches.
|
||||
|
||||
Luna started to climb up the oak tree, and as she got closer to the shining light, she saw a small fairy with delicate wings sitting on a branch. The fairy noticed Luna but didn't say anything, so Luna introduced herself.
|
||||
|
||||
"Hello, my name is Luna, and I have just moved to this town. I wanted to meet you and say hello, Miss Fairy," Luna said with a polite smile.
|
||||
|
||||
The fairy looked up, smiled, and said, "Hello, Luna. My name is Tia. I'm the guardian of the forest, and I protect all the animals living around here. What brings you to the woods?"
|
||||
|
||||
Luna explained that she loved adventures and that she was fascinated by the stories of the fairy in the oak tree. Tia was pleased to meet such a curious and adventurous girl, and she asked Luna if she would like to help her with a problem.
|
||||
|
||||
"I have been trying to find a way to keep the forest clean from all the litter and trash that people leave behind. Do you have any ideas, Luna? Maybe you can help me," Tia said.
|
||||
|
||||
Luna thought for a moment and then replied, "I have an idea. Can we make a game out of it? Kids in my school love treasure hunts. We could make a treasure hunt for the kids, but instead of gold, they could find trash and litter around the woods. And in return, we can reward them with something fun, like a treehouse picnic."
|
||||
|
||||
Tia loved Luna's idea and was impressed with her cleverness. Luna and Tia spent the rest of the afternoon, making plans for the treasure hunt. Luna headed back to town with a smile on her face, ready to make a difference.
|
||||
|
||||
The next few days, Luna worked hard to promote the treasure hunt amongst the local kids. She made posters and flyers and went door-to-door in her neighborhood to tell everyone about the event.
|
||||
|
||||
Finally, the big day arrived, and Luna and Tia watched as excited children ran around the woods with bags, picking up trash, and filling them up. By the end of the day, the woods were so clean that even Tia was surprised.
|
||||
|
||||
The kids gathered in a treehouse, and Luna served a picnic, while Tia gave every one of them a small trinket thanking them for helping keep the woods clean.
|
||||
|
||||
As Luna headed back home that day, she realized that sometimes the best adventure isn't about finding treasure, but about doing things that make a difference in the world.
|
||||
|
||||
From that day on, Luna and Tia became good friends, and Luna visited the oak tree often. Together they came up with many other ways to help the forest and its creatures, and Luna's love for the woods and nature grew with each passing day.
|
||||
|
||||
And so, Luna's adventure in the woods with the fairy in the oak tree ended on a happy note. She had found a new friend, created a treasure hunt that made a difference, and learned the inestimable value of kindness and helping those around her.
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
Title: Luna and the Oak Tree Fairy
|
||||
|
||||
Once upon a time, there was a little girl named Luna who loved exploring the woods behind her house. She would spend hours wandering among the trees, looking for interesting leaves and flowers, and listening to the rustling of the leaves as the wind blew through them.
|
||||
|
||||
One day, as Luna was wandering through the woods, she came across a massive oak tree. As she looked up toward its branches, she noticed something move. It was a fairy! She was wearing a leafy green dress, and her wings shimmered in the sunlight.
|
||||
|
||||
Luna was absolutely enchanted. She had heard stories of fairies before, but she had never actually seen one. "Hello there," she called up to the fairy. "Can you hear me?"
|
||||
|
||||
The fairy looked down at her, surprised. "Why yes, I can hear you perfectly well," she said. "Who are you, and what are you doing here?"
|
||||
|
||||
"My name is Luna," the little girl replied. "I like exploring the forest. What about you? Do you live in this tree?"
|
||||
|
||||
"I do indeed," said the fairy. "This is my home. My name is Rowan, and I am the guardian of this forest. I keep watch over all of the creatures who live here."
|
||||
|
||||
Luna was fascinated. She had never heard of a fairy being a guardian of a whole forest before. "That's amazing!" she exclaimed. "Can I come up and visit you?"
|
||||
|
||||
"Of course," said Rowan. "Just climb up onto one of the branches, and I'll come over to meet you."
|
||||
|
||||
Luna carefully scaled the tree, her heart beating with excitement. When she reached the branch where Rowan was waiting, the fairy greeted her warmly. "Welcome to my home," she said. "Would you like some fairy tea?"
|
||||
|
||||
Luna had never heard of fairy tea before, but she was willing to try anything. Rowan reached into her satchel and pulled out a tiny cup and saucer made from acorn shells. She poured some steaming liquid from a tiny teapot into the cup and handed it to Luna.
|
||||
|
||||
To her surprise, the tea was delicious. It tasted like honey and wildflowers, with a hint of mint. Luna sipped it contentedly, feeling warm and happy.
|
||||
|
||||
"So, Luna," said Rowan. "What brings you here today? Did you come looking for an adventure?"
|
||||
|
||||
Luna nodded eagerly. "I'm always looking for an adventure," she said. "Do you know of any good ones?"
|
||||
|
||||
Rowan smiled. "Actually, I do," she said. "There is a pond about two miles from here that is said to have magical properties. Legend has it that if you swim in the pond, you will be granted a wish."
|
||||
|
||||
Luna's eyes widened. "Really? That sounds amazing! Can we go there?"
|
||||
|
||||
"Of course," said Rowan. "I will guide you. But be careful, Luna. There are dangers in the forest. We must watch out for wild animals and thorny bushes."
|
||||
|
||||
Together, Luna and Rowan set off toward the pond, following a winding path through the woods. They encountered all sorts of wonders along the way, including a family of raccoons and a field of sunflowers.
|
||||
|
||||
Eventually, they arrived at the edge of the pond. It was a sparkling blue color, with lily pads floating on the surface. Luna gazed at it, spellbound.
|
||||
|
||||
"Are you ready, Luna?" said Rowan. "Remember, make your wish wisely. And don't stay in the water for too long, or the magic will wear off."
|
||||
|
||||
Luna took a deep breath and plunged into the water, feeling the coolness wash over her. She swam around for a while, enjoying the sensation of weightlessness.
|
||||
|
||||
When she was ready, she closed her eyes and made her wish. She wished for her family and friends to always be happy, healthy, and safe.
|
||||
|
||||
After just a few moments, she felt a tingling sensation in her fingers and toes. She knew the magic had worked.
|
||||
|
||||
As she swam back to shore, she looked up and saw Rowan smiling down at her. "Well done, Luna," the fairy said. "Your wish has been granted. And you have truly proven yourself to be an adventurer today."
|
||||
|
||||
Luna beamed with pride. She had made a new friend and had an amazing adventure all in one day. As they walked back to the oak tree, she knew that she would never forget this magical experience.
|
||||
|
||||
And so, Luna and Rowan became the best of friends, going on many more adventures together in the enchanted forest. With Rowan as her guide, Luna knew that she would always find wonder and joy in the world around her.
|
||||
|
|
@ -2,25 +2,59 @@
|
|||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import threading
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import math
|
||||
import pickle
|
||||
from enum import Enum
|
||||
from tempfile import NamedTemporaryFile
|
||||
import signal
|
||||
|
||||
import board
|
||||
import digitalio
|
||||
import openai
|
||||
import pygame
|
||||
from rpi_backlight import Backlight
|
||||
|
||||
# from listener import Listener
|
||||
from book import Book
|
||||
from listener import Listener
|
||||
|
||||
STORY_WORD_LENGTH = 800
|
||||
REED_SWITCH_PIN = board.D17
|
||||
|
||||
# Image Names
|
||||
WELCOME_IMAGE = "welcome.png"
|
||||
BACKGROUND_IMAGE = "paper_background.png"
|
||||
LOADING_IMAGE = "loading.png"
|
||||
BUTTON_BACK_IMAGE = "button_back.png"
|
||||
BUTTON_NEXT_IMAGE = "button_next.png"
|
||||
|
||||
# Asset Paths
|
||||
IMAGES_PATH = os.path.dirname(sys.argv[0]) + "images/"
|
||||
FONTS_PATH = os.path.dirname(sys.argv[0]) + "fonts/"
|
||||
|
||||
# Font Path, Size
|
||||
TITLE_FONT = (FONTS_PATH + "Desdemona Black Regular.otf", 48)
|
||||
TITLE_COLOR = (0, 0, 0)
|
||||
TEXT_FONT = (FONTS_PATH + "times new roman.ttf", 24)
|
||||
TEXT_COLOR = (0, 0, 0)
|
||||
|
||||
# Delays to control the speed of the text
|
||||
WORD_DELAY = 0.1
|
||||
WELCOME_IMAGE_DELAY = 3
|
||||
# TODO: This will be verbally prompted later
|
||||
STORY_REQUEST = (
|
||||
"Please tell me a story about a fairy who lives in an"
|
||||
" oak tree and a little girl named Luna who goes on an adventure."
|
||||
)
|
||||
TITLE_FADE_TIME = 0.05
|
||||
TITLE_FADE_STEPS = 25
|
||||
TEXT_FADE_TIME = 0.25
|
||||
TEXT_FADE_STEPS = 51
|
||||
|
||||
# Whitespace Settings in Pixels
|
||||
PAGE_TOP_MARGIN = 20
|
||||
PAGE_SIDE_MARGIN = 20
|
||||
PAGE_BOTTOM_MARGIN = 0
|
||||
PAGE_NAV_HEIGHT = 100
|
||||
EXTRA_LINE_SPACING = 0
|
||||
PARAGRAPH_SPACING = 30
|
||||
|
||||
# ChatGPT Parameters
|
||||
SYSTEM_ROLE = "You are a master AI Storyteller that can tell a story of any length."
|
||||
|
|
@ -32,20 +66,6 @@ ENERGY_THRESHOLD = 1000 # Energy level for mic to detect
|
|||
PHRASE_TIMEOUT = 3.0 # Space between recordings for sepating phrases
|
||||
RECORD_TIMEOUT = 30
|
||||
|
||||
# TODO: Tweak this to fix the issue of missing words
|
||||
STORY_PROMPT = (
|
||||
f"Write a complete story with a title and a body of approximately "
|
||||
f"{STORY_WORD_LENGTH} words long and a happy ending. The specific "
|
||||
f'story request is "{STORY_REQUEST}". '
|
||||
)
|
||||
|
||||
# Not sure if we will need this
|
||||
def dont_quit(_signal, _frame):
|
||||
print("Caught signal: {}".format(_signal))
|
||||
|
||||
|
||||
signal.signal(signal.SIGHUP, dont_quit)
|
||||
|
||||
# Import keys from environment variables
|
||||
openai.api_key = os.environ.get("OPENAI_API_KEY")
|
||||
|
||||
|
|
@ -53,34 +73,499 @@ if openai.api_key is None:
|
|||
print("Please set the OPENAI_API_KEY environment variable first.")
|
||||
sys.exit(1)
|
||||
|
||||
# Package up the text to send to ChatGPT
|
||||
def sendchat(prompt):
|
||||
completion = openai.ChatCompletion.create(
|
||||
model=CHATGPT_MODEL,
|
||||
messages=[
|
||||
{"role": "system", "content": SYSTEM_ROLE},
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
)
|
||||
# Send the heard text to ChatGPT and return the result
|
||||
return completion.choices[0].message.content
|
||||
|
||||
class Position(Enum):
|
||||
TOP = 0
|
||||
CENTER = 1
|
||||
BOTTOM = 2
|
||||
LEFT = 3
|
||||
RIGHT = 4
|
||||
|
||||
|
||||
# Transcribe the audio data to text using Whisper
|
||||
def transcribe(wav_data):
|
||||
print("Transcribing...")
|
||||
attempts = 0
|
||||
while attempts < 3:
|
||||
class Button:
|
||||
def __init__(self, x, y, image, action, draw_function):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.image = image
|
||||
self.action = action
|
||||
self._width = self.image.get_width()
|
||||
self._height = self.image.get_height()
|
||||
self._visible = False
|
||||
self._draw_function = draw_function
|
||||
|
||||
def is_in_bounds(self, position):
|
||||
x, y = position
|
||||
return (
|
||||
self.x <= x <= self.x + self.width and self.y <= y <= self.y + self.height
|
||||
)
|
||||
|
||||
def show(self):
|
||||
self._draw_function(self.image, self.x, self.y)
|
||||
self._visible = True
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self._width
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self._height
|
||||
|
||||
@property
|
||||
def visible(self):
|
||||
return self._visible
|
||||
|
||||
|
||||
class Textarea:
|
||||
def __init__(self, x, y, width, height):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return {"width": self.width, "height": self.height}
|
||||
|
||||
|
||||
class Book:
|
||||
def __init__(self, rotation=0):
|
||||
self.paragraph_number = 0
|
||||
self.page = 0
|
||||
self.pages = []
|
||||
self.stories = []
|
||||
self.story = 0
|
||||
self.rotation = rotation
|
||||
self.images = {}
|
||||
self.fonts = {}
|
||||
self.width = 0
|
||||
self.height = 0
|
||||
self.back_button = None
|
||||
self.next_button = None
|
||||
self.textarea = None
|
||||
self.screen = None
|
||||
self.saved_screen = None
|
||||
self.sleeping = False
|
||||
self.sleep_check_delay = 0.1
|
||||
self._sleep_check_thread = None
|
||||
self.running = True
|
||||
# Use a cursor to keep track of where we are in the text area
|
||||
self.cursor = {"x": 0, "y": 0}
|
||||
self.listener = Listener(ENERGY_THRESHOLD, PHRASE_TIMEOUT, RECORD_TIMEOUT)
|
||||
self.backlight = Backlight()
|
||||
|
||||
def init(self):
|
||||
# Output to the LCD instead of the console
|
||||
os.putenv("DISPLAY", ":0")
|
||||
|
||||
# Initialize the display
|
||||
pygame.init()
|
||||
self.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
|
||||
pygame.display.set_allow_screensaver(False)
|
||||
self.width = self.screen.get_height()
|
||||
self.height = self.screen.get_width()
|
||||
|
||||
# Preload images
|
||||
self._load_image("welcome", WELCOME_IMAGE)
|
||||
self._load_image("background", BACKGROUND_IMAGE)
|
||||
self._load_image("loading", LOADING_IMAGE)
|
||||
|
||||
# Preload fonts
|
||||
self._load_font("title", TITLE_FONT)
|
||||
self._load_font("text", TEXT_FONT)
|
||||
|
||||
# Add buttons
|
||||
back_button_image = pygame.image.load(IMAGES_PATH + BUTTON_BACK_IMAGE)
|
||||
next_button_image = pygame.image.load(IMAGES_PATH + BUTTON_NEXT_IMAGE)
|
||||
button_spacing = (
|
||||
self.width - (back_button_image.get_width() + next_button_image.get_width())
|
||||
) // 3
|
||||
button_ypos = (
|
||||
self.height
|
||||
- PAGE_NAV_HEIGHT
|
||||
+ (PAGE_NAV_HEIGHT - next_button_image.get_height()) // 2
|
||||
)
|
||||
self.back_button = Button(
|
||||
button_spacing,
|
||||
button_ypos,
|
||||
back_button_image,
|
||||
self.previous_page,
|
||||
self._display_surface,
|
||||
)
|
||||
self.next_button = Button(
|
||||
self.width - button_spacing - next_button_image.get_width(),
|
||||
button_ypos,
|
||||
next_button_image,
|
||||
self.next_page,
|
||||
self._display_surface,
|
||||
)
|
||||
|
||||
# Add Text Area
|
||||
self.textarea = Textarea(
|
||||
PAGE_SIDE_MARGIN,
|
||||
PAGE_TOP_MARGIN,
|
||||
self.width - PAGE_SIDE_MARGIN * 2,
|
||||
self.height - PAGE_NAV_HEIGHT - PAGE_TOP_MARGIN - PAGE_BOTTOM_MARGIN,
|
||||
)
|
||||
|
||||
pygame.mouse.set_visible(False)
|
||||
self.screen.fill((255, 255, 255))
|
||||
self._sleep_check_thread = threading.Thread(target=self._handle_sleep)
|
||||
self._sleep_check_thread.start()
|
||||
|
||||
def deinit(self):
|
||||
self.running = False
|
||||
self._sleep_check_thread.join()
|
||||
self.backlight.power = True
|
||||
|
||||
def _handle_sleep(self):
|
||||
reed_switch = digitalio.DigitalInOut(REED_SWITCH_PIN)
|
||||
reed_switch.direction = digitalio.Direction.INPUT
|
||||
reed_switch.pull = digitalio.Pull.UP
|
||||
|
||||
while self.running:
|
||||
if self.sleeping and reed_switch.value: # Book Open
|
||||
self.wake()
|
||||
elif not self.sleeping and not reed_switch.value: # Book Closed
|
||||
self.sleep()
|
||||
time.sleep(self.sleep_check_delay)
|
||||
|
||||
def handle_events(self):
|
||||
if not self.sleeping:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
raise SystemExit
|
||||
if event.type == pygame.MOUSEBUTTONDOWN:
|
||||
self._handle_mousedown_event(event)
|
||||
|
||||
def _handle_mousedown_event(self, event):
|
||||
if event.button == 1:
|
||||
# If button pressed while visible, trigger action
|
||||
coords = self._rotate_mouse_pos(event.pos)
|
||||
for button in [self.back_button, self.next_button]:
|
||||
if button.visible and button.is_in_bounds(coords):
|
||||
button.action()
|
||||
|
||||
def _rotate_mouse_pos(self, point):
|
||||
# Recalculate the mouse position based on the rotation of the screen
|
||||
# So that we have the coordinates relative to the upper left corner of the screen
|
||||
angle = 360 - self.rotation
|
||||
y, x = point
|
||||
x -= self.width // 2
|
||||
y -= self.height // 2
|
||||
x, y = x * math.sin(math.radians(angle)) + y * math.cos(
|
||||
math.radians(angle)
|
||||
), x * math.cos(math.radians(angle)) - y * math.sin(math.radians(angle))
|
||||
x += self.width // 2
|
||||
y += self.height // 2
|
||||
return (round(x), round(y))
|
||||
|
||||
def _load_image(self, name, filename):
|
||||
try:
|
||||
with NamedTemporaryFile(suffix=".wav") as temp_file:
|
||||
result = openai.Audio.translate_raw(
|
||||
WHISPER_MODEL, wav_data, temp_file.name
|
||||
image = pygame.image.load(IMAGES_PATH + filename)
|
||||
self.images[name] = image
|
||||
except pygame.error:
|
||||
pass
|
||||
|
||||
def _load_font(self, name, details):
|
||||
self.fonts[name] = pygame.font.Font(details[0], details[1])
|
||||
|
||||
def _display_surface(self, surface, x=0, y=0, target_surface=None):
|
||||
# Display a surface either positionally or with a specific x,y coordinate
|
||||
buffer = self._create_transparent_buffer((self.width, self.height))
|
||||
buffer.blit(surface, (x, y))
|
||||
if target_surface is None:
|
||||
buffer = pygame.transform.rotate(buffer, self.rotation)
|
||||
self.screen.blit(buffer, (0, 0))
|
||||
else:
|
||||
target_surface.blit(buffer, (0, 0))
|
||||
|
||||
def _fade_in_surface(self, surface, x, y, fade_time, fade_steps=50):
|
||||
background = self._create_transparent_buffer((self.width, self.height))
|
||||
self._display_surface(self.images["background"], 0, 0, background)
|
||||
|
||||
buffer = self._create_transparent_buffer(surface.get_size())
|
||||
fade_delay = round(
|
||||
fade_time / fade_steps * 1000
|
||||
) # Time to delay in ms between each fade step
|
||||
|
||||
for alpha in range(0, 255, round(255 / fade_steps)):
|
||||
buffer.blit(background, (-x, -y))
|
||||
surface.set_alpha(alpha)
|
||||
buffer.blit(surface, (0, 0))
|
||||
self._display_surface(buffer, x, y)
|
||||
pygame.display.update()
|
||||
pygame.time.wait(fade_delay)
|
||||
|
||||
def display_current_page(self):
|
||||
self._display_surface(self.images["background"], 0, 0)
|
||||
pygame.display.update()
|
||||
|
||||
page_data = self.pages[self.page]
|
||||
|
||||
# Display the title
|
||||
if page_data["title"]:
|
||||
self._display_title_text(page_data["title"])
|
||||
|
||||
self._fade_in_surface(
|
||||
page_data["buffer"],
|
||||
self.textarea.x,
|
||||
self.textarea.y + page_data["text_position"],
|
||||
TEXT_FADE_TIME,
|
||||
TEXT_FADE_STEPS,
|
||||
)
|
||||
|
||||
# Display the navigation buttons
|
||||
if self.page > 0 or self.story > 0:
|
||||
self.back_button.show()
|
||||
self.next_button.show()
|
||||
pygame.display.update()
|
||||
|
||||
@staticmethod
|
||||
def _create_transparent_buffer(size):
|
||||
if isinstance(size, (tuple, list)):
|
||||
(width, height) = size
|
||||
elif isinstance(size, dict):
|
||||
width = size["width"]
|
||||
height = size["height"]
|
||||
else:
|
||||
raise ValueError(f"Invalid size {size}. Should be tuple, list, or dict.")
|
||||
buffer = pygame.Surface((width, height), pygame.SRCALPHA, 32)
|
||||
buffer = buffer.convert_alpha()
|
||||
return buffer
|
||||
|
||||
def _display_title_text(self, text, y=0):
|
||||
# Render the title as multiple lines if too big
|
||||
lines = self._wrap_text(text, self.fonts["title"], self.textarea.width)
|
||||
self.cursor["y"] = y
|
||||
for line in lines:
|
||||
words = line.split(" ")
|
||||
self.cursor["x"] = (
|
||||
self.textarea.width // 2 - self.fonts["title"].size(line)[0] // 2
|
||||
)
|
||||
for word in words:
|
||||
text = self.fonts["title"].render(word + " ", True, TITLE_COLOR)
|
||||
self._fade_in_surface(
|
||||
text,
|
||||
self.cursor["x"] + self.textarea.x,
|
||||
self.cursor["y"] + self.textarea.y,
|
||||
TITLE_FADE_TIME,
|
||||
TITLE_FADE_STEPS,
|
||||
)
|
||||
return result["text"].strip()
|
||||
except (openai.error.ServiceUnavailableError, openai.error.APIError):
|
||||
time.sleep(3)
|
||||
attempts += 1
|
||||
return "I wasn't able to understand you. Please repeat that."
|
||||
pygame.display.update()
|
||||
self.cursor["x"] += text.get_width()
|
||||
time.sleep(WORD_DELAY)
|
||||
self.cursor["y"] += self.fonts["title"].size(line)[1]
|
||||
|
||||
def _title_text_height(self, text):
|
||||
lines = self._wrap_text(text, self.fonts["title"], self.textarea.width)
|
||||
height = 0
|
||||
for line in lines:
|
||||
height += self.fonts["title"].size(line)[1]
|
||||
return height
|
||||
|
||||
@staticmethod
|
||||
def _wrap_text(text, font, width):
|
||||
lines = []
|
||||
line = ""
|
||||
for word in text.split(" "):
|
||||
if font.size(line + word)[0] < width:
|
||||
line += word + " "
|
||||
else:
|
||||
lines.append(line)
|
||||
line = word + " "
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
def previous_page(self):
|
||||
if self.page > 0 or self.story > 0:
|
||||
self.page -= 1
|
||||
if self.page < 0:
|
||||
self.story -= 1
|
||||
self.load_story(self.stories[self.story])
|
||||
self.page = len(self.pages) - 1
|
||||
self.display_current_page()
|
||||
|
||||
def next_page(self):
|
||||
self.page += 1
|
||||
if self.page >= len(self.pages):
|
||||
if self.story < len(self.stories) - 1:
|
||||
self.story += 1
|
||||
self.load_story(self.stories[self.story])
|
||||
self.page = 0
|
||||
else:
|
||||
self.new_story()
|
||||
self.display_current_page()
|
||||
|
||||
def display_loading(self):
|
||||
self._display_surface(self.images["loading"], 0, 0)
|
||||
pygame.display.update()
|
||||
|
||||
def display_welcome(self):
|
||||
self._display_surface(self.images["welcome"], 0, 0)
|
||||
pygame.display.update()
|
||||
|
||||
def display_message(self, message):
|
||||
self._display_surface(self.images["background"], 0, 0)
|
||||
height = self._title_text_height(message)
|
||||
self._display_title_text(message, self.height // 2 - height // 2)
|
||||
|
||||
def load_story(self, story):
|
||||
# Parse out the title and story and render into pages
|
||||
self.pages = []
|
||||
title = story.split("Title: ")[1].split("\n\n")[0]
|
||||
page = self._add_page(title)
|
||||
paragraphs = story.split("\n\n")[1:]
|
||||
paragraphs.append("The End.")
|
||||
for paragraph in paragraphs:
|
||||
lines = self._wrap_text(paragraph, self.fonts["text"], self.textarea.width)
|
||||
for line in lines:
|
||||
self.cursor["x"] = 0
|
||||
text = self.fonts["text"].render(line, True, TEXT_COLOR)
|
||||
if (
|
||||
self.cursor["y"] + self.fonts["text"].get_height()
|
||||
> page["buffer"].get_height()
|
||||
):
|
||||
page = self._add_page()
|
||||
|
||||
self._display_surface(
|
||||
text, self.cursor["x"], self.cursor["y"], page["buffer"]
|
||||
)
|
||||
self.cursor["y"] += self.fonts["text"].size(line)[1]
|
||||
|
||||
if self.cursor["y"] > 0:
|
||||
self.cursor["y"] += PARAGRAPH_SPACING
|
||||
print(f"Loaded story at index {self.story} with {len(self.pages)} pages")
|
||||
|
||||
def _add_page(self, title=None):
|
||||
page = {
|
||||
"title": title,
|
||||
"text_position": 0,
|
||||
}
|
||||
if title:
|
||||
page["text_position"] = self._title_text_height(title) + PARAGRAPH_SPACING
|
||||
page["buffer"] = self._create_transparent_buffer(
|
||||
(self.textarea.width, self.textarea.height - page["text_position"])
|
||||
)
|
||||
self.cursor["y"] = 0
|
||||
self.pages.append(page)
|
||||
return page
|
||||
|
||||
def load_settings(self):
|
||||
storydata = {
|
||||
"history": [],
|
||||
"settings": {
|
||||
"story": 0,
|
||||
"page": 0,
|
||||
},
|
||||
}
|
||||
# Load the story data if it exists
|
||||
if os.path.exists(os.path.dirname(sys.argv[0]) + "storydata.bin"):
|
||||
print("Loading previous story data")
|
||||
with open(os.path.dirname(sys.argv[0]) + "storydata.bin", "rb") as f:
|
||||
storydata = pickle.load(f)
|
||||
self.stories = storydata["history"]
|
||||
self.story = storydata["settings"]["story"]
|
||||
|
||||
if storydata["history"] and storydata["settings"]["story"] < len(
|
||||
storydata["history"]
|
||||
):
|
||||
# Load the last story
|
||||
self.load_story(storydata["history"][storydata["settings"]["story"]])
|
||||
self.page = storydata["settings"]["page"]
|
||||
# If something changed and caused the current page to be too
|
||||
# large, just go to the last page of the story
|
||||
if self.page >= len(self.pages):
|
||||
self.page = len(self.pages) - 1
|
||||
|
||||
def save_settings(self):
|
||||
storydata = {
|
||||
"history": self.stories,
|
||||
"settings": {
|
||||
"story": self.story,
|
||||
"page": self.page,
|
||||
},
|
||||
}
|
||||
with open(os.path.dirname(sys.argv[0]) + "storydata.bin", "wb") as f:
|
||||
pickle.dump(storydata, f)
|
||||
|
||||
def new_story(self):
|
||||
self.display_message("What story would you like to hear today?")
|
||||
|
||||
while not self.listener.speech_waiting():
|
||||
self.listener.listen()
|
||||
|
||||
story_request = self._transcribe(self.listener.get_speech())
|
||||
story_prompt = self._make_story_prompt(story_request)
|
||||
|
||||
print("Getting new response. This may take a minute or two...")
|
||||
self.display_loading()
|
||||
response = self._sendchat(story_prompt)
|
||||
with open(os.path.dirname(sys.argv[0]) + "response.txt", "w") as f:
|
||||
f.write(response)
|
||||
print(response)
|
||||
|
||||
self.stories.append(response)
|
||||
self.story = len(self.stories) - 1
|
||||
self.page = 0
|
||||
|
||||
self.load_story(response)
|
||||
|
||||
def sleep(self):
|
||||
self.sleeping = True
|
||||
self.sleep_check_delay = 1
|
||||
self.saved_screen = self.screen.copy()
|
||||
self.screen.fill((0, 0, 0))
|
||||
pygame.display.update()
|
||||
self.backlight.power = False
|
||||
|
||||
def wake(self):
|
||||
# Turn on the screen
|
||||
self.backlight.power = True
|
||||
if self.saved_screen:
|
||||
self.screen.blit(self.saved_screen, (0, 0))
|
||||
pygame.display.update()
|
||||
self.saved_screen = None
|
||||
self.sleep_check_delay = 0.1
|
||||
self.sleeping = False
|
||||
|
||||
@staticmethod
|
||||
def _make_story_prompt(request):
|
||||
return (
|
||||
f"Write a complete story with a title and a body of approximately "
|
||||
f"{STORY_WORD_LENGTH} words long and a happy ending. The specific "
|
||||
f'story request is "{request}". '
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _transcribe(wav_data):
|
||||
# Transcribe the audio data to text using Whisper
|
||||
print("Transcribing...")
|
||||
attempts = 0
|
||||
while attempts < 3:
|
||||
try:
|
||||
with NamedTemporaryFile(suffix=".wav") as temp_file:
|
||||
result = openai.Audio.translate_raw(
|
||||
WHISPER_MODEL, wav_data, temp_file.name
|
||||
)
|
||||
return result["text"].strip()
|
||||
except (openai.error.ServiceUnavailableError, openai.error.APIError):
|
||||
time.sleep(3)
|
||||
attempts += 1
|
||||
return "I wasn't able to understand you. Please repeat that."
|
||||
|
||||
@staticmethod
|
||||
def _sendchat(prompt):
|
||||
# Package up the text to send to ChatGPT
|
||||
completion = openai.ChatCompletion.create(
|
||||
model=CHATGPT_MODEL,
|
||||
messages=[
|
||||
{"role": "system", "content": SYSTEM_ROLE},
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
)
|
||||
# Send the heard text to ChatGPT and return the result
|
||||
return completion.choices[0].message.content
|
||||
|
||||
|
||||
def parse_args():
|
||||
|
|
@ -102,33 +587,33 @@ def main(args):
|
|||
book = Book(args.rotation)
|
||||
book.init()
|
||||
|
||||
# Center and display the image
|
||||
book.display_welcome()
|
||||
time.sleep(WELCOME_IMAGE_DELAY)
|
||||
try:
|
||||
# Center and display the image
|
||||
book.display_welcome()
|
||||
start_time = time.monotonic()
|
||||
book.load_settings()
|
||||
|
||||
# To save on credits just use the response from last time
|
||||
if os.path.exists(os.path.dirname(sys.argv[0]) + "response.txt"):
|
||||
print("Using cached response")
|
||||
with open(os.path.dirname(sys.argv[0]) + "response.txt", "r") as f:
|
||||
response = f.read()
|
||||
else:
|
||||
print("Getting new response. This may take a minute or two...")
|
||||
book.display_loading()
|
||||
response = sendchat(STORY_PROMPT)
|
||||
with open(os.path.dirname(sys.argv[0]) + "response.txt", "w") as f:
|
||||
f.write(response)
|
||||
print(response)
|
||||
# Continue showing the image until the minimum amount of time has passed
|
||||
time.sleep(max(0, WELCOME_IMAGE_DELAY - (time.monotonic() - start_time)))
|
||||
|
||||
book.parse_story(response)
|
||||
book.display_current_page()
|
||||
if not book.stories:
|
||||
book.new_story()
|
||||
|
||||
while True:
|
||||
book.handle_events()
|
||||
time.sleep(0.1)
|
||||
book.display_current_page()
|
||||
|
||||
while True:
|
||||
if not book.sleeping:
|
||||
book.handle_events()
|
||||
time.sleep(0.1)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
book.save_settings()
|
||||
book.deinit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main(parse_args())
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
main(parse_args())
|
||||
|
||||
# TODO:
|
||||
# * Play with prompt parameters
|
||||
|
|
|
|||
Loading…
Reference in a new issue