|
|
""" |
|
|
NWFWO Practice – Gradio MCP app for ChatGPT |
|
|
|
|
|
This file does two things: |
|
|
1) Runs a normal Gradio web app (for debugging in your browser) |
|
|
2) Exposes an MCP server + HTML UI card for ChatGPT Apps |
|
|
""" |
|
|
|
|
|
import gradio as gr |
|
|
from dataclasses import dataclass |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass |
|
|
class NWFWOExample: |
|
|
|
|
|
transliteration: str |
|
|
|
|
|
correct_nwfwo: str |
|
|
|
|
|
explanation: str | None = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@gr.mcp.tool() |
|
|
def check_nwfwo( |
|
|
transliteration: str, |
|
|
correct_nwfwo: str, |
|
|
user_guess: str, |
|
|
explanation: str | None = None, |
|
|
): |
|
|
""" |
|
|
Simple checker for NWFWO practice. |
|
|
|
|
|
Arguments (ChatGPT will send these as JSON): |
|
|
- transliteration: the foreign sentence/word in transliteration |
|
|
- correct_nwfwo: the correct NWFWO answer |
|
|
- user_guess: what the learner typed or selected |
|
|
- explanation: optional teacher explanation |
|
|
|
|
|
Returns: a dict that ChatGPT AND the UI card can use. |
|
|
""" |
|
|
norm_correct = correct_nwfwo.strip().lower() |
|
|
norm_guess = user_guess.strip().lower() |
|
|
|
|
|
is_correct = norm_guess == norm_correct |
|
|
|
|
|
|
|
|
if is_correct: |
|
|
feedback = "✅ Correct! Your NWFWO matches the target." |
|
|
else: |
|
|
feedback = ( |
|
|
"❌ Not quite. Compare your NWFWO with the correct one " |
|
|
"and think about which sounds or letters changed." |
|
|
) |
|
|
|
|
|
result = { |
|
|
"transliteration": transliteration, |
|
|
"correct_nwfwo": correct_nwfwo, |
|
|
"user_guess": user_guess, |
|
|
"is_correct": is_correct, |
|
|
"feedback": feedback, |
|
|
"explanation": explanation |
|
|
or "This NWFWO shows how the foreign writing maps to the native word.", |
|
|
} |
|
|
|
|
|
return result |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def local_check_ui(transliteration, correct_nwfwo, user_guess, explanation): |
|
|
res = check_nwfwo( |
|
|
transliteration=transliteration, |
|
|
correct_nwfwo=correct_nwfwo, |
|
|
user_guess=user_guess, |
|
|
explanation=explanation, |
|
|
) |
|
|
return ( |
|
|
f"Transliteration: {res['transliteration']}\n" |
|
|
f"Your NWFWO: {res['user_guess']}\n" |
|
|
f"Correct NWFWO: {res['correct_nwfwo']}\n\n" |
|
|
f"{res['feedback']}\n\n" |
|
|
f"Explanation: {res['explanation']}" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Blocks() as demo: |
|
|
gr.Markdown("## NWFWO Practice – Local Debug UI") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
transliteration_box = gr.Textbox( |
|
|
label="Transliteration (foreign text written in your script)", |
|
|
value="salaam", |
|
|
) |
|
|
correct_nwfwo_box = gr.Textbox( |
|
|
label="Correct NWFWO", |
|
|
value="salaam", |
|
|
) |
|
|
user_guess_box = gr.Textbox( |
|
|
label="Your guess NWFWO", |
|
|
value="salam", |
|
|
) |
|
|
explanation_box = gr.Textbox( |
|
|
label="Explanation (optional)", |
|
|
value="This example shows how long vs short vowels work.", |
|
|
) |
|
|
btn = gr.Button("Check") |
|
|
|
|
|
with gr.Column(): |
|
|
result_box = gr.Textbox( |
|
|
label="Result", |
|
|
lines=8, |
|
|
) |
|
|
|
|
|
btn.click( |
|
|
local_check_ui, |
|
|
inputs=[ |
|
|
transliteration_box, |
|
|
correct_nwfwo_box, |
|
|
user_guess_box, |
|
|
explanation_box, |
|
|
], |
|
|
outputs=[result_box], |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@gr.mcp.resource( |
|
|
"ui://widget/nwfwo-practice-card.html", |
|
|
mime_type="text/html+skybridge", |
|
|
) |
|
|
def nwfwo_html_card(): |
|
|
""" |
|
|
This HTML will appear as a card inside ChatGPT when this tool runs. |
|
|
|
|
|
It can read: |
|
|
- window.openai.toolInput (what ChatGPT sent into the tool) |
|
|
- window.openai.toolOutput (what our Python tool returned) |
|
|
""" |
|
|
html = r""" |
|
|
<div id="nwfwo-card-root"></div> |
|
|
<script> |
|
|
const root = document.getElementById("nwfwo-card-root"); |
|
|
|
|
|
function render() { |
|
|
const input = (window.openai && window.openai.toolInput) || {}; |
|
|
const output = (window.openai && window.openai.toolOutput) || {}; |
|
|
|
|
|
const transliteration = input.transliteration || output.transliteration || "salaam"; |
|
|
const userGuess = input.user_guess || output.user_guess || "salam"; |
|
|
const correctNwfwo = input.correct_nwfwo || output.correct_nwfwo || "salaam"; |
|
|
|
|
|
const isCorrect = output.is_correct; |
|
|
const feedback = output.feedback || ""; |
|
|
const explanation = output.explanation || ""; |
|
|
|
|
|
let badge = ""; |
|
|
if (typeof isCorrect === "boolean") { |
|
|
badge = isCorrect |
|
|
? '<span style="padding:4px 8px;border-radius:999px;background:#d4edda;">Correct</span>' |
|
|
: '<span style="padding:4px 8px;border-radius:999px;background:#f8d7da;">Try again</span>'; |
|
|
} |
|
|
|
|
|
root.innerHTML = ` |
|
|
<div style=" |
|
|
border-radius:16px; |
|
|
border:1px solid #ddd; |
|
|
padding:16px; |
|
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif; |
|
|
max-width: 500px; |
|
|
"> |
|
|
<div style="font-size:14px;color:#666;margin-bottom:4px;"> |
|
|
NWFWO Practice |
|
|
</div> |
|
|
<div style="font-size:18px;font-weight:600;margin-bottom:12px;"> |
|
|
Transliteration |
|
|
</div> |
|
|
<div style="padding:8px 12px;border-radius:12px;background:#f5f5f5;margin-bottom:12px;"> |
|
|
${transliteration} |
|
|
</div> |
|
|
|
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;"> |
|
|
<div> |
|
|
<div style="font-size:14px;color:#666;">Your NWFWO</div> |
|
|
<div style="font-size:16px;">${userGuess}</div> |
|
|
</div> |
|
|
<div> |
|
|
<div style="font-size:14px;color:#666;">Target NWFWO</div> |
|
|
<div style="font-size:16px;font-weight:600;">${correctNwfwo}</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div style="margin-bottom:8px;"> |
|
|
${badge} |
|
|
</div> |
|
|
|
|
|
<div style="font-size:14px;margin-top:8px;border-top:1px solid #eee;padding-top:8px;"> |
|
|
<div style="font-weight:600;margin-bottom:4px;">Feedback</div> |
|
|
<div>${feedback}</div> |
|
|
</div> |
|
|
|
|
|
<div style="font-size:13px;color:#555;margin-top:8px;"> |
|
|
<div style="font-weight:600;margin-bottom:4px;">Explanation</div> |
|
|
<div>${explanation}</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
render(); |
|
|
</script> |
|
|
""" |
|
|
return html |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
|
|
|
demo.launch( |
|
|
mcp_server=True, |
|
|
) |
|
|
|