Miranda-Bot / app.py
SkylarS300's picture
Create app.py
e0f0bbb verified
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=["*"],
)
@app.get("/list-chats/{uid}")
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="/")