| import ast |
| import operator as op |
| import re |
| import gradio as gr |
|
|
| |
| |
| |
| 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) |
|
|
| |
| |
| |
| 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 |
|
|
| |
| |
| |
| 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(""" |
| <div class="divider"></div> |
| """) |
|
|
| |
| 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]) |
|
|
| |
| 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]) |
|
|
| |
| 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]) |
|
|
| |
| 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]) |
|
|
| |
| 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) |