File size: 6,195 Bytes
e0f0bbb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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="/")