import os import gradio as gr import requests from supabase import create_client # The frontend runs on a machine the USER controls, so it only ever gets keys # that are safe in a browser. SUPABASE_ANON_KEY is the public, publishable key — # safe precisely because RLS guards every row (db/03_policies.sql). The # service_role key never comes near this file. BACKEND_URL = os.environ.get("BACKEND_URL", "http://127.0.0.1:8000/diagnose") supabase = create_client( os.environ["SUPABASE_URL"], os.environ["SUPABASE_ANON_KEY"], ) # --------------------------------------------------------------------------- # Auth: get the user a passport (JWT) before they can use the app. # # We do NOT build a users table or hash passwords — Supabase Auth does that. # We just ask it to sign someone up or log them in, and keep the access_token # it hands back. That token is the passport we attach to every backend call. # --------------------------------------------------------------------------- def _explain(error: Exception) -> str: """Turn a Supabase auth error into one human sentence.""" message = getattr(error, "message", None) or str(error) return f"That did not work: {message}" def log_in(email, password): """Exchange email + password for a session, then reveal the chat.""" if not email or not password: return None, gr.update(), gr.update(), "Enter an email and a password." try: result = supabase.auth.sign_in_with_password( {"email": email, "password": password} ) except Exception as error: # wrong password, unknown user, etc. return None, gr.update(), gr.update(), _explain(error) session = result.session if session is None: return None, gr.update(), gr.update(), "Could not start a session." # Logged in: stash the token, hide the login box, show the chat. return ( session.access_token, gr.update(visible=False), gr.update(visible=True), "", ) def sign_up(email, password): """Create the account. This project has 'Confirm email' ON, so no session is returned yet — the user must click the link in their inbox first, then log in. (If you turn confirmation OFF, a session comes straight back and the `session is not None` branch below logs them in immediately.)""" if not email or not password: return None, gr.update(), gr.update(), "Enter an email and a password." try: result = supabase.auth.sign_up({"email": email, "password": password}) except Exception as error: # already registered, weak password, etc. return None, gr.update(), gr.update(), _explain(error) session = result.session if session is None: # Email confirmation is on: account made, but no passport until verified. return ( None, gr.update(), gr.update(), "Account created. Check your email to confirm, then log in.", ) return ( session.access_token, gr.update(visible=False), gr.update(visible=True), "", ) # --------------------------------------------------------------------------- # The chat, now carrying the passport. # --------------------------------------------------------------------------- def diagnose(message, history, token): if not token: return "Your session expired. Please reload and log in again." try: response = requests.post( BACKEND_URL, json={"workflow_description": message}, # The passport. The backend hands this straight to Supabase to learn # who is asking — it never trusts a name in the request body. headers={"Authorization": f"Bearer {token}"}, timeout=60, ) response.raise_for_status() return response.text except requests.RequestException as e: return f"Could not reach the backend.\n\n{e}" with gr.Blocks(title="Workflow Diagnoser") as demo: # Holds the JWT for the length of the browser session. token_state = gr.State(None) # ---- Login view (what you see first) ---- with gr.Column(visible=True) as login_view: gr.Markdown( "# Workflow Diagnoser\n" "Log in or sign up to start. Your conversations are private to you." ) email = gr.Textbox(label="Email", placeholder="you@example.com") password = gr.Textbox(label="Password", type="password") with gr.Row(): login_btn = gr.Button("Log in", variant="primary") signup_btn = gr.Button("Sign up") status = gr.Markdown() # ---- Chat view (hidden until you have a passport) ---- with gr.Column(visible=False) as chat_view: gr.ChatInterface( diagnose, additional_inputs=[token_state], title="Workflow Diagnoser", description="Describe one repeated task you do at work.", ) outputs = [token_state, login_view, chat_view, status] login_btn.click(log_in, [email, password], outputs) signup_btn.click(sign_up, [email, password], outputs) if __name__ == "__main__": demo.launch()