Spaces:
Sleeping
Sleeping
| # --- Imports --- | |
| import streamlit as st | |
| import PyPDF2 | |
| import requests | |
| import json | |
| from sentence_transformers import SentenceTransformer | |
| import numpy as np | |
| from sklearn.metrics.pairwise import cosine_similarity | |
| from datetime import datetime | |
| from groq import Groq | |
| # --- Page config --- | |
| st.set_page_config( | |
| page_title="π¨ First Aid Emergency Assistant", | |
| page_icon="π¨", | |
| layout="wide", | |
| initial_sidebar_state="expanded" | |
| ) | |
| # --- Enhanced Professional UI CSS --- | |
| st.markdown(""" | |
| <style> | |
| /* Main App Background */ | |
| .stApp { | |
| background: linear-gradient(135deg, #0F1419 0%, #1a1d29 50%, #2d3748 100%); | |
| color: #ffffff; | |
| } | |
| /* Header Styling - Made more compact */ | |
| .main-header { | |
| text-align: center; | |
| padding: 1.5rem 0; | |
| background: linear-gradient(135deg, rgba(79, 172, 254, 0.1) 0%, rgba(0, 242, 254, 0.1) 100%); | |
| border-radius: 15px; | |
| margin-bottom: 1rem; | |
| backdrop-filter: blur(20px); | |
| border: 1px solid rgba(79, 172, 254, 0.2); | |
| box-shadow: 0 8px 40px rgba(79, 172, 254, 0.1); | |
| } | |
| .main-header h1 { | |
| font-size: 2rem; | |
| font-weight: 700; | |
| margin: 0; | |
| background: linear-gradient(135deg, #4FACFE 0%, #00F2FE 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| } | |
| .main-header p { | |
| margin: 0.3rem 0 0 0; | |
| font-size: 1rem; | |
| opacity: 0.8; | |
| color: #a0aec0; | |
| } | |
| /* Fixed Height Chat Container */ | |
| .chat-container { | |
| background: rgba(26, 32, 46, 0.8); | |
| border-radius: 20px; | |
| padding: 1.5rem; | |
| margin: 1rem 0; | |
| height: 450px; | |
| overflow-y: auto; | |
| box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); | |
| border: 1px solid rgba(79, 172, 254, 0.1); | |
| backdrop-filter: blur(20px); | |
| } | |
| /* Enhanced Scrollbar Styling */ | |
| .chat-container::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| .chat-container::-webkit-scrollbar-track { | |
| background: rgba(160, 174, 192, 0.1); | |
| border-radius: 10px; | |
| } | |
| .chat-container::-webkit-scrollbar-thumb { | |
| background: linear-gradient(135deg, #4FACFE 0%, #00F2FE 100%); | |
| border-radius: 10px; | |
| } | |
| .chat-container::-webkit-scrollbar-thumb:hover { | |
| background: linear-gradient(135deg, #00F2FE 0%, #4FACFE 100%); | |
| } | |
| /* User Message */ | |
| .user-message { | |
| background: linear-gradient(135deg, #4FACFE 0%, #00F2FE 100%); | |
| color: white; | |
| padding: 12px 16px; | |
| border-radius: 18px 18px 6px 18px; | |
| margin: 12px 0; | |
| max-width: 70%; | |
| margin-left: auto; | |
| box-shadow: 0 4px 15px rgba(79, 172, 254, 0.3); | |
| font-size: 0.95rem; | |
| line-height: 1.4; | |
| word-wrap: break-word; | |
| display: block; | |
| } | |
| /* Bot Message */ | |
| .bot-message { | |
| background: rgba(45, 55, 72, 0.95); | |
| color: #f7fafc; | |
| padding: 14px 18px; | |
| border-radius: 18px 18px 18px 6px; | |
| margin: 12px 0; | |
| max-width: 85%; | |
| margin-right: auto; | |
| border: 1px solid rgba(79, 172, 254, 0.2); | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); | |
| font-size: 0.95rem; | |
| line-height: 1.5; | |
| word-wrap: break-word; | |
| display: block; | |
| } | |
| /* Enhanced readability for bot messages */ | |
| .bot-message strong { | |
| color: #63b3ed !important; | |
| font-weight: 600; | |
| } | |
| .bot-message h1, .bot-message h2, .bot-message h3 { | |
| color: #4FACFE !important; | |
| margin: 0.8rem 0 0.4rem 0 !important; | |
| font-weight: 600; | |
| } | |
| .bot-message ul, .bot-message ol { | |
| margin: 0.5rem 0; | |
| padding-left: 1.2rem; | |
| } | |
| .bot-message li { | |
| margin: 0.3rem 0; | |
| color: #e2e8f0; | |
| } | |
| /* Input Container */ | |
| .input-container { | |
| background: rgba(26, 32, 46, 0.95); | |
| padding: 1.5rem 2rem; | |
| border-radius: 20px; | |
| margin: 1.5rem 0; | |
| box-shadow: 0 -8px 40px rgba(0, 0, 0, 0.3); | |
| border: 1px solid rgba(79, 172, 254, 0.2); | |
| backdrop-filter: blur(20px); | |
| } | |
| /* Text Input Styling */ | |
| .stTextInput > div > div > input { | |
| background: rgba(45, 55, 72, 0.9) !important; | |
| border: 2px solid rgba(79, 172, 254, 0.3) !important; | |
| border-radius: 25px !important; | |
| padding: 14px 20px !important; | |
| font-size: 16px !important; | |
| color: #f7fafc !important; | |
| transition: all 0.3s ease !important; | |
| height: 50px !important; | |
| } | |
| .stTextInput > div > div > input:focus { | |
| border-color: #4FACFE !important; | |
| box-shadow: 0 0 0 0.3rem rgba(79, 172, 254, 0.3) !important; | |
| background: rgba(45, 55, 72, 1) !important; | |
| } | |
| .stTextInput > div > div > input::placeholder { | |
| color: #cbd5e0 !important; | |
| opacity: 0.8 !important; | |
| } | |
| /* Button Styling */ | |
| .stButton > button, .stForm > div > div > button { | |
| background: linear-gradient(135deg, #4FACFE 0%, #00F2FE 100%) !important; | |
| color: white !important; | |
| border: none !important; | |
| border-radius: 25px !important; | |
| padding: 14px 28px !important; | |
| font-weight: 600 !important; | |
| font-size: 16px !important; | |
| transition: all 0.3s ease !important; | |
| box-shadow: 0 4px 20px rgba(79, 172, 254, 0.3) !important; | |
| width: 100% !important; | |
| height: 50px !important; | |
| } | |
| .stButton > button:hover, .stForm > div > div > button:hover { | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 8px 30px rgba(79, 172, 254, 0.5) !important; | |
| background: linear-gradient(135deg, #00F2FE 0%, #4FACFE 100%) !important; | |
| } | |
| /* Clear Chat Button Styling */ | |
| .clear-button { | |
| background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%) !important; | |
| margin-top: 0.5rem !important; | |
| } | |
| .clear-button:hover { | |
| background: linear-gradient(135deg, #ee5a24 0%, #ff6b6b 100%) !important; | |
| } | |
| /* Sidebar Styling */ | |
| .sidebar-content { | |
| background: rgba(45, 55, 72, 0.8); | |
| padding: 1rem; | |
| border-radius: 15px; | |
| margin: 0.5rem 0; | |
| border: 1px solid rgba(79, 172, 254, 0.2); | |
| color: #e2e8f0; | |
| } | |
| /* Feedback Section */ | |
| .feedback-container { | |
| background: rgba(34, 197, 94, 0.1); | |
| border: 1px solid rgba(34, 197, 94, 0.3); | |
| border-radius: 15px; | |
| padding: 1rem; | |
| margin: 1rem 0; | |
| } | |
| .feedback-container h4 { | |
| color: #22c55e !important; | |
| margin: 0 0 0.5rem 0; | |
| } | |
| /* Hide Streamlit default elements */ | |
| #MainMenu {visibility: hidden;} | |
| footer {visibility: hidden;} | |
| header {visibility: hidden;} | |
| /* Mobile responsiveness */ | |
| @media (max-width: 768px) { | |
| .main-header h1 { | |
| font-size: 1.5rem; | |
| } | |
| .user-message, .bot-message { | |
| max-width: 90%; | |
| font-size: 0.9rem; | |
| } | |
| .input-container { | |
| padding: 1rem; | |
| } | |
| .chat-container { | |
| height: 350px; | |
| padding: 1rem; | |
| } | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # --- GROQ API setup --- | |
| def setup_groq(): | |
| try: | |
| return st.secrets["GROQ_API_KEY"] | |
| except: | |
| st.error("β Missing GROQ_API_KEY in `.streamlit/secrets.toml`") | |
| st.stop() | |
| client = Groq(api_key=setup_groq()) | |
| # --- PDF Processing --- | |
| def load_pdf(): | |
| try: | |
| with open("First-Aid.pdf", "rb") as f: | |
| reader = PyPDF2.PdfReader(f) | |
| text = "".join(page.extract_text() + "\n" for page in reader.pages) | |
| return text | |
| except: | |
| st.error("β Failed to load 'First-Aid.pdf'. Upload it to the app root.") | |
| st.stop() | |
| # --- Knowledge Base Setup --- | |
| def setup_knowledge_base(): | |
| text = load_pdf() | |
| chunks, current_chunk = [], "" | |
| for sentence in text.split('\n'): | |
| if len(current_chunk + sentence) < 1000: | |
| current_chunk += sentence + "\n" | |
| else: | |
| if current_chunk.strip(): chunks.append(current_chunk.strip()) | |
| current_chunk = sentence + "\n" | |
| if current_chunk.strip(): chunks.append(current_chunk.strip()) | |
| model = SentenceTransformer('all-MiniLM-L6-v2') | |
| embeddings = model.encode(chunks) | |
| return chunks, embeddings, model | |
| # --- Context Matching --- | |
| def find_relevant_context(query, chunks, embeddings, model, top_k=3): | |
| query_embedding = model.encode([query]) | |
| similarities = cosine_similarity(query_embedding, embeddings)[0] | |
| top_indices = np.argsort(similarities)[-top_k:][::-1] | |
| return "\n\n".join([chunks[i] for i in top_indices]) | |
| # --- Query GROQ --- | |
| def query_groq(prompt, api_key): | |
| url = "https://api.groq.com/openai/v1/chat/completions" | |
| headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} | |
| data = { | |
| "model": "llama3-70b-8192", | |
| "messages": [ | |
| { | |
| "role": "system", | |
| "content": """You are a First Aid Emergency Assistant. Answer only first aid-related queries. Be clear, concise, and step-by-step. For serious emergencies, advise calling 911. If asked off-topic, respond with: "π¨ I'm specialized in First Aid emergencies only!".""" | |
| }, | |
| {"role": "user", "content": prompt} | |
| ], | |
| "temperature": 0.3, | |
| "max_tokens": 1000 | |
| } | |
| try: | |
| r = requests.post(url, headers=headers, json=data, timeout=30) | |
| r.raise_for_status() | |
| return r.json()["choices"][0]["message"]["content"] | |
| except Exception as e: | |
| return f"β GROQ API Error: {str(e)}" | |
| # --- Session State --- | |
| if "messages" not in st.session_state: | |
| st.session_state.messages = [{ | |
| "role": "assistant", | |
| "content": "π¨ **Hello! I'm your First Aid Emergency Assistant.**\n\nAsk me about CPR, bleeding, choking, burns, or any first aid emergency. I'm here to help you with step-by-step guidance!" | |
| }] | |
| if "knowledge_base" not in st.session_state: | |
| with st.spinner("π Loading knowledge base..."): | |
| chunks, embeddings, model = setup_knowledge_base() | |
| st.session_state.knowledge_base = { | |
| "chunks": chunks, "embeddings": embeddings, "model": model | |
| } | |
| if "feedback_shown" not in st.session_state: | |
| st.session_state.feedback_shown = False | |
| if "response_count" not in st.session_state: | |
| st.session_state.response_count = 0 | |
| # --- Main App Layout --- | |
| # Compact Header | |
| st.markdown(''' | |
| <div class="main-header"> | |
| <h1>π¨ First Aid Emergency Assistant</h1> | |
| <p>Your AI-powered emergency response guide</p> | |
| </div> | |
| ''', unsafe_allow_html=True) | |
| # Main content area | |
| col1, col2 = st.columns([3, 1]) | |
| with col1: | |
| # Create a container with fixed height for chat messages | |
| chat_container = st.container() | |
| with chat_container: | |
| # Create the scrollable chat area using st.container with height | |
| st.markdown('<div class="chat-container">', unsafe_allow_html=True) | |
| # Display chat messages | |
| for i, msg in enumerate(st.session_state.messages): | |
| if msg["role"] == "user": | |
| st.markdown(f'<div class="user-message">{msg["content"]}</div>', unsafe_allow_html=True) | |
| else: | |
| st.markdown(f'<div class="bot-message"><strong>π€ First Aid Assistant</strong><br>{msg["content"]}</div>', unsafe_allow_html=True) | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # Show feedback request after first response | |
| if st.session_state.response_count >= 1 and not st.session_state.feedback_shown: | |
| st.markdown(''' | |
| <div class="feedback-container"> | |
| <h4>π¬ How was this assistance?</h4> | |
| <p style="margin: 0; color: #a0aec0;">Your feedback helps us improve emergency response guidance.</p> | |
| </div> | |
| ''', unsafe_allow_html=True) | |
| feedback_col1, feedback_col2, feedback_col3 = st.columns(3) | |
| with feedback_col1: | |
| if st.button("π Helpful"): | |
| st.success("Thank you for your feedback!") | |
| st.session_state.feedback_shown = True | |
| st.rerun() | |
| with feedback_col2: | |
| if st.button("π Not helpful"): | |
| st.info("Thanks for letting us know. We'll work to improve!") | |
| st.session_state.feedback_shown = True | |
| st.rerun() | |
| with feedback_col3: | |
| if st.button("βοΈ Skip"): | |
| st.session_state.feedback_shown = True | |
| st.rerun() | |
| # Enhanced Input Container with better spacing | |
| st.markdown('<div class="input-container">', unsafe_allow_html=True) | |
| # Form for Enter key support and auto-clear | |
| with st.form(key="chat_form", clear_on_submit=True): | |
| input_col1, input_col2 = st.columns([4, 1]) | |
| with input_col1: | |
| user_input = st.text_input( | |
| "", | |
| placeholder="What do to in case of heart pain?", | |
| key="user_input_form", | |
| label_visibility="collapsed" | |
| ) | |
| with input_col2: | |
| send = st.form_submit_button("Send π") | |
| # Clear Chat Button | |
| if st.button("ποΈ Clear Chat", key="clear_chat", help="Clear all messages and start fresh"): | |
| st.session_state.messages = [{ | |
| "role": "assistant", | |
| "content": "π¨ **Hello! I'm your First Aid Emergency Assistant.**\n\nAsk me about CPR, bleeding, choking, burns, or any first aid emergency. I'm here to help you with step-by-step guidance!" | |
| }] | |
| st.session_state.feedback_shown = False | |
| st.session_state.response_count = 0 | |
| st.rerun() | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # Sidebar with medical disclaimer and info | |
| with col2: | |
| st.markdown("### π©Ί Medical Disclaimer") | |
| with st.expander("β οΈ Important - Click to read", expanded=False): | |
| st.markdown(""" | |
| **MEDICAL DISCLAIMER:** | |
| This tool provides general first aid guidance only. | |
| π¨ **In real emergencies:** | |
| - Call emergency services immediately | |
| - This is NOT a substitute for professional medical care | |
| - Always seek professional help for serious injuries | |
| Use this tool for educational purposes and basic guidance only. | |
| """) | |
| st.markdown("### π― What I Can Help With") | |
| st.markdown(''' | |
| <div class="sidebar-content"> | |
| β’ <strong>CPR</strong> and rescue breathing<br> | |
| β’ <strong>Wounds</strong> and bleeding control<br> | |
| β’ <strong>Burns</strong> and scalds<br> | |
| β’ <strong>Fractures</strong> and sprains<br> | |
| β’ <strong>Choking</strong> procedures<br> | |
| β’ <strong>Poisoning</strong> emergencies<br> | |
| β’ <strong>Heart attack</strong> signs<br> | |
| β’ <strong>Snake bites</strong> and stings<br> | |
| β’ And much more! | |
| </div> | |
| ''', unsafe_allow_html=True) | |
| # Process Input (works with both Enter key and button click) | |
| if send and user_input and user_input.strip(): | |
| # Add user message | |
| st.session_state.messages.append({"role": "user", "content": user_input}) | |
| # Get AI response | |
| try: | |
| kb = st.session_state.knowledge_base | |
| context = find_relevant_context(user_input, kb["chunks"], kb["embeddings"], kb["model"]) | |
| full_prompt = f""" | |
| Based on this first aid manual content, answer the question: {user_input} | |
| Context: | |
| {context} | |
| Please provide clear, step-by-step guidance. Use bullet points or numbered lists when appropriate. | |
| """ | |
| response = query_groq(full_prompt, setup_groq()) | |
| # Add emergency warning for serious cases | |
| if any(x in user_input.lower() for x in ["heart attack", "stroke", "not breathing", "unconscious", "severe bleeding", "choking"]): | |
| response = f"π¨ **CALL EMERGENCY SERVICES IMMEDIATELY!**\n\n{response}" | |
| # Add bot response | |
| st.session_state.messages.append({"role": "assistant", "content": response}) | |
| st.session_state.response_count += 1 | |
| except Exception as e: | |
| st.session_state.messages.append({ | |
| "role": "assistant", | |
| "content": f"β Sorry, I encountered an error: {str(e)}. Please try again or rephrase your question." | |
| }) | |
| # Rerun to show updated chat | |
| st.rerun() | |
| # Footer | |
| st.markdown("---") | |
| st.markdown(''' | |
| <div style="text-align: center; opacity: 0.6; padding: 0.5rem; font-size: 0.9rem;"> | |
| π€ First Aid Emergency Assistant | Always consult medical professionals for serious emergencies | |
| </div> | |
| ''', unsafe_allow_html=True) |