{ "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": [ "
" ], "text/plain": [ "" ] }, "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", "This assistant does **not** give medical advice. It only books appointments within allowed hours.\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 }