| import os |
| import json |
| import textwrap |
| from datetime import datetime |
| from typing import Any, Dict, List, Tuple |
|
|
| import gradio as gr |
|
|
| try: |
| from anthropic import Anthropic |
| except ImportError: |
| Anthropic = None |
|
|
|
|
| APP_TITLE = "Business-to-Technical AI On-Ramp β Adaptive Technical Coach" |
| CS50_EMBED_URL = "https://www.youtube.com/embed/JP7ITIXGpHk" |
| DEFAULT_MODEL = os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-6") |
| DEFAULT_BASE_URL = os.getenv("ANTHROPIC_BASE_URL", "https://api.anthropic.com") |
| SERVER_API_KEY = os.getenv("ANTHROPIC_API_KEY", "") |
| ANTHROPIC_VERSION = os.getenv("ANTHROPIC_VERSION", "2023-06-01") |
|
|
|
|
| def md(text: str) -> str: |
| return textwrap.dedent(text).strip() |
|
|
|
|
| def now_string() -> str: |
| return datetime.now().strftime("%Y-%m-%d %H:%M") |
|
|
|
|
| def default_profile() -> Dict[str, Any]: |
| return { |
| "name": "Learner", |
| "goal": "Become technically fluent enough to understand, scope, and discuss AI/software systems confidently.", |
| "background": "Business / product / operations", |
| "hours_per_week": 4, |
| "track": "Business-to-Technical Foundations", |
| "completed_lessons": [], |
| "weak_areas": [], |
| "notes": [], |
| "usage_count": 0, |
| "last_visit": "First visit", |
| "favorite_topics": [], |
| } |
|
|
|
|
| CURRICULUM: Dict[str, Dict[str, Any]] = { |
| "Orientation": { |
| "level": "Foundational", |
| "objective": "Build the mental model for how software, data, and AI systems fit together.", |
| "lessons": { |
| "What software engineering actually is": md(""" |
| # What software engineering actually is |
| |
| **Plain-English view** |
| Software engineering is the discipline of building software so that it works, stays understandable, |
| can be improved safely, and does not collapse the moment requirements change. |
| |
| **Business analogy** |
| It is the difference between a one-off spreadsheet heroics effort and a repeatable, documented, |
| quality-controlled operating process. |
| |
| **Technical view** |
| Software engineering is not only writing code. It also includes: |
| - structuring files and modules |
| - version control |
| - testing |
| - debugging |
| - interfaces and APIs |
| - deployment |
| - maintenance and iteration |
| |
| **What beginners usually misunderstand** |
| 1. They think code = software. |
| 2. They underestimate maintenance. |
| 3. They do not realize the importance of environment consistency, interfaces, and testing. |
| |
| **What to internalize** |
| Good software is code plus process plus clarity plus reliability. |
| |
| **Mini exercise** |
| Take a tool you use at work and describe: |
| - what inputs it needs |
| - what outputs it creates |
| - what could break |
| - who maintains it |
| """), |
| "How an AI product is layered": md(""" |
| # How an AI product is layered |
| |
| A useful AI product usually has multiple layers: |
| 1. **User interface layer** β where the person interacts with the app. |
| 2. **Application logic layer** β rules, routing, and business logic. |
| 3. **Model layer** β ML model, LLM, or rules engine. |
| 4. **Data layer** β files, databases, APIs, vector stores, logs. |
| 5. **Deployment layer** β hosting, containers, CI/CD, observability. |
| |
| **Why this matters** |
| A lot of business-side learners think the model is the whole product. |
| It almost never is. |
| |
| **Key takeaway** |
| A product is a system, not a single model call. |
| """), |
| "Rules vs ML vs LLM": md(""" |
| # Rules vs ML vs LLM |
| |
| **Rules engine** |
| Use this when the logic is explicit and deterministic. |
| Example: "If customer is platinum and amount < threshold, route to fast lane." |
| |
| **Classical ML** |
| Use this when you have structured data and want to predict a label, class, or number. |
| Example: churn prediction, fraud scoring, forecasting. |
| |
| **LLM workflow** |
| Use this when the task is language-heavy or knowledge-heavy. |
| Example: summarization, extraction, search, drafting, document Q&A. |
| |
| **Practical lesson** |
| Not every business problem is an LLM problem. |
| Many are better solved with rules first. |
| """), |
| }, |
| }, |
| "Python & Programming": { |
| "level": "Foundational", |
| "objective": "Become comfortable reading code and understanding basic logic.", |
| "lessons": { |
| "Reading Python without panic": md(""" |
| # Reading Python without panic |
| |
| When reading Python, scan in this order: |
| 1. Imports |
| 2. Functions |
| 3. Inputs |
| 4. Outputs |
| 5. Control flow |
| 6. Main app wiring |
| |
| **Common building blocks** |
| - variables store values |
| - functions package behavior |
| - conditionals choose among branches |
| - loops repeat work |
| - dictionaries map keys to values |
| - lists store sequences |
| |
| **Best first habit** |
| Do not try to understand every character. |
| First answer: what problem is this script solving? |
| """), |
| "Functions, inputs, outputs, and control flow": md(""" |
| # Functions, inputs, outputs, and control flow |
| |
| A function is a reusable unit of logic. |
| |
| ```python |
| def classify_budget(amount): |
| if amount < 1000: |
| return "small" |
| elif amount < 10000: |
| return "medium" |
| return "large" |
| ``` |
| |
| **How to read it** |
| - input: `amount` |
| - internal logic: compare against thresholds |
| - output: a category string |
| |
| **Why this matters** |
| Many production systems are still mostly functions wrapped inside APIs or user interfaces. |
| """), |
| "Files, modules, and project structure": md(""" |
| # Files, modules, and project structure |
| |
| Beginners often put everything in one file. That works for day 1, but scales badly. |
| |
| **Typical small project structure** |
| ```text |
| project/ |
| βββ app.py |
| βββ requirements.txt |
| βββ README.md |
| βββ utils.py |
| βββ tests/ |
| ``` |
| |
| **Mental model** |
| - `app.py` = main entry point |
| - `utils.py` = helper logic |
| - `README.md` = explanation for humans |
| - `requirements.txt` = dependencies |
| - `tests/` = checks that reduce breakage |
| """), |
| }, |
| }, |
| "APIs & Data Exchange": { |
| "level": "Core", |
| "objective": "Understand how software systems talk to each other.", |
| "lessons": { |
| "What an API is": md(""" |
| # What an API is |
| |
| An API is a contract for software-to-software communication. |
| |
| **Simple model** |
| - client sends request |
| - server receives request |
| - server returns response |
| |
| **Typical API concepts** |
| - endpoint |
| - method / verb |
| - authentication |
| - request body |
| - response body |
| - status code |
| |
| **Why this matters** |
| In production, models are often exposed behind APIs rather than being run manually inside notebooks. |
| """), |
| "JSON, GET, POST, and status codes": md(""" |
| # JSON, GET, POST, and status codes |
| |
| **JSON** |
| A text format that represents structured data. |
| |
| Example: |
| ```json |
| { |
| "customer": "Acme", |
| "priority": "high", |
| "amount": 2500 |
| } |
| ``` |
| |
| **HTTP verbs** |
| - `GET` = read |
| - `POST` = create or trigger |
| - `PUT/PATCH` = update |
| - `DELETE` = remove |
| |
| **Status codes** |
| - `200` = success |
| - `400` = client error / bad request |
| - `401` = unauthorized |
| - `404` = not found |
| - `500` = server error |
| """), |
| "FastAPI in plain English": md(""" |
| # FastAPI in plain English |
| |
| FastAPI is a Python framework for turning Python functions into web endpoints. |
| |
| **Why people like it** |
| - fast to develop |
| - strongly typed |
| - clean request/response validation |
| - auto-generated docs |
| |
| **Conceptual pattern** |
| Python function -> API endpoint -> request validation -> response |
| """), |
| }, |
| }, |
| "Deployment & Platform": { |
| "level": "Core", |
| "objective": "Understand how apps move from local code to a live service.", |
| "lessons": { |
| "Git and GitHub": md(""" |
| # Git and GitHub |
| |
| Git is version control. GitHub is a hosted platform for repositories and collaboration. |
| |
| **Why it matters** |
| - keeps history |
| - supports branching |
| - enables pull requests |
| - acts as the trigger point for automation |
| |
| **Mental model** |
| Git is the memory of your project. |
| """), |
| "Docker and containers": md(""" |
| # Docker and containers |
| |
| Docker helps package an app with its environment so it runs more consistently. |
| |
| **Important distinction** |
| - image = blueprint |
| - container = running instance |
| |
| **Why it matters** |
| It reduces the classic problem: "it works on my machine." |
| """), |
| "CI/CD and GitHub Actions": md(""" |
| # CI/CD and GitHub Actions |
| |
| CI/CD automates checks and sometimes deployments when code changes. |
| |
| **Typical flow** |
| push code -> run tests -> build app -> optionally deploy |
| |
| **Why this matters for a business-side learner** |
| It helps you understand why engineering teams care about pipelines before release. |
| """), |
| "Kubernetes without the hype": md(""" |
| # Kubernetes without the hype |
| |
| Kubernetes is a system for orchestrating containers at scale. |
| |
| **Important advice** |
| Do not start here. |
| Understand scripts, APIs, Git, and containers first. |
| |
| **Words to recognize** |
| - pod |
| - deployment |
| - service |
| - ingress |
| - secret |
| """), |
| }, |
| }, |
| "AI Product Architecture": { |
| "level": "Advanced beginner", |
| "objective": "Connect business problems to realistic technical architectures.", |
| "lessons": { |
| "From idea to architecture": md(""" |
| # From idea to architecture |
| |
| A useful first-pass architecture answer should name: |
| - user |
| - trigger |
| - inputs |
| - transformation steps |
| - model / logic layer |
| - outputs |
| - storage/logging |
| - deployment target |
| |
| **Practical rule** |
| If you cannot explain the system in boxes and arrows, the scope is still too fuzzy. |
| """), |
| "MLOps at a high level": md(""" |
| # MLOps at a high level |
| |
| MLOps is the operational discipline around machine learning systems. |
| |
| It includes: |
| - data versioning |
| - experiment tracking |
| - deployment |
| - monitoring |
| - retraining / iteration |
| |
| **Key distinction** |
| A model notebook is not a production ML system. |
| """), |
| "LLM apps, RAG, agents, and MCP": md(""" |
| # LLM apps, RAG, agents, and MCP |
| |
| **LLM app** |
| Uses a language model for reasoning or generation. |
| |
| **RAG** |
| Retrieval-augmented generation combines retrieval of relevant context with generation. |
| |
| **Agent** |
| A workflow where the model can select from tools or take multiple action steps. |
| |
| **MCP** |
| A standard for exposing tools, resources, and prompts to AI applications. |
| """), |
| }, |
| }, |
| } |
|
|
|
|
| REFERENCE_LIBRARY = md(""" |
| # Free references |
| |
| ## Programming and software foundations |
| - CS50 Python: https://cs50.harvard.edu/python |
| - CS50 YouTube channel: https://www.youtube.com/cs50 |
| - Pro Git: https://git-scm.com/book/en/v2 |
| |
| ## AI and product building |
| - Hugging Face Learn: https://huggingface.co/learn |
| - Hugging Face LLM Course: https://huggingface.co/learn/llm-course/chapter1/1 |
| - Full Stack Deep Learning: https://fullstackdeeplearning.com/ |
| |
| ## Developer docs |
| - FastAPI: https://fastapi.tiangolo.com/ |
| - Requests: https://requests.readthedocs.io/ |
| - Docker Get Started: https://docs.docker.com/get-started/ |
| - GitHub Actions: https://docs.github.com/actions |
| - Kubernetes docs: https://kubernetes.io/docs/home/ |
| - Model Context Protocol: https://modelcontextprotocol.io/ |
| |
| ## Hugging Face / Gradio docs |
| - Spaces Overview: https://huggingface.co/docs/hub/spaces-overview |
| - Spaces Config Reference: https://huggingface.co/docs/hub/spaces-config-reference |
| - Gradio Docs: https://www.gradio.app/docs |
| - Gradio ChatInterface: https://www.gradio.app/docs/gradio/chatinterface |
| - Gradio State in Blocks: https://www.gradio.app/guides/state-in-blocks |
| """) |
|
|
|
|
| QUIZ_BANK: Dict[str, Dict[str, Any]] = { |
| "API Fundamentals": { |
| "questions": [ |
| { |
| "prompt": "What is the best plain-English description of an API?", |
| "choices": [ |
| "A machine learning model registry", |
| "A contract for software-to-software communication", |
| "A file format for Docker images", |
| ], |
| "answer": "A contract for software-to-software communication", |
| "explanation": "APIs define how one system asks another for data or actions.", |
| }, |
| { |
| "prompt": "Which HTTP method is most associated with creating or triggering work?", |
| "choices": ["GET", "POST", "DELETE"], |
| "answer": "POST", |
| "explanation": "POST is commonly used to submit data or create work on the server.", |
| }, |
| { |
| "prompt": "What does a 404 status code usually mean?", |
| "choices": ["Unauthorized", "Server exploded", "Resource not found"], |
| "answer": "Resource not found", |
| "explanation": "404 indicates that the requested endpoint or resource could not be found.", |
| }, |
| ], |
| }, |
| "Deployment Basics": { |
| "questions": [ |
| { |
| "prompt": "What is a Docker image?", |
| "choices": [ |
| "A blueprint used to create a running container", |
| "A screenshot of a deployed app", |
| "A Git branch used for production", |
| ], |
| "answer": "A blueprint used to create a running container", |
| "explanation": "An image is the packaged recipe; a container is the running instance.", |
| }, |
| { |
| "prompt": "What is the main purpose of CI/CD?", |
| "choices": [ |
| "To automate build, test, and deployment workflows", |
| "To compress videos for production", |
| "To store passwords in a repository", |
| ], |
| "answer": "To automate build, test, and deployment workflows", |
| "explanation": "CI/CD reduces manual release friction and catches issues earlier.", |
| }, |
| { |
| "prompt": "What is the best beginner advice about Kubernetes?", |
| "choices": [ |
| "Start there before learning Git", |
| "Ignore containers and skip straight to clusters", |
| "Learn scripts, APIs, Git, and containers first", |
| ], |
| "answer": "Learn scripts, APIs, Git, and containers first", |
| "explanation": "Kubernetes makes much more sense after foundational deployment concepts.", |
| }, |
| ], |
| }, |
| } |
|
|
|
|
| CODE_LABS: Dict[str, Dict[str, str]] = { |
| "Read a simple Python function": { |
| "code": md(""" |
| def classify_ticket(priority, customer_tier): |
| if priority == "critical": |
| return "Escalate immediately" |
| if customer_tier == "enterprise": |
| return "Fast-track review" |
| return "Normal queue" |
| """), |
| "walkthrough": md(""" |
| ## Walkthrough |
| |
| This function accepts two inputs: |
| - `priority` |
| - `customer_tier` |
| |
| It applies **ordered decision logic**: |
| 1. If priority is critical, it immediately escalates. |
| 2. Otherwise, if the customer is enterprise, it fast-tracks. |
| 3. Otherwise, it uses the normal queue. |
| |
| **Why this matters** |
| This is a simple rules engine. Not every automation problem needs ML. |
| """), |
| }, |
| "Read a tiny API example": { |
| "code": md(""" |
| from fastapi import FastAPI |
| |
| app = FastAPI() |
| |
| @app.get("/health") |
| def health(): |
| return {"status": "ok"} |
| """), |
| "walkthrough": md(""" |
| ## Walkthrough |
| |
| - `FastAPI()` creates the application. |
| - `@app.get("/health")` defines an endpoint. |
| - When that endpoint is called, the function returns JSON. |
| |
| **Why health checks exist** |
| Operations teams want a lightweight way to ask: "Is this service alive?" |
| """), |
| }, |
| "Read a tiny CI workflow": { |
| "code": md(""" |
| name: ci |
| on: [push, pull_request] |
| |
| jobs: |
| test: |
| runs-on: ubuntu-latest |
| steps: |
| - uses: actions/checkout@v4 |
| - uses: actions/setup-python@v5 |
| with: |
| python-version: '3.10' |
| - run: python -m py_compile app.py |
| """), |
| "walkthrough": md(""" |
| ## Walkthrough |
| |
| This workflow says: |
| - run on push or pull request |
| - create a job named `test` |
| - use a GitHub-hosted Ubuntu machine |
| - check out the repo |
| - install Python 3.10 |
| - syntax-check `app.py` |
| |
| **Why it matters** |
| This is a quality gate. Even a basic workflow helps catch obvious errors before release. |
| """), |
| }, |
| } |
|
|
|
|
| def all_lessons() -> List[str]: |
| values = [] |
| for module in CURRICULUM.values(): |
| values.extend(module["lessons"].keys()) |
| return values |
|
|
|
|
| def build_dashboard(profile: Dict[str, Any]) -> str: |
| total_lessons = sum(len(module["lessons"]) for module in CURRICULUM.values()) |
| completed = profile.get("completed_lessons", []) |
| weak_areas = profile.get("weak_areas", []) |
| notes = profile.get("notes", [])[-5:] |
|
|
| next_lesson = None |
| for module in CURRICULUM.values(): |
| for lesson_name in module["lessons"].keys(): |
| if lesson_name not in completed: |
| next_lesson = lesson_name |
| break |
| if next_lesson: |
| break |
|
|
| note_lines = "\n".join([f"- {n}" for n in notes]) if notes else "- No notes saved yet" |
| weak_lines = "\n".join([f"- {w}" for w in weak_areas]) if weak_areas else "- No weak areas flagged yet" |
|
|
| pct = round((len(completed) / total_lessons) * 100, 1) if total_lessons else 0.0 |
|
|
| return md(f""" |
| # Learning Dashboard |
| |
| **Learner:** {profile.get('name', 'Learner')} |
| |
| **Current track:** {profile.get('track', 'Business-to-Technical Foundations')} |
| |
| **Goal:** {profile.get('goal', '')} |
| |
| **Background:** {profile.get('background', '')} |
| |
| **Hours per week:** {profile.get('hours_per_week', 4)} |
| |
| **Progress:** {len(completed)}/{total_lessons} lessons marked complete ({pct}%) |
| |
| **Recommended next lesson:** {next_lesson or 'You completed everything currently loaded. Expand the curriculum next.'} |
| |
| **Last visit:** {profile.get('last_visit', 'Unknown')} |
| |
| **Usage count:** {profile.get('usage_count', 0)} |
| |
| ## Weak areas to revisit |
| {weak_lines} |
| |
| ## Recent saved notes |
| {note_lines} |
| """) |
|
|
|
|
| def load_profile(profile_state: Dict[str, Any]): |
| profile = profile_state or default_profile() |
| profile["usage_count"] = int(profile.get("usage_count", 0)) + 1 |
| profile["last_visit"] = now_string() |
| completed = profile.get("completed_lessons", []) |
| return ( |
| profile, |
| build_dashboard(profile), |
| profile.get("name", "Learner"), |
| profile.get("goal", ""), |
| profile.get("background", "Business / product / operations"), |
| profile.get("hours_per_week", 4), |
| profile.get("track", "Business-to-Technical Foundations"), |
| completed, |
| ) |
|
|
|
|
| def save_profile(name: str, goal: str, background: str, hours: int, track: str, completed: List[str], profile_state: Dict[str, Any]): |
| profile = profile_state or default_profile() |
| profile.update({ |
| "name": name or "Learner", |
| "goal": goal or profile.get("goal", ""), |
| "background": background or profile.get("background", ""), |
| "hours_per_week": int(hours), |
| "track": track, |
| "completed_lessons": sorted(set(completed or [])), |
| "last_visit": now_string(), |
| }) |
| return profile, build_dashboard(profile) |
|
|
|
|
| def add_note(note_text: str, profile_state: Dict[str, Any]): |
| profile = profile_state or default_profile() |
| clean = (note_text or "").strip() |
| if clean: |
| profile.setdefault("notes", []).append(f"{now_string()} β {clean}") |
| return profile, build_dashboard(profile), "" |
|
|
|
|
| def render_module_summary(module_name: str) -> str: |
| module = CURRICULUM[module_name] |
| lesson_lines = "\n".join([f"- {lesson}" for lesson in module["lessons"].keys()]) |
| return md(f""" |
| # {module_name} |
| |
| **Level:** {module['level']} |
| |
| **Objective:** {module['objective']} |
| |
| ## Lessons |
| {lesson_lines} |
| """) |
|
|
|
|
| def render_lesson(module_name: str, lesson_name: str) -> str: |
| return CURRICULUM[module_name]["lessons"][lesson_name] |
|
|
|
|
| def get_lesson_choices(module_name: str): |
| lessons = list(CURRICULUM[module_name]["lessons"].keys()) |
| return gr.Dropdown(choices=lessons, value=lessons[0]) |
|
|
|
|
| def generate_30_day_plan(track: str, hours: int, background: str, goal: str) -> str: |
| hours = int(hours) |
| pace_note = ( |
| "Keep the scope intentionally small: one concept block plus one hands-on interaction per week." |
| if hours <= 4 |
| else "You have enough time to combine concept learning with small implementation exercises every week." |
| ) |
|
|
| return md(f""" |
| # Personalized 30-Day Plan |
| |
| **Track:** {track} |
| |
| **Background:** {background} |
| |
| **Weekly time budget:** {hours} hours |
| |
| **Primary goal:** {goal} |
| |
| **Pacing note:** {pace_note} |
| |
| ## Week 1 β Mental models |
| - Learn the difference between rules, ML, and LLM workflows. |
| - Read the Orientation module in this app. |
| - Watch the embedded CS50 introduction video. |
| - Write a one-page summary of how an AI product is layered. |
| |
| ## Week 2 β Read code without panic |
| - Work through the Python & Programming module. |
| - Use the Code Lab tab to explain one snippet in your own words. |
| - Learn what functions, conditionals, dictionaries, and files do. |
| |
| ## Week 3 β APIs and services |
| - Learn JSON, GET, POST, and status codes. |
| - Read the FastAPI lesson. |
| - Explain what `/health` and `/predict` would mean in a product meeting. |
| |
| ## Week 4 β Deployment thinking |
| - Learn Git, Docker, and CI/CD at a conceptual level. |
| - Use the Architecture Coach tab with 2-3 business problems. |
| - End by writing your own architecture summary for one realistic product idea. |
| |
| ## Success criteria after 30 days |
| 1. You can explain what an API is in plain English and technical language. |
| 2. You can describe the difference between a script, a service, a container, and deployment. |
| 3. You can match a business problem to a first-pass technical approach. |
| 4. You can read a small app repo without feeling overwhelmed. |
| """) |
|
|
|
|
| def architecture_coach(problem: str, data_readiness: str, risk_level: str, horizon: str, delivery_target: str) -> str: |
| text = (problem or "").lower() |
|
|
| if any(word in text for word in ["summarize", "extract", "search", "document", "chat", "email"]): |
| approach = "LLM or retrieval-style workflow" |
| why = "The problem looks language-heavy and context-heavy." |
| stack = "Python + Gradio or FastAPI + model API + retrieval layer if needed" |
| elif any(word in text for word in ["predict", "forecast", "classify", "churn", "fraud", "score"]): |
| approach = "Supervised ML prototype" |
| why = "The problem appears to need structured prediction." |
| stack = "Python + pandas + baseline ML + FastAPI or notebook-to-service progression" |
| elif any(word in text for word in ["route", "approve", "quote", "policy", "form", "workflow", "if ", "then"]): |
| approach = "Rules engine / workflow automation first" |
| why = "The logic sounds structured enough that deterministic rules may beat AI initially." |
| stack = "Python decision rules + UI + audit logging" |
| else: |
| approach = "Process mapping first, AI second" |
| why = "The problem statement is still broad or underspecified." |
| stack = "Map workflow, define inputs and outputs, then choose rules/ML/LLM" |
|
|
| data_note = { |
| "No clean data": "Expect a painful data-preparation phase before promising ML performance.", |
| "Some spreadsheets / exports": "Good enough for a prototype if the fields are interpretable.", |
| "Clean structured data": "Strong starting position for scoped prototyping.", |
| "Mostly documents / text": "Natural fit for extraction, retrieval, summarization, or chat workflows.", |
| }[data_readiness] |
|
|
| risk_note = { |
| "Low": "Move fast with feedback loops.", |
| "Medium": "Add explicit review checkpoints and lightweight validation.", |
| "High / regulated": "Bias hard toward auditability, human review, traceability, and narrow scope.", |
| }[risk_level] |
|
|
| target_note = { |
| "Clickable demo": "Gradio is an excellent first destination.", |
| "Internal service": "FastAPI plus authentication and logging becomes more relevant.", |
| "Production-minded prototype": "Think in terms of APIs, containers, tests, and deployment discipline.", |
| }[delivery_target] |
|
|
| return md(f""" |
| # Architecture recommendation |
| |
| **Recommended first approach:** {approach} |
| |
| **Why:** {why} |
| |
| **Suggested starter stack:** {stack} |
| |
| **Data note:** {data_note} |
| |
| **Risk note:** {risk_note} |
| |
| **Timeline:** {horizon} |
| |
| **Delivery target note:** {target_note} |
| |
| ## First three actions |
| 1. Write the exact business decision the tool should improve. |
| 2. Gather 10-20 realistic examples. |
| 3. Define what a good output looks like before adding more complexity. |
| """) |
|
|
|
|
| def render_code_lab(lab_name: str) -> Tuple[str, str]: |
| lab = CODE_LABS[lab_name] |
| return lab["code"], lab["walkthrough"] |
|
|
|
|
| def get_quiz_ui(quiz_name: str): |
| questions = QUIZ_BANK[quiz_name]["questions"] |
| updates = [] |
| for q in questions: |
| updates.append(gr.Radio(choices=q["choices"], label=q["prompt"], value=None)) |
| return updates |
|
|
|
|
| def grade_quiz(quiz_name: str, q1: str, q2: str, q3: str, profile_state: Dict[str, Any]): |
| profile = profile_state or default_profile() |
| questions = QUIZ_BANK[quiz_name]["questions"] |
| answers = [q1, q2, q3] |
| score = 0 |
| feedback = [] |
|
|
| for idx, (question, user_answer) in enumerate(zip(questions, answers), start=1): |
| correct = question["answer"] |
| if user_answer == correct: |
| score += 1 |
| feedback.append(f"β
Q{idx}: Correct β {question['explanation']}") |
| else: |
| feedback.append(f"β Q{idx}: Correct answer = **{correct}** β {question['explanation']}") |
|
|
| if score < len(questions): |
| profile.setdefault("weak_areas", []).append(quiz_name) |
| profile["weak_areas"] = sorted(set(profile["weak_areas"])) |
|
|
| result = md("\n".join([f"- {line}" for line in feedback])) |
| summary = md(f""" |
| # Quiz score: {score}/{len(questions)} |
| |
| {result} |
| """) |
| return profile, summary, build_dashboard(profile) |
|
|
|
|
| TUTOR_SYSTEM_PROMPT = md(""" |
| You are an adaptive technical tutor for a business-to-technical AI learning app. |
| Your job is to teach clearly, patiently, and concretely. |
| |
| Priorities: |
| 1. Explain in plain English first. |
| 2. Then give a more technical version. |
| 3. Use business analogies when helpful. |
| 4. Never assume the learner already knows software engineering vocabulary. |
| 5. Keep answers practical, structured, and confidence-building. |
| 6. When asked about tools, explain when to use them and when not to. |
| 7. When asked about architecture, map the problem to rules vs ML vs LLM thinking. |
| 8. Encourage the learner to practice with a tiny next step. |
| """) |
|
|
|
|
| def local_tutor_response(message: str, mode: str, current_lesson: str, track: str) -> str: |
| text = (message or "").lower() |
|
|
| intro = { |
| "Beginner-friendly": "I'll explain this in plain English first.", |
| "Business analogy": "I'll frame this like a business process and operating model discussion.", |
| "Technical transition": "I'll bridge from business understanding into technical implementation language.", |
| "Quiz me": "I'll answer, then end with a short self-check question.", |
| }[mode] |
|
|
| if any(k in text for k in ["api", "endpoint", "json", "post", "get"]): |
| body = md(f""" |
| {intro} |
| |
| **API explanation** |
| An API is a structured contract for one software system to talk to another. |
| |
| **Plain-English version** |
| Think of it like a standardized intake form between teams. |
| If the form is filled out correctly, the receiving side knows what action to take. |
| |
| **Technical version** |
| A client sends an HTTP request to an endpoint using verbs like GET or POST. |
| The server validates the input, performs logic, and returns a response, often in JSON. |
| |
| **Why it matters** |
| In real AI products, models are usually wrapped behind APIs so other apps can call them safely. |
| |
| **Tiny next step** |
| Open the lesson **What an API is** or **JSON, GET, POST, and status codes** in this app and compare the vocabulary. |
| """) |
| elif any(k in text for k in ["docker", "container", "deployment"]): |
| body = md(f""" |
| {intro} |
| |
| **Docker explanation** |
| Docker packages an app with the environment it needs so it runs more consistently. |
| |
| **Plain-English version** |
| Instead of handing someone only the recipe, you also ship the kitchen setup. |
| |
| **Technical version** |
| A Docker image is the packaged blueprint. A container is the running instance created from that image. |
| |
| **Why it matters** |
| Deployment becomes easier when the runtime is standardized. |
| |
| **Tiny next step** |
| Read the **Docker and containers** lesson, then explain in one paragraph why 'works on my machine' is a deployment problem. |
| """) |
| elif any(k in text for k in ["kubernetes", "k8s"]): |
| body = md(f""" |
| {intro} |
| |
| **Kubernetes explanation** |
| Kubernetes manages containers across a larger environment. |
| |
| **Most important advice** |
| Do not start with Kubernetes. |
| Start with understanding scripts, services, Git, and containers first. |
| |
| **Technical version** |
| Kubernetes helps manage scaling, rollout, service discovery, and resilience for containerized apps. |
| |
| **Tiny next step** |
| Learn what a service, container, and health check are before diving deeper. |
| """) |
| elif any(k in text for k in ["mcp", "agent", "rag", "llm"]): |
| body = md(f""" |
| {intro} |
| |
| **LLM / RAG / agents / MCP explanation** |
| These all sit in the family of AI application design, but they are not the same thing. |
| |
| - **LLM app**: uses a language model directly. |
| - **RAG**: retrieves context first, then generates. |
| - **Agent**: can select tools or take multiple action steps. |
| - **MCP**: a standard way to expose tools, resources, and prompts to AI applications. |
| |
| **Tiny next step** |
| Read the lesson **LLM apps, RAG, agents, and MCP** and then ask me to compare two of those directly. |
| """) |
| else: |
| body = md(f""" |
| {intro} |
| |
| I can help with this, but I want to structure it so it actually builds skill. |
| |
| **Current track:** {track} |
| **Current lesson context:** {current_lesson} |
| |
| **A good way to think about your question** |
| 1. What is the business problem? |
| 2. What are the inputs and outputs? |
| 3. Is the first solution rules-based, ML-based, or LLM-based? |
| 4. What layer are we talking about: UI, API, model, data, or deployment? |
| |
| Send me the question again with one of these forms: |
| - "Explain X in plain English" |
| - "Compare X vs Y" |
| - "Turn this business problem into an architecture" |
| - "Quiz me on X" |
| - "Explain this code" |
| """) |
|
|
| if mode == "Quiz me": |
| body += "\n\n**Self-check:** In one sentence, what practical problem does this concept solve?" |
|
|
| return body |
|
|
|
|
| def call_anthropic_chat(message: str, history: List[Dict[str, Any]], mode: str, current_lesson: str, track: str, runtime_key: str, model_name: str, base_url: str) -> str: |
| api_key = (runtime_key or "").strip() or SERVER_API_KEY |
| model = (model_name or "").strip() or DEFAULT_MODEL |
| endpoint_base = (base_url or "").strip() or DEFAULT_BASE_URL |
|
|
| if not api_key or not model or Anthropic is None: |
| return local_tutor_response(message, mode, current_lesson, track) |
|
|
| system_prompt = TUTOR_SYSTEM_PROMPT + "\n\n" + f"Tutor mode: {mode}. Current lesson: {current_lesson}. Current track: {track}." |
|
|
| messages = [] |
| normalized_history = history or [] |
| for item in normalized_history[-8:]: |
| if isinstance(item, dict): |
| role = item.get("role", "user") |
| content = item.get("content", "") |
| if role in {"user", "assistant"} and str(content).strip(): |
| messages.append({"role": role, "content": str(content)}) |
| elif isinstance(item, (list, tuple)) and len(item) == 2: |
| user_msg, assistant_msg = item |
| if user_msg: |
| messages.append({"role": "user", "content": str(user_msg)}) |
| if assistant_msg: |
| messages.append({"role": "assistant", "content": str(assistant_msg)}) |
|
|
| messages.append({"role": "user", "content": message}) |
|
|
| try: |
| client_kwargs = {"api_key": api_key} |
| if endpoint_base and endpoint_base != "https://api.anthropic.com": |
| client_kwargs["base_url"] = endpoint_base.rstrip("/") |
| client = Anthropic(**client_kwargs) |
| response = client.messages.create( |
| model=model, |
| max_tokens=900, |
| system=system_prompt, |
| messages=messages, |
| ) |
| parts = [] |
| for block in getattr(response, "content", []) or []: |
| txt = getattr(block, "text", None) |
| if txt: |
| parts.append(txt) |
| if parts: |
| return "\n\n".join(parts) |
| return local_tutor_response(message, mode, current_lesson, track) |
| except Exception as exc: |
| return md(f""" |
| I could not reach the live Anthropic API, so I am falling back to the built-in tutor. |
| |
| **Error summary:** `{type(exc).__name__}: {exc}` |
| |
| --- |
| |
| {local_tutor_response(message, mode, current_lesson, track)} |
| """) |
|
|
|
|
| def test_anthropic_connection(api_key_state: str, model_name: str, base_url: str) -> str: |
| api_key = (api_key_state or "").strip() or SERVER_API_KEY |
| model = (model_name or "").strip() or DEFAULT_MODEL |
| endpoint_base = (base_url or "").strip() or DEFAULT_BASE_URL |
|
|
| if Anthropic is None: |
| return "β The `anthropic` Python package is not installed. Add it to requirements.txt and rebuild the Space." |
| if not api_key: |
| return "β No API key detected. Add `ANTHROPIC_API_KEY` in Space Settings β Secrets or save a browser key in the app." |
| if not model: |
| return "β No model name detected. Set `ANTHROPIC_MODEL` or type one in the app." |
|
|
| try: |
| client_kwargs = {"api_key": api_key} |
| if endpoint_base and endpoint_base != "https://api.anthropic.com": |
| client_kwargs["base_url"] = endpoint_base.rstrip("/") |
| client = Anthropic(**client_kwargs) |
| response = client.messages.create( |
| model=model, |
| max_tokens=32, |
| system="You are a connectivity test. Reply with exactly: LIVE_CONNECTION_OK", |
| messages=[{"role": "user", "content": "ping"}], |
| ) |
| text_parts = [getattr(block, "text", "") for block in getattr(response, "content", []) or []] |
| reply = " ".join([x for x in text_parts if x]).strip() or "(empty text response)" |
| return md(f""" |
| β
**Live Anthropic connection succeeded** |
| |
| - **Model:** `{model}` |
| - **Base URL:** `{endpoint_base}` |
| - **Reply preview:** `{reply}` |
| """) |
| except Exception as exc: |
| return md(f""" |
| β **Live Anthropic connection failed** |
| |
| - **Model:** `{model}` |
| - **Base URL:** `{endpoint_base}` |
| - **Error:** `{type(exc).__name__}: {exc}` |
| |
| Check whether the key is a direct Anthropic API key, whether the model name is valid for your account, and whether you accidentally set a non-default base URL. |
| """) |
| def save_user_api_key(user_api_key: str, api_key_state: str, model_name: str): |
| stored = (user_api_key or api_key_state or "").strip() |
| if stored: |
| save_msg = "β
API key stored in this browser state for this app." |
| else: |
| save_msg = "No user API key stored. The app will use the Space secret if configured, otherwise the built-in tutor." |
| return stored, save_msg, app_status_message(stored, model_name) |
|
|
|
|
| def app_status_message(api_key_state: str, model_name: str): |
| if api_key_state.strip() or SERVER_API_KEY: |
| source = "browser-supplied key" if api_key_state.strip() else "Space secret ANTHROPIC_API_KEY" |
| model = model_name.strip() or DEFAULT_MODEL or "(set ANTHROPIC_MODEL or enter a model)" |
| return md(f""" |
| β
**AI tutor live mode should be available** |
| - Key source: **{source}** |
| - Model: `{model}` |
| - Provider: `Anthropic Messages API` via the official Python SDK |
| - Use the **Test live connection** button below to verify the key and model directly. |
| """) |
| return md(""" |
| β **Local tutor mode only** |
| No live API key is currently available. |
| |
| To enable the live tutor, either: |
| 1. set `ANTHROPIC_API_KEY` in Hugging Face Space Secrets, or |
| 2. paste a temporary key below and save it into browser storage. |
| """) |
|
|
|
|
| INTRO_HTML = f""" |
| <div style="padding: 12px 0 4px 0;"> |
| <div style="background: linear-gradient(135deg, #0f172a, #1e3a8a); color: white; border-radius: 18px; padding: 22px;"> |
| <h1 style="margin-top: 0;">{APP_TITLE}</h1> |
| <p style="font-size: 16px; line-height: 1.6;"> |
| This app is designed to help a business-side or early-transition learner become substantially more technical over time. |
| It combines structured curriculum, architecture coaching, quizzes, code reading, and an optional live AI tutor. |
| </p> |
| <p style="font-size: 15px; line-height: 1.6; margin-bottom: 0;"> |
| Start with the video below, then use the Dashboard and Curriculum tabs to build momentum. |
| </p> |
| </div> |
| </div> |
| <div style="margin-top: 14px; border-radius: 16px; overflow: hidden; border: 1px solid #dbe4ff;"> |
| <iframe |
| width="100%" |
| height="480" |
| src="{CS50_EMBED_URL}" |
| title="CS50P Lecture 0" |
| frameborder="0" |
| allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" |
| allowfullscreen> |
| </iframe> |
| </div> |
| <p style="margin-top: 8px; font-size: 14px; color: #475569;"> |
| Embedded intro: CS50P Lecture 0. If the video does not load in your browser, open it directly on YouTube. |
| </p> |
| """ |
|
|
|
|
| CUSTOM_CSS = """ |
| .gradio-container {max-width: 1300px !important;} |
| #hero-note {font-size: 0.95rem; color: #475569;} |
| .section-card {border: 1px solid #e2e8f0; border-radius: 16px; padding: 14px; background: white;} |
| """ |
|
|
|
|
| with gr.Blocks(title=APP_TITLE, delete_cache=(3600, 3600)) as demo: |
| |
| profile_store = gr.BrowserState(default_profile()) |
| api_key_store = gr.BrowserState("") |
|
|
| gr.HTML(INTRO_HTML) |
|
|
| with gr.Row(): |
| dashboard_md = gr.Markdown() |
| tutor_status_md = gr.Markdown(value=app_status_message("", DEFAULT_MODEL)) |
|
|
| with gr.Tab("Dashboard & Profile"): |
| with gr.Row(): |
| with gr.Column(scale=1): |
| learner_name = gr.Textbox(label="Learner name", placeholder="Example: Jordan") |
| learning_goal = gr.Textbox( |
| label="Main goal", |
| lines=3, |
| placeholder="Example: I want to become technically fluent enough to understand AI product architecture and talk to engineers confidently.", |
| ) |
| background = gr.Textbox(label="Current background", placeholder="Example: business / product / operations") |
| hours_per_week = gr.Slider(1, 15, value=4, step=1, label="Hours per week available") |
| track = gr.Dropdown( |
| choices=[ |
| "Business-to-Technical Foundations", |
| "AI Product Manager Transition", |
| "Business Analyst to Technical Builder", |
| "Future ML/AI Operator", |
| ], |
| value="Business-to-Technical Foundations", |
| label="Learning track", |
| ) |
| completed_lessons = gr.CheckboxGroup(choices=all_lessons(), label="Completed lessons") |
| save_profile_btn = gr.Button("Save profile & refresh dashboard", variant="primary") |
|
|
| with gr.Column(scale=1): |
| notes_box = gr.Textbox( |
| label="Saved notes", |
| placeholder="Type a learning reflection, a confusing term, or a project idea here.", |
| lines=5, |
| ) |
| save_note_btn = gr.Button("Save note") |
| gr.Markdown(md(""" |
| ### How to use this app well |
| 1. Save your profile. |
| 2. Work through one module at a time. |
| 3. Mark lessons complete as you go. |
| 4. Use the tutor only after attempting your own explanation. |
| 5. Revisit weak areas flagged by quizzes. |
| """)) |
|
|
| with gr.Tab("Curriculum"): |
| with gr.Row(): |
| with gr.Column(scale=1): |
| module_name = gr.Dropdown(choices=list(CURRICULUM.keys()), value=list(CURRICULUM.keys())[0], label="Module") |
| module_summary = gr.Markdown(value=render_module_summary(list(CURRICULUM.keys())[0])) |
| with gr.Column(scale=2): |
| lesson_name = gr.Dropdown( |
| choices=list(CURRICULUM[list(CURRICULUM.keys())[0]]["lessons"].keys()), |
| value=list(CURRICULUM[list(CURRICULUM.keys())[0]]["lessons"].keys())[0], |
| label="Lesson", |
| ) |
| lesson_content = gr.Markdown(value=render_lesson(list(CURRICULUM.keys())[0], list(CURRICULUM[list(CURRICULUM.keys())[0]]["lessons"].keys())[0])) |
|
|
| with gr.Tab("30-Day Plan"): |
| plan_btn = gr.Button("Generate personalized 30-day plan", variant="primary") |
| plan_md = gr.Markdown() |
|
|
| with gr.Tab("Architecture Coach"): |
| problem_statement = gr.Textbox( |
| lines=6, |
| label="Describe the business problem", |
| placeholder="Example: We want to help account managers summarize large customer email threads and draft the next recommended response.", |
| ) |
| with gr.Row(): |
| data_readiness = gr.Radio( |
| ["No clean data", "Some spreadsheets / exports", "Clean structured data", "Mostly documents / text"], |
| value="Mostly documents / text", |
| label="Data situation", |
| ) |
| risk_level = gr.Radio(["Low", "Medium", "High / regulated"], value="Medium", label="Risk level") |
| time_horizon = gr.Radio(["2 weeks", "1 month", "2+ months"], value="1 month", label="Timeline") |
| delivery_target = gr.Radio( |
| ["Clickable demo", "Internal service", "Production-minded prototype"], |
| value="Clickable demo", |
| label="Delivery target", |
| ) |
| architecture_btn = gr.Button("Generate architecture recommendation", variant="primary") |
| architecture_md = gr.Markdown() |
|
|
| with gr.Tab("AI Tutor"): |
| gr.Markdown(md(""" |
| Use this tab as an adaptive coach. |
| |
| - If a Space secret named `ANTHROPIC_API_KEY` is configured, the app can use it. |
| - You can also paste your own API key below and store it in browser state for this app. |
| - If no key is configured, the tutor still works using built-in teaching logic. |
| """)) |
| with gr.Row(): |
| tutor_mode = gr.Radio( |
| ["Beginner-friendly", "Business analogy", "Technical transition", "Quiz me"], |
| value="Beginner-friendly", |
| label="Tutor mode", |
| ) |
| current_lesson = gr.Dropdown(choices=all_lessons(), value=all_lessons()[0], label="Current lesson context") |
| with gr.Row(): |
| user_api_key = gr.Textbox(label="Optional user API key", type="password", placeholder="Paste only if you want this browser session to use your own key") |
| model_name = gr.Textbox(label="Model name", value=DEFAULT_MODEL, placeholder="Enter a Claude model name or set ANTHROPIC_MODEL in Space Secrets/Variables") |
| base_url = gr.Textbox(label="API base URL", value=DEFAULT_BASE_URL) |
| save_key_btn = gr.Button("Save API key for this app in this browser") |
| save_key_status = gr.Markdown() |
|
|
| def tutor_fn(message, history, mode, lesson, track_value, runtime_key, model_value, base_value): |
| return call_anthropic_chat(message, history, mode, lesson, track_value, runtime_key, model_value, base_value) |
|
|
| chatbot = gr.ChatInterface( |
| fn=tutor_fn, |
| additional_inputs=[tutor_mode, current_lesson, track, api_key_store, model_name, base_url], |
| save_history=True, |
| fill_height=True, |
| ) |
| test_connection_btn = gr.Button("Test live connection") |
| test_connection_out = gr.Markdown() |
|
|
| with gr.Tab("Code Lab"): |
| with gr.Row(): |
| code_lab_name = gr.Dropdown(choices=list(CODE_LABS.keys()), value=list(CODE_LABS.keys())[0], label="Choose a code lab") |
| with gr.Row(): |
| code_view = gr.Code(value=CODE_LABS[list(CODE_LABS.keys())[0]]["code"], language="python", label="Code or config snippet") |
| code_walkthrough = gr.Markdown(value=CODE_LABS[list(CODE_LABS.keys())[0]]["walkthrough"]) |
|
|
| with gr.Tab("Quiz & Review"): |
| quiz_name = gr.Dropdown(choices=list(QUIZ_BANK.keys()), value=list(QUIZ_BANK.keys())[0], label="Quiz set") |
| quiz_q1 = gr.Radio(choices=QUIZ_BANK[list(QUIZ_BANK.keys())[0]]["questions"][0]["choices"], label=QUIZ_BANK[list(QUIZ_BANK.keys())[0]]["questions"][0]["prompt"]) |
| quiz_q2 = gr.Radio(choices=QUIZ_BANK[list(QUIZ_BANK.keys())[0]]["questions"][1]["choices"], label=QUIZ_BANK[list(QUIZ_BANK.keys())[0]]["questions"][1]["prompt"]) |
| quiz_q3 = gr.Radio(choices=QUIZ_BANK[list(QUIZ_BANK.keys())[0]]["questions"][2]["choices"], label=QUIZ_BANK[list(QUIZ_BANK.keys())[0]]["questions"][2]["prompt"]) |
| grade_btn = gr.Button("Grade quiz", variant="primary") |
| quiz_result = gr.Markdown() |
|
|
| with gr.Tab("References & Setup"): |
| gr.Markdown(REFERENCE_LIBRARY) |
| gr.Markdown(md(""" |
| ## Hugging Face Space setup tips |
| - Put API keys in **Space Settings -> Secrets**, not in the repo. |
| - For this app, the expected secret names are: |
| - `ANTHROPIC_API_KEY` |
| - `ANTHROPIC_MODEL` (optional; can also be a public variable if non-sensitive) |
| - `ANTHROPIC_BASE_URL` (optional if using a non-default endpoint) |
| - The app will also work without any live model key because it includes a built-in local tutor. |
| """)) |
|
|
| |
| demo.load( |
| load_profile, |
| inputs=[profile_store], |
| outputs=[profile_store, dashboard_md, learner_name, learning_goal, background, hours_per_week, track, completed_lessons], |
| ) |
| demo.load(app_status_message, inputs=[api_key_store, model_name], outputs=[tutor_status_md]) |
|
|
| |
| save_profile_btn.click( |
| save_profile, |
| inputs=[learner_name, learning_goal, background, hours_per_week, track, completed_lessons, profile_store], |
| outputs=[profile_store, dashboard_md], |
| ) |
|
|
| |
| save_note_btn.click( |
| add_note, |
| inputs=[notes_box, profile_store], |
| outputs=[profile_store, dashboard_md, notes_box], |
| ) |
|
|
| |
| module_name.change( |
| lambda m: (render_module_summary(m), gr.Dropdown(choices=list(CURRICULUM[m]["lessons"].keys()), value=list(CURRICULUM[m]["lessons"].keys())[0]), render_lesson(m, list(CURRICULUM[m]["lessons"].keys())[0])), |
| inputs=[module_name], |
| outputs=[module_summary, lesson_name, lesson_content], |
| ) |
| lesson_name.change(render_lesson, inputs=[module_name, lesson_name], outputs=[lesson_content]) |
|
|
| |
| plan_btn.click(generate_30_day_plan, inputs=[track, hours_per_week, background, learning_goal], outputs=[plan_md]) |
|
|
| |
| architecture_btn.click( |
| architecture_coach, |
| inputs=[problem_statement, data_readiness, risk_level, time_horizon, delivery_target], |
| outputs=[architecture_md], |
| ) |
|
|
| |
| save_key_btn.click( |
| save_user_api_key, |
| inputs=[user_api_key, api_key_store, model_name], |
| outputs=[api_key_store, save_key_status, tutor_status_md], |
| ) |
| model_name.change(app_status_message, inputs=[api_key_store, model_name], outputs=[tutor_status_md]) |
| test_connection_btn.click( |
| test_anthropic_connection, |
| inputs=[api_key_store, model_name, base_url], |
| outputs=[test_connection_out], |
| ) |
|
|
| |
| code_lab_name.change(render_code_lab, inputs=[code_lab_name], outputs=[code_view, code_walkthrough]) |
|
|
| |
| def update_quiz_ui(name: str): |
| questions = QUIZ_BANK[name]["questions"] |
| return ( |
| gr.Radio(choices=questions[0]["choices"], label=questions[0]["prompt"], value=None), |
| gr.Radio(choices=questions[1]["choices"], label=questions[1]["prompt"], value=None), |
| gr.Radio(choices=questions[2]["choices"], label=questions[2]["prompt"], value=None), |
| ) |
|
|
| quiz_name.change(update_quiz_ui, inputs=[quiz_name], outputs=[quiz_q1, quiz_q2, quiz_q3]) |
| grade_btn.click( |
| grade_quiz, |
| inputs=[quiz_name, quiz_q1, quiz_q2, quiz_q3, profile_store], |
| outputs=[profile_store, quiz_result, dashboard_md], |
| ) |
|
|
|
|
| demo.launch(theme=gr.themes.Soft(), css=CUSTOM_CSS) |
|
|