Khanmx99's picture
Deploy Pak Angels AI Tutor
785b4e2 verified
Raw
History Blame Contribute Delete
9.89 kB
from __future__ import annotations
from functools import partial
import gradio as gr
from config import APP_NAME, APP_SUBTITLE, get_openai_api_key, get_openai_model
from openai_service import format_openai_error, stream_tutor_response
from prompts import ABOUT_PAK_ANGELS, MODULES, build_system_instructions
BUILD_ID = "pak-angels-zerogpu-2026-07-11-v2"
try:
import spaces
except ImportError:
class _SpacesFallback:
@staticmethod
def GPU(*args, **kwargs):
if args and callable(args[0]) and len(args) == 1 and not kwargs:
return args[0]
def decorator(function):
return function
return decorator
spaces = _SpacesFallback()
print(f"Starting {APP_NAME} build {BUILD_ID}")
PRIVACY_NOTICE = (
"Privacy notice: Do not enter confidential, proprietary, financial, medical, "
"personal, or otherwise sensitive information into the AI Tutor."
)
CSS = """
:root {
--pak-blue: #0b5cab;
--pak-blue-dark: #073f78;
--pak-blue-soft: #eaf4ff;
--pak-line: #d8e5f2;
--pak-text: #14213d;
}
body,
.gradio-container {
background: #f7fbff !important;
color: var(--pak-text);
}
.main-shell {
max-width: 1180px;
margin: 0 auto;
}
.hero {
background: linear-gradient(135deg, #ffffff 0%, #eaf4ff 62%, #d6ebff 100%);
border: 1px solid var(--pak-line);
border-radius: 8px;
padding: 24px;
margin-bottom: 14px;
}
.hero h1 {
color: var(--pak-blue-dark);
font-size: 38px;
line-height: 1.1;
margin: 0 0 8px 0;
}
.hero p {
margin: 5px 0;
font-size: 16px;
}
.mode-label {
border-left: 5px solid var(--pak-blue);
background: #ffffff;
border-radius: 8px;
padding: 14px 16px;
box-shadow: 0 1px 4px rgba(11, 92, 171, 0.08);
}
.privacy {
background: #fff8e8;
border: 1px solid #f1d28c;
border-radius: 8px;
padding: 12px 14px;
font-size: 14px;
}
.side-panel {
background: #ffffff;
border: 1px solid var(--pak-line);
border-radius: 8px;
padding: 14px;
}
.suggestion-button {
min-height: 46px;
}
button.primary {
background: var(--pak-blue) !important;
border-color: var(--pak-blue) !important;
}
"""
def hero_html() -> str:
return f"""
<div class="hero">
<h1>{APP_NAME}</h1>
<p><strong>{APP_SUBTITLE}</strong></p>
<p>Pak Angels AI Tutor helps students, faculty, professionals,
entrepreneurs, and startup founders learn Artificial Intelligence,
build practical applications, design intelligent workflows, automate
business processes, and develop AI-powered startups.</p>
</div>
"""
def module_summary_html(module_name: str) -> str:
module = MODULES[module_name]
about = ""
if module_name == "About Pak Angels":
about = f"<p>{ABOUT_PAK_ANGELS}</p>"
return f"""
<div class="mode-label">
<strong>Selected learning mode:</strong> {module_name}<br>
<span>{module["summary"]}</span>
{about}
</div>
"""
def topics_markdown(module_name: str) -> str:
topics = "\n".join(f"- {topic}" for topic in MODULES[module_name]["topics"])
return f"### Topics in this mode\n{topics}"
def get_suggestion(module_name: str, index: int) -> str:
suggestions = MODULES[module_name]["suggestions"]
return suggestions[index] if index < len(suggestions) else ""
def update_module(module_name: str):
suggestions = MODULES[module_name]["suggestions"]
button_updates = [
gr.update(value=suggestion, visible=True) for suggestion in suggestions[:5]
]
while len(button_updates) < 5:
button_updates.append(gr.update(value="", visible=False))
return (
module_summary_html(module_name),
topics_markdown(module_name),
*button_updates,
)
def add_user_message(message: str, history: list[dict[str, str]] | None):
history = list(history or [])
message = (message or "").strip()
if not message:
return "", history
history.append({"role": "user", "content": message})
return "", history
def generate_response(history: list[dict[str, str]] | None, module_name: str):
history = list(history or [])
if not history or history[-1]["role"] != "user":
yield history
return
api_key = get_openai_api_key()
if not api_key:
message = (
"OPENAI_API_KEY is not configured. In Hugging Face Spaces, add it under "
"Settings -> Variables and secrets -> New secret, then restart the Space."
)
history.append({"role": "assistant", "content": message})
yield history
return
history.append({"role": "assistant", "content": ""})
try:
for delta in stream_tutor_response(
api_key=api_key,
model=get_openai_model(),
system_instructions=build_system_instructions(module_name),
messages=history[:-1],
):
history[-1]["content"] += delta
yield history
except Exception as error:
history[-1]["content"] = format_openai_error(error)
yield history
@spaces.GPU(duration=120)
def submit_message(message: str, history: list[dict[str, str]] | None, module_name: str):
textbox, updated_history = add_user_message(message, history)
yield textbox, updated_history
for streamed_history in generate_response(updated_history, module_name):
yield textbox, streamed_history
def submit_suggestion(
suggestion_index: int,
history: list[dict[str, str]] | None,
module_name: str,
):
question = get_suggestion(module_name, suggestion_index)
yield from submit_message(question, history, module_name)
def clear_conversation():
return []
def build_app() -> gr.Blocks:
with gr.Blocks(
title=APP_NAME,
css=CSS,
theme=gr.themes.Soft(primary_hue="blue", neutral_hue="slate"),
) as demo:
with gr.Column(elem_classes=["main-shell"]):
gr.HTML(hero_html())
with gr.Row(equal_height=False):
with gr.Column(scale=1, min_width=260, elem_classes=["side-panel"]):
module_selector = gr.Radio(
choices=list(MODULES.keys()),
value="Home",
label="Learning mode",
)
gr.Textbox(
value=get_openai_model(),
label="OpenAI model",
interactive=False,
)
new_button = gr.Button("New Conversation")
clear_button = gr.Button("Clear Chat")
with gr.Accordion("About Pak Angels", open=False):
gr.Markdown(ABOUT_PAK_ANGELS)
with gr.Column(scale=3, min_width=420):
module_summary = gr.HTML(module_summary_html("Home"))
gr.HTML(f'<div class="privacy">{PRIVACY_NOTICE}</div>')
topics = gr.Markdown(topics_markdown("Home"))
gr.Markdown("### Suggested questions")
suggestion_buttons = []
with gr.Row():
suggestion_buttons.append(
gr.Button(get_suggestion("Home", 0), elem_classes=["suggestion-button"])
)
suggestion_buttons.append(
gr.Button(get_suggestion("Home", 1), elem_classes=["suggestion-button"])
)
with gr.Row():
suggestion_buttons.append(
gr.Button(get_suggestion("Home", 2), elem_classes=["suggestion-button"])
)
suggestion_buttons.append(
gr.Button(get_suggestion("Home", 3), elem_classes=["suggestion-button"])
)
suggestion_buttons.append(
gr.Button(get_suggestion("Home", 4), elem_classes=["suggestion-button"])
)
chatbot = gr.Chatbot(
label="Pak Angels AI Tutor",
type="messages",
height=520,
show_copy_button=True,
allow_tags=False,
)
message_box = gr.Textbox(
label="Ask Pak Angels AI Tutor",
placeholder="Ask a question or choose a suggested question above.",
lines=3,
)
send_button = gr.Button("Send", variant="primary")
module_selector.change(
update_module,
inputs=[module_selector],
outputs=[module_summary, topics, *suggestion_buttons],
)
send_button.click(
submit_message,
inputs=[message_box, chatbot, module_selector],
outputs=[message_box, chatbot],
)
message_box.submit(
submit_message,
inputs=[message_box, chatbot, module_selector],
outputs=[message_box, chatbot],
)
for index, button in enumerate(suggestion_buttons):
button.click(
partial(submit_suggestion, index),
inputs=[chatbot, module_selector],
outputs=[message_box, chatbot],
)
new_button.click(clear_conversation, outputs=[chatbot])
clear_button.click(clear_conversation, outputs=[chatbot])
return demo
demo = build_app()
if __name__ == "__main__":
demo.queue().launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)