Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import pandas as pd | |
| from sentence_transformers import SentenceTransformer | |
| import numpy as np | |
| from sklearn.metrics.pairwise import cosine_similarity | |
| import requests | |
| import os | |
| # Page config | |
| st.set_page_config( | |
| page_title="Structural BIM Assistant", | |
| page_icon="🏗️", | |
| layout="wide" | |
| ) | |
| # Custom CSS for Arabic support and styling | |
| st.markdown(""" | |
| <style> | |
| .stApp { | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
| } | |
| .main-header { | |
| background: linear-gradient(90deg, #1e40af 0%, #3b82f6 100%); | |
| padding: 2rem; | |
| border-radius: 10px; | |
| color: white; | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| box-shadow: 0 4px 6px rgba(0,0,0,0.1); | |
| } | |
| .question-card { | |
| background: white; | |
| padding: 1.5rem; | |
| border-radius: 10px; | |
| margin: 1rem 0; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| border-right: 4px solid #3b82f6; | |
| } | |
| .answer-card { | |
| background: #f0f9ff; | |
| padding: 1.5rem; | |
| border-radius: 10px; | |
| margin: 1rem 0; | |
| border-right: 4px solid #10b981; | |
| } | |
| .category-badge { | |
| display: inline-block; | |
| background: #dbeafe; | |
| color: #1e40af; | |
| padding: 0.25rem 0.75rem; | |
| border-radius: 20px; | |
| font-size: 0.85rem; | |
| margin: 0.25rem; | |
| } | |
| .rarity-badge { | |
| display: inline-block; | |
| background: #dcfce7; | |
| color: #166534; | |
| padding: 0.25rem 0.75rem; | |
| border-radius: 20px; | |
| font-size: 0.85rem; | |
| margin: 0.25rem; | |
| } | |
| .stButton>button { | |
| background: linear-gradient(90deg, #3b82f6 0%, #2563eb 100%); | |
| color: white; | |
| border: none; | |
| padding: 0.75rem 2rem; | |
| border-radius: 8px; | |
| font-weight: bold; | |
| } | |
| .arabic-text { | |
| direction: rtl; | |
| text-align: right; | |
| font-size: 1.1rem; | |
| line-height: 1.8; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Initialize session state | |
| if 'chat_history' not in st.session_state: | |
| st.session_state.chat_history = [] | |
| # Load FAQ Data | |
| def load_faq_data(): | |
| """Load FAQ data from local CSV file""" | |
| try: | |
| # Try to read from local file first | |
| if os.path.exists('faq_data.csv'): | |
| df = pd.read_csv('faq_data.csv', skiprows=1) | |
| elif os.path.exists('src/faq_data.csv'): | |
| df = pd.read_csv('src/faq_data.csv', skiprows=1) | |
| else: | |
| st.error("❌ faq_data.csv not found. Please upload the CSV file.") | |
| return None | |
| # Set proper column names | |
| df.columns = ['No', 'Category', 'Rarety', 'Questions', 'Additional Questions Data (image)', | |
| 'Answers', 'Additional data_01 (image)', 'Additional data_02 (video links or extra links)'] | |
| # Clean and filter | |
| df.columns = df.columns.str.strip() | |
| df = df[df['Questions'].notna() & (df['Questions'] != '') & (df['Questions'].str.len() > 5)] | |
| st.success(f"✅ تم تحميل {len(df)} سؤال بنجاح!") | |
| return df | |
| except Exception as e: | |
| st.error(f"Error loading FAQ data: {e}") | |
| return None | |
| # Load embedding model | |
| def load_embedding_model(): | |
| """Load sentence transformer model for semantic search""" | |
| try: | |
| model = SentenceTransformer('sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2') | |
| return model | |
| except Exception as e: | |
| st.error(f"Error loading model: {e}") | |
| return None | |
| # Hugging Face API call | |
| def query_huggingface(prompt, api_token): | |
| """Query Hugging Face API with free model""" | |
| API_URL = "https://api-inference.huggingface.co/models/mistralai/Mistral-7B-Instruct-v0.2" | |
| headers = {"Authorization": f"Bearer {api_token}"} | |
| payload = { | |
| "inputs": prompt, | |
| "parameters": { | |
| "max_new_tokens": 500, | |
| "temperature": 0.7, | |
| "top_p": 0.9, | |
| "return_full_text": False | |
| } | |
| } | |
| try: | |
| response = requests.post(API_URL, headers=headers, json=payload) | |
| if response.status_code == 200: | |
| result = response.json() | |
| if isinstance(result, list) and len(result) > 0: | |
| return result[0].get('generated_text', '').strip() | |
| elif response.status_code == 503: | |
| return "⏳ النموذج يتم تحميله حالياً، جرب مرة أخرى بعد 20 ثانية..." | |
| else: | |
| return f"خطأ في الاتصال بالنموذج: {response.status_code}" | |
| except Exception as e: | |
| return f"خطأ: {str(e)}" | |
| return "عذراً، لم أتمكن من الحصول على إجابة" | |
| # Search FAQ database | |
| def search_faq(question, df, model, threshold=0.6): | |
| """Search FAQ database using semantic similarity""" | |
| if df is None or model is None: | |
| return None, 0 | |
| try: | |
| question_embedding = model.encode([question]) | |
| faq_questions = df['Questions'].tolist() | |
| faq_embeddings = model.encode(faq_questions) | |
| similarities = cosine_similarity(question_embedding, faq_embeddings)[0] | |
| max_idx = np.argmax(similarities) | |
| max_similarity = similarities[max_idx] | |
| if max_similarity >= threshold: | |
| return df.iloc[max_idx], max_similarity | |
| return None, max_similarity | |
| except Exception as e: | |
| st.error(f"Error in search: {e}") | |
| return None, 0 | |
| # Main app | |
| def main(): | |
| # Header | |
| st.markdown(""" | |
| <div class="main-header"> | |
| <h1>🏗️ Structural BIM Assistant</h1> | |
| <p>مساعدك الذكي لأسئلة Revit والهندسة الإنشائية</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Sidebar | |
| with st.sidebar: | |
| st.header("⚙️ الإعدادات") | |
| # Try to get token from environment | |
| default_token = os.getenv("HUGGINGFACE_TOKEN", "") | |
| # API Token input | |
| api_token = st.text_input( | |
| "🔑 Hugging Face Token", | |
| type="password", | |
| value=default_token, | |
| help="سيتم استخدام التوكن من الإعدادات تلقائياً" | |
| ) | |
| if not api_token: | |
| st.warning("⚠️ يرجى إضافة Hugging Face Token في Settings → Repository secrets") | |
| # Search threshold | |
| threshold = st.slider( | |
| "🎯 دقة البحث", | |
| min_value=0.3, | |
| max_value=0.9, | |
| value=0.6, | |
| step=0.1, | |
| help="كلما زادت القيمة، كلما كانت النتائج أدق" | |
| ) | |
| st.divider() | |
| # Statistics | |
| df = load_faq_data() | |
| if df is not None: | |
| st.metric("📚 عدد الأسئلة في قاعدة البيانات", len(df)) | |
| st.subheader("📊 التصنيفات") | |
| category_counts = df['Category'].value_counts() | |
| for cat, count in category_counts.items(): | |
| if pd.notna(cat): | |
| st.write(f"• {cat}: {count}") | |
| st.divider() | |
| if st.button("🗑️ مسح المحادثة"): | |
| st.session_state.chat_history = [] | |
| st.rerun() | |
| # Load models | |
| df = load_faq_data() | |
| model = load_embedding_model() | |
| if df is None: | |
| st.error("⚠️ فشل تحميل قاعدة البيانات") | |
| return | |
| if model is None: | |
| st.error("⚠️ فشل تحميل نموذج البحث") | |
| return | |
| # Example questions | |
| with st.expander("💡 أسئلة شائعة - اضغط لاختيار"): | |
| col1, col2 = st.columns(2) | |
| examples = df.head(6)['Questions'].tolist() | |
| for idx, example in enumerate(examples): | |
| col = col1 if idx % 2 == 0 else col2 | |
| with col: | |
| if st.button(example[:80] + "...", key=f"ex_{idx}"): | |
| st.session_state.user_question = example | |
| # Chat interface | |
| st.markdown("### 💬 اسأل سؤالك") | |
| user_question = st.text_area( | |
| "اكتب سؤالك هنا:", | |
| height=100, | |
| key="question_input", | |
| placeholder="مثال: كيف أعمل Level جديد في Revit؟" | |
| ) | |
| col1, col2 = st.columns([1, 5]) | |
| with col1: | |
| ask_button = st.button("🚀 إرسال", use_container_width=True) | |
| # Process question | |
| if ask_button and user_question.strip(): | |
| with st.spinner("🔍 جاري البحث عن الإجابة..."): | |
| result, similarity = search_faq(user_question, df, model, threshold) | |
| st.markdown(f""" | |
| <div class="question-card"> | |
| <strong>❓ سؤالك:</strong> | |
| <p class="arabic-text">{user_question}</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| if result is not None: | |
| st.success(f"✅ وجدت إجابة في قاعدة البيانات (دقة: {similarity*100:.1f}%)") | |
| st.markdown(f""" | |
| <div class="answer-card"> | |
| <div> | |
| <span class="category-badge">📁 {result['Category']}</span> | |
| <span class="rarity-badge">⭐ {result['Rarety']}</span> | |
| </div> | |
| <br> | |
| <strong>✅ الإجابة:</strong> | |
| <p class="arabic-text">{result['Answers']}</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| if pd.notna(result.get('Additional data_02 (video links or extra links)')): | |
| links = result['Additional data_02 (video links or extra links)'] | |
| st.info(f"🔗 روابط إضافية: {links}") | |
| with st.expander("📝 السؤال الأصلي في قاعدة البيانات"): | |
| st.write(result['Questions']) | |
| else: | |
| st.warning(f"⚠️ لم أجد إجابة مطابقة في قاعدة البيانات (أقرب تطابق: {similarity*100:.1f}%)") | |
| if api_token: | |
| st.info("🤖 سأحاول الإجابة باستخدام الذكاء الاصطناعي...") | |
| prompt = f"""أنت مساعد متخصص في Structural BIM و Revit. | |
| أجب على السؤال التالي بشكل مختصر ومفيد باللغة العربية: | |
| السؤال: {user_question} | |
| الإجابة:""" | |
| ai_response = query_huggingface(prompt, api_token) | |
| st.markdown(f""" | |
| <div class="answer-card"> | |
| <strong>🤖 إجابة الذكاء الاصطناعي:</strong> | |
| <p class="arabic-text">{ai_response}</p> | |
| <br> | |
| <em style="color: #6b7280; font-size: 0.9rem;"> | |
| ⚠️ هذه الإجابة من الذكاء الاصطناعي وليست من قاعدة البيانات. | |
| يُرجى التحقق من دقتها. | |
| </em> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| else: | |
| st.error("⚠️ يرجى إضافة Hugging Face Token") | |
| ai_response = "لا يوجد إجابة متاحة" | |
| st.session_state.chat_history.append({ | |
| 'question': user_question, | |
| 'answer': result['Answers'] if result is not None else ai_response, | |
| 'source': 'database' if result is not None else 'ai', | |
| 'similarity': similarity | |
| }) | |
| # Chat history | |
| if st.session_state.chat_history: | |
| st.markdown("---") | |
| st.markdown("### 📜 سجل المحادثات") | |
| for idx, chat in enumerate(reversed(st.session_state.chat_history[-5:])): | |
| with st.expander(f"💬 {chat['question'][:60]}..."): | |
| st.markdown(f"**السؤال:** {chat['question']}") | |
| st.markdown(f"**الإجابة:** {chat['answer']}") | |
| source_emoji = "📚" if chat['source'] == 'database' else "🤖" | |
| st.caption(f"{source_emoji} المصدر: {'قاعدة البيانات' if chat['source'] == 'database' else 'الذكاء الاصطناعي'}") | |
| if __name__ == "__main__": | |
| main() |