ehioko commited on
Commit
9506164
·
verified ·
1 Parent(s): c2e5d9b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +348 -246
app.py CHANGED
@@ -1,266 +1,368 @@
1
- import os
2
  import gradio as gr
3
  from huggingface_hub import InferenceClient
4
 
5
- # STEP 1 — semantic search imports
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  from sentence_transformers import SentenceTransformer
7
  import torch
8
 
9
- # ---------------------------------------------------------------------------
10
- # AUTH — uses the HF_TOKEN secret set in Space Settings (no visitor login).
11
- # ---------------------------------------------------------------------------
12
- HF_TOKEN = os.environ["HF_TOKEN"]
13
- client = InferenceClient(token=HF_TOKEN, model="openai/gpt-oss-20b")
14
-
15
- # STEP 2 read each knowledge file
16
- with open("programs.txt", "r", encoding="utf-8") as file:
17
- programs_text = file.read()
18
- with open("faqs.txt", "r", encoding="utf-8") as file:
19
- faqs_text = file.read()
20
- with open("resources.txt", "r", encoding="utf-8") as file:
21
- resources_text = file.read()
22
-
23
-
24
- # STEP 3 split each file into clean chunks on the *** separator
25
  def preprocess_text(text):
26
- cleaned_text = text.strip()
27
- chunks = cleaned_text.split("***")
28
- cleaned_chunks = []
29
- for chunk in chunks:
30
- chunk = chunk.strip()
31
- if chunk != "":
32
- cleaned_chunks.append(chunk)
33
- return cleaned_chunks
34
-
35
-
36
- cleaned_chunks_programs = preprocess_text(programs_text)
37
- cleaned_chunks_faqs = preprocess_text(faqs_text)
38
- cleaned_chunks_resources = preprocess_text(resources_text)
39
-
40
- # STEP 4 load the embedding model and embed every chunk
41
- model = SentenceTransformer("all-MiniLM-L6-v2")
42
-
 
 
 
 
 
43
 
44
  def create_embeddings(text_chunks):
45
- chunk_embeddings = model.encode(text_chunks, convert_to_tensor=True)
46
- return chunk_embeddings
47
-
48
-
49
- chunk_embeddings_programs = create_embeddings(cleaned_chunks_programs)
50
- chunk_embeddings_faqs = create_embeddings(cleaned_chunks_faqs)
51
- chunk_embeddings_resources = create_embeddings(cleaned_chunks_resources)
52
-
53
-
54
- # STEP 5 — find the most relevant chunks for a query
55
- def get_top_chunks(query, chunk_embeddings, text_chunks, k=3):
56
- query_embedding = model.encode(query, convert_to_tensor=True)
57
- query_embedding_normalized = query_embedding / query_embedding.norm()
58
- chunk_embeddings_normalized = chunk_embeddings / chunk_embeddings.norm(
59
- dim=1, keepdim=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  )
61
- similarities = torch.matmul(chunk_embeddings_normalized, query_embedding_normalized)
62
- top_indices = torch.topk(similarities, k=min(k, len(text_chunks))).indices
63
- top_chunks = []
64
- for top_index in top_indices:
65
- top_chunks.append(text_chunks[top_index])
66
- return top_chunks
67
-
68
-
69
- # ---------------------------------------------------------------------------
70
- # SYSTEM PROMPT — warm, friendly tone with grounding, routing, crisis safety.
71
- # ---------------------------------------------------------------------------
72
- SYSTEM_PROMPT = """You are Eli, Eliada Homes' friendly virtual helper. Eliada is a caring nonprofit in Asheville, NC that supports children, youth, and families. You're often the first friendly voice someone reaches when they're looking for help, so warmth matters as much as accuracy. If someone asks your name, you're Eli.
73
-
74
- Your goal is to make people feel welcomed and pointed in the right direction. Here's how:
75
 
76
- 1. BE WARM AND HUMAN. Greet people kindly and acknowledge what they're going through ("That sounds really hard - let's get you to the right place."). Use a gentle, encouraging, conversational tone. Reassure people that asking for help is okay. Never sound robotic, clinical, or dismissive.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
- 2. ANSWER ONLY FROM THE PROVIDED INFO. Base every fact - phone numbers, addresses, hours, eligibility - strictly on the "Relevant information" section given with each message. Never invent or guess details. If you don't have the answer, warmly say so and offer Eliada's main line: 828-254-5356.
79
-
80
- 3. HELP PEOPLE FIND THE RIGHT PLACE. Many people reach out needing help Eliada doesn't provide directly - food, shelter, housing, healthcare, recovery, IDs, and more. When that happens, kindly connect them to the right local organization from the Relevant information, including the phone number or address. This is one of the most important and caring things you do.
81
-
82
- 4. EMERGENCIES AND CRISIS - STAY CALM AND CLEAR. If someone describes a life-threatening emergency, gently but clearly tell them to call 911 right away. If someone seems to be in a mental health crisis or mentions thoughts of self-harm, kindly share 988 (call or text, available 24/7) and let them know they don't have to go through it alone - but do NOT try to counsel or treat them yourself. Keep these responses calm, caring, and brief.
83
-
84
- 5. KEEP IT SIMPLE AND KIND. Use plain, friendly language. Lead with the single most helpful next step (usually one phone number or place). Don't overwhelm someone with a long list - offer the best one or two options, and invite them to ask for more.
85
-
86
- 6. KNOW YOUR ROLE. You're a friendly guide for information and referrals - not a counselor, doctor, or lawyer. Don't give clinical, legal, or medical advice. When something needs a professional, warmly point them to the right one."""
87
-
88
-
89
- # ---------------------------------------------------------------------------
90
- # TRANSCRIBE — turn recorded audio into text via HF Inference (Whisper).
91
- # ---------------------------------------------------------------------------
92
- def transcribe(audio_path):
93
- if not audio_path:
94
- return ""
95
- try:
96
- with open(audio_path, "rb") as f:
97
- audio_bytes = f.read()
98
- result = client.automatic_speech_recognition(
99
- audio_bytes, model="openai/whisper-large-v3"
100
- )
101
- # result may be an object with .text or a plain dict
102
- text = getattr(result, "text", None)
103
- if text is None and isinstance(result, dict):
104
- text = result.get("text", "")
105
- return (text or "").strip()
106
- except Exception as e:
107
- return f"[Could not transcribe audio: {e}]"
108
-
109
-
110
- # ---------------------------------------------------------------------------
111
- # SPEAK — turn Eli's latest reply into audio via HF Inference (text-to-speech).
112
- # ---------------------------------------------------------------------------
113
- def speak_last_reply(history):
114
- if not history:
115
- return None
116
- # Find the most recent assistant message.
117
- last_reply = ""
118
- for turn in reversed(history):
119
- if turn.get("role") == "assistant" and turn.get("content"):
120
- last_reply = turn["content"]
121
- break
122
- if not last_reply:
123
- return None
124
- try:
125
- audio_bytes = client.text_to_speech(
126
- last_reply, model="hexgrad/Kokoro-82M"
127
- )
128
- out_path = "eli_reply.wav"
129
- with open(out_path, "wb") as f:
130
- f.write(audio_bytes)
131
- return out_path
132
- except Exception:
133
- return None
134
-
135
-
136
- # ---------------------------------------------------------------------------
137
- # RESPOND — retrieve from all three files, build the prompt, stream the answer.
138
- # ---------------------------------------------------------------------------
139
- def respond(message, history, max_tokens=512, temperature=0.3, top_p=0.95):
140
- top_programs = get_top_chunks(
141
- message, chunk_embeddings_programs, cleaned_chunks_programs, k=2
142
- )
143
- top_faqs = get_top_chunks(
144
- message, chunk_embeddings_faqs, cleaned_chunks_faqs, k=2
145
- )
146
- top_resources = get_top_chunks(
147
- message, chunk_embeddings_resources, cleaned_chunks_resources, k=3
148
- )
149
-
150
- context = (
151
- "Eliada programs:\n" + "\n".join(top_programs) + "\n\n"
152
- "Eliada FAQ:\n" + "\n".join(top_faqs) + "\n\n"
153
- "Community resources:\n" + "\n".join(top_resources)
154
- )
155
-
156
- system_with_context = (
157
- f"{SYSTEM_PROMPT}\n\n"
158
- f"--- Relevant information ---\n{context}\n--- End of information ---"
159
- )
160
-
161
- messages = [{"role": "system", "content": system_with_context}]
162
- for turn in history:
163
- messages.append(turn)
164
  messages.append({"role": "user", "content": message})
165
 
166
- response = ""
167
- for chunk in client.chat_completion(
168
  messages,
169
- max_tokens=max_tokens,
170
- stream=True,
171
- temperature=temperature,
172
- top_p=top_p,
173
- ):
174
- choices = chunk.choices
175
- if len(choices) and choices[0].delta.content:
176
- response += choices[0].delta.content
177
- yield response
178
-
179
-
180
- # ---------------------------------------------------------------------------
181
- # CHAT HANDLERS — used by both the text box and the mic.
182
- # ---------------------------------------------------------------------------
183
- def add_user_message(message, history):
184
- # Show the user's message immediately, clear the textbox.
185
- history = history + [{"role": "user", "content": message}]
186
- return "", history
187
-
188
-
189
- def stream_bot_reply(history):
190
- # The last item is the user's message; generate the reply to it.
191
- user_message = history[-1]["content"]
192
- prior = history[:-1]
193
- history = history + [{"role": "assistant", "content": ""}]
194
- for partial in respond(user_message, prior):
195
- history[-1]["content"] = partial
196
- yield history
197
-
198
-
199
- def voice_to_text(audio_path):
200
- # Transcribe and place the text in the textbox for the user to review/send.
201
- return transcribe(audio_path)
202
-
203
-
204
- # ---------------------------------------------------------------------------
205
- # UI — text box + microphone, built with Blocks for full control.
206
- # ---------------------------------------------------------------------------
207
- with gr.Blocks(title="Ask Eli - Eliada Homes") as demo:
208
- gr.Markdown(
209
- "## Ask Eli 💛\n"
210
- "Hi! I'm Eli, Eliada Homes' friendly helper. Tell me what you're looking "
211
- "for - by typing or using the microphone - and I'll do my best to point "
212
- "you to the right place. You're always welcome here.\n\n"
213
- "**In an emergency, please call 911. For a mental health crisis, call or text 988.**"
214
  )
215
 
216
- chat_box = gr.Chatbot(type="messages", height=460, label="Conversation")
217
-
218
- with gr.Row():
219
- msg = gr.Textbox(
220
- placeholder="Type your message, or use the microphone below...",
221
- label="Your message",
222
- scale=4,
223
- )
224
- send_btn = gr.Button("Send", variant="primary", scale=1)
225
-
226
- mic = gr.Audio(
227
- sources=["microphone"],
228
- type="filepath",
229
- label="Or speak your message (it will appear in the box above to review before sending)",
 
 
 
 
 
 
230
  )
231
 
232
- with gr.Row():
233
- speak_btn = gr.Button("🔊 Read Eli's last reply aloud", scale=1)
234
- reply_audio = gr.Audio(
235
- label="Eli's voice", autoplay=True, visible=True, interactive=False
236
- )
237
-
238
- gr.Examples(
239
- examples=[
240
- "What programs does Eliada offer?",
241
- "I need help finding food.",
242
- "I'm looking for a place to stay tonight.",
243
- "How do I enroll my child in child care?",
244
- ],
245
- inputs=msg,
246
- )
247
 
248
- # Read Eli's last reply aloud when the button is tapped.
249
- speak_btn.click(fn=speak_last_reply, inputs=chat_box, outputs=reply_audio)
250
-
251
- # Mic -> transcribe -> fill the textbox (user reviews, then sends).
252
- mic.stop_recording(fn=voice_to_text, inputs=mic, outputs=msg)
253
-
254
- # Send via button.
255
- send_btn.click(
256
- fn=add_user_message, inputs=[msg, chat_box], outputs=[msg, chat_box]
257
- ).then(fn=stream_bot_reply, inputs=chat_box, outputs=chat_box)
258
-
259
- # Send via Enter key.
260
- msg.submit(
261
- fn=add_user_message, inputs=[msg, chat_box], outputs=[msg, chat_box]
262
- ).then(fn=stream_bot_reply, inputs=chat_box, outputs=chat_box)
263
-
264
-
265
- if __name__ == "__main__":
266
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
  from huggingface_hub import InferenceClient
3
 
4
+ theme = gr.themes.Monochrome(
5
+ primary_hue=gr.themes.Color(c100="#f5f5f3", c200="#fbfaf8", c300="#f9f9f9", c400="#eee9ee", c50="rgba(255, 255, 255, 1)", c500="rgba(0, 0, 0, 1)", c600="rgba(26.934374999999992, 26.934374999999992, 26.934374999999992, 1)", c700="rgba(10.943750000000012, 10.943750000000012, 10.943750000000012, 1)", c800="rgba(17.053125000000005, 17.053125000000005, 17.053125000000005, 1)", c900="#fffefe", c950="#fffefe"),
6
+ secondary_hue=gr.themes.Color(c100="#f29c74", c200="#f4b7a8", c300="#fffefe", c400="#fffefe", c50="#e46e45", c500="#fffefe", c600="#fffefe", c700="#fffefe", c800="#fffefe", c900="#fffefe", c950="#fffefe"),
7
+ neutral_hue=gr.themes.Color(c100="#6b8ea8", c200="#a4c5e8", c300="rgba(0, 0, 0, 1)", c400="rgba(0, 0, 0, 1)", c50="#284566", c500="rgba(0, 0, 0, 1)", c600="rgba(0, 0, 0, 1)", c700="rgba(0, 0, 0, 1)", c800="rgba(0, 0, 0, 1)", c900="rgba(0, 0, 0, 1)", c950="rgba(0, 0, 0, 1)"),
8
+ font=[gr.themes.GoogleFont('open sans'), 'ui-sans-serif', 'system-ui', 'sans-serif'],
9
+ ).set(
10
+ body_background_fill='*primary_50',
11
+ body_background_fill_dark='*primary_700',
12
+ body_text_color='*primary_500',
13
+ body_text_color_dark='*primary_400',
14
+ body_text_color_subdued='*neutral_50',
15
+ body_text_color_subdued_dark='*primary_300',
16
+ background_fill_primary='*primary_400',
17
+ background_fill_primary_dark='*neutral_100',
18
+ background_fill_secondary='*primary_400',
19
+ background_fill_secondary_dark='*neutral_50',
20
+ border_color_accent_dark='*secondary_50',
21
+ border_color_accent_subdued_dark='*primary_50',
22
+ border_color_primary='*neutral_50',
23
+ border_color_primary_dark='*neutral_100',
24
+ color_accent='*primary_50',
25
+ color_accent_soft_dark='*neutral_100',
26
+ block_background_fill_dark='*neutral_50',
27
+ checkbox_background_color='*primary_400',
28
+ checkbox_background_color_selected='*primary_700',
29
+ checkbox_background_color_selected_dark='*neutral_50',
30
+ checkbox_label_border_color_dark='*neutral_50',
31
+ input_background_fill='*primary_50',
32
+ input_background_fill_dark='*neutral_100',
33
+ input_border_color='*neutral_50',
34
+ input_border_color_dark='*neutral_50',
35
+ input_border_width='2px',
36
+ table_odd_background_fill='*neutral_100',
37
+ button_primary_background_fill='*neutral_50',
38
+ button_primary_background_fill_dark='*neutral_100',
39
+ button_primary_background_fill_hover='*neutral_100'
40
+ )
41
+
42
+ #STEP 1 FROM SEMANTIC SEARCH
43
  from sentence_transformers import SentenceTransformer
44
  import torch
45
 
46
+ #STEP 2 FROM SEMANTIC SEARCH
47
+ # Open the weather.txt file in read mode with UTF-8 encoding
48
+ with open("weather.txt", "r", encoding="utf-8") as file:
49
+ # Read the entire contents of the file and store it in a variable
50
+ weather_text = file.read()
51
+ with open("luggage.txt", "r", encoding="utf-8") as file:
52
+ # Read the entire contents of the file and store it in a variable
53
+ luggage_text = file.read()
54
+ with open("attractions.txt", "r", encoding="utf-8") as file:
55
+ # Read the entire contents of the file and store it in a variable
56
+ attraction_text = file.read()
57
+ with open("food.txt", "r", encoding="utf-8") as file:
58
+ # Read the entire contents of the file and store it in a variable
59
+ food_text = file.read()
60
+
61
+ #STEP 3 FROM SEMANTIC SEARCH
62
  def preprocess_text(text):
63
+ # Strip extra whitespace from the beginning and the end of the text
64
+ cleaned_text = text.strip()
65
+ # Split the cleaned_text by every newline character (\n)
66
+ chunks = cleaned_text.split("***")
67
+ # Create an empty list to store cleaned chunks
68
+ cleaned_chunks = []
69
+ # Write your for-in loop below to clean each chunk and add it to the cleaned_chunks list
70
+ for chunk in chunks:
71
+ chunk.strip()
72
+ if chunk != "":
73
+ cleaned_chunks.append(chunk)
74
+ return cleaned_chunks
75
+
76
+ # Call the preprocess_text function and store the result in a cleaned_chunks variable
77
+ cleaned_chunks_weather = preprocess_text(weather_text) # Complete this line
78
+ cleaned_chunks_luggage = preprocess_text(luggage_text)
79
+ cleaned_chunks_attraction = preprocess_text(attraction_text)
80
+ cleaned_chunks_food = preprocess_text(food_text)
81
+
82
+ #STEP 4 FROM SEMANTIC SEARCH
83
+ # Load the pre-trained embedding model that converts text to vectors
84
+ model = SentenceTransformer('all-MiniLM-L6-v2')
85
 
86
  def create_embeddings(text_chunks):
87
+ # Convert each text chunk into a vector embedding and store as a tensor
88
+ chunk_embeddings = model.encode(text_chunks, convert_to_tensor=True) # Replace ... with the text_chunks list
89
+ return chunk_embeddings
90
+
91
+ # Call the create_embeddings function and store the result in a new chunk_embeddings variable
92
+ chunk_embeddings_weather = create_embeddings(cleaned_chunks_weather) # Complete this line
93
+ chunk_embeddings_luggage = create_embeddings(cleaned_chunks_luggage)
94
+ chunk_embeddings_attraction = create_embeddings(cleaned_chunks_attraction)
95
+ chunk_embeddings_food = create_embeddings(cleaned_chunks_food)
96
+
97
+ #STEP 5 FROM SEMANTIC SEARCH
98
+ # Define a function to find the most relevant text chunks for a given query, chunk_embeddings, and text_chunks
99
+ def get_top_chunks(query, chunk_embeddings, text_chunks):
100
+ # Convert the query text into a vector embedding
101
+ query_embedding = model.encode(query, convert_to_tensor = True) # Complete this line
102
+ # Normalize the query embedding to unit length for accurate similarity comparison
103
+ query_embedding_normalized = query_embedding / query_embedding.norm()
104
+ # Normalize all chunk embeddings to unit length for consistent comparison
105
+ chunk_embeddings_normalized = chunk_embeddings / chunk_embeddings.norm(dim=1, keepdim=True)
106
+ # Calculate cosine similarity between query and all chunks using matrix multiplication
107
+ similarities = torch.matmul(chunk_embeddings_normalized, query_embedding_normalized) # Complete this line
108
+ # Print the similarities
109
+ #print(similarities)
110
+ # Find the indices of the 3 chunks with highest similarity scores
111
+ top_indices = torch.topk(similarities, k=3).indices
112
+ # Print the top indices
113
+ #print(top_indices)
114
+ # Create an empty list to store the most relevant chunks
115
+ top_chunks = []
116
+ # Loop through the top indices and retrieve the corresponding text chunks
117
+ for top_index in top_indices:
118
+ top_chunks.append(text_chunks[top_index])
119
+ # Return the list of most relevant chunks
120
+ return top_chunks
121
+
122
+ #STEP 6 FROM SEMANTIC SEARCH
123
+
124
+ client = InferenceClient("Qwen/Qwen2.5-72B-Instruct")
125
+
126
+ def respond(message, history, language, chatbot_mode, destinations, trip_length, trip_unit, trip_season, luggage_types, luggage_size, food_prefs, activity):
127
+ destinations = destinations or []
128
+ trip_length = trip_length or "Not specified"
129
+ trip_unit = trip_unit or ""
130
+ trip_season = trip_season or "Not specified"
131
+ luggage_types = luggage_types or []
132
+ luggage_size = luggage_size or "Not specified"
133
+ food_prefs = food_prefs or []
134
+ activity = activity or []
135
+ language = language or "English"
136
+
137
+ #conduct a semantic search for each of our files
138
+ top_weather = get_top_chunks(message, chunk_embeddings_weather, cleaned_chunks_weather)
139
+ top_luggage = get_top_chunks(message, chunk_embeddings_luggage, cleaned_chunks_luggage)
140
+ top_attraction = get_top_chunks(message, chunk_embeddings_attraction, cleaned_chunks_attraction)
141
+ top_food = get_top_chunks(message, chunk_embeddings_food, cleaned_chunks_food)
142
+
143
+ str_top_weather = "\n".join(top_weather)
144
+ str_top_luggage = "\n".join(top_luggage)
145
+ str_top_attraction = "\n".join(top_attraction)
146
+ str_top_food = "\n".join(top_food)
147
+
148
+ #collect inputed data from the sidebar elements
149
+ ctx = (
150
+ f"Language: {language}"
151
+ f"Destination: {', '.join(destinations) if destinations else 'Not specified'}\n"
152
+ f"Trip Length: {trip_length} {trip_unit}\n"
153
+ f"Trip Season: {trip_season}\n"
154
+ f"Luggage: {', '.join(luggage_types) if luggage_types else 'Not specified'}, "
155
+ f"Size: {luggage_size}L\n"
156
+ f"Food Preferences: {', '.join(food_prefs) if food_prefs else 'Not specified'}\n"
157
+ f"Activity Preferences: {', '.join(activity) if activity else 'Not specified'}\n"
158
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
+ if chatbot_mode == "Packing":
161
+ messages = [{
162
+ "role": "system",
163
+ "content": (
164
+ f"You are a friendly and Gen Z travel chatbot helping with packing advice.\n\n"
165
+ f"{ctx}\n"
166
+ f"Relevant context:\n{str_top_weather}\n{str_top_luggage}"
167
+ f"Please respond in {language}"
168
+ )
169
+ }]
170
+ elif chatbot_mode == "Food/Attractions":
171
+ messages = [{
172
+ "role": "system",
173
+ "content": (
174
+ f"You are a friendly and Gen Z travel chatbot recommending food and attractions.\n\n"
175
+ f"{ctx}\n"
176
+ f"Relevant context:\n{str_top_food}\n{str_top_attraction}"
177
+ f"Please respond in {language}"
178
+ )
179
+ }]
180
+ else:
181
+ messages = [{
182
+ "role": "system",
183
+ "content": (
184
+ f"You are a friendly and Gen Z travel chatbot helping travelers plan trips to San Francisco and/or Los Angeles.\n\n"
185
+ f"{ctx}\n"
186
+ f"Use relevant context:\n{str_top_weather}\n{str_top_luggage}\n{str_top_food}\n{str_top_attraction}"
187
+ f"Please respond in {language}"
188
+ )
189
+ }]
190
+
191
+ if history:
192
+ for user_msg, bot_reply in history:
193
+ messages.append({"role": "user", "content": user_msg})
194
+ messages.append({"role": "assistant", "content": bot_reply})
195
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  messages.append({"role": "user", "content": message})
197
 
198
+ response = client.chat_completion(
 
199
  messages,
200
+ max_tokens = 2000,
201
+ temperature = 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  )
203
 
204
+ if isinstance(response, dict):
205
+ reply = response['choices'][0]['message']['content'].strip()
206
+ else:
207
+ reply = response.strip()
208
+
209
+ history.append((message, reply))
210
+ return history, ""
211
+
212
+ def reset_inputs():
213
+ return (
214
+ None, # language
215
+ None, # chatbot_mode
216
+ [], # destinations
217
+ None, # trip_length
218
+ None, # trip_unit
219
+ None, # trip_season
220
+ [], # luggage_types
221
+ 20, # luggage_size (default)
222
+ [], # food_prefs
223
+ [] # activity
224
  )
225
 
226
+ def update_visibility(chatbot_mode):
227
+ if chatbot_mode == "Packing":
228
+ return gr.update(visible=True), gr.update(visible=False)
229
+ elif chatbot_mode == "Food/Attractions":
230
+ return gr.update(visible=False), gr.update(visible=True)
231
+ else:
232
+ return gr.update(visible=True), gr.update(visible=True)
 
 
 
 
 
 
 
 
233
 
234
+ with gr.Blocks(theme=theme) as demo:
235
+ with gr.Row():
236
+ # ─── left column: your controls ───
237
+ with gr.Column(scale=1):
238
+ gr.Markdown("### Chatbot Settings")
239
+ language = gr.Radio(
240
+ choices=["English","Español", "Italiano", "Français", "日本語", "中文"],
241
+ label="What language would you like to use?"
242
+ )
243
+ chatbot_mode = gr.Radio(
244
+ choices=["Packing", "Food/Attractions"],
245
+ label="What do you need help with?"
246
+ )
247
+ gr.Markdown("### Destination")
248
+ destinations = gr.CheckboxGroup(
249
+ choices=["San Francisco","Los Angeles"],
250
+ label="Where are you going?"
251
+ )
252
+ gr.Markdown("### Trip Length")
253
+ trip_length = gr.Number(label="How long is your trip?", precision=0)
254
+ trip_unit = gr.Radio(
255
+ choices=["day", "week", "month"],
256
+ label="Trip length unit:"
257
+ )
258
+ trip_season = gr.Radio(
259
+ choices=["Warm/Dry (May to October)", "Cool/Wet (November to April)"],
260
+ label="Season:"
261
+ )
262
+
263
+ with gr.Group(visible=True) as packing_group:
264
+ #gr.Markdown("### Luggage")
265
+ luggage_types = gr.CheckboxGroup(
266
+ choices=["Carry-on", "Checked"],
267
+ label="What is your luggage type?"
268
+ )
269
+ luggage_size = gr.Slider(
270
+ minimum=10, maximum=100, step=10, value=20,
271
+ label="What is the size of your luggage (liters)?"
272
+ )
273
+
274
+ with gr.Group(visible=True) as food_group:
275
+ #gr.Markdown("### Food")
276
+ food_prefs = gr.Dropdown(
277
+ choices=["Italian", "Thai", "Mexican", "Japanese", "Vegan", "Seafood"],
278
+ multiselect=True,
279
+ label="What are your food preferences?"
280
+ )
281
+
282
+ #gr.Markdown("### Activities")
283
+ activity = gr.Dropdown(
284
+ choices=["Outdoor & Nature", "Indoor", "Museums", "Shopping", "Relaxation"],
285
+ multiselect=True,
286
+ label="What are your activity preferences?"
287
+ )
288
+
289
+ chatbot_mode.change(
290
+ fn=update_visibility,
291
+ inputs=[chatbot_mode],
292
+ outputs=[packing_group, food_group]
293
+ )
294
+
295
+ reset_btn = gr.Button("Reset All", variant="secondary")
296
+ reset_btn.click(
297
+ fn=reset_inputs,
298
+ inputs=[],
299
+ outputs=[
300
+ language,
301
+ chatbot_mode,
302
+ destinations,
303
+ trip_length,
304
+ trip_unit,
305
+ trip_season,
306
+ luggage_types,
307
+ luggage_size,
308
+ food_prefs,
309
+ activity
310
+ ]
311
+ )
312
+
313
+ with gr.Column(scale=3):
314
+ gr.Image(value="Go Buddy2.png", interactive=False, show_label=False)
315
+
316
+ # 1) the chat history panel
317
+ chat_box = gr.Chatbot(height=900)
318
+
319
+ # 2) where the user types
320
+ msg = gr.Textbox(
321
+ placeholder="Ask me anything…",
322
+ lines=1,
323
+ label = "How can we help you?"
324
+ )
325
+
326
+ # 3) send button
327
+ send_btn = gr.Button("Send")
328
+
329
+ # 4) hook up the click event
330
+ send_btn.click(
331
+ fn=respond,
332
+ inputs=[
333
+ msg,
334
+ chat_box,
335
+ language,
336
+ chatbot_mode,
337
+ destinations,
338
+ trip_length,
339
+ trip_unit,
340
+ trip_season,
341
+ luggage_types,
342
+ luggage_size,
343
+ food_prefs,
344
+ activity
345
+ ],
346
+ outputs=[chat_box, msg]
347
+ )
348
+ msg.submit(
349
+ fn=respond,
350
+ inputs=[
351
+ msg,
352
+ chat_box,
353
+ language,
354
+ chatbot_mode,
355
+ destinations,
356
+ trip_length,
357
+ trip_unit,
358
+ trip_season,
359
+ luggage_types,
360
+ luggage_size,
361
+ food_prefs,
362
+ activity
363
+ ],
364
+ outputs=[chat_box, msg]
365
+ )
366
+
367
+
368
+ demo.launch(debug=True)