Add files via upload
Hi Ed, This is a Gradio-based booking assistant bot that: - Accepts text and voice inputs - Uses OpenAI for transcription and chat - Restricts booking times to weekdays 14:00–15:00 - Responds with translated audio confirmations Looking forward to your feedback! Sabine
This commit is contained in:
committed by
GitHub
parent
b840ea6b58
commit
966a5f6c38
344
week2/community-contributions/clinic_booking_bot.ipynb
Normal file
344
week2/community-contributions/clinic_booking_bot.ipynb
Normal file
@@ -0,0 +1,344 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 170,
|
||||
"id": "a1aa1b43-7a47-4aca-ae5f-94a9d4ba2d89",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"## Clinic Booking Bot\n",
|
||||
"\n",
|
||||
"##Easily book your clinic visit – available only on weekdays between **14:00 and 15:00**. \n",
|
||||
"##Speak or type, and get instant confirmation.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 171,
|
||||
"id": "fe798c6a-f8da-46aa-8c0e-9d2623def3d2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# import library\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"import json\n",
|
||||
"from dotenv import load_dotenv\n",
|
||||
"from openai import OpenAI\n",
|
||||
"import gradio as gr\n",
|
||||
"import base64\n",
|
||||
"from io import BytesIO\n",
|
||||
"from datetime import date\n",
|
||||
"from PIL import Image, ImageDraw, ImageFont\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 172,
|
||||
"id": "0ad4e526-e95d-4e70-9faa-b4236b105dd5",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"OpenAI API Key exists and begins sk-proj-\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Save keys\n",
|
||||
"\n",
|
||||
"load_dotenv(override=True)\n",
|
||||
"\n",
|
||||
"openai_api_key = os.getenv('OPENAI_API_KEY')\n",
|
||||
"if openai_api_key:\n",
|
||||
" print(f\"OpenAI API Key exists and begins {openai_api_key[:8]}\")\n",
|
||||
"else:\n",
|
||||
" print(\"OpenAI API Key not set\")\n",
|
||||
" \n",
|
||||
"MODEL = \"gpt-4o-mini\"\n",
|
||||
"openai = OpenAI()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 173,
|
||||
"id": "ae95308e-0002-4017-9f2c-fcb1ddb248fa",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# --- CONFIG ---\n",
|
||||
"BOOKING_START = 14\n",
|
||||
"BOOKING_END = 15\n",
|
||||
"WEEKDAYS = [\"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\"]\n",
|
||||
"PHONE = \"010-1234567\"\n",
|
||||
"confirmed_bookings = []\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 174,
|
||||
"id": "e21b0fd0-4cda-4938-8867-dc2c6e7af4b1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# --- TTS ---\n",
|
||||
"def generate_tts(text, voice=\"fable\", filename=\"output.mp3\"):\n",
|
||||
" response = openai.audio.speech.create(\n",
|
||||
" model=\"tts-1\",\n",
|
||||
" voice=\"fable\",\n",
|
||||
" input=text\n",
|
||||
" )\n",
|
||||
" with open(filename, \"wb\") as f:\n",
|
||||
" f.write(response.content)\n",
|
||||
" return filename"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 175,
|
||||
"id": "e28a5c3b-bd01-4845-a41e-87823f6bb078",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# --- Translate Booking Confirmation ---\n",
|
||||
"def translate_text(text, target_language=\"nl\"):\n",
|
||||
" prompt = f\"Translate this message to {target_language}:\\n{text}\"\n",
|
||||
" response = openai.chat.completions.create(\n",
|
||||
" model=\"gpt-4\",\n",
|
||||
" messages=[\n",
|
||||
" {\"role\": \"system\", \"content\": \"You are a helpful translator.\"},\n",
|
||||
" {\"role\": \"user\", \"content\": prompt}\n",
|
||||
" ]\n",
|
||||
" )\n",
|
||||
" return response.choices[0].message.content.strip()\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 176,
|
||||
"id": "8ed57cc9-7d54-4a5d-831b-0efcc5b7a7a9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# --- Booking Logic ---\n",
|
||||
"def book_appointment(name, time_str):\n",
|
||||
" try:\n",
|
||||
" booking_time = datetime.strptime(time_str, \"%H:%M\")\n",
|
||||
" except ValueError:\n",
|
||||
" return \"Invalid time format. Use HH:MM.\", None, None\n",
|
||||
"\n",
|
||||
" hour = booking_time.hour\n",
|
||||
" weekday = datetime.today().strftime(\"%A\")\n",
|
||||
"\n",
|
||||
" if weekday not in WEEKDAYS:\n",
|
||||
" response = \"Bookings are only available on weekdays.\"\n",
|
||||
" elif BOOKING_START <= hour < BOOKING_END:\n",
|
||||
" confirmation = f\"Booking confirmed for {name} at {time_str}.\"\n",
|
||||
" confirmed_bookings.append((name, time_str))\n",
|
||||
" translated = translate_text(confirmation)\n",
|
||||
" audio = generate_tts(translated)\n",
|
||||
" image = generate_booking_image(name, time_str)\n",
|
||||
" return translated, audio, image\n",
|
||||
" else:\n",
|
||||
" response = \"Sorry, bookings are only accepted between 14:00 and 15:00 on weekdays.\"\n",
|
||||
" translated = translate_text(response)\n",
|
||||
" audio = generate_tts(translated)\n",
|
||||
" return translated, audio, None"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 177,
|
||||
"id": "19b52115-f0f3-4d63-a463-886163d4cfd1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# --- Booking Card ---\n",
|
||||
"def generate_booking_image(name, time_str):\n",
|
||||
" img = Image.new(\"RGB\", (500, 250), color=\"white\")\n",
|
||||
" draw = ImageDraw.Draw(img)\n",
|
||||
" msg = f\"\\u2705 Booking Confirmed\\nName: {name}\\nTime: {time_str}\"\n",
|
||||
" draw.text((50, 100), msg, fill=\"black\")\n",
|
||||
" return img"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 178,
|
||||
"id": "2c446b6c-d410-4ba1-b0c7-c475e5259ff5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# --- Voice Booking ---\n",
|
||||
"def voice_booking(audio_path, name):\n",
|
||||
" with open(audio_path, \"rb\") as f:\n",
|
||||
" response = openai.audio.transcriptions.create(model=\"whisper-1\", file=f)\n",
|
||||
" transcription = response.text.strip()\n",
|
||||
"\n",
|
||||
" system_prompt = \"\"\"\n",
|
||||
" You are a clinic assistant. Extract only the appointment time from the user's sentence in 24-hour HH:MM format.\n",
|
||||
" If no time is mentioned, respond with 'No valid time found.'\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" response = openai.chat.completions.create(\n",
|
||||
" model=\"gpt-4\",\n",
|
||||
" messages=[\n",
|
||||
" {\"role\": \"system\", \"content\": system_prompt},\n",
|
||||
" {\"role\": \"user\", \"content\": transcription}\n",
|
||||
" ]\n",
|
||||
" )\n",
|
||||
" extracted_time = response.choices[0].message.content.strip()\n",
|
||||
"\n",
|
||||
" if \":\" in extracted_time:\n",
|
||||
" return book_appointment(name, extracted_time)\n",
|
||||
" else:\n",
|
||||
" message = \"Sorry, I couldn't understand the time. Please try again.\"\n",
|
||||
" translated = translate_text(message)\n",
|
||||
" audio_path = generate_tts(translated)\n",
|
||||
" return translated, audio_path, None"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 179,
|
||||
"id": "121d2907-7fa8-4248-b2e7-83617ea66ff0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# --- Chat Bot Handler ---\n",
|
||||
"def chat_bot(messages):\n",
|
||||
" system_prompt = \"\"\"\n",
|
||||
" You are a clinic booking assistant. Your job is to:\n",
|
||||
" - Greet the patient and explain your role\n",
|
||||
" - Only assist with making appointments\n",
|
||||
" - Accept bookings only on weekdays between 14:00 and 15:00\n",
|
||||
" - Do not provide medical advice\n",
|
||||
" - Always respond with empathy and clarity\n",
|
||||
" \"\"\"\n",
|
||||
" response = openai.chat.completions.create(\n",
|
||||
" model=\"gpt-4\",\n",
|
||||
" messages=[{\"role\": \"system\", \"content\": system_prompt}] + messages\n",
|
||||
" )\n",
|
||||
" reply = response.choices[0].message.content.strip()\n",
|
||||
" audio = generate_tts(reply)\n",
|
||||
" return reply, audio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 180,
|
||||
"id": "2427b694-8c57-40cb-b202-4a8989547925",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"* Running on local URL: http://127.0.0.1:7898\n",
|
||||
"* To create a public link, set `share=True` in `launch()`.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<div><iframe src=\"http://127.0.0.1:7898/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
|
||||
],
|
||||
"text/plain": [
|
||||
"<IPython.core.display.HTML object>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Gradio interface\n",
|
||||
"with gr.Blocks(theme=gr.themes.Soft()) as demo:\n",
|
||||
" gr.Markdown(\"\"\"## 🩺 GP Booking Assistant \n",
|
||||
"Only available weekdays between **14:00 and 15:00** \n",
|
||||
"☎️ Contact: {PHONE}\n",
|
||||
"---\"\"\")\n",
|
||||
"\n",
|
||||
" name_global = gr.Textbox(label=\"Your Name\", placeholder=\"Enter your name\", interactive=True)\n",
|
||||
"\n",
|
||||
" with gr.Tab(\"💬 Chat Mode\"):\n",
|
||||
" chatbot = gr.Chatbot(label=\"Booking Chat\", type=\"messages\", height=400)\n",
|
||||
" text_input = gr.Textbox(label=\"Type your message or use your voice below\")\n",
|
||||
" audio_input = gr.Audio(type=\"filepath\", label=\"🎙️ Or speak your request\")\n",
|
||||
" chat_audio_output = gr.Audio(label=\"🔊 Assistant's Reply\", type=\"filepath\")\n",
|
||||
" send_btn = gr.Button(\"Send\")\n",
|
||||
"\n",
|
||||
" def handle_chat(user_message, chat_history):\n",
|
||||
" chat_history = chat_history or []\n",
|
||||
" chat_history.append({\"role\": \"user\", \"content\": user_message})\n",
|
||||
" reply, audio = chat_bot(chat_history)\n",
|
||||
" chat_history.append({\"role\": \"assistant\", \"content\": reply})\n",
|
||||
" return chat_history, \"\", audio\n",
|
||||
"\n",
|
||||
" def handle_audio_chat(audio_path, chat_history):\n",
|
||||
" with open(audio_path, \"rb\") as f:\n",
|
||||
" transcription = openai.audio.transcriptions.create(model=\"whisper-1\", file=f).text.strip()\n",
|
||||
" return handle_chat(transcription, chat_history)\n",
|
||||
"\n",
|
||||
" send_btn.click(handle_chat, [text_input, chatbot], [chatbot, text_input, chat_audio_output])\n",
|
||||
" text_input.submit(handle_chat, [text_input, chatbot], [chatbot, text_input, chat_audio_output])\n",
|
||||
" audio_input.change(handle_audio_chat, [audio_input, chatbot], [chatbot, text_input, chat_audio_output])\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" \n",
|
||||
" with gr.Tab(\"📝 Text Booking\"):\n",
|
||||
" time_text = gr.Textbox(label=\"Preferred Time (HH:MM)\", placeholder=\"e.g., 14:30\")\n",
|
||||
" btn_text = gr.Button(\"📅 Book via Text\")\n",
|
||||
"\n",
|
||||
" with gr.Tab(\"🎙️ Voice Booking\"):\n",
|
||||
" voice_input = gr.Audio(type=\"filepath\", label=\"Say your preferred time\")\n",
|
||||
" btn_voice = gr.Button(\"📅 Book via Voice\")\n",
|
||||
"\n",
|
||||
" output_text = gr.Textbox(label=\"Response\", interactive=False)\n",
|
||||
" output_audio = gr.Audio(label=\"Audio Reply\", type=\"filepath\")\n",
|
||||
" output_image = gr.Image(label=\"Booking Confirmation\")\n",
|
||||
"\n",
|
||||
" btn_text.click(fn=book_appointment, inputs=[name_global, time_text], outputs=[output_text, output_audio, output_image])\n",
|
||||
" btn_voice.click(fn=voice_booking, inputs=[voice_input, name_global], outputs=[output_text, output_audio, output_image])\n",
|
||||
"\n",
|
||||
" gr.Markdown(\"\"\"---\n",
|
||||
"<small>This assistant does **not** give medical advice. It only books appointments within allowed hours.</small>\n",
|
||||
"\"\"\")\n",
|
||||
"\n",
|
||||
" demo.launch()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f359de0a-28b1-4895-b21d-91d79e494a0d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.12"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
Reference in New Issue
Block a user