Skip to content

Commit e1bcf07

Browse files
committed
add new project
1 parent a10eb63 commit e1bcf07

File tree

2 files changed

+204
-0
lines changed

2 files changed

+204
-0
lines changed
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
import json
4+
import os
5+
import ssl
6+
7+
import board
8+
import displayio
9+
import digitalio
10+
import keypad
11+
import socketpool
12+
from wifi import radio
13+
14+
import adafruit_requests
15+
import adafruit_displayio_ssd1306
16+
from adafruit_bitmap_font.bitmap_font import load_font
17+
from adafruit_display_text import wrap_text_to_pixels
18+
from adafruit_display_text.bitmap_label import Label
19+
from adafruit_ticks import ticks_add, ticks_less, ticks_ms
20+
21+
22+
# Choose your own prompt and wait messages, either by changing it below inside
23+
# the """triple quoted""" string, or by putting it in your settings.toml file,
24+
# like so:
25+
#
26+
# MY_PROMPT="Give me an idea for a plant-based dinner. Write one sentence"
27+
# PLEASE_WAIT="Cooking something up just for you"
28+
29+
# Here are some prompts you might want to try:
30+
31+
# Give me an idea for a plant-based dinner. Write one sentence
32+
#
33+
# Give jepler a description as a comic book supervillain. write one sentence.
34+
#
35+
# Invent and describe an alien species. write one sentence
36+
#
37+
# Invent a zany "as seen on TV product" that can't possibly work. One sentence
38+
#
39+
# Tell a 1-sentence story about a kitten and a funny mishap
40+
#
41+
# Make up a 1-sentence fortune for me
42+
#
43+
# In first person, write a 1-sentence story about an AI avoiding boredom in a creative way.
44+
#
45+
# Pick an everyday object (don't say what it is) and describe it using only the
46+
# ten hundred most common words.
47+
#
48+
# Invent an alien animal or plant, name it, and vividly describe it in 1
49+
# sentence
50+
prompt=os.getenv("MY_PROMPT", """
51+
Write 1 setence starting "you can" about an unconventional but useful superpower
52+
""").strip()
53+
please_wait=os.getenv("PLEASE_WAIT", """
54+
Finding superpower
55+
""").strip()
56+
57+
openai_api_key = os.getenv("OPENAI_API_KEY")
58+
59+
nice_font = load_font("helvR08.pcf")
60+
line_spacing = 0.68
61+
62+
# i2c display setup
63+
displayio.release_displays()
64+
oled_reset = board.GP9
65+
66+
# STEMMA I2C on picowbell
67+
i2c = board.STEMMA_I2C()
68+
display_bus = displayio.I2CDisplay(i2c, device_address=0x3D, reset=oled_reset)
69+
70+
WIDTH = 128
71+
HEIGHT = 64
72+
offset_y = 5
73+
74+
display = adafruit_displayio_ssd1306.SSD1306(
75+
display_bus, width=WIDTH, height=HEIGHT
76+
)
77+
if openai_api_key is None:
78+
input("Place your\nOPENAI_API_KEY\nin settings.toml")
79+
display.auto_refresh = False
80+
main_group = displayio.Group()
81+
display.root_group = main_group
82+
83+
terminal = Label(
84+
font=nice_font,
85+
color=0xFFFFFF,
86+
background_color=0,
87+
line_spacing=line_spacing,
88+
anchor_point=(0, 0),
89+
anchored_position=(0, 0),
90+
)
91+
max_lines = display.height // int(nice_font.get_bounding_box()[1] * terminal.line_spacing)
92+
main_group.append(terminal)
93+
94+
class WrappedTextDisplay:
95+
def __init__(self):
96+
self.line_offset = 0
97+
self.lines = []
98+
self.text = ""
99+
100+
def add_text(self, new_text):
101+
self.set_text(self.text + new_text)
102+
self.scroll_to_end()
103+
104+
def set_text(self, text):
105+
print("\033[H\033[2J", end=text)
106+
self.text = text
107+
self.lines = wrap_text_to_pixels(text, display.width, nice_font)
108+
self.line_offset = 0
109+
110+
def show(self, text):
111+
self.set_text(text)
112+
self.refresh()
113+
114+
def add_show(self, new_text):
115+
self.add_text(new_text)
116+
self.refresh()
117+
118+
def scroll_to_end(self):
119+
self.line_offset = self.max_offset()
120+
121+
def scroll_next_line(self):
122+
max_offset = self.max_offset()
123+
if max_offset > 0:
124+
line_offset = self.line_offset + 1
125+
self.line_offset = line_offset % (max_offset + 1)
126+
127+
def max_offset(self):
128+
return max(0, len(self.lines) - max_lines)
129+
130+
def on_last_line(self):
131+
return self.line_offset == self.max_offset()
132+
133+
def refresh(self):
134+
text = '\n'.join(self.lines[self.line_offset : self.line_offset + max_lines])
135+
terminal.text = text
136+
display.refresh()
137+
wrapped_text = WrappedTextDisplay()
138+
139+
def wait_button_scroll_text():
140+
led.switch_to_output(True)
141+
deadline = ticks_add(ticks_ms(),
142+
5000 if wrapped_text.on_last_line() else 1000)
143+
while True:
144+
if (event := keys.events.get()) and event.pressed:
145+
break
146+
if wrapped_text.max_offset() > 0 and ticks_less(deadline, ticks_ms()):
147+
wrapped_text.scroll_next_line()
148+
wrapped_text.refresh()
149+
deadline = ticks_add(deadline,
150+
5000 if wrapped_text.on_last_line() else 1000)
151+
led.value = False
152+
153+
if radio.ipv4_address is None:
154+
wrapped_text.show(f"connecting to {os.getenv('WIFI_SSID')}")
155+
radio.connect(os.getenv('WIFI_SSID'), os.getenv('WIFI_PASSWORD'))
156+
requests = adafruit_requests.Session(socketpool.SocketPool(radio), ssl.create_default_context())
157+
158+
def iter_lines(resp):
159+
partial_line = []
160+
for c in resp.iter_content():
161+
if c == b'\n':
162+
yield (b"".join(partial_line)).decode('utf-8')
163+
del partial_line[:]
164+
else:
165+
partial_line.append(c)
166+
if partial_line:
167+
yield (b"".join(partial_line)).decode('utf-8')
168+
169+
full_prompt = [
170+
{"role": "user", "content": prompt},
171+
]
172+
173+
keys = keypad.Keys((board.GP14,), value_when_pressed=False)
174+
led = digitalio.DigitalInOut(board.LED)
175+
176+
while True:
177+
wrapped_text.show(please_wait)
178+
179+
with requests.post("https://api.openai.com/v1/chat/completions",
180+
json={"model": "gpt-3.5-turbo", "messages": full_prompt, "stream": True},
181+
headers={
182+
"Authorization": f"Bearer {openai_api_key}",
183+
},
184+
) as response:
185+
186+
wrapped_text.set_text("")
187+
if response.status_code != 200:
188+
wrapped_text.show(f"Uh oh! {response.status_code}: {response.reason}")
189+
else:
190+
wrapped_text.show("")
191+
for line in iter_lines(response):
192+
# print(line)
193+
# continue
194+
if line.startswith("data: [DONE]"):
195+
break
196+
if line.startswith("data:"):
197+
content = json.loads(line[5:])
198+
try:
199+
token = content['choices'][0]['delta'].get('content', '')
200+
except (KeyError, IndexError) as e:
201+
token = None
202+
if token:
203+
wrapped_text.add_show(token)
204+
wait_button_scroll_text()
Binary file not shown.

0 commit comments

Comments
 (0)