File size: 16,937 Bytes
965ac15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
from datetime import datetime
from core.pineconeqa import PineconeQA
import gradio as gr
from config import get_settings
from openai import OpenAI
from utils.models import DatabaseManager
import json
import hashlib
import tempfile
import os 

class MedicalChatbot:
    def __init__(self):
        self.settings = get_settings()
        self.qa_system = PineconeQA(
            pinecone_api_key=self.settings.PINECONE_API_KEY,
            openai_api_key=self.settings.OPENAI_API_KEY,
            index_name=self.settings.INDEX_NAME
        )
        self.client = OpenAI(api_key=self.settings.OPENAI_API_KEY)
        self.db = DatabaseManager()
        self.current_doctor = None
        self.current_session_id = None
    
    def handle_session(self, doctor_name):
        """Create a new session if doctor name changes or no session exists"""
        # Always create a new session
        self.current_session_id = self.db.create_session(doctor_name)
        self.current_doctor = doctor_name
        return self.current_session_id

   
    def get_user_identifier(self, request: gr.Request):
        """Create a unique user identifier from IP and user agent"""
        if request is None:
            return "anonymous"
        
        identifier = f"{request.client.host}_{request.headers.get('User-Agent', 'unknown')}"
        return hashlib.sha256(identifier.encode()).hexdigest()[:32]
        
    def detect_message_type(self, message):
        """Use ChatGPT to detect if the message is a basic interaction or a knowledge query"""
        try:
            response = self.client.chat.completions.create(
                model="gpt-4",
                messages=[
                    {
                        "role": "system",
                        "content": """Analyze the following message and determine if it's:
                        1. A basic interaction like hello, thanks, how are you(greetings, thanks, farewell, etc.)
                        2. A question or request for information
                        return only 'basic' if the message is only for greeting, or return query 
                        Respond with just the type: 'basic' or 'query'"""
                    },
                    {"role": "user", "content": message}
                ],
                temperature=0.3,
                max_tokens=10
            )
            return response.choices[0].message.content.strip().lower()
        except Exception as e:
            print(f'error encountered. returning query.\nError: {str(e)}')
            return "query"

    def get_chatgpt_response(self, message, history):
        """Get a response from ChatGPT"""
        try:
            chat_history = []
            for human, assistant in history:
                chat_history.extend([
                    {"role": "user", "content": human},
                    {"role": "assistant", "content": assistant}
                ])

            messages = [
                {
                    "role": "system",
                    "content": """ "You are an expert assistant for biomedical question-answering tasks. "
                    "You will be provided with context retrieved from medical literature."
                    "The medical literature is all from PubMed Open Access Articles. "
                    "Use this context to answer the question as accurately as possible. "
                    "The response might not be added precisely, so try to derive the answers from it as much as possible."
                    "If the context does not contain the required information, explain why. "
                    "Provide a concise and accurate answer """
                }
            ] + chat_history + [
                {"role": "user", "content": message}
            ]
            
            response = self.client.chat.completions.create(
                model="gpt-4",
                messages=messages,
                temperature=0.7,
                max_tokens=500
            )
            return response.choices[0].message.content
        except Exception as e:
            return f"I apologize, but I encountered an error: {str(e)}"

    def synthesize_answer(self, query, context_docs, history):
        """Synthesize an answer from multiple context documents using ChatGPT"""
        try:
            context = "\n\n".join([doc.page_content for doc in context_docs])
            
            messages = [
                {
                    "role": "system",
                    "content": """You are a medical expert assistant. Using the provided context, 
                    synthesize a comprehensive, accurate answer. If the context doesn't contain 
                    enough relevant information, say so and provide general medical knowledge. 
                    Always maintain a professional yet accessible tone."""
                },
                {
                    "role": "user",
                    "content": f"""Context information:\n{context}\n\n
                    Based on this context and your medical knowledge, please answer the following question:\n{query}"""
                }
            ]
            
            response = self.client.chat.completions.create(
                model="gpt-4",
                messages=messages,
                temperature=0.2,
                max_tokens=1000
            )
            
            return response.choices[0].message.content
        except Exception as e:
            return f"I apologize, but I encountered an error synthesizing the answer: {str(e)}"

    def format_sources_for_db(self, sources):
        """Format sources for database storage"""
        if not sources:
            return None
            
        sources_data = []
        for doc in sources:
            sources_data.append({
                'title': doc.metadata.get('title'),
                'source': doc.metadata.get('source'),
                'timestamp': datetime.utcnow().isoformat()
            })
        return json.dumps(sources_data)

    def respond(self, message, history, doctor_name: str, request: gr.Request = None):
        """Main response function for the chatbot"""
        try:
           
            
            # Don't reuse sessions - ensure we're using the current session ID
            if not hasattr(self, 'current_session_id') or not self.current_session_id:
                self.current_session_id = self.db.create_session(doctor_name)
            
            
            # Log user message
            self.db.log_message(
                session_id=self.current_session_id,
                message=message,
                is_user=True
            )
            
            # Rest of your existing respond method remains the same...
            message_type = self.detect_message_type(message)
            
            if message_type == "basic":
                response = self.get_chatgpt_response(message, history)
                self.db.log_message(
                    session_id=self.current_session_id,
                    message=response,
                    is_user=False
                )
                return response
            
            retriever_response = self.qa_system.ask(message)
            
            if "error" in retriever_response:
                response = self.get_chatgpt_response(message, history)
                self.db.log_message(
                    session_id=self.current_session_id,
                    message=response,
                    is_user=False
                )
                return response
            
            if retriever_response.get("context") and len(retriever_response["context"]) > 0:
                synthesized_answer = self.synthesize_answer(
                    message,
                    retriever_response["context"],
                    history
                )
                
                sources = self.format_sources(retriever_response["context"])
                final_response = synthesized_answer + sources
                
                self.db.log_message(
                    session_id=self.current_session_id,
                    message=final_response,
                    is_user=False,
                    sources=self.format_sources_for_db(retriever_response["context"])
                )
                return final_response
            else:
                response = self.get_chatgpt_response(message, history)
                fallback_response = "I couldn't find specific information about this in my knowledge base, but here's what I can tell you:\n\n" + response
                
                self.db.log_message(
                    session_id=self.current_session_id,
                    message=fallback_response,
                    is_user=False
                )
                return fallback_response

        except Exception as e:
            error_message = f"I apologize, but I encountered an error: {str(e)}"
            if self.current_session_id:
                self.db.log_message(
                    session_id=self.current_session_id,
                    message=error_message,
                    is_user=False
                )
            return error_message
        
    def format_sources(self, sources):
        """Format sources into a readable string"""
        if not sources:
            return ""
        
        formatted = "\n\n📚 Sources Used:\n"
        seen_sources = set()
        
        for doc in sources:
            source_id = (doc.metadata.get('title', ''), doc.metadata.get('source', ''))
            if source_id not in seen_sources:
                seen_sources.add(source_id)
                formatted += f"\n• {doc.metadata.get('title', 'Untitled')}\n"
                if doc.metadata.get('source'):
                    formatted += f"  Link: {doc.metadata['source']}\n"
        
        return formatted
    def transcribe_audio(self, audio_path):
        """Transcribe audio using OpenAI Whisper"""
        try:
            with open(audio_path, "rb") as audio_file:
                transcript = self.client.audio.transcriptions.create(
                    model="whisper-1",
                    file=audio_file
                )
            return transcript.text
        except Exception as e:
            print(f"Error transcribing audio: {str(e)}")
            return None
    def process_audio_input(self, audio_path, history, doctor_name):
        """Process audio input and return both text and audio response"""
        try:
            # Transcribe the audio
            transcription = self.transcribe_audio(audio_path)
            if not transcription:
                return "Sorry, I couldn't understand the audio.", None
            
            # Get text response
            text_response = self.respond(transcription, history, doctor_name)
            
            # Convert response to speech
            # audio_response = self.text_to_speech(text_response)
            
            return text_response
        except Exception as e:
            return f"Error processing audio: {str(e)}"
def main():
    med_chatbot = MedicalChatbot()
    
    with gr.Blocks(theme=gr.themes.Soft()) as interface:
        gr.Markdown("# Medical Knowledge Assistant")
        gr.Markdown("Ask me anything about medical topics using text or voice.")
        
        session_state = gr.State()
        doctor_state = gr.State()
        
        # Doctor Name Input
        with gr.Row():
            doctor_name = gr.Textbox(
                label="Doctor Name",
                placeholder="Enter your name",
                show_label=True,
                container=True,
                scale=2,
                interactive=True
            )
        
        # Main Chat Interface
        with gr.Row():
            with gr.Column(scale=4):
                chatbot = gr.Chatbot(height=400)
                
                # Text Input Area
                with gr.Row():
                    text_input = gr.Textbox(
                        placeholder="Type your message here...",
                        scale=8
                    )
                    send_button = gr.Button("Send", scale=1)
                
                # Audio Input Area
                with gr.Row():
                    audio = gr.Audio(
                        sources=["microphone"],
                        type="filepath",
                        label="Voice Message",
                        interactive=True
                    )

        # Audio Output Area
        audio_output = gr.Audio(
            label="AI Voice Response",
            visible=True,
            interactive=False
        )

        # Initialize session handler
        def init_session(doctor, current_doctor):
            if not doctor or doctor == current_doctor:
                return None, current_doctor
            return med_chatbot.db.create_session(doctor), doctor

        # Text input handler
        def on_text_submit(message, history, doctor, session_id, current_doctor):
            if not session_id or doctor != current_doctor:
                session_id, current_doctor = init_session(doctor, current_doctor)
            
            med_chatbot.current_session_id = session_id
            response = med_chatbot.respond(message, history, doctor)
            history.append((message, response))
            return "", history, None, session_id, current_doctor

        # Audio input handler with numpy array
        def on_audio_submit(audio_path, history, doctor, session_id, current_doctor):
            try:
                if audio_path is None:
                    return history, None, session_id, current_doctor

                # Initialize session if needed
                if not session_id or doctor != current_doctor:
                    session_id, current_doctor = init_session(doctor, current_doctor)

                # Set current session
                med_chatbot.current_session_id = session_id

                # Transcribe the audio
                transcription = med_chatbot.transcribe_audio(audio_path)
                if not transcription:
                    return history, None, session_id, current_doctor

                # Log the transcription as a user message in the database
                med_chatbot.db.log_message(
                    session_id=session_id,
                    message=transcription,
                    is_user=True
                )

                # Append transcription to the chatbot history
                history.append((f"🎤 {transcription}", None))  # User message, no AI response yet

                # Process the transcription as a user query
                ai_response = med_chatbot.respond(transcription, history, doctor)

                # Append AI response to the chatbot history
                history[-1] = (f"🎤 {transcription}", ai_response)  # Update with AI response

                # Log the AI response in the database
                med_chatbot.db.log_message(
                    session_id=session_id,
                    message=ai_response,
                    is_user=False
                )

                return history, session_id, current_doctor

            except Exception as e:
                print(f"Error processing audio: {str(e)}")
                return history, None, session_id, current_doctor

                # Set up event handlers
        doctor_name.submit(
            fn=init_session,
            inputs=[doctor_name, doctor_state],
            outputs=[session_state, doctor_state]
        )
        
        send_button.click(
            fn=on_text_submit,
            inputs=[text_input, chatbot, doctor_name, session_state, doctor_state],
            outputs=[text_input, chatbot, audio_output, session_state, doctor_state]
        )
        
        text_input.submit(
            fn=on_text_submit,
            inputs=[text_input, chatbot, doctor_name, session_state, doctor_state],
            outputs=[text_input, chatbot, audio_output, session_state, doctor_state]
        )

        # Audio submission
        audio.stop_recording(
            fn=on_audio_submit,
            inputs=[audio, chatbot, doctor_name, session_state, doctor_state],
            outputs=[chatbot, session_state, doctor_state]
        )
        
        # Examples
        gr.Examples(
            examples=[
                ["Hello, how are you?", "Dr. Smith"],
                ["What are the common causes of iron deficiency anemia?", "Dr. Smith"],
                ["What are the latest treatments for type 2 diabetes?", "Dr. Smith"],
                ["Can you explain the relationship between diet and heart disease?", "Dr. Smith"]
            ],
            inputs=[text_input, doctor_name]
        )
    
    interface.launch(
        share=True,
        server_name="0.0.0.0",
        server_port=7860
    )

if __name__ == "__main__":
    main()