ceymox commited on
Commit
13f4b4c
·
verified ·
1 Parent(s): a758c59

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +636 -0
app.py CHANGED
@@ -0,0 +1,636 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import datetime
4
+ import pytz
5
+ import uuid
6
+ import re
7
+ import json
8
+ from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
9
+ from google.oauth2 import service_account
10
+ from googleapiclient.discovery import build
11
+ import os
12
+ import gc
13
+ import logging
14
+
15
+ # Set up logging
16
+ logging.basicConfig(level=logging.INFO)
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # Log startup
20
+ logger.info("Starting appointment booking application...")
21
+
22
+ # Set up timezone
23
+ IST = pytz.timezone('Asia/Kolkata')
24
+
25
+ # ===== CONFIGURATION =====
26
+
27
+ # Model ID on Hugging Face
28
+ MODEL_ID = "meta-llama/Meta-Llama-3.1-8B-Instruct"
29
+
30
+ # Google Calendar API Configuration
31
+ SCOPES = ['https://www.googleapis.com/auth/calendar']
32
+ SERVICE_ACCOUNT_FILE = 'service-account-key.json'
33
+ CALENDAR_ID = '26f5856049fab3d6648a2f1dea57c70370de6bc1629a5182be1511b0e75d11d3@group.calendar.google.com' # Update with your calendar ID if not using primary
34
+
35
+ # Local appointments database (for backup)
36
+ appointments_db = {}
37
+
38
+ # ===== GOOGLE CALENDAR FUNCTIONS =====
39
+
40
+ def get_calendar_service():
41
+ """Get Google Calendar service"""
42
+ try:
43
+ # Check if Google credentials are stored in env variable
44
+ google_credentials = os.environ.get('GOOGLE_CREDENTIALS')
45
+ if google_credentials:
46
+ logger.info("Using Google credentials from environment variable")
47
+ # Write the credentials to a temporary file
48
+ with open('temp_credentials.json', 'w') as f:
49
+ f.write(google_credentials)
50
+ temp_file_path = 'temp_credentials.json'
51
+ credentials = service_account.Credentials.from_service_account_file(
52
+ temp_file_path, scopes=SCOPES)
53
+ elif os.path.exists(SERVICE_ACCOUNT_FILE):
54
+ logger.info(f"Using Google credentials from file: {SERVICE_ACCOUNT_FILE}")
55
+ # Use the file on disk
56
+ credentials = service_account.Credentials.from_service_account_file(
57
+ SERVICE_ACCOUNT_FILE, scopes=SCOPES)
58
+ else:
59
+ logger.warning("No Google Calendar credentials found")
60
+ return None
61
+
62
+ service = build('calendar', 'v3', credentials=credentials)
63
+ return service
64
+ except Exception as e:
65
+ logger.error(f"Error getting calendar service: {e}")
66
+ return None
67
+
68
+ def add_to_google_calendar(appointment_details):
69
+ """Add an appointment to Google Calendar"""
70
+ try:
71
+ service = get_calendar_service()
72
+ if not service:
73
+ return None
74
+
75
+ # Format start and end time
76
+ date_str = appointment_details["date"]
77
+ time_str = appointment_details["time"]
78
+
79
+ # Parse date and time
80
+ date_parts = date_str.split('-')
81
+ year, month, day = int(date_parts[0]), int(date_parts[1]), int(date_parts[2])
82
+
83
+ time_parts = time_str.split(' ')
84
+ time_val = time_parts[0]
85
+ meridian = time_parts[1] if len(time_parts) > 1 else 'AM'
86
+
87
+ hours, minutes = map(int, time_val.split(':'))
88
+
89
+ if meridian.upper() == 'PM' and hours != 12:
90
+ hours += 12
91
+ if meridian.upper() == 'AM' and hours == 12:
92
+ hours = 0
93
+
94
+ # Create datetime objects
95
+ start_time = datetime.datetime(year, month, day, hours, minutes, 0, tzinfo=IST)
96
+ end_time = start_time + datetime.timedelta(hours=1) # Default 1 hour appointment
97
+
98
+ # Create event
99
+ event = {
100
+ 'summary': f"Appointment with {appointment_details['name']}",
101
+ 'location': 'Office',
102
+ 'description': 'Appointment booked via AI Assistant',
103
+ 'start': {
104
+ 'dateTime': start_time.isoformat(),
105
+ 'timeZone': 'Asia/Kolkata',
106
+ },
107
+ 'end': {
108
+ 'dateTime': end_time.isoformat(),
109
+ 'timeZone': 'Asia/Kolkata',
110
+ },
111
+ 'reminders': {
112
+ 'useDefault': False,
113
+ 'overrides': [
114
+ {'method': 'email', 'minutes': 24 * 60},
115
+ {'method': 'popup', 'minutes': 10},
116
+ ],
117
+ },
118
+ }
119
+
120
+ # Add unique ID to track for cancellation
121
+ appointment_id = appointment_details.get('appointment_id', str(uuid.uuid4()))
122
+ event['extendedProperties'] = {
123
+ 'private': {
124
+ 'appointment_id': appointment_id
125
+ }
126
+ }
127
+
128
+ # Insert event
129
+ created_event = service.events().insert(calendarId=CALENDAR_ID, body=event).execute()
130
+ return created_event['id']
131
+
132
+ except Exception as e:
133
+ logger.error(f"Error adding to Google Calendar: {e}")
134
+ return None
135
+
136
+ # ===== FUNCTION DEFINITIONS =====
137
+
138
+ function_definitions = [
139
+ {
140
+ "name": "book_appointment",
141
+ "description": "Book an appointment",
142
+ "parameters": {
143
+ "type": "object",
144
+ "properties": {
145
+ "name": {
146
+ "type": "string",
147
+ "description": "The name of the person"
148
+ },
149
+ "date": {
150
+ "type": "string",
151
+ "description": "The date in YYYY-MM-DD format"
152
+ },
153
+ "time": {
154
+ "type": "string",
155
+ "description": "The time of the appointment (e.g., '10:00 AM')"
156
+ }
157
+ },
158
+ "required": ["name", "date", "time"]
159
+ }
160
+ }
161
+ ]
162
+
163
+ # ===== FUNCTION IMPLEMENTATIONS =====
164
+
165
+ def book_appointment(appointment_details):
166
+ """Book an appointment with just name, date and time"""
167
+ try:
168
+ # Generate a unique appointment ID
169
+ appointment_id = str(uuid.uuid4())[:8] # Shorter ID for simplicity
170
+
171
+ # Add appointment ID to details
172
+ appointment_details['appointment_id'] = appointment_id
173
+
174
+ # Store in local database
175
+ appointments_db[appointment_id] = appointment_details
176
+
177
+ # Add to Google Calendar
178
+ calendar_event_id = add_to_google_calendar(appointment_details)
179
+
180
+ if calendar_event_id:
181
+ # Store the calendar event ID
182
+ appointments_db[appointment_id]['calendar_event_id'] = calendar_event_id
183
+
184
+ return {
185
+ "success": True,
186
+ "appointment_id": appointment_id,
187
+ "message": "Appointment successfully booked and added to calendar",
188
+ "details": {
189
+ "name": appointment_details["name"],
190
+ "date": appointment_details["date"],
191
+ "time": appointment_details["time"],
192
+ "location": "Office"
193
+ }
194
+ }
195
+ else:
196
+ return {
197
+ "success": True,
198
+ "appointment_id": appointment_id,
199
+ "message": "Appointment booked but failed to add to calendar (offline mode)",
200
+ "details": {
201
+ "name": appointment_details["name"],
202
+ "date": appointment_details["date"],
203
+ "time": appointment_details["time"],
204
+ "location": "Office"
205
+ }
206
+ }
207
+ except Exception as e:
208
+ logger.error(f"Error in book_appointment: {e}")
209
+ return {
210
+ "success": False,
211
+ "message": f"Failed to book appointment: {str(e)}"
212
+ }
213
+
214
+ # ===== MODEL MANAGEMENT =====
215
+
216
+ # Global model and tokenizer - SINGLETON PATTERN
217
+ model = None
218
+ tokenizer = None
219
+
220
+ def free_memory():
221
+ """Free memory by clearing cache and running garbage collection"""
222
+ gc.collect()
223
+ if torch.cuda.is_available():
224
+ torch.cuda.empty_cache()
225
+ logger.info(f"GPU memory allocated: {torch.cuda.memory_allocated() / 1024**3:.2f} GB")
226
+ logger.info(f"GPU memory reserved: {torch.cuda.memory_reserved() / 1024**3:.2f} GB")
227
+
228
+ def load_llama_model():
229
+ """Load the Llama 3.1 model and tokenizer using singleton pattern"""
230
+ global model, tokenizer
231
+
232
+ # If model already loaded, return the existing instances
233
+ if model is not None and tokenizer is not None:
234
+ return True
235
+
236
+ logger.info("Loading Llama 3.1 model and tokenizer...")
237
+ free_memory()
238
+
239
+ try:
240
+ # Set up quantization config for better memory efficiency
241
+ quantization_config = BitsAndBytesConfig(
242
+ load_in_4bit=True,
243
+ bnb_4bit_compute_dtype=torch.float16,
244
+ bnb_4bit_quant_type="nf4",
245
+ bnb_4bit_use_double_quant=True
246
+ )
247
+
248
+ # Load tokenizer
249
+ tokenizer_local = AutoTokenizer.from_pretrained(MODEL_ID)
250
+ logger.info("Tokenizer loaded successfully")
251
+
252
+ # Load model with optimized settings
253
+ model_local = AutoModelForCausalLM.from_pretrained(
254
+ MODEL_ID,
255
+ quantization_config=quantization_config,
256
+ device_map="auto",
257
+ torch_dtype=torch.float16,
258
+ low_cpu_mem_usage=True
259
+ )
260
+ logger.info("Model loaded successfully")
261
+
262
+ # Store in global variables
263
+ model = model_local
264
+ tokenizer = tokenizer_local
265
+
266
+ free_memory()
267
+ logger.info("Model and tokenizer initialization complete")
268
+ return True
269
+
270
+ except Exception as e:
271
+ logger.error(f"Error loading model: {e}")
272
+ return False
273
+
274
+ # ===== CHAT PROCESSING =====
275
+
276
+ def format_prompt_with_functions(messages, system_prompt):
277
+ """Format the prompt for Llama 3.1 with function definitions"""
278
+ # Add function definitions to system prompt
279
+ full_system_prompt = system_prompt + "\n\n"
280
+ full_system_prompt += "You have access to the following functions that you MUST use for specific user queries:\n"
281
+
282
+ for func in function_definitions:
283
+ full_system_prompt += f"- {func['name']}: {func['description']}\n"
284
+ full_system_prompt += " Parameters:\n"
285
+ for param_name, param_info in func['parameters']['properties'].items():
286
+ required = "required" if param_name in func['parameters'].get('required', []) else "optional"
287
+ full_system_prompt += f" - {param_name} ({required}): {param_info.get('description', '')}\n"
288
+
289
+ full_system_prompt += "\nIMPORTANT: When a user asks to book an appointment, you MUST respond using the following JSON format:\n"
290
+ full_system_prompt += '```json\n{"function_call": {"name": "function_name", "arguments": {"arg1": "value1", "arg2": "value2"}}}\n```\n'
291
+ full_system_prompt += "You MUST collect all required information first: name, date, and time."
292
+ full_system_prompt += "\n\nFor non-function-calling queries, respond in a conversational manner."
293
+
294
+ # Format conversation history
295
+ formatted_messages = [
296
+ {"role": "system", "content": full_system_prompt}
297
+ ]
298
+
299
+ # Add conversation history
300
+ for message in messages:
301
+ if message["role"] == "function":
302
+ # Convert function results to assistant format for Llama 3.1
303
+ formatted_messages.append({
304
+ "role": "assistant",
305
+ "content": f"I'll process the function result: {message['content']}"
306
+ })
307
+ else:
308
+ formatted_messages.append(message)
309
+
310
+ return formatted_messages
311
+
312
+ def extract_function_call(response_text):
313
+ """Extract function call from model response"""
314
+ # Look for JSON block in the response
315
+ json_pattern = r'```json\s*(.*?)\s*```'
316
+ json_matches = re.findall(json_pattern, response_text, re.DOTALL)
317
+
318
+ if not json_matches:
319
+ # Try alternative pattern without markdown
320
+ json_pattern = r'({.*"function_call".*})'
321
+ json_matches = re.findall(json_pattern, response_text, re.DOTALL)
322
+
323
+ if json_matches:
324
+ try:
325
+ for json_str in json_matches:
326
+ parsed_json = json.loads(json_str.strip())
327
+ if "function_call" in parsed_json:
328
+ function_call = parsed_json["function_call"]
329
+ return {
330
+ "id": str(uuid.uuid4()),
331
+ "name": function_call["name"],
332
+ "arguments": function_call["arguments"]
333
+ }
334
+ except json.JSONDecodeError:
335
+ logger.error(f"Failed to parse JSON: {json_matches[0]}")
336
+
337
+ return None
338
+
339
+ def safe_generate(inputs, max_new_tokens=512):
340
+ """Safely generate text with error handling and memory management"""
341
+ global model, tokenizer
342
+
343
+ try:
344
+ free_memory()
345
+
346
+ # Generate with appropriate settings
347
+ outputs = model.generate(
348
+ inputs,
349
+ max_new_tokens=max_new_tokens,
350
+ temperature=0.7,
351
+ top_p=0.9,
352
+ do_sample=True,
353
+ pad_token_id=tokenizer.eos_token_id
354
+ )
355
+
356
+ response_text = tokenizer.decode(outputs[0][inputs.shape[1]:], skip_special_tokens=True)
357
+ free_memory()
358
+ return response_text
359
+ except Exception as e:
360
+ logger.error(f"Error in generation: {e}")
361
+ free_memory()
362
+ return f"Error generating response: {str(e)}"
363
+
364
+ def process_chat(message, chat_history):
365
+ """Process a chat message, calling functions when necessary"""
366
+ global model, tokenizer
367
+
368
+ if model is None or tokenizer is None:
369
+ error_msg = "Model not loaded properly. Please click 'Reload Model' and try again."
370
+ new_history = chat_history + [(message, error_msg)]
371
+ return new_history, new_history
372
+
373
+ try:
374
+ # Create system prompt
375
+ system_prompt = """You are a friendly appointment booking assistant. You help users book appointments by collecting their name, preferred date, and time.
376
+
377
+ Be polite and helpful. For any appointment request, make sure to collect the person's name, date (in YYYY-MM-DD format), and time (e.g., '10:00 AM').
378
+
379
+ If the user doesn't specify all the required information, politely ask for the missing details before booking."""
380
+
381
+ # Convert Gradio chat history to message format
382
+ messages = []
383
+
384
+ # Limit history to last 3 exchanges to save memory
385
+ limited_chat_history = chat_history[-3:] if len(chat_history) > 3 else chat_history
386
+
387
+ for user_msg, bot_msg in limited_chat_history:
388
+ messages.append({"role": "user", "content": user_msg})
389
+ messages.append({"role": "assistant", "content": bot_msg})
390
+
391
+ # Add current message
392
+ messages.append({"role": "user", "content": message})
393
+
394
+ # Format messages with function calling info
395
+ formatted_messages = format_prompt_with_functions(messages, system_prompt)
396
+
397
+ # Generate model response with error handling
398
+ try:
399
+ inputs = tokenizer.apply_chat_template(
400
+ formatted_messages,
401
+ tokenize=True,
402
+ add_generation_prompt=True,
403
+ return_tensors="pt"
404
+ ).to(model.device)
405
+
406
+ # First generation
407
+ response_text = safe_generate(inputs, max_new_tokens=512)
408
+ logger.info(f"Model response: {response_text[:100]}...")
409
+
410
+ # Check if response contains a function call
411
+ function_call = extract_function_call(response_text)
412
+
413
+ if function_call and function_call["name"] == "book_appointment":
414
+ # Execute the booking function
415
+ function_result = book_appointment(function_call["arguments"])
416
+ logger.info(f"Function result: {json.dumps(function_result)[:200]}...")
417
+
418
+ # Add the function result to messages
419
+ messages.append({
420
+ "role": "assistant",
421
+ "content": response_text,
422
+ })
423
+
424
+ messages.append({
425
+ "role": "function",
426
+ "name": "book_appointment",
427
+ "content": json.dumps(function_result)
428
+ })
429
+
430
+ # Format messages for second call
431
+ formatted_messages = format_prompt_with_functions(messages, system_prompt)
432
+
433
+ # Generate second response
434
+ inputs = tokenizer.apply_chat_template(
435
+ formatted_messages,
436
+ tokenize=True,
437
+ add_generation_prompt=True,
438
+ return_tensors="pt"
439
+ ).to(model.device)
440
+
441
+ second_response = safe_generate(inputs, max_new_tokens=512)
442
+ logger.info(f"Second model response: {second_response[:100]}...")
443
+
444
+ # Update chat history
445
+ new_chat_history = chat_history + [(message, second_response)]
446
+ return new_chat_history, new_chat_history
447
+ else:
448
+ # No function call, just return the response
449
+ new_chat_history = chat_history + [(message, response_text)]
450
+ return new_chat_history, new_chat_history
451
+
452
+ except Exception as e:
453
+ logger.error(f"Error in generation: {e}")
454
+ error_msg = f"Sorry, I couldn't generate a response. Please try a simpler question or try again later."
455
+ new_chat_history = chat_history + [(message, error_msg)]
456
+ return new_chat_history, new_chat_history
457
+ except Exception as e:
458
+ logger.error(f"Error in process_chat: {e}")
459
+ error_msg = f"Sorry, I encountered an error. Please try again."
460
+ new_chat_history = chat_history + [(message, error_msg)]
461
+ return new_chat_history, new_chat_history
462
+
463
+ # ===== GRADIO INTERFACE =====
464
+
465
+ def create_gradio_interface():
466
+ """Create the Gradio interface for the chatbot"""
467
+ logger.info("Creating Gradio interface...")
468
+
469
+ with gr.Blocks(css="""
470
+ .gradio-container {max-width: 800px !important}
471
+ .chat-window {height: 600px !important; overflow-y: auto}
472
+ """) as demo:
473
+ gr.Markdown("# Simple Appointment Booking Assistant")
474
+ gr.Markdown("### Tell me your name, date and time to book an appointment")
475
+
476
+ # Model status indicator
477
+ with gr.Row():
478
+ model_status = gr.Textbox(
479
+ label="Model Status",
480
+ value="Loading model...",
481
+ interactive=False
482
+ )
483
+
484
+ # Calendar integration status
485
+ with gr.Row():
486
+ calendar_status = gr.Textbox(
487
+ label="Calendar Integration Status",
488
+ value="Checking Google Calendar integration...",
489
+ interactive=False
490
+ )
491
+
492
+ # Function to check Google Calendar connectivity
493
+ def check_calendar_integration():
494
+ try:
495
+ service = get_calendar_service()
496
+ if service:
497
+ return "Google Calendar integration is active. Appointments will be saved to calendar."
498
+ else:
499
+ return "Google Calendar integration is not available. Appointments will only be stored in memory."
500
+ except Exception as e:
501
+ logger.error(f"Error checking calendar integration: {str(e)}")
502
+ return f"Error checking calendar integration: {str(e)}"
503
+
504
+ # Chatbot interface
505
+ chatbot = gr.Chatbot(
506
+ [],
507
+ elem_id="chatbot",
508
+ label="Chat with Appointment Assistant",
509
+ height=500
510
+ )
511
+
512
+ with gr.Row():
513
+ msg = gr.Textbox(
514
+ show_label=False,
515
+ placeholder="Type your message here...",
516
+ container=False
517
+ )
518
+ submit = gr.Button("Send")
519
+
520
+ with gr.Row():
521
+ clear = gr.Button("Clear Conversation")
522
+ reload_model = gr.Button("Reload Model")
523
+
524
+ # Provide instructions
525
+ with gr.Accordion("Instructions", open=False):
526
+ gr.Markdown("""
527
+ ## How to use this appointment booking assistant:
528
+
529
+ Simply tell the assistant you want to book an appointment and provide:
530
+ 1. Your name
531
+ 2. The date you want (in YYYY-MM-DD format)
532
+ 3. The time you want (like "10:00 AM")
533
+
534
+ ### Example messages:
535
+ - "I'd like to book an appointment"
536
+ - "Book an appointment for John Smith on 2025-05-20 at 2:30 PM"
537
+ - "Can I schedule a meeting tomorrow at 10 AM?"
538
+ """)
539
+
540
+ chat_history = gr.State([])
541
+
542
+ def initialize_model():
543
+ """Initialize the model on app startup"""
544
+ success = load_llama_model()
545
+ status = "Model loaded successfully!" if success else "Error loading model. Try clicking 'Reload Model'."
546
+ cal_status = check_calendar_integration()
547
+ return status, [], cal_status
548
+
549
+ def reload_model_click():
550
+ """Force reload the model and free memory"""
551
+ global model, tokenizer
552
+ # Clear global variables
553
+ model = None
554
+ tokenizer = None
555
+ # Free memory
556
+ free_memory()
557
+ # Reload model
558
+ success = load_llama_model()
559
+ status = "Model reloaded successfully!" if success else "Error reloading model. Check logs for details."
560
+ cal_status = check_calendar_integration()
561
+ return status, [], cal_status
562
+
563
+ # Set up event handlers
564
+ submit.click(
565
+ process_chat,
566
+ inputs=[msg, chat_history],
567
+ outputs=[chatbot, chat_history]
568
+ ).then(
569
+ lambda: "",
570
+ None,
571
+ msg
572
+ )
573
+
574
+ msg.submit(
575
+ process_chat,
576
+ inputs=[msg, chat_history],
577
+ outputs=[chatbot, chat_history]
578
+ ).then(
579
+ lambda: "",
580
+ None,
581
+ msg
582
+ )
583
+
584
+ clear.click(
585
+ lambda: [],
586
+ inputs=None,
587
+ outputs=[chat_history]
588
+ ).then(
589
+ lambda: [],
590
+ inputs=None,
591
+ outputs=[chatbot]
592
+ )
593
+
594
+ reload_model.click(
595
+ reload_model_click,
596
+ inputs=None,
597
+ outputs=[model_status, chat_history, calendar_status]
598
+ ).then(
599
+ lambda: [],
600
+ inputs=None,
601
+ outputs=[chatbot]
602
+ )
603
+
604
+ # Initial welcome message
605
+ demo.load(
606
+ initialize_model,
607
+ inputs=None,
608
+ outputs=[model_status, chat_history, calendar_status]
609
+ ).then(
610
+ lambda: [("", "Hello! I'm your appointment booking assistant. I can help you schedule an appointment. Just let me know your name, the date, and time you prefer.")],
611
+ inputs=None,
612
+ outputs=[chatbot]
613
+ )
614
+
615
+ return demo
616
+
617
+ # ===== MAIN EXECUTION =====
618
+
619
+ if __name__ == "__main__":
620
+ logger.info("===== Simple Appointment Booking Assistant =====")
621
+ logger.info("Using Llama 3.1-8B-Instruct")
622
+
623
+ # Set PyTorch environment variables for memory efficiency
624
+ os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "max_split_size_mb:128,garbage_collection_threshold:0.8"
625
+
626
+ try:
627
+ # Create and launch the Gradio interface
628
+ logger.info("Creating demo...")
629
+ demo = create_gradio_interface()
630
+ logger.info("Demo created, launching...")
631
+ demo.launch(share=False, debug=True)
632
+ logger.info("Gradio interface launched successfully")
633
+ except Exception as e:
634
+ logger.error(f"Error launching Gradio interface: {e}")
635
+ import traceback
636
+ logger.error(traceback.format_exc())