Spaces:
Sleeping
Sleeping
Download src/streamlit_app.py from InsafDost/InsafDostAI: direct link, hf CLI and curl.
- Browser
- Download file 10.5 kB
-
https://huggingface.co/spaces/InsafDost/InsafDostAI/resolve/main/src/streamlit_app.py
- Command line
-
hf download hf://spaces/InsafDost/InsafDostAI/src/streamlit_app.py
-
curl -L -o streamlit_app.py https://huggingface.co/spaces/InsafDost/InsafDostAI/resolve/main/src/streamlit_app.py
10.5 kB
| import streamlit as st | |
| import os | |
| import json | |
| import gc | |
| import re | |
| import numpy as np | |
| from typing import TypedDict, List | |
| from langchain_groq import ChatGroq | |
| # ββ 1. SECRETS & ENV SETUP ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| os.environ["GROQ_API_KEY"] = os.environ.get("GROQ_API_KEY", "").strip() | |
| os.environ["QDRANT_URL"] = "https://03e73cb7-1191-4c3b-82c3-3fd2213e7ea7.sa-east-1-0.aws.cloud.qdrant.io" | |
| os.environ["QDRANT_API_KEY"] = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOiJtIn0.mpQIWe6JRaesx4OlMpiL22gIhgyIg7rBwitwzZtz8z0" | |
| # ββ 2. APP CONFIG & STYLE βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| st.set_page_config(page_title="InsafDost AI", page_icon="βοΈ", layout="wide") | |
| st.markdown(""" | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;900&family=JetBrains+Mono&family=Source+Serif+4&display=swap'); | |
| html, body, [data-testid="stAppViewContainer"] { background-color: #0a0d14 !important; color: #e6dfc8 !important; } | |
| .insaf-logo { font-family: 'Playfair Display', serif; font-size: 3.5rem; font-weight: 900; text-align: center; background: linear-gradient(135deg, #e8d5a0 0%, #b4913c 40%, #e8d5a0 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } | |
| .insaf-tagline { font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; letter-spacing: 0.35em; color: #9cb08b; text-align: center; text-transform: uppercase; margin-bottom: 2rem; } | |
| div.stButton > button { | |
| background: linear-gradient(135deg, #7a5c1a 0%, #c4a14c 45%, #e8d080 55%, #b4913c 75%, #7a5c1a 100%) !important; | |
| color: #08090f !important; font-weight: 900 !important; font-size: 1.2rem !important; | |
| border-radius: 4px !important; padding: 1rem !important; width: 100% !important; border: none !important; | |
| } | |
| .case-card { background: rgba(15,22,35,0.8); border: 1px solid rgba(180,145,60,0.2); padding: 2rem; border-radius: 8px; margin-bottom: 2rem; } | |
| .badge { display: inline-block; font-family: 'JetBrains Mono', monospace; font-size: 0.7rem; padding: 5px 12px; border-radius: 4px; margin-right: 8px; font-weight: bold; border: 1px solid rgba(180,145,60,0.3); } | |
| .legal-answer { font-family: 'Source Serif 4', serif; font-size: 1.15rem; line-height: 1.9; border-left: 4px solid #b4913c; padding-left: 1.5rem; margin: 1.5rem 0; color: #f2ebd9; white-space: pre-wrap; } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| if "results" not in st.session_state: | |
| st.session_state.results = [] | |
| # ββ 3. RESOURCE LOADER ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def load_resources(): | |
| from langchain_qdrant import QdrantVectorStore | |
| from langchain_huggingface import HuggingFaceEmbeddings | |
| from qdrant_client import QdrantClient | |
| reasoner = ChatGroq(model="llama-3.3-70b-versatile", temperature=0.0, groq_api_key=os.environ["GROQ_API_KEY"]) | |
| fast_llm = ChatGroq(model="llama-3.1-8b-instant", temperature=0.0, groq_api_key=os.environ["GROQ_API_KEY"]) | |
| embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2", model_kwargs={'device': 'cpu'}) | |
| qdrant_client = QdrantClient(url=os.environ["QDRANT_URL"], api_key=os.environ["QDRANT_API_KEY"]) | |
| vectorstore = QdrantVectorStore(client=qdrant_client, collection_name="pakistan_law", embedding=embeddings) | |
| try: | |
| from FlagEmbedding import FlagReranker | |
| reranker = FlagReranker("BAAI/bge-reranker-base", use_fp16=True) | |
| except: | |
| reranker = None | |
| gc.collect() | |
| return reasoner, fast_llm, vectorstore, reranker | |
| # ββ 4. MULTI-CASE SPLITTER ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def split_multiple_cases(text): | |
| parts = re.split(r'(?i)case\s*\d+\s*[:\-]|Case\s*#\d+\s*[:\-]|---', text) | |
| cleaned = [p.strip() for p in parts if len(p.strip()) > 50] | |
| return cleaned if cleaned else [text.strip()] | |
| # ββ 5. LANGGRAPH PIPELINE βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def load_graph(_reasoner, _fast_llm, _vectorstore, _reranker): | |
| from langgraph.graph import StateGraph, START, END | |
| class InsafState(TypedDict): | |
| raw_text: str | |
| category: str | |
| legal_keywords: str | |
| precedents: List[str] | |
| precedent_meta: List[dict] | |
| final_answer: str | |
| audit_score: float | |
| def processor_node(state): | |
| prompt = f"Return STRICT JSON: {{\"category\":\"Criminal|Civil|Family\", \"keywords\":\"5 keywords\"}}. Case: {state['raw_text']}" | |
| try: | |
| raw_res = _fast_llm.invoke(prompt).content | |
| match = re.search(r'\{.*\}', raw_res, re.S) | |
| data = json.loads(match.group()) | |
| except: | |
| data = {"category": "Civil", "keywords": state['raw_text'][:50]} | |
| return {"category": data.get("category", "Civil"), "legal_keywords": data.get("keywords", "")} | |
| def retriever_node(state): | |
| raw_docs = _vectorstore.similarity_search_with_score(state["legal_keywords"], k=10) | |
| if _reranker and raw_docs: | |
| texts = [d[0].page_content[:1000] for d in raw_docs] | |
| rr_scores = _reranker.compute_score([[f"{state['category']} law Pakistan", t] for t in texts]) | |
| ranked = sorted(zip(raw_docs, rr_scores), key=lambda x: x[1], reverse=True)[:4] | |
| final_docs, scores = [r[0] for r in ranked], [float(r[1]) for r in ranked] | |
| else: | |
| final_docs, scores = raw_docs[:4], [0.5]*len(raw_docs[:4]) | |
| return { | |
| "precedents": [d[0].page_content for d in final_docs], | |
| "precedent_meta": [{"source": d[0].metadata.get("source", "Unknown"), "score": round(scores[i], 3)} for i, d in enumerate(final_docs)] | |
| } | |
| def reasoner_node(state): | |
| context = "\n".join([f"[{i+1}] Authority: {state['precedent_meta'][i]['source']}\n{p[:1500]}" for i, p in enumerate(state['precedents'])]) | |
| prompt = f"Using ONLY precedents: {context}. Analyze Case: {state['raw_text']}. Use [Number] citations. Keep it professional." | |
| return {"final_answer": _reasoner.invoke(prompt).content} | |
| def auditor_node(state): | |
| answer = state["final_answer"] | |
| sentences = [s for s in re.split(r'(?<=[.!?])\s+', answer) if len(s) > 20] | |
| context = "\n".join([p[:500] for p in state['precedents']]) | |
| audit_prompt = f"Verify claims against context. Return JSON: [{{\"id\": 0, \"v\": true/false}}].\nCONTEXT: {context}\nSENTENCES: {sentences}" | |
| try: | |
| raw_audit = _fast_llm.invoke(audit_prompt).content | |
| match = re.search(r'\[.*\]', raw_audit, re.S) | |
| audit_results = json.loads(match.group()) | |
| supported = sum(1 for item in audit_results if item.get('v')) | |
| score = supported / len(sentences) if sentences else 0 | |
| except: | |
| score = 0.5 | |
| return {"audit_score": score} | |
| builder = StateGraph(InsafState) | |
| builder.add_node("processor", processor_node) | |
| builder.add_node("retriever", retriever_node) | |
| builder.add_node("reasoner", reasoner_node) | |
| builder.add_node("auditor", auditor_node) | |
| builder.add_edge(START, "processor") | |
| builder.add_edge("processor", "retriever") | |
| builder.add_edge("retriever", "reasoner") | |
| builder.add_edge("reasoner", "auditor") | |
| builder.add_edge("auditor", END) | |
| return builder.compile() | |
| # ββ 6. UI FLOW ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| st.markdown('<div class="insaf-logo">InsafDost</div><div class="insaf-tagline">Elite Multi-Case Intelligence Β· Pakistan</div>', unsafe_allow_html=True) | |
| r_llm, f_llm, v_store, rr_eng = load_resources() | |
| user_input = st.text_area("Input Legal Situations", height=250, placeholder="Case 1: [Description]\n---\nCase 2: [Description]...") | |
| if st.button("Execute Analysis βΆ", use_container_width=True): | |
| if user_input.strip(): | |
| cases = split_multiple_cases(user_input) | |
| st.session_state.results = [] | |
| with st.status("βοΈ Processing Legal Batch...") as status: | |
| graph = load_graph(r_llm, f_llm, v_store, rr_eng) | |
| for i, case_text in enumerate(cases): | |
| status.write(f"Analyzing Situation {i+1} of {len(cases)}...") | |
| res = graph.invoke({"raw_text": case_text}) | |
| res['_case_num'] = i + 1 | |
| st.session_state.results.append(res) | |
| gc.collect() | |
| status.update(label="β All Cases Analyzed", state="complete") | |
| st.rerun() | |
| # ββ 7. DISPLAY RESULTS (MULTI-CARD) βββββββββββββββββββββββββββββββββββββββββββ | |
| if st.session_state.results: | |
| st.markdown("### Analysis Results") | |
| for res in st.session_state.results: | |
| with st.container(): | |
| st.markdown('<div class="case-card">', unsafe_allow_html=True) | |
| score = res.get('audit_score', 0) | |
| conf_color = "#5ed673" if score > 0.7 else "#e6bc4c" if score > 0.4 else "#e65555" | |
| st.markdown(f""" | |
| <h4 style="color:#b4913c; margin-top:0;">Case Record #{res['_case_num']}</h4> | |
| <div style="margin-bottom: 1.5rem;"> | |
| <span class="badge" style="color:#d4b45b;">{res['category']} Law</span> | |
| <span class="badge" style="color:{conf_color}; border-color:{conf_color};">Grounding Accuracy: {score*100:.0f}%</span> | |
| </div> | |
| <div class="legal-answer">{res['final_answer']}</div> | |
| """, unsafe_allow_html=True) | |
| with st.expander("View Cited Authorities"): | |
| for j, p in enumerate(res['precedents']): | |
| st.markdown(f"**[{j+1}] {res['precedent_meta'][j]['source']}**") | |
| st.caption(p[:500] + "...") | |
| st.markdown('</div>', unsafe_allow_html=True) |