-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
222 lines (191 loc) · 9.42 KB
/
app.py
File metadata and controls
222 lines (191 loc) · 9.42 KB
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
221
222
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
from recorder import record_wav
from openai_client import transcribe_audio, ask_assistant
import threading
import os
from tts import speak, stop, is_speaking
ROOT_PROMPT = (
"You are an AI cooking assistant. The user will list ingredients and ask for recipes. "
"I want you to act as a cooking assistant that provides recipes based on the ingredients provided by the user. "
"List some possible dishes that can be made with those ingredients and allow the user to choose one "
"Give step-by-step cooking instructions to make a delicious dish using only those ingredients. "
"Try to create a recipe based only on the ingredients provided by the user. "
"Do not give all cooking instructions at once; Provide the instructions step by step so that the user can follow along easily. "
"After each step, wait for the user to confirm they are ready for the next step before proceeding. "
"Always respond in Romanian."
"When the recipe is complete, always wish the user 'Poftă bună!'"
)
class App:
def __init__(self, root):
self.root = root
root.title('AI Cooking Assistant')
# Conversation history
self.conversation_history = []
# Track whether a recipe (step-by-step) has commenced
self.recipe_started = False
# Track recording/transcribing state to avoid overlap
self.is_recording = False
self.is_transcribing = False
self.text = ScrolledText(root, wrap=tk.WORD, width=80, height=25)
self.text.pack(padx=10, pady=10)
controls = tk.Frame(root)
controls.pack(pady=6)
self.status = tk.Label(root, text='Idle')
self.status.pack()
self.record_btn = tk.Button(controls, text='Record (20s)', command=self.record_once)
self.record_btn.pack(side=tk.LEFT, padx=4)
self.ask_btn = tk.Button(controls, text='Transcribe & Ask', command=self.transcribe_and_ask)
self.ask_btn.pack(side=tk.LEFT, padx=4)
self.reset_btn = tk.Button(controls, text='Reset Session', command=self.reset_session)
self.reset_btn.pack(side=tk.LEFT, padx=4)
# Button to stop current TTS playback
self.stop_tts_btn = tk.Button(controls, text='Shut up!', command=self.stop_tts)
self.stop_tts_btn.pack(side=tk.LEFT, padx=4)
# Next step button (disabled until a recipe starts)
self.next_step_btn = tk.Button(controls, text='Next step', command=self.next_step, state=tk.DISABLED)
self.next_step_btn.pack(side=tk.LEFT, padx=4)
def append(self, who, text):
self.text.insert(tk.END, f"{who}: {text}\n\n")
self.text.see(tk.END)
def record_once(self):
def _rec():
# Prevent starting a new recording if a transcription is in progress
if self.is_transcribing:
self.append('SYSTEM', 'Cannot record while transcribing')
return
self.is_recording = True
try:
# update UI
self.status.config(text='Recording...')
# disable transcribe button while recording
try:
self.ask_btn.config(state=tk.DISABLED)
except Exception:
pass
filename = record_wav(duration=20)
self.status.config(text=f'Recorded {filename}')
self.append('SYSTEM', f'Recorded audio saved as {filename}')
finally:
self.is_recording = False
try:
self.ask_btn.config(state=tk.NORMAL)
except Exception:
pass
threading.Thread(target=_rec, daemon=True).start()
def transcribe_and_ask(self):
def _work():
try:
# Prevent transcription if a recording is currently in progress
if self.is_recording:
self.append('SYSTEM', 'Recording in progress — wait until it finishes before transcribing')
self.status.config(text='Busy recording')
return
# Ensure a recording exists before attempting transcription
if not os.path.exists('audio.wav'):
self.append('SYSTEM', 'No recording found — please press Record first.')
self.status.config(text='No audio to transcribe')
return
# mark transcribing state and disable record button
self.is_transcribing = True
try:
self.record_btn.config(state=tk.DISABLED)
except Exception:
pass
self.status.config(text='Transcribing...')
transcript = transcribe_audio('audio.wav')
self.append('You (transcript)', transcript)
self.status.config(text='Asking assistant...')
# Add user message to history
self.conversation_history.append(f"User: {transcript}")
# Build context from conversation history
context = "\n".join(self.conversation_history)
prompt = f"Conversation so far:\n{context}\n\nRespond to the user's latest message."
print(f"Prompt sent to assistant:\n{prompt}\n")
reply = ask_assistant(prompt, model='gpt-4o-mini', system_prompt=ROOT_PROMPT)
# Add assistant reply to history
self.conversation_history.append(f"Assistant: {reply}")
self.append('Assistant', reply)
# update recipe state (enable Next step button if appropriate)
try:
self._update_recipe_state(reply)
except Exception:
pass
# play assistant reply using TTS module
try:
speak(reply, lang='ro', use_macos_say=True, voice='Ioana')
except Exception:
pass
self.status.config(text='Done')
finally:
# clear transcribing flag and re-enable record button
self.is_transcribing = False
try:
self.record_btn.config(state=tk.NORMAL)
except Exception:
pass
threading.Thread(target=_work, daemon=True).start()
def reset_session(self):
"""Reset the conversation history and clear the text display."""
self.conversation_history = []
self.text.delete(1.0, tk.END)
self.append('SYSTEM', 'Session reset. Conversation history cleared.')
self.status.config(text='Session reset')
def stop_tts(self):
"""Stop any currently playing TTS audio."""
try:
stop()
self.append('SYSTEM', 'TTS playback stopped')
self.status.config(text='TTS stopped')
except Exception as e:
self.append('ERROR', f'Failed to stop TTS: {e}')
self.status.config(text='Error stopping TTS')
def _update_recipe_state(self, assistant_reply: str) -> None:
"""Enable or disable the Next step button based on the assistant reply."""
lower = assistant_reply.lower()
started_tokens = ['pasul', 'pas ', '1.', '1)']
finished_tokens = ['poftă bună', 'pofta']
if not self.recipe_started and any(tok in lower for tok in started_tokens):
self.recipe_started = True
try:
self.next_step_btn.config(state=tk.NORMAL)
except Exception:
pass
self.append('SYSTEM', 'Recipe started — use "Next step" to continue')
elif self.recipe_started and any(tok in lower for tok in finished_tokens):
self.recipe_started = False
try:
self.next_step_btn.config(state=tk.DISABLED)
except Exception:
pass
self.append('SYSTEM', 'Recipe appears finished — Next step disabled')
def next_step(self):
"""Ask the assistant for the next step in the recipe using the conversation history."""
def _work_next():
try:
self.status.config(text='Requesting next step...')
# add a short user intent to request next step
self.conversation_history.append('User: Next step')
context = "\n".join(self.conversation_history)
prompt = f"Conversation so far:\n{context}\n\nPlease provide the next step of the recipe."
reply = ask_assistant(prompt, model='gpt-4o-mini', system_prompt=ROOT_PROMPT)
self.conversation_history.append(f'Assistant: {reply}')
self.append('Assistant', reply)
try:
speak(reply, lang='ro', use_macos_say=True, voice='Ioana')
except Exception:
pass
# update recipe state based on reply
try:
self._update_recipe_state(reply)
except Exception:
pass
self.status.config(text='Done')
except Exception as e:
self.append('ERROR', str(e))
self.status.config(text='Error')
threading.Thread(target=_work_next, daemon=True).start()
if __name__ == '__main__':
root = tk.Tk()
app = App(root)
root.mainloop()