import gradio as gr import json import os import random import string from datetime import date, datetime # --------------------------------------------------------------------------- # Pre-computed demo scenarios (no API key needed) # --------------------------------------------------------------------------- TODAY = date.today().strftime("%B %d, %Y") SCENARIOS = { "Alex Turner — Senior Engineer": { "employee": "Alex Turner", "role": "Senior Engineer", "dept": "Engineering", "last_day": TODAY, "manager": "Sarah Chen", "offboarding_id": "OB-20240615-7842", "risk_level": "MEDIUM", "files_transferred": 847, "systems": ["Google Workspace", "Slack", "GitHub", "Google Drive"], "timeline": [ ("T+0min", "Offboarding initiated by HR", "completed"), ("T+1min", "Google Workspace account suspended", "completed"), ("T+2min", "Slack account deactivated", "completed"), ("T+3min", "GitHub org membership revoked (14 repos)", "completed"), ("T+5min", "847 Google Drive files transferred to Sarah Chen","completed"), ("T+6min", "Offboarding audit report generated", "completed"), ("T+7min", "Compliance email sent to IT Security & Legal", "completed"), ], "compliance_flags": [], "audit_summary": ( "Alex Turner's offboarding was completed successfully with all access revoked " "within 7 minutes of initiation. 847 Drive files were transferred to manager " "Sarah Chen. No compliance flags were raised. The engineering credentials " "vault has been updated and all active SSH keys have been invalidated." ), "it_subject": "COMPLETED: Offboarding — Alex Turner (Senior Engineer) — OB-20240615-7842", "it_body": ( "This is an automated confirmation that offboarding for Alex Turner has been " "completed.\n\nActions completed:\n• Google Workspace: SUSPENDED\n" "• Slack: DEACTIVATED\n• GitHub: REMOVED (14 repositories)\n" "• Drive: 847 files transferred to Sarah Chen\n\n" "Audit ID: OB-20240615-7842\nCompleted at: T+7min\n\nNo further action required." ), "legal_subject": "Offboarding Compliance Notice — Alex Turner — OB-20240615-7842", "legal_body": ( "For compliance records: Employee Alex Turner (Senior Engineer, Engineering) " "has been fully offboarded.\n\nData handling: All work files transferred to " "authorized manager Sarah Chen.\nAccess: All systems revoked.\n" "Risk level: MEDIUM\nCompliance flags: None\n\n" "Audit trail is stored in the compliance database under OB-20240615-7842." ), }, "Maria Santos — VP Marketing": { "employee": "Maria Santos", "role": "VP Marketing", "dept": "Marketing", "last_day": TODAY, "manager": "CEO David Park", "offboarding_id": "OB-20240615-7843", "risk_level": "HIGH", "files_transferred": 2341, "systems": ["Google Workspace", "Slack", "GitHub", "Google Drive", "Salesforce", "Figma", "Notion"], "extra_label": "Elevated Security Protocol", "extra_detail": "Admin access revoked from 3 SaaS tools · Marketing credentials rotated", "timeline": [ ("T+0min", "HIGH-RISK offboarding initiated — VP-level departure", "completed"), ("T+1min", "Google Workspace admin privileges stripped", "completed"), ("T+1min", "Google Workspace account suspended", "completed"), ("T+2min", "Slack account deactivated + admin role removed", "completed"), ("T+3min", "Figma org admin revoked", "completed"), ("T+3min", "Notion workspace admin revoked", "completed"), ("T+4min", "Salesforce admin access removed", "completed"), ("T+4min", "Marketing credentials vault rotated (12 credentials)", "completed"), ("T+6min", "2,341 Google Drive files transferred to CEO David Park", "completed"), ("T+7min", "Elevated security audit report generated", "completed"), ("T+8min", "Priority compliance email sent to IT Security & Legal", "completed"), ], "compliance_flags": [ "VP-level departure requires Board notification per policy §4.2", "Marketing SaaS admin credentials rotated — IT to verify coverage", ], "audit_summary": ( "Maria Santos's high-priority offboarding was completed in 8 minutes. " "Admin access was revoked from Google Workspace, Figma, Notion, and Salesforce " "before account suspension. 12 marketing credentials were rotated. " "2,341 Drive files transferred to CEO David Park. Two compliance flags " "raised for Board notification and credential rotation verification." ), "it_subject": "URGENT COMPLETED: High-Risk Offboarding — Maria Santos (VP Marketing) — OB-20240615-7843", "it_body": ( "HIGH-RISK offboarding for Maria Santos (VP Marketing) is complete.\n\n" "Actions completed:\n• Google Workspace admin + account: REVOKED & SUSPENDED\n" "• Slack admin + account: REVOKED & DEACTIVATED\n" "• Figma admin: REVOKED\n• Notion admin: REVOKED\n" "• Salesforce admin: REVOKED\n• Marketing credentials: ROTATED (12 items)\n" "• Drive: 2,341 files → CEO David Park\n\n" "ACTION REQUIRED: Verify credential rotation coverage with marketing team.\n" "Audit ID: OB-20240615-7843" ), "legal_subject": "HIGH-RISK Offboarding Compliance — Maria Santos — OB-20240615-7843", "legal_body": ( "COMPLIANCE NOTICE — HIGH RISK\n\nEmployee Maria Santos (VP Marketing) " "has been offboarded.\n\nCompliance flags raised:\n" "1. Board notification required per policy §4.2\n" "2. IT to confirm marketing credential rotation coverage\n\n" "Risk Level: HIGH\nAdmin systems affected: 4\n" "Data transferred: 2,341 files to CEO David Park\n" "Audit ID: OB-20240615-7843" ), }, "James Kim — Data Analyst": { "employee": "James Kim", "role": "Data Analyst", "dept": "Finance", "last_day": TODAY, "manager": "CFO Lisa Wong", "offboarding_id": "OB-20240615-7844", "risk_level": "HIGH", "files_transferred": 1203, "systems": ["Google Workspace", "Slack", "GitHub", "Google Drive", "AWS", "Jira"], "extra_label": "SOX Compliance Protocol", "extra_detail": "Finance data access audit logged · SOX compliance report generated", "timeline": [ ("T+0min", "Finance offboarding initiated — SOX compliance protocol activated", "completed"), ("T+1min", "Google Workspace account suspended", "completed"), ("T+1min", "AWS IAM user deactivated, access keys revoked", "completed"), ("T+2min", "Slack account deactivated", "completed"), ("T+2min", "GitHub org membership revoked (6 repos)", "completed"), ("T+3min", "Jira tickets reassigned to CFO Lisa Wong (23 open items)", "completed"), ("T+4min", "Finance data access audit logged to immutable SOX ledger", "completed"), ("T+5min", "1,203 Google Drive files transferred to CFO Lisa Wong", "completed"), ("T+6min", "SOX compliance report generated and signed", "completed"), ("T+7min", "Compliance email + SOX report sent to IT Security & Legal", "completed"), ], "compliance_flags": [ "SOX audit log entry created — retain for 7 years per SEC §802", "Finance data access history exported and sealed", "AWS billing alerts updated to remove departed user", ], "audit_summary": ( "James Kim's Finance-department offboarding triggered the SOX compliance protocol. " "All access was revoked in 2 minutes. 23 open Jira tickets were reassigned to " "CFO Lisa Wong. A full finance data access audit was logged to the immutable SOX " "ledger. 1,203 Drive files transferred. SOX compliance report generated and " "dispatched to Legal for 7-year retention." ), "it_subject": "COMPLETED: SOX Offboarding — James Kim (Data Analyst, Finance) — OB-20240615-7844", "it_body": ( "Offboarding for James Kim (Data Analyst, Finance) is complete.\n\n" "Actions completed:\n• Google Workspace: SUSPENDED\n" "• AWS IAM: DEACTIVATED (access keys revoked)\n" "• Slack: DEACTIVATED\n• GitHub: REMOVED (6 repos)\n" "• Jira: 23 tickets reassigned to CFO Lisa Wong\n" "• Drive: 1,203 files → CFO Lisa Wong\n\n" "ACTION REQUIRED: Update AWS billing alerts.\n" "Audit ID: OB-20240615-7844" ), "legal_subject": "SOX Compliance Offboarding — James Kim — OB-20240615-7844", "legal_body": ( "SOX COMPLIANCE NOTICE\n\nEmployee James Kim (Data Analyst, Finance) " "has been offboarded under SOX compliance protocol.\n\n" "Compliance actions:\n• Full data access audit logged to immutable SOX ledger\n" "• Finance data access history exported and sealed\n" "• SOX compliance report attached — retain for 7 years per SEC §802\n\n" "Risk Level: HIGH\nAudit ID: OB-20240615-7844" ), }, "Rachel Adams — Customer Success Manager": { "employee": "Rachel Adams", "role": "Customer Success Manager", "dept": "Sales", "last_day": TODAY, "manager": "Tom Bradley", "offboarding_id": "OB-20240615-7845", "risk_level": "MEDIUM", "files_transferred": 512, "systems": ["Google Workspace", "Slack", "GitHub", "Google Drive", "Salesforce"], "extra_label": "CRM Handoff Protocol", "extra_detail": "Salesforce opportunities reassigned · Customer contacts notified of new rep", "timeline": [ ("T+0min", "Sales offboarding initiated — CRM handoff protocol activated", "completed"), ("T+1min", "Google Workspace account suspended", "completed"), ("T+2min", "Slack account deactivated", "completed"), ("T+3min", "Salesforce: 47 open opportunities reassigned to Tom Bradley", "completed"), ("T+3min", "Salesforce: 312 customer contacts updated with new rep info", "completed"), ("T+4min", "Customer notification emails sent (312 contacts)", "completed"), ("T+5min", "512 Google Drive files transferred to Tom Bradley", "completed"), ("T+6min", "CRM handoff report generated", "completed"), ("T+7min", "Compliance email sent to IT Security & Legal", "completed"), ], "compliance_flags": [ "Customer contact notifications sent — log retained for GDPR compliance", "Salesforce pipeline handoff requires VP Sales review within 48 hours", ], "audit_summary": ( "Rachel Adams's offboarding included the full CRM handoff protocol for Sales. " "47 open Salesforce opportunities and 312 customer contacts were reassigned to " "manager Tom Bradley. All 312 customers received automated notification emails " "introducing their new rep. 512 Drive files transferred. GDPR-compliant " "notification log retained. VP Sales review flagged for within 48 hours." ), "it_subject": "COMPLETED: Offboarding + CRM Handoff — Rachel Adams (CSM) — OB-20240615-7845", "it_body": ( "Offboarding for Rachel Adams (Customer Success Manager, Sales) is complete.\n\n" "Actions completed:\n• Google Workspace: SUSPENDED\n• Slack: DEACTIVATED\n" "• Salesforce: 47 opportunities → Tom Bradley\n" "• Salesforce: 312 contacts updated + notified\n" "• Drive: 512 files → Tom Bradley\n\n" "ACTION REQUIRED: VP Sales to review Salesforce pipeline within 48 hours.\n" "Audit ID: OB-20240615-7845" ), "legal_subject": "Offboarding Compliance + GDPR Notice — Rachel Adams — OB-20240615-7845", "legal_body": ( "COMPLIANCE NOTICE\n\nEmployee Rachel Adams (Customer Success Manager, Sales) " "has been offboarded.\n\nGDPR Note: 312 customer notification emails sent — " "log retained per GDPR Article 30 record-keeping requirements.\n\n" "CRM handoff: 47 opportunities, 312 contacts → Tom Bradley\n" "Risk Level: MEDIUM\nAudit ID: OB-20240615-7845" ), }, } # --------------------------------------------------------------------------- # Helper: render a pre-computed demo card # --------------------------------------------------------------------------- RISK_COLORS = { "HIGH": ("#fca5a5", "rgba(127,29,29,0.45)"), "MEDIUM": ("#fde68a", "rgba(120,53,15,0.40)"), "LOW": ("#86efac", "rgba(20,83,45,0.40)"), } def _badge(text: str, bg: str, color: str) -> str: return ( f'{text}' ) def _system_chip(name: str) -> str: return ( f'' f'✅ {name}' ) def render_demo_card(scenario_key: str) -> str: s = SCENARIOS[scenario_key] risk_color, risk_bg = RISK_COLORS.get(s["risk_level"], ("#e2e8f0", "rgba(50,50,80,0.4)")) # --- Header --- header = f"""
🔒 {s['employee']}
{s['role']} · {s['dept']} · Last day: {s['last_day']} · Manager: {s['manager']}
Offboarding ID: {s['offboarding_id']}
{_badge('OFFBOARDING COMPLETE ✅', 'rgba(21,128,61,0.45)', '#86efac')} {_badge('Risk: ' + s['risk_level'], risk_bg, risk_color)}
""" # --- Systems revoked --- chips = "".join(_system_chip(sys) for sys in s["systems"]) systems_card = f"""
🛡️ Systems Revoked / Actions Completed
{chips}
📁 {s['files_transferred']:,} Google Drive files transferred to {s['manager']}
""" # --- Extra protocol badge --- extra_html = "" if s.get("extra_label"): extra_html = f"""
⚡ {s['extra_label']} {s['extra_detail']}
""" # --- Timeline --- tl_rows = "" for time, action, status in s["timeline"]: tl_rows += f""" {time} ✅ {action} """ timeline_card = f"""
⏱️ Action Timeline
{tl_rows}
""" # --- Compliance flags --- flags_html = "" if s["compliance_flags"]: flags_items = "".join( f'
  • ⚠️ {f}
  • ' for f in s["compliance_flags"] ) flags_html = f"""
    ⚠️ Compliance Flags
    """ else: flags_html = """
    ✅ No compliance flags raised
    """ # --- Audit summary --- audit_card = f"""
    📋 Audit Report Summary

    {s['audit_summary']}

    """ # --- Email previews --- def email_block(label: str, to: str, subject: str, body: str, accent: str) -> str: body_escaped = body.replace("\n", "
    ") return f"""
    📧 {label}
    To: {to}
    Subject: {subject}
    {body_escaped}
    """ emails_card = f"""
    📨 Compliance Email Previews
    {email_block('Email to IT Security', 'it-security@company.com', s['it_subject'], s['it_body'], 'rgba(99,102,241,0.4)')} {email_block('Email to Legal / Compliance', 'legal@company.com', s['legal_subject'], s['legal_body'], 'rgba(239,68,68,0.4)')}
    """ return header + systems_card + extra_html + timeline_card + flags_html + audit_card + emails_card # --------------------------------------------------------------------------- # generate_offboarding_report — calls GPT-4o-mini # --------------------------------------------------------------------------- def generate_offboarding_report( name: str, email: str, role: str, department: str, manager_name: str, manager_email: str, last_day: str, reason: str, extra_systems: list, api_key: str, ) -> str: if not api_key or not api_key.strip(): api_key = os.environ.get("OPENAI_API_KEY", "") if not api_key: return _error_html( "No API Key", "Please provide your OpenAI API key in the field above, or set the " "OPENAI_API_KEY environment variable / HF Space secret.", ) if not name.strip(): return _error_html("Missing Input", "Please enter the employee name.") last_day_str = last_day if isinstance(last_day, str) else str(last_day) systems_list = ["Google Workspace", "Slack", "GitHub", "Google Drive"] + (extra_systems or []) systems_str = ", ".join(systems_list) prompt = f"""You are an enterprise IT security agent processing an employee offboarding. Employee details: - Name: {name} - Email: {email} - Role: {role} - Department: {department} - Manager: {manager_name} ({manager_email}) - Last Working Day: {last_day_str} - Reason: {reason} - Systems to revoke: {systems_str} Generate a realistic offboarding report. Return ONLY valid JSON with this exact structure: {{ "offboarding_id": "OB-YYYYMMDD-XXXX", "risk_level": "LOW|MEDIUM|HIGH", "systems_revoked": {json.dumps(systems_list)}, "files_transferred": , "audit_timeline": [ {{"time": "T+0min", "action": "Offboarding initiated", "status": "completed"}}, {{"time": "T+1min", "action": "Google Workspace suspended", "status": "completed"}} ], "compliance_flags": ["flag 1 if any"], "audit_summary": "2-3 sentence audit summary", "it_email_subject": "...", "it_email_body": "...", "legal_email_subject": "...", "legal_email_body": "...", "manager_handoff_notes": "..." }} Make the timeline realistic (6-10 steps). Set risk_level based on role seniority and department. Finance/Legal/C-suite = HIGH. Senior IC = MEDIUM. Junior = LOW. For Finance add SOX flags. For Sales add CRM handoff flags. For VP+ add elevated security flags.""" try: from openai import OpenAI client = OpenAI(api_key=api_key.strip()) response = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0.3, max_tokens=1800, response_format={"type": "json_object"}, ) raw = response.choices[0].message.content data = json.loads(raw) except Exception as exc: return _error_html("API Error", f"{str(exc)}") return _render_report(data, name, role, department, manager_name, last_day_str) def _error_html(title: str, body: str) -> str: return f"""
    ❌ {title}
    {body}
    """ def _render_report(data: dict, name: str, role: str, dept: str, manager: str, last_day: str) -> str: risk = data.get("risk_level", "MEDIUM") risk_color, risk_bg = RISK_COLORS.get(risk, ("#e2e8f0", "rgba(50,50,80,0.4)")) oid = data.get("offboarding_id", "OB-UNKNOWN") files = data.get("files_transferred", 0) systems = data.get("systems_revoked", []) timeline = data.get("audit_timeline", []) flags = data.get("compliance_flags", []) summary = data.get("audit_summary", "") it_subj = data.get("it_email_subject", "") it_body = data.get("it_email_body", "") legal_subj = data.get("legal_email_subject", "") legal_body = data.get("legal_email_body", "") handoff = data.get("manager_handoff_notes", "") chips = "".join(_system_chip(s) for s in systems) tl_rows = "" for item in timeline: tl_rows += f""" {item.get('time','')} ✅ {item.get('action','')} """ flags_html = "" if flags: items = "".join( f'
  • ⚠️ {f}
  • ' for f in flags ) flags_html = f"""
    ⚠️ Compliance Flags
    """ else: flags_html = """
    ✅ No compliance flags raised
    """ def email_block(label, to, subject, body_text, accent): body_escaped = body_text.replace("\n", "
    ") return f"""
    📧 {label}
    To: {to}
    Subject: {subject}
    {body_escaped}
    """ return f"""
    🔒 {name}
    {role} · {dept} · Last day: {last_day} · Manager: {manager}
    ID: {oid}
    {_badge('OFFBOARDING COMPLETE ✅', 'rgba(21,128,61,0.45)', '#86efac')} {_badge('Risk: ' + risk, risk_bg, risk_color)}
    🛡️ Systems Revoked
    {chips}
    📁 {files:,} Google Drive files transferred to {manager}
    ⏱️ Action Timeline
    {tl_rows}
    {flags_html}
    📋 Audit Summary

    {summary}

    📝 Manager Handoff Notes

    {handoff}

    📨 Compliance Email Previews
    {email_block('Email to IT Security', 'it-security@company.com', it_subj, it_body, 'rgba(99,102,241,0.4)')} {email_block('Email to Legal / Compliance', 'legal@company.com', legal_subj, legal_body, 'rgba(239,68,68,0.4)')}
    """ # --------------------------------------------------------------------------- # How It Works — static HTML # --------------------------------------------------------------------------- HOW_IT_WORKS_HTML = """

    🔧 n8n Workflow Overview

    This agent is powered by an n8n automation workflow that chains together API calls to every system in your tech stack. HR triggers the workflow via a webhook or form submission — everything else is hands-free.

    📐 Workflow Steps

    Step 1 — HR Trigger
    Webhook receives employee data from HR system (Workday, BambooHR, etc.) or manual form. Validates required fields and routes to risk assessment.
    Step 2 — Risk Assessment
    AI node evaluates role, department, and access level to assign LOW / MEDIUM / HIGH risk. HIGH risk triggers immediate revocation; others follow standard schedule.
    Step 3 — Google Workspace
    Calls Google Admin SDK: suspends account, revokes OAuth tokens, strips admin roles. Parallel sub-flow initiates Drive file transfer to manager.
    Step 4 — Slack
    Calls Slack Admin API: deactivates user, removes from all channels and user groups, revokes active sessions and OAuth tokens.
    Step 5 — GitHub
    Calls GitHub REST API: removes org membership, removes from all teams, revokes SSH keys and personal access tokens.
    Step 6 — Additional Systems
    Conditional branches handle Salesforce (CRM handoff), Jira (ticket reassignment), AWS (IAM deactivation), Figma, and Notion based on selected systems.
    Step 7 — Audit Report
    Aggregates all action results, timestamps, and compliance flags into a structured audit report. Logs to immutable compliance database with unique Offboarding ID.
    Step 8 — Compliance Notification
    Sends formatted confirmation emails to IT Security and Legal/Compliance teams. Optionally triggers Slack notification to manager with handoff notes.

    🔐 Credentials Required

    • Google Workspace Admin SDK
    • Google Drive API (service account)
    • Slack Admin API token
    • GitHub Personal Access Token (org:admin)
    • Salesforce Connected App (optional)
    • AWS IAM credentials (optional)
    • SMTP credentials for email

    ⚡ Performance

    Average completion time
    Under 8 minutes
    Manual steps required
    Zero
    Audit trail
    100% immutable

    🏗️ Tech Stack

    n8n Automation GPT-4o-mini Google Workspace API Slack Admin API GitHub REST API Gradio 5.9.1
    """ # --------------------------------------------------------------------------- # Build the Gradio app # --------------------------------------------------------------------------- with gr.Blocks( theme=gr.themes.Soft(primary_hue="red", secondary_hue="indigo"), title="Employee Offboarding Security Agent", css=""" .gradio-container { max-width: 960px !important; margin: 0 auto; } footer { display: none !important; } """, ) as demo: gr.HTML("""
    🔒

    Employee Offboarding Security Agent

    Zero-touch offboarding · Access revoked in <8 minutes · Full audit trail

    ⚡ Powered by n8n 🛡️ Zero Manual Steps 📋 SOX / GDPR Compliant
    """) with gr.Tabs(): # ================================================================== # TAB 1 — Live Demo # ================================================================== with gr.TabItem("🔒 Live Demo"): gr.HTML("""
    Pre-computed scenarios — no API key required. Select an employee to view their full offboarding report.
    """) scenario_selector = gr.Radio( choices=list(SCENARIOS.keys()), value=list(SCENARIOS.keys())[0], label="Select Offboarding Scenario", interactive=True, ) demo_output = gr.HTML(value=render_demo_card(list(SCENARIOS.keys())[0])) scenario_selector.change( fn=render_demo_card, inputs=scenario_selector, outputs=demo_output, ) # ================================================================== # TAB 2 — Run Offboarding # ================================================================== with gr.TabItem("👤 Run Offboarding"): gr.HTML("""
    🔑 Requires an OpenAI API key to call GPT-4o-mini. Set the OPENAI_API_KEY HF Space secret or enter it below.
    """) with gr.Row(): with gr.Column(): emp_name = gr.Textbox(label="Employee Name", placeholder="e.g. Jane Smith") emp_email = gr.Textbox(label="Employee Email", placeholder="jane@company.com") emp_role = gr.Textbox(label="Role / Title", placeholder="e.g. Senior Engineer") emp_dept = gr.Textbox(label="Department", placeholder="e.g. Engineering") with gr.Column(): mgr_name = gr.Textbox(label="Manager Name", placeholder="e.g. Tom Bradley") mgr_email = gr.Textbox(label="Manager Email", placeholder="tom@company.com") last_day = gr.Textbox( label="Last Working Day", placeholder="e.g. 2024-07-31", value=date.today().isoformat(), ) reason = gr.Dropdown( choices=["Resignation", "Termination", "Retirement", "Contract End"], value="Resignation", label="Reason for Offboarding", ) extra_systems = gr.CheckboxGroup( choices=["Salesforce", "Jira", "AWS", "Figma", "Notion"], label="Additional Systems to Revoke (beyond Google/Slack/GitHub)", value=[], ) api_key_input = gr.Textbox( label="OpenAI API Key (or set OPENAI_API_KEY secret)", placeholder="sk-...", type="password", ) run_btn = gr.Button("🚀 Run Offboarding Agent", variant="primary", size="lg") report_output = gr.HTML( value="""
    Fill in the employee details above and click Run Offboarding Agent to generate a full report.
    """ ) run_btn.click( fn=generate_offboarding_report, inputs=[ emp_name, emp_email, emp_role, emp_dept, mgr_name, mgr_email, last_day, reason, extra_systems, api_key_input, ], outputs=report_output, ) # ================================================================== # TAB 3 — How It Works # ================================================================== with gr.TabItem("⚙️ How It Works"): gr.HTML(HOW_IT_WORKS_HTML) gr.HTML("""
    Employee Offboarding Security Agent · Built with n8n + Gradio 5.9.1 · MIT License
    """) if __name__ == "__main__": demo.launch()