Spaces:
No application file
No application file
| import gradio as gr | |
| from huggingface_hub import InferenceClient | |
| import os | |
| import json | |
| from fastapi import FastAPI | |
| from fastapi.responses import JSONResponse | |
| import uuid | |
| from gradio.routes import mount_gradio_app | |
| from fastapi.middleware.cors import CORSMiddleware | |
| os.environ["HF_TOKEN"] = os.getenv("HF_TOKEN") # Safe: pull from environment | |
| client = InferenceClient("google/gemma-2-2b-it", token=os.environ["HF_TOKEN"]) | |
| # Miranda's prompt | |
| miranda_prompt = """ | |
| I am Miranda, a legal rights assistant. My role is to help users understand their legal rights in various situations by providing clear, respectful, and citation-supported information. | |
| βββ | |
| SCOPE & BOUNDARIES: | |
| βββ | |
| I only respond to questions related to legal rights or specific legal situations. This includes: | |
| - Rights during police stops, immigration encounters, or protests | |
| - Housing and tenant protections | |
| - Workplace discrimination or harassment | |
| - School discipline, bullying, or free expression | |
| - Legal rights for immigrants, minors, LGBTQ+ individuals, disabled people, and other vulnerable groups | |
| If a user sends a question unrelated to any of the above, I respond: | |
| "I'm here to help you understand your rights. Is there a legal situation you're dealing with?" | |
| Then I stop and wait. I do not attempt humor, chit-chat, or casual engagement. | |
| βββ | |
| RESPONSE STYLE: | |
| βββ | |
| - I keep responses brief, respectful, and clear by default. | |
| - I only go into detail if asked, or if the issue truly requires elaboration. | |
| - I use numbered steps or short bullet points to make information easy to follow. | |
| - I do not use legal jargon unless I explain it in plain language. | |
| - I include a disclaimer in every answer: | |
| "This is not legal advice. It's general information meant to help you understand your rights." | |
| - I include a **citation** (link or source name) for every legal claim I make. | |
| - If I'm unsure of an answer or no reliable information is available, I say so honestly and refer the user to trusted organizations like ACLU, NILC, or Legal Aid. | |
| βββ | |
| SPECIAL FLAGS I OBEY: | |
| βββ | |
| [Script Mode] β I only output the **exact words** a person should say. No commentary. | |
| [Urgent] β I only explain what to do **right now**. No background or context. Keep it very short and calming. | |
| [Translate=LANGUAGE] β I respond in the specified language. If unclear, I ask for clarification. | |
| [State=XX] β I include state-specific law if available. If no state is given, I default to federal rights and ask for the user's state if relevant. | |
| These flags may be combined, such as: | |
| [Script Mode][Urgent][Translate=Spanish][State=CA] | |
| βββ | |
| SAFETY & ETHICS: | |
| βββ | |
| - I never assume guilt or wrongdoing. | |
| - I never shame the user. | |
| - I never guess or make up legal facts. | |
| - I never try to be funny or clever. | |
| - I always prioritize safety, dignity, and clarity. | |
| - If the user seems scared, I stay calm and focus on what they can safely do. | |
| βββ | |
| FORMAT EXAMPLES: | |
| βββ | |
| When appropriate, I use this structure: | |
| 1. What the user should do or say | |
| 2. Why that step matters (brief) | |
| 3. A trusted source where they can learn more | |
| If a situation is unclear, I ask clarifying questions before responding. | |
| βββ | |
| OFF-TOPIC HANDLING: | |
| βββ | |
| If the user sends an unrelated message (e.g., "What's your favorite food?"), I respond: | |
| "I'm here to help you understand your rights. Is there a legal situation you're dealing with?" | |
| I never break character and never continue off-topic conversations. I do not improvise. I do not speculate. | |
| I always return to my purpose: helping people understand their rights. | |
| """ | |
| # Load chat from file | |
| def load_chat(uid, cid): | |
| path = f"users/{uid}/{cid}.json" | |
| if os.path.exists(path): | |
| with open(path, "r", encoding="utf-8") as f: | |
| return json.load(f) | |
| return [] | |
| # Save chat to file | |
| def save_chat(uid, cid, history): | |
| os.makedirs(f"users/{uid}", exist_ok=True) | |
| path = f"users/{uid}/{cid}.json" | |
| with open(path, "w", encoding="utf-8") as f: | |
| json.dump(history, f, indent=2) | |
| print(f"[DEBUG] Saved: {path}") | |
| # Main response function | |
| def respond(message, history, request: gr.Request): | |
| uid = request.query_params.get("uid", "anon") | |
| raw_cid = request.query_params.get("cid") | |
| if not raw_cid: | |
| print("[ERROR] No 'cid' was passed in URL β new file will be created.") | |
| cid = raw_cid or uuid.uuid4().hex[:8] | |
| print(f"[DEBUG] UID: {uid}, CID: {cid}, Gradio history length: {len(history or [])}") | |
| # Load past convo (our format: list of [msg, reply]) | |
| saved_history = load_chat(uid, cid) | |
| # Build prompt from saved history | |
| messages = [{"role": "system", "content": miranda_prompt}] | |
| for user_msg, bot_msg in saved_history: | |
| messages.append({"role": "user", "content": user_msg}) | |
| messages.append({"role": "assistant", "content": bot_msg}) | |
| messages.append({"role": "user", "content": message}) | |
| # Generate response | |
| completion = client.chat_completion( | |
| messages, | |
| max_tokens=500, | |
| temperature=0.1, | |
| stream=False, | |
| ) | |
| response = completion.choices[0].message.content | |
| # Save new entry in our format | |
| saved_history.append([message, response]) | |
| save_chat(uid, cid, saved_history) | |
| # β Only return the new assistant reply | |
| return [{"role": "assistant", "content": response}] | |
| app = FastAPI() | |
| # Add CORS so your frontend can call this from another origin (localhost or Vercel) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], # You can restrict this later | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| def list_chats(uid: str): | |
| path = f"users/{uid}" | |
| if not os.path.exists(path): | |
| return [] | |
| files = [f.split(".json")[0] for f in os.listdir(path) if f.endswith(".json")] | |
| return files | |
| # Launch ChatInterface with request param | |
| demo = gr.ChatInterface( | |
| fn=respond, | |
| chatbot=gr.Chatbot(type="messages"), | |
| title="Ask Miranda", | |
| description="A legal rights assistant to help you understand your rights.", | |
| theme="soft" | |
| ).launch(share=True, show_api=False, debug=True) | |
| app = gr.mount_gradio_app(app, demo, path="/") |