import ast import operator as op import re import gradio as gr # ---------------------------- # Safe arithmetic evaluator # ---------------------------- ALLOWED_OPERATORS = { ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul, ast.Div: op.truediv, ast.Mod: op.mod, ast.Pow: op.pow, ast.UAdd: op.pos, ast.USub: op.neg, } def _safe_eval_node(node): if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return node.value if isinstance(node, ast.BinOp) and type(node.op) in ALLOWED_OPERATORS: return ALLOWED_OPERATORS[type(node.op)]( _safe_eval_node(node.left), _safe_eval_node(node.right), ) if isinstance(node, ast.UnaryOp) and type(node.op) in ALLOWED_OPERATORS: return ALLOWED_OPERATORS[type(node.op)](_safe_eval_node(node.operand)) raise ValueError("Unsupported expression") def safe_eval(expr: str): expr = expr.replace("×", "*").replace("÷", "/").replace("−", "-") tree = ast.parse(expr, mode="eval") return _safe_eval_node(tree.body) def format_result(value): if isinstance(value, float) and value.is_integer(): return str(int(value)) return str(value) # ---------------------------- # Calculator actions # ---------------------------- def append_value(current, value): if current in ("0", "Error"): current = "" if value in "+-*/": if not current and value != "-": return "0", "0" if current.endswith(("+", "-", "*", "/", ".")): current = current[:-1] return current + value, current + value def clear_display(): return "0", "0" def backspace(current): if current in ("0", "Error", ""): return "0", "0" new_value = current[:-1] if not new_value: new_value = "0" return new_value, new_value def evaluate(current): try: result = safe_eval(current) result = format_result(result) return result, result except: return "Error", "Error" def toggle_sign(current): if current in ("0", "Error", ""): return "-", "-" m = re.search(r"(-?\d+(?:\.\d+)?)$", current) if not m: return current, current start, end = m.span() num = m.group(0) if num.startswith("-"): num = num[1:] else: num = "-" + num new_value = current[:start] + num return new_value, new_value def percent(current): if current in ("0", "Error", ""): return "0", "0" if current.endswith((".", "+", "-", "*", "/")): return current, current new_value = current + "/100" return new_value, new_value def parens(current): if current in ("0", "Error"): current = "" open_count = current.count("(") close_count = current.count(")") if not current or current.endswith(("+", "-", "*", "/", "(")): ch = "(" elif open_count > close_count: ch = ")" else: ch = "(" new_value = current + ch return new_value, new_value def make_append(v): def fn(current): return append_value(current, v) return fn # ---------------------------- # UI # ---------------------------- CSS = """ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@300;400;600&display=swap'); *{font-family: 'Orbitron', sans-serif !important;} body, .gradio-container { background: #000 !important; color: white !important; } .phone { max-width: 390px; margin: 0 auto; padding: 16px 14px 20px; } .display textarea { background: #000 !important; color: #fff !important; border: none !important; box-shadow: none !important; text-align: right !important; font-size: 72px !important; font-weight: 300 !important; height: 170px !important; padding: 10px 0 0 0 !important; resize: none !important; } .divider { height: 1px; background: rgba(255,255,255,0.18); margin: 10px 0 18px 0; } .topbar { display: flex; justify-content: space-between; align-items: center; color: rgba(255,255,255,0.8); font-size: 18px; margin-bottom: 8px; opacity: 0.9; } button { height: 82px !important; min-height: 82px !important; border-radius: 999px !important; font-size: 30px !important; font-weight: 500 !important; border: none !important; box-shadow: none !important; } .digit button { background: #272727 !important; color: #fff !important; } .utility button { background: #a5a5a5 !important; color: #000 !important; } .operator button { background: #bfbfbf !important; color: #000 !important; } .equals button { background: #34c759 !important; color: #fff !important; } .zero button { border-radius: 999px !important; } """ with gr.Blocks(css=CSS, title="Calculator") as demo: with gr.Column(elem_classes="phone"): display = gr.Textbox( value="0", show_label=False, interactive=False, container=False, lines=1, elem_classes="display", ) state = gr.State("0") gr.HTML("""
""") # Row 1 with gr.Row(): with gr.Column(scale=1, min_width=0): gr.Button("C", elem_classes=["utility"]).click(clear_display, None, [state, display]) with gr.Column(scale=1, min_width=0): gr.Button("()", elem_classes=["utility"]).click(parens, state, [state, display]) with gr.Column(scale=1, min_width=0): gr.Button("%", elem_classes=["utility"]).click(percent, state, [state, display]) with gr.Column(scale=1, min_width=0): gr.Button("÷", elem_classes=["operator"]).click(make_append("/"), state, [state, display]) # Row 2 with gr.Row(): for label in ["7", "8", "9"]: with gr.Column(scale=1, min_width=0): gr.Button(label, elem_classes=["digit"]).click(make_append(label), state, [state, display]) with gr.Column(scale=1, min_width=0): gr.Button("×", elem_classes=["operator"]).click(make_append("*"), state, [state, display]) # Row 3 with gr.Row(): for label in ["4", "5", "6"]: with gr.Column(scale=1, min_width=0): gr.Button(label, elem_classes=["digit"]).click(make_append(label), state, [state, display]) with gr.Column(scale=1, min_width=0): gr.Button("−", elem_classes=["operator"]).click(make_append("-"), state, [state, display]) # Row 4 with gr.Row(): for label in ["1", "2", "3"]: with gr.Column(scale=1, min_width=0): gr.Button(label, elem_classes=["digit"]).click(make_append(label), state, [state, display]) with gr.Column(scale=1, min_width=0): gr.Button("+", elem_classes=["operator"]).click(make_append("+"), state, [state, display]) # Row 5 with gr.Row(): with gr.Column(scale=1, min_width=0): gr.Button("+/-", elem_classes=["utility"]).click(toggle_sign, state, [state, display]) with gr.Column(scale=2, min_width=0): gr.Button("0", elem_classes=["digit", "zero"]).click(make_append("0"), state, [state, display]) with gr.Column(scale=1, min_width=0): gr.Button(".", elem_classes=["digit"]).click(make_append("."), state, [state, display]) with gr.Column(scale=1, min_width=0): gr.Button("=", elem_classes=["equals"]).click(evaluate, state, [state, display]) demo.launch(server_name="0.0.0.0", server_port=7860)