Download app.py from Vanshcc/IC: direct link, hf CLI and curl.
- Browser
- Download file 20.8 kB
-
https://huggingface.co/spaces/Vanshcc/IC/resolve/main/app.py
- Command line
-
hf download hf://spaces/Vanshcc/IC/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Vanshcc/IC/resolve/main/app.py
20.8 kB
| import streamlit as st | |
| import json | |
| import torch | |
| import numpy as np | |
| from sentence_transformers import SentenceTransformer | |
| from flashrank import Ranker, RerankRequest | |
| import lancedb | |
| import os | |
| import requests | |
| from groq import Groq | |
| from dotenv import load_dotenv | |
| # Load environment variables | |
| load_dotenv() | |
| # =============================== | |
| # PAGE CONFIG & CONSTITUTION THEME | |
| # =============================== | |
| st.set_page_config(page_title="Constitution of India - Knowledge System", page_icon="⚖️", layout="wide") | |
| st.markdown(""" | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Crimson+Text:wght@400;600;700&family=Lato:wght@300;400;700&display=swap'); | |
| /* INDIAN CONSTITUTION THEME - Saffron, White, Green */ | |
| /* Global Background */ | |
| html, body, [class*="css"] { font-family: 'Lato', sans-serif; } | |
| .stApp { | |
| background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #0a0a0a 100%); | |
| color: #e8e8e8; | |
| } | |
| /* ASHOKA CHAKRA ANIMATION */ | |
| @keyframes rotateChakra { | |
| from { transform: rotate(0deg); } | |
| to { transform: rotate(360deg); } | |
| } | |
| .chakra-container { | |
| position: fixed; | |
| width: 80px; | |
| height: 80px; | |
| z-index: 999; | |
| opacity: 0.3; | |
| pointer-events: none; | |
| } | |
| .chakra-left { left: 30px; top: 30px; } | |
| .chakra-right { right: 30px; top: 30px; } | |
| .chakra { | |
| width: 100%; | |
| height: 100%; | |
| border: 4px solid #000080; | |
| border-radius: 50%; | |
| position: relative; | |
| animation: rotateChakra 20s linear infinite; | |
| } | |
| .chakra::before { | |
| content: ''; | |
| position: absolute; | |
| width: 100%; | |
| height: 2px; | |
| background: #000080; | |
| top: 50%; | |
| left: 0; | |
| } | |
| .chakra::after { | |
| content: ''; | |
| position: absolute; | |
| width: 2px; | |
| height: 100%; | |
| background: #000080; | |
| left: 50%; | |
| top: 0; | |
| } | |
| /* TRICOLOR ACCENT BAR */ | |
| .tricolor-bar { | |
| height: 6px; | |
| background: linear-gradient(to right, #FF9933 33%, #FFFFFF 33%, #FFFFFF 66%, #138808 66%); | |
| margin: 1rem 0; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.5); | |
| } | |
| /* HEADERS */ | |
| .main-title { | |
| font-family: 'Crimson Text', serif !important; | |
| font-size: 3.5rem; | |
| background: linear-gradient(to bottom, #FFD700, #FFA500); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| text-align: center; | |
| text-shadow: 0 4px 10px rgba(0,0,0,0.8); | |
| font-weight: 700; | |
| } | |
| .sub-title { | |
| font-family: 'Crimson Text', serif !important; | |
| text-align: center; | |
| color: #a0a0a0; | |
| font-size: 1.1rem; | |
| letter-spacing: 3px; | |
| text-transform: uppercase; | |
| margin-bottom: 2rem; | |
| } | |
| /* LEGAL DOCUMENT CARDS */ | |
| .answer-box, .glass-card { | |
| background: linear-gradient(135deg, #1e1e2e 0%, #2a2a3e 100%); | |
| border: 2px solid #4a4a6a; | |
| color: #e8e8e8 !important; | |
| padding: 2.5rem; | |
| border-radius: 8px; | |
| box-shadow: | |
| 0 10px 30px rgba(0,0,0,0.7), | |
| inset 0 0 40px rgba(255, 153, 51, 0.05); | |
| font-family: 'Lato', sans-serif !important; | |
| line-height: 1.8; | |
| margin-bottom: 2rem; | |
| position: relative; | |
| } | |
| /* Tricolor left border accent */ | |
| .answer-box::before, .glass-card::before { | |
| content: ''; | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| bottom: 0; | |
| width: 6px; | |
| background: linear-gradient(to bottom, #FF9933 33%, #FFFFFF 33%, #FFFFFF 66%, #138808 66%); | |
| } | |
| .answer-box h3, .glass-card h3 { color: #FFD700 !important; font-weight: 700; } | |
| .answer-box ul li, .glass-card ul li { color: #d0d0d0 !important; } | |
| /* ARTICLE PILLS (Legal Citation Style) */ | |
| .source-pill { | |
| display: inline-block; | |
| background: linear-gradient(135deg, #000080 0%, #0000CD 100%); | |
| border: 1px solid #4169E1; | |
| color: #FFFFFF !important; | |
| padding: 0.4rem 1rem; | |
| border-radius: 5px; | |
| font-size: 0.8rem; | |
| margin-right: 0.5rem; | |
| font-family: 'Crimson Text', serif !important; | |
| box-shadow: 0 2px 5px rgba(0,0,0,0.4); | |
| font-weight: 600; | |
| } | |
| .source-pill:hover { transform: scale(1.05); background: linear-gradient(135deg, #0000CD 0%, #4169E1 100%); cursor: pointer; } | |
| /* Sidebar */ | |
| .metadata-section { | |
| background: rgba(30, 30, 46, 0.8); | |
| border: 2px solid #4a4a6a; | |
| border-radius: 10px; | |
| padding: 1rem; | |
| } | |
| /* INPUT STYLING */ | |
| div[data-testid="stTextInput"] input, | |
| div[data-testid="stChatInput"] textarea, | |
| div[data-testid="stChatInput"] input { | |
| background: linear-gradient(135deg, #2a2a3e 0%, #1e1e2e 100%) !important; | |
| color: #FFD700 !important; | |
| font-family: 'Lato', sans-serif !important; | |
| font-weight: 500 !important; | |
| border: 2px solid #4a4a6a !important; | |
| border-radius: 12px !important; | |
| padding: 1rem !important; | |
| box-shadow: inset 0 0 15px rgba(255, 153, 51, 0.1) !important; | |
| } | |
| /* PIN INPUT TO BOTTOM */ | |
| [data-testid="stChatInput"], | |
| [data-testid="stBottom"] { | |
| position: fixed !important; | |
| bottom: 10px !important; | |
| left: 0 !important; | |
| right: 0 !important; | |
| margin-inline: auto !important; | |
| width: 100% !important; | |
| max-width: 800px !important; | |
| padding: 0 !important; | |
| z-index: 10000 !important; | |
| background-color: transparent !important; | |
| background-image: none !important; | |
| } | |
| [data-testid="stBottom"] > div { | |
| background-color: transparent !important; | |
| } | |
| div[data-testid="stTextInput"] input::placeholder, | |
| div[data-testid="stChatInput"] textarea::placeholder { | |
| color: #a0a0a0 !important; | |
| font-style: italic; | |
| opacity: 0.8 !important; | |
| } | |
| div[data-testid="stTextInput"] div[data-baseweb="base-input-container"], | |
| div[data-testid="stChatInput"] { | |
| background-color: transparent !important; | |
| border: none !important; | |
| } | |
| div[data-testid="stTextInput"] label { | |
| color: #FFD700 !important; | |
| font-family: 'Crimson Text', serif !important; | |
| } | |
| div[data-testid="stChatInput"] button { | |
| color: #FFD700 !important; | |
| } | |
| /* TOKEN PILLS */ | |
| .token-pill { | |
| display: inline-block; | |
| background: #1e1e2e; | |
| border: 1px solid #4a4a6a; | |
| color: #FFD700 !important; | |
| padding: 0.2rem 0.8rem; | |
| border-radius: 4px; | |
| font-size: 0.75rem; | |
| margin-left: 0.3rem; | |
| font-family: 'Lato', sans-serif !important; | |
| box-shadow: 0 2px 5px rgba(0,0,0,0.4); | |
| vertical-align: middle; | |
| } | |
| </style> | |
| <!-- DECORATIVE ELEMENTS --> | |
| <div class="chakra-container chakra-left"><div class="chakra"></div></div> | |
| <div class="chakra-container chakra-right"><div class="chakra"></div></div> | |
| """, unsafe_allow_html=True) | |
| # =============================== | |
| # LOAD RESOURCES | |
| # =============================== | |
| def load_resources(): | |
| model = SentenceTransformer("BAAI/bge-m3", device='cpu') | |
| ranker = Ranker(model_name="ms-marco-MiniLM-L-12-v2", cache_dir="./opt") | |
| db = lancedb.connect("./.lancedb") | |
| tbl = db.open_table("constitution_knowledge") | |
| return model, ranker, tbl | |
| model, ranker, tbl = load_resources() | |
| # =============================== | |
| # LLM LOGIC | |
| # =============================== | |
| def get_answer_from_llm(query, context): | |
| system_prompt = """You are a constitutional law expert and scholar of the Indian Constitution. | |
| Answer precisely using the provided context from the Constitution of India. | |
| CRITICAL RULES: | |
| 1. Cite specific Articles, Parts, and Chapters when answering. | |
| 2. Maintain legal accuracy and constitutional interpretation. | |
| 3. If the context doesn't contain the answer, say so clearly.""" | |
| messages = [ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": f"Context from Constitution:\n{context}\n\nQuestion: {query}"} | |
| ] | |
| # Token estimation | |
| input_text = system_prompt + f"Context from Constitution:\n{context}\n\nQuestion: {query}" | |
| in_tokens = len(input_text) // 4 | |
| errors = [] | |
| # 1. Try SambaNova First (Ramayana style model name) | |
| s_key = os.getenv("SAMBANOVA_API_KEY") | |
| if s_key and s_key != "your_key_here": | |
| try: | |
| url = "https://api.sambanova.ai/v1/chat/completions" | |
| headers = {"Authorization": f"Bearer {s_key}", "Content-Type": "application/json"} | |
| # Switched to 70B model without 'Meta-' prefix | |
| data = {"model": "Llama-3.1-70B-Instruct", "messages": messages, "temperature": 0.1, "stream": False} | |
| res = requests.post(url, headers=headers, json=data, timeout=10) | |
| if res.status_code == 200: | |
| ans = res.json()['choices'][0]['message']['content'] | |
| out_tokens = len(ans) // 4 | |
| return ans, "SambaNova (70B)", in_tokens, out_tokens | |
| else: | |
| errors.append(f"SambaNova Error: {res.status_code} - {res.text}") | |
| except Exception as e: | |
| errors.append(f"SambaNova Exception: {str(e)}") | |
| # 2. Fallback to Groq | |
| g_key = os.getenv("GROQ_API_KEY") | |
| if g_key: | |
| try: | |
| client = Groq(api_key=g_key) | |
| for m in ["llama-3.3-70b-versatile", "mixtral-8x7b-32768", "llama-3.1-8b-instant"]: | |
| try: | |
| res = client.chat.completions.create(model=m, messages=messages, temperature=0.2) | |
| ans = res.choices[0].message.content | |
| out_tokens = len(ans) // 4 | |
| return ans, m, in_tokens, out_tokens | |
| except Exception as e: | |
| errors.append(f"Groq {m} Error: {str(e)}") | |
| if "429" in str(e) or "400" in str(e): continue | |
| else: break | |
| except Exception as e: | |
| errors.append(f"Groq Setup Error: {str(e)}") | |
| # 3. Fallback to Local Ollama | |
| try: | |
| url = "http://localhost:11434/api/chat" | |
| data = { | |
| "model": "llama3.1:8b-instruct-q4_K_M", | |
| "messages": messages, | |
| "stream": False, | |
| "options": {"temperature": 0.2} | |
| } | |
| res = requests.post(url, json=data, timeout=30) | |
| if res.status_code == 200: | |
| ans = res.json()['message']['content'] | |
| out_tokens = len(ans) // 4 | |
| return ans, "Ollama (llama3.1)", in_tokens, out_tokens | |
| else: | |
| errors.append(f"Ollama Error: {res.status_code}") | |
| except Exception as e: | |
| errors.append(f"Ollama Exception: {str(e)}") | |
| return f"[ERROR] All API calls failed. Errors: {errors}", "None", 0, 0 | |
| # =============================== | |
| # MAIN UI | |
| # =============================== | |
| st.markdown(""" | |
| <style> | |
| .block-container { | |
| padding-top: 1rem; | |
| padding-bottom: 5rem; | |
| max-width: 90% !important; | |
| margin: 0 auto !important; | |
| } | |
| html { | |
| font-size: 80% !important; | |
| } | |
| body { | |
| zoom: 100%; | |
| -moz-transform: none; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| st.markdown('<h1 class="main-title">Constitution of India</h1>', unsafe_allow_html=True) | |
| st.markdown('<p class="sub-title">Legal Knowledge System</p>', unsafe_allow_html=True) | |
| # TRICOLOR DIVIDER | |
| st.markdown('<div class="tricolor-bar"></div>', unsafe_allow_html=True) | |
| # Session State for History | |
| if "messages" not in st.session_state: | |
| st.session_state.messages = [] | |
| # Layout: Content First | |
| content_placeholder = st.empty() | |
| # Render History or Landing Page | |
| with content_placeholder.container(): | |
| if not st.session_state.messages: | |
| st.markdown(""" | |
| <div class="glass-card"> | |
| <h3>Welcome to the Constitutional Knowledge System</h3> | |
| <p>Access the structured knowledge of the Indian Constitution through semantic search and legal reasoning.</p> | |
| <ul> | |
| <li><b>Query Articles</b>: Ask about specific constitutional provisions.</li> | |
| <li><b>Understand Rights</b>: Explore Fundamental Rights and Directive Principles.</li> | |
| <li><b>Government Structure</b>: Learn about Union and State governance.</li> | |
| <li><b>Legal Procedures</b>: Understand amendment processes and emergency provisions.</li> | |
| </ul> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Render Conversation | |
| for msg in st.session_state.messages: | |
| if msg["role"] == "user": | |
| st.markdown(f'<div style="text-align: right; color: #FFD700; font-family: Crimson Text; margin: 1rem; font-weight: 600;">Query: {msg["content"]}</div>', unsafe_allow_html=True) | |
| elif msg["role"] == "assistant": | |
| st.markdown(msg["content"], unsafe_allow_html=True) | |
| # Input Pinned to Bottom | |
| if user_query := st.chat_input("Ask a question about the Constitution..."): | |
| # Append User Msg | |
| st.session_state.messages.append({"role": "user", "content": user_query}) | |
| with content_placeholder.container(): | |
| st.markdown(f'<div style="text-align: right; color: #FFD700; font-family: Crimson Text; margin: 1rem; font-weight: 600;">Query: {user_query}</div>', unsafe_allow_html=True) | |
| # Process Query | |
| limit_n = 3 | |
| is_summary = any(k in user_query.lower() for k in ["summarize", "summary", "overview", "explain all"]) | |
| # PART DETECTION | |
| target_part = None | |
| parts = { | |
| "fundamental rights": "PART III", | |
| "directive principles": "PART IV", | |
| "union": "PART V", | |
| "states": "PART VI", | |
| "judiciary": "PART V", | |
| "elections": "PART XV", | |
| "emergency": "PART XVIII", | |
| "amendment": "PART XX" | |
| } | |
| for k, v in parts.items(): | |
| if k in user_query.lower(): | |
| target_part = v | |
| break | |
| # Increase search depth for better recall | |
| limit_n = 10 | |
| if is_summary: limit_n = 15 | |
| if target_part: limit_n = 20 | |
| q_vec = model.encode(user_query) | |
| # Search top 30-60 candidates to ensure we don't miss key Articles like Art 3 | |
| search_res = tbl.search(q_vec).limit(limit_n * 3).to_list() | |
| # Filter by Part if detected | |
| if target_part: | |
| filtered_res = [r for r in search_res if target_part in r.get('part', '')] | |
| if filtered_res: | |
| search_res = filtered_res | |
| passages = [{"id": i, "text": r['text'], "meta": r} for i, r in enumerate(search_res)] | |
| # Rerank top 8 candidates (increased from 5) | |
| reranked = ranker.rerank(RerankRequest(query=user_query, passages=passages))[:10 if is_summary or target_part else 8] | |
| # Deduplicate chunks based on Article Number OR Text content (CRITICAL FIX) | |
| seen_keys = set() | |
| unique_reranked = [] | |
| for r in reranked: | |
| # Create a unique key: Try Article Number first + Part, else full text | |
| art_num = r['meta'].get('article_number', '').strip() | |
| part = r['meta'].get('part', '').strip() | |
| if art_num and "Article" in art_num: | |
| key = f"{art_num}-{part}" | |
| else: | |
| key = r['meta']['text'][:100] # Use first 100 chars of text as proxy | |
| if key not in seen_keys: | |
| seen_keys.add(key) | |
| unique_reranked.append(r) | |
| # Take top 3 UNIQUE results | |
| reranked = unique_reranked[:3] | |
| ctx = "\n---\n".join([f"{r['meta'].get('article_number', 'Section')}: {r['meta']['text']}" for r in reranked]) | |
| if len(ctx) > 6000: ctx = ctx[:6000] + "... [TRUNCATED]" | |
| # Generate Answer | |
| with st.spinner("Analyzing constitutional provisions..."): | |
| errors = [] | |
| # Prepare Prompt (Strict Context Only) | |
| system_prompt = """You are a strict legal assistant. | |
| Answer the question ONLY using the provided Context from the Constitution of India. | |
| CRITICAL RULES: | |
| 1. Do NOT use your own internal knowledge. Use ONLY the provided context. | |
| 2. If the answer is not explicitly mentioned in the context, state: "The provided context does not contain information about [topic]." | |
| 3. Do not assume or hallucinate Articles not present in the text below. | |
| 4. Cite specific Articles from the context provided.""" | |
| messages = [ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": f"Context from Constitution:\n{ctx}\n\nQuestion: {user_query}"} | |
| ] | |
| # Calculate Input Tokens (Approx) | |
| in_tokens = len(system_prompt + ctx + user_query) // 4 | |
| # 1. Try SambaNova First (DISABLED) | |
| # s_key = os.getenv("SAMBANOVA_API_KEY") | |
| # ans = None # Ensure ans is initialized | |
| # if s_key and s_key != "your_key_here": | |
| # ... (Logic commented out) ... | |
| ans = None | |
| # 2. Fallback to Groq (DISABLED) | |
| # if not ans: | |
| # ... (Logic commented out) ... | |
| # 3. Use Local Ollama (EXCLUSIVE) | |
| if not ans: | |
| try: | |
| url = "http://localhost:11434/api/chat" | |
| data = { | |
| "model": "llama3.1:8b-instruct-q4_K_M", | |
| "messages": messages, | |
| "stream": False, | |
| "options": {"temperature": 0.2, "num_ctx": 8192} | |
| } | |
| res = requests.post(url, json=data, timeout=120) # Increased timeout for local processing | |
| if res.status_code == 200: | |
| ans_text = res.json()['message']['content'] | |
| out_tokens = len(ans_text) // 4 | |
| ans, m_used, in_tok, out_tok = ans_text, "Ollama (local)", in_tokens, out_tokens | |
| else: | |
| errors.append(f"Ollama Error: {res.status_code} - {res.text}") | |
| except Exception as e: | |
| errors.append(f"Ollama Exception: {str(e)}") | |
| # Final Check | |
| if not ans: | |
| ans = f"[ERROR] All API calls failed. Errors: {errors}" | |
| m_used, in_tok, out_tok = "None", 0, 0 | |
| # Build HTML | |
| sources_html = "".join([ | |
| f'<span class="source-pill">{r["meta"].get("article_number", "Section")} - {r["meta"].get("part", "")}</span>' | |
| for r in reranked | |
| ]) | |
| answer_html = f""" | |
| <div class="answer-box"> | |
| <h3>Legal Analysis</h3> | |
| {ans} | |
| <br><br> | |
| <div style="font-size: 0.8rem; border-top: 2px solid #4a4a6a; padding-top: 10px; margin-top: 10px; display: flex; justify-content: space-between; align-items: center;"> | |
| <div><strong>Citations:</strong><br>{sources_html}</div> | |
| <div style="text-align: right;"> | |
| <span class="token-pill">In: {in_tok}</span> | |
| <span class="token-pill">Out: {out_tok}</span> | |
| <span class="token-pill">{m_used}</span> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| # Append Assistant Msg | |
| st.session_state.messages.append({"role": "assistant", "content": answer_html}) | |
| st.rerun() | |
| # =============================== | |
| # SIDEBAR | |
| # =============================== | |
| with st.sidebar: | |
| st.markdown('<div class="metadata-section">', unsafe_allow_html=True) | |
| st.markdown("### System Metadata") | |
| st.write(f"**Index Type:** LanceDB Vector") | |
| st.write(f"**Model:** BGE-M3 (1024D)") | |
| st.write(f"**Reranker:** FlashRank") | |
| st.write(f"**Total Articles:** {len(tbl)}") | |
| with st.expander("Example Queries"): | |
| st.markdown(""" | |
| <small> | |
| - What are fundamental rights?<br> | |
| - Role of the President<br> | |
| - How to amend the Constitution?<br> | |
| - Directive Principles explained<br> | |
| - Emergency provisions | |
| </small> | |
| """, unsafe_allow_html=True) | |
| st.markdown('</div>', unsafe_allow_html=True) | |