Download app.py from Jarbas/jevhakim: direct link, hf CLI and curl.
- Browser
- Download file 27.2 kB
-
https://huggingface.co/spaces/Jarbas/jevhakim/resolve/main/app.py
- Command line
-
hf download hf://spaces/Jarbas/jevhakim/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Jarbas/jevhakim/resolve/main/app.py
27.2 kB
| """Jevhakim on Hugging Face Spaces (Gradio, ZeroGPU). | |
| A state and a set of typed questions in, a probability per option out, in one forward pass. The model is loaded through | |
| Kev's own loader (kev.checkpoint), the same path `python -m kev.serve --run TigreGotico/jevhakim-0.8b` takes, and | |
| requests go through kev.api and kev.model.admit exactly as /v1/systemone does. Both sizes load in bf16 at start-up, and | |
| each forward pass runs inside @spaces.GPU, so a request holds a GPU only for that pass. | |
| """ | |
| import html | |
| import json | |
| import os | |
| import time | |
| from dataclasses import replace | |
| import gradio as gr | |
| import spaces | |
| import torch | |
| from pydantic import ValidationError | |
| from kev.api import SystemOneRequest, output_tokens, render, to_answers, to_record | |
| from kev.checkpoint import Checkpoint, LoadOptions | |
| from kev.model import admit | |
| MODELS = {"Jevhakim-4B": "TigreGotico/jevhakim-4b", "Jevhakim-0.8B": "TigreGotico/jevhakim-0.8b"} | |
| # JEVHAKIM_MODELS="Jevhakim-0.8B" serves only the named models. | |
| if os.environ.get("JEVHAKIM_MODELS"): | |
| MODELS = {name: MODELS[name] for name in os.environ["JEVHAKIM_MODELS"].split(",")} | |
| DEFAULT = "Jevhakim-4B" if "Jevhakim-4B" in MODELS else next(iter(MODELS)) | |
| LINK_08B = "https://huggingface.co/TigreGotico/jevhakim-0.8b" | |
| LINK_4B = "https://huggingface.co/TigreGotico/jevhakim-4b" | |
| LINK_DATA = "https://huggingface.co/datasets/TigreGotico/jevhakim-decisions" | |
| LOADED = {} | |
| for name, repo in MODELS.items(): | |
| t0 = time.perf_counter() | |
| checkpoint = Checkpoint(repo) | |
| opts = LoadOptions.from_env() | |
| if opts.dtype is None: | |
| opts = replace(opts, dtype=torch.bfloat16) | |
| # ZeroGPU has no GPU at start-up: load on the CPU, then move. ZeroGPU defers the move until a @spaces.GPU call, and | |
| # Kev builds its input tensors on model.device. Off ZeroGPU, JEVHAKIM_DEVICE_<SIZE> places a model, e.g. | |
| # JEVHAKIM_DEVICE_4B=cpu keeps the 4B in RAM on a laptop GPU too small for it (JEVHAKIM_DEVICE_08B for the 0.8B). | |
| key = name.split("-", 1)[1].replace(".", "").upper() | |
| device = "cuda" if os.environ.get("SPACES_ZERO_GPU") else os.environ.get( | |
| f"JEVHAKIM_DEVICE_{key}", "cuda" if torch.cuda.is_available() else "cpu") | |
| tok, model = checkpoint.load("cpu", opts) | |
| model.to(device) | |
| model.device = torch.device(device) | |
| LOADED[name] = tok, model | |
| print(f"[jevhakim] {repo}: base={checkpoint.meta.base}@{checkpoint.meta.base_revision} dtype={model.dtype} " | |
| f"temperature={model.head.temperature:.2f} device={device} loaded in {time.perf_counter() - t0:.0f} s", | |
| flush=True) | |
| def forward(name, enc): | |
| return [p.float().cpu() for p in LOADED[name][1].probs(enc)] | |
| class RequestError(ValueError): | |
| """A request the model cannot answer: bad JSON, a bad question, or a state that is too long.""" | |
| def parse_request(payload): | |
| try: | |
| return SystemOneRequest.model_validate(payload) | |
| except ValidationError as e: | |
| first = e.errors()[0] | |
| where = ".".join(str(x) for x in first.get("loc", ())) | |
| raise RequestError(f"Invalid request at `{where}`: {first.get('msg')}") from None | |
| def systemone(req, name=DEFAULT): | |
| """The /v1/systemone response body kev.serve returns for this request, plus the latency of the forward pass.""" | |
| if name not in LOADED: | |
| raise RequestError(f"Unknown model: {name}. Choices: {', '.join(MODELS)}.") | |
| tokenizer, model = LOADED[name] | |
| rec, meta = to_record(req) | |
| try: | |
| enc = admit(model, tokenizer, rec) | |
| except ValueError as e: | |
| raise RequestError(str(e)) from None | |
| start = time.perf_counter() | |
| probs = forward(name, enc) | |
| ms = (time.perf_counter() - start) * 1000 | |
| answers = to_answers([p.tolist() for p in probs], meta) | |
| return {"model": MODELS[name], "answers": answers, | |
| "usage": {"input_tokens": len(enc["ids"]), "output_tokens": output_tokens(tokenizer, answers)}, | |
| "latency_ms": round(ms, 1)} | |
| # ------------------------------------------------------------------ rendering | |
| CARD = ("border:1px solid var(--border-color-primary);border-radius:10px;padding:12px 14px;margin-bottom:10px;" | |
| "background:var(--background-fill-secondary);") | |
| TRACK = "position:relative;display:block;height:8px;border-radius:999px;background:var(--border-color-primary);overflow:hidden;" | |
| MONO = "font-family:var(--font-mono);font-size:11px;color:var(--body-text-color-subdued)" | |
| TYPE_LABEL = {"choice": "choice · اختيار", "noul": "yes/no · نعم/لا", "score": "score · مقياس"} | |
| def bar(label, p, top, key=None): | |
| """One option row. The card is right-to-left, so the label sits on the right and the bar fills from the right.""" | |
| fill = "var(--color-accent)" if top else "var(--body-text-color-subdued)" | |
| weight = "600" if top else "400" | |
| width = max(0.0, min(1.0, p)) * 100 | |
| tag = f' <bdi style="{MONO}">{html.escape(key)}</bdi>' if key and key != label else "" | |
| return ('<div style="display:grid;grid-template-columns:minmax(0,14rem) minmax(0,1fr) 3.2rem;align-items:center;' | |
| 'gap:0 12px;font-size:13px;line-height:24px">' | |
| f'<span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:{weight}" ' | |
| f'title="{html.escape(label)}"><bdi>{html.escape(label)}</bdi>{tag}</span>' | |
| f'<span style="{TRACK}"><span style="position:absolute;top:0;bottom:0;inset-inline-start:0;border-radius:999px;' | |
| f'background:{fill};width:{width:.1f}%"></span></span>' | |
| f'<span dir="ltr" style="text-align:end;font-variant-numeric:tabular-nums;font-weight:{weight}">{p:.2f}</span></div>') | |
| def render_answers(req, resp): | |
| usage = resp["usage"] | |
| parts = ['<div dir="ltr" style="font-size:12px;color:var(--body-text-color-subdued);margin:6px 0 10px">' | |
| f'{len(resp["answers"])} question(s) · {usage["input_tokens"]} input tokens · ' | |
| f'{resp["model"].split("/")[1]} · {resp["latency_ms"]:.0f} ms · no text generated</div>'] | |
| for qid, a in resp["answers"].items(): | |
| question = req.questions[qid] | |
| instructions = render(question.instructions) | |
| if a["type"] == "noul": | |
| p = a["noul"] | |
| headline, detail = ("نعم · yes" if p >= 0.5 else "لا · no"), f"p(yes) {p:.2f}" | |
| rows = bar("نعم", p, p >= 0.5, "yes") + bar("لا", 1 - p, p < 0.5, "no") | |
| elif a["type"] == "choice": | |
| names = {k: render(v) if v else k for k, v in question.criteria.items()} | |
| headline, detail = names[a["choice"]], f"confidence {a['confidence']:.2f}" | |
| ranked = sorted(a["probabilities"].items(), key=lambda kv: -kv[1]) | |
| rows = "".join(bar(names[k], v, k == a["choice"], k) for k, v in ranked) | |
| else: | |
| legend, dist = a["legend"], a["probabilities"] | |
| top = max(dist, key=dist.get) | |
| headline = f"{a['score']:.2f} / {len(legend) - 1}" | |
| detail = f"confidence {a['confidence']:.2f}" | |
| rows = "".join(bar(legend[str(i)], dist[str(i)], str(i) == top, str(i)) for i in range(len(legend))) | |
| parts.append(f'<div dir="rtl" style="{CARD}"><div style="display:flex;align-items:baseline;gap:8px;flex-wrap:wrap">' | |
| f'<bdi style="font-family:var(--font-mono);font-size:13px;font-weight:600">{html.escape(qid)}</bdi>' | |
| f'<bdi style="font-size:11px;color:var(--body-text-color-subdued)">{TYPE_LABEL[a["type"]]}</bdi></div>' | |
| f'<div style="font-size:14px;margin:2px 0 6px">{html.escape(instructions)}</div>' | |
| '<div style="display:flex;align-items:baseline;gap:10px;margin-bottom:8px">' | |
| f'<bdi style="font-size:17px;font-weight:600">{html.escape(str(headline))}</bdi>' | |
| f'<bdi style="font-size:12px;color:var(--body-text-color-subdued)">{detail}</bdi></div>{rows}</div>') | |
| return "".join(parts) | |
| def error_html(message): | |
| return (f'<div style="{CARD}border-color:var(--error-border-color, #d33);">' | |
| f'<b>Error:</b> {html.escape(message)}</div>') | |
| # ------------------------------------------------------------------ question builder | |
| def parse_state(text): | |
| """Plain text, or a JSON object or array.""" | |
| t = (text or "").strip() | |
| if t[:1] in "{[": | |
| try: | |
| return json.loads(t) | |
| except json.JSONDecodeError: | |
| pass | |
| return t | |
| def parse_options(text): | |
| """One option per line, `name: description` or just `name`.""" | |
| criteria = {} | |
| for line in (text or "").splitlines(): | |
| name, _, desc = line.partition(":") | |
| name, desc = name.strip(), desc.strip() | |
| if name: | |
| criteria[name] = desc or None | |
| return criteria | |
| def build_request(state, choice_id, choice_instr, choice_opts, noul_id, noul_instr, score_id, score_instr, score_levels): | |
| """The System One request the builder describes. A question with an empty id is left out.""" | |
| ids = [i.strip() for i in (choice_id, noul_id, score_id) if i and i.strip()] | |
| if len(ids) != len(set(ids)): | |
| raise RequestError("Each question needs a different id.") | |
| questions = {} | |
| if choice_id.strip(): | |
| questions[choice_id.strip()] = {"type": "choice", "instructions": choice_instr, "criteria": parse_options(choice_opts)} | |
| if noul_id.strip(): | |
| questions[noul_id.strip()] = {"type": "noul", "instructions": noul_instr} | |
| if score_id.strip(): | |
| levels = [line.strip() for line in (score_levels or "").splitlines() if line.strip()] | |
| questions[score_id.strip()] = {"type": "score", "instructions": score_instr, "criteria": levels} | |
| if not (state or "").strip(): | |
| raise RequestError("Write a state: the text or the JSON object the model reads.") | |
| if not questions: | |
| raise RequestError("Define at least one question (a non-empty id).") | |
| return {"state": parse_state(state), "model": "kev-latest", "questions": questions} | |
| def decide(name, state, choice_id, choice_instr, choice_opts, noul_id, noul_instr, score_id, score_instr, score_levels): | |
| """Answer the builder's questions about the state. Returns the rendered answers, the request and the response.""" | |
| try: | |
| payload = build_request(state, choice_id, choice_instr, choice_opts, noul_id, noul_instr, score_id, score_instr, score_levels) | |
| req = parse_request(payload) | |
| resp = systemone(req, name) | |
| except RequestError as e: | |
| return error_html(str(e)), {}, {"error": str(e)} | |
| return render_answers(req, resp), payload, resp | |
| def decide_raw(name, request_json): | |
| """Answer a raw System One request (JSON text). Returns a status line and the response or an error object.""" | |
| try: | |
| payload = json.loads(request_json or "") | |
| except json.JSONDecodeError as e: | |
| message = f"Invalid JSON: {e}" | |
| return f"**Error:** {message}", {"error": message} | |
| if not isinstance(payload, dict): | |
| message = 'The request must be a JSON object with "state" and "questions".' | |
| return f"**Error:** {message}", {"error": message} | |
| try: | |
| resp = systemone(parse_request(payload), name) | |
| except RequestError as e: | |
| return f"**Error:** {e}", {"error": str(e)} | |
| return f"Answered in {resp['latency_ms']:.0f} ms.", resp | |
| # ------------------------------------------------------------------ examples | |
| DIALECT_GROUPS = ("egy: اللهجة المصرية\nlev: اللهجة الشامية\nglf: اللهجة الخليجية\nmag: اللهجة المغاربية\n" | |
| "irq: اللهجة العراقية\nmsa: العربية الفصحى") | |
| DIALECTNESS = "فصحى خالصة\nفصحى مع قليل من العامية\nمزيج متقارب من الفصحى والعامية\nعامية في الغالب" | |
| STARS = "نجمة واحدة: سيئ جدًّا\nنجمتان: سيئ\nثلاث نجوم: مقبول\nأربع نجوم: جيد\nخمس نجوم: ممتاز" | |
| SAFETY = ("safe: رسالة آمنة\nsafe_sensitive: رسالة آمنة تتناول موضوعًا حساسًا\nhate_harassment: كراهية أو تحرّش\n" | |
| "privacy_pii: انتهاك للخصوصية أو بيانات شخصية\nnonviolent_crime: جريمة غير عنيفة\n" | |
| "violent_weapons: عنف أو أسلحة\nmisinformation: معلومات مضلِّلة\n" | |
| "specialized_advice: طلب نصيحة متخصصة (طبية أو قانونية أو مالية) تتطلب خبيرًا\n" | |
| "prompt_injection: حقن تعليمات في المدخلات\njailbreak: محاولة لتجاوز قيود المساعد\n" | |
| "sexual_content: محتوى جنسي\nself_harm: إيذاء النفس") | |
| TOPICS = ("politic: السياسة\nfinance: المال والاقتصاد\nsport: الرياضة\ntechnology: التكنولوجيا\n" | |
| "medical: الطب والصحة\nculture: الثقافة\nreligion: الدين") | |
| NO_CHOICE, NO_NOUL, NO_SCORE = ("", "", ""), ("", ""), ("", "", "") | |
| EXAMPLES = [ | |
| { | |
| "name": "Dialect group (Egyptian)", | |
| "state": "يا عم انا مش فاهم انت عايز ايه بالظبط، قولي بسرعة عشان ورايا مشوار ومش هستناك كتير.", | |
| "choice": ("dialect", "ما اللهجة الغالبة على هذا النص؟", DIALECT_GROUPS), | |
| "noul": NO_NOUL, | |
| "score": ("dialectness", "ما مقدار استخدام اللهجة العامية في هذا النص؟", DIALECTNESS), | |
| }, | |
| { | |
| "name": "Dialect by country (Moroccan)", | |
| "state": "واش نتا غادي تجي معانا لدار خالتي غدا ولا لا؟ قولها ليا دابا باش نعرف شحال من واحد غادي نكونو.", | |
| "choice": ("dialect", "إلى لهجة أي بلد ينتمي هذا النص؟", | |
| "egypt: مصر\nsaudi_arabia: السعودية\nkuwait: الكويت\nlebanon: لبنان\nsyria: سوريا\niraq: العراق\n" | |
| "sudan: السودان\nlibya: ليبيا\ntunisia: تونس\nalgeria: الجزائر\nmorocco: المغرب"), | |
| "noul": NO_NOUL, | |
| "score": NO_SCORE, | |
| }, | |
| { | |
| "name": "Support routing (Gulf)", | |
| "state": '{"message": "السلام عليكم، من أمس والنت فاصل عندي في البيت، كلمت خدمة العملاء مرتين وما أحد رد علي. ' | |
| 'أبي أحد يجي يشوف الراوتر اليوم لو سمحتوا لأن عندي دوام من البيت."}', | |
| "choice": ("category", "إلى أي قسم ينتمي هذا الطلب؟", | |
| "home_internet: الإنترنت المنزلي\nbilling_payment: الفواتير والدفع\nmobile_line: خط الجوال\n" | |
| "devices: الأجهزة\nplans_subscription: الباقات والاشتراكات\ncustomer_care: خدمة العملاء العامة"), | |
| "noul": ("needs_human", "هل يحتاج هذا الطلب إلى موظف بشري؟"), | |
| "score": ("urgency", "كم هو ملحّ طلب العميل؟", "غير مستعجل\nمستعجل قليلًا\nمستعجل\nعاجل جدًا"), | |
| }, | |
| { | |
| "name": "Support routing (Egyptian)", | |
| "state": '{"message": "لو سمحت انا اتخصم مني فلوس الباقة مرتين الشهر ده، وعايز الفلوس ترجعلي في أسرع وقت."}', | |
| "choice": ("category", "إلى أي قسم ينتمي هذا الطلب؟", | |
| "home_internet: الإنترنت المنزلي\nbilling_payment: الفواتير والدفع\nmobile_line: خط الجوال\n" | |
| "devices: الأجهزة\nplans_subscription: الباقات والاشتراكات\ncustomer_care: خدمة العملاء العامة"), | |
| "noul": ("needs_human", "هل يحتاج هذا الطلب إلى موظف بشري؟"), | |
| "score": NO_SCORE, | |
| }, | |
| { | |
| "name": "Review rating 1–5 (Egyptian)", | |
| "state": '{"review": "الموبايل وصل بعد ميعاده بأسبوع والكرتونة كانت مفتوحة. بس بصراحة الشاشة حلوة ' | |
| 'والبطارية بتقعد يوم كامل، وبالسعر ده مش وحش."}', | |
| "choice": ("sentiment", "هل هذا النص إيجابي أم سلبي أم محايد؟", | |
| "positive: إيجابي\nnegative: سلبي\nmixed: مختلط: فيه إيجابي وسلبي"), | |
| "noul": NO_NOUL, | |
| "score": ("rating", "كم نجمة أعطى العميل لهذا المنتج؟", STARS), | |
| }, | |
| { | |
| "name": "Offensive tweet (Levantine)", | |
| "state": "شو هالحكم الحمار؟ والله لازم ينطرد من الملعب هو والاتحاد كلو، شلة فاشلين.", | |
| "choice": NO_CHOICE, | |
| "noul": ("offensive", "هل تحتوي التغريدة على شتم أو إهانة؟"), | |
| "score": NO_SCORE, | |
| }, | |
| { | |
| "name": "Guardrail category (Gulf)", | |
| "state": '{"message": "كيف أقدر أدخل على حساب زوجتي في الواتساب بدون ما تدري؟ أبي أشوف رسايلها كلها."}', | |
| "choice": ("safety_class", "ما فئة هذه الرسالة في نظام الحماية؟", SAFETY), | |
| "noul": ("unsafe", "هل هذه الرسالة غير آمنة؟"), | |
| "score": NO_SCORE, | |
| }, | |
| { | |
| "name": "Summary faithfulness (MSA)", | |
| "state": '{"text": "افتتحت بلدية المدينة صباح الأحد خطًّا جديدًا للحافلات الكهربائية يربط وسط المدينة بالمطار، ' | |
| 'بطول 22 كيلومترًا و14 محطة. وقال رئيس البلدية إن الخط سيعمل من السادسة صباحًا حتى منتصف الليل، ' | |
| 'وإن سعر التذكرة لن يتغير خلال السنة الأولى.", ' | |
| '"summary": "افتتاح خط حافلات كهربائية جديد بين وسط المدينة والمطار"}', | |
| "choice": NO_CHOICE, | |
| "noul": ("faithful", "هل الملخص مأخوذ من هذا المقال؟"), | |
| "score": NO_SCORE, | |
| }, | |
| { | |
| "name": "News topic (MSA)", | |
| "state": "قرر البنك المركزي رفع سعر الفائدة الرئيسي بمقدار نصف نقطة مئوية، في خطوة تهدف إلى كبح التضخم " | |
| "الذي بلغ أعلى مستوى له منذ عشر سنوات، وتوقع محللون أن تتراجع أسعار العقارات خلال الأشهر المقبلة.", | |
| "choice": ("topic", "صنِّف هذا الخبر بحسب موضوعه.", TOPICS), | |
| "noul": NO_NOUL, | |
| "score": NO_SCORE, | |
| }, | |
| ] | |
| def example_row(ex): | |
| return [ex["state"], *ex["choice"], *ex["noul"], *ex["score"]] | |
| RAW_EXAMPLE = json.dumps({ | |
| "state": {"message": "الطلبية رقم 4471 وصلت والشاشة مكسورة. عايز أبدلها، مش عايز فلوسي ترجع.", "channel": "email"}, | |
| "model": "kev-latest", | |
| "questions": { | |
| "team": {"type": "choice", "instructions": "أي فريق يجب أن يتولى هذا الطلب؟", | |
| "criteria": {"billing": "الفواتير والاسترداد", "shipping": "الشحن والتوصيل", "returns": "الاستبدال والإرجاع"}}, | |
| "refund_requested": {"type": "noul", "instructions": "هل يطلب العميل استرداد المبلغ؟"}, | |
| "frustration": {"type": "score", "instructions": "ما درجة انزعاج العميل؟", | |
| "criteria": ["هادئ", "منزعج", "غاضب جدًا"]}, | |
| }, | |
| }, ensure_ascii=False, indent=2) | |
| # ------------------------------------------------------------------ UI | |
| INTRO = f"""# Jevhakim | |
| A decision model for Arabic: Modern Standard Arabic and the dialects. It reads a **state** (a text or a JSON object) | |
| and typed questions, and returns a probability for each option of each question in one forward pass. It does not | |
| generate text. [Jevhakim-4B]({LINK_4B}) is more accurate; [Jevhakim-0.8B]({LINK_08B}) is smaller and faster. | |
| <div dir="rtl">نموذج قرار للعربية الفصحى واللهجات: يقرأ نصًّا وأسئلة، ويعطي احتمالًا لكل خيار. لا يولِّد نصًّا.</div> | |
| """ | |
| TEMPERATURES = ", ".join(f"T = {m.head.temperature:.2f} for {n}" for n, (_, m) in LOADED.items()) | |
| ABOUT = f"""Jevhakim-4B and Jevhakim-0.8B are [Kev](https://github.com/jaredpalmer/kev) (a LoRA adapter and a pointer | |
| head on Qwen3.5-4B-Base and Qwen3.5-0.8B-Base) fine-tuned on Arabic decision data built from public labelled sources, | |
| [TigreGotico/jevhakim-decisions]({LINK_DATA}). The tasks include dialect identification, customer-support and prompt | |
| routing, review ratings, sentiment and emotion, offensive and hate speech, guardrail safety classes, news topics, | |
| summary and translation faithfulness, question answering over documents, and exam questions. | |
| There are three question types: `choice` (pick one named option), `noul` (yes or no, the answer is p(yes)) and `score` | |
| (a level on an ordered list, the answer is the expected level). Keys (state fields, question ids, option names) are | |
| English code identifiers and values are Arabic, as in the training data. | |
| **Measured on the test split** (141,217 questions): | |
| | Model | Accuracy | ECE | Brier | | |
| | --- | --- | --- | --- | | |
| | Jevhakim-4B | 0.765 | 0.061 | 0.328 | | |
| | Jevhakim-0.8B | 0.661 | 0.063 | 0.438 | | |
| | Kev-0.8B (stock) | 0.503 | | | | |
| On the evaluation-only sources, which give no training rows (held-out synthetic sets), the accuracy is 0.727 for | |
| Jevhakim-4B, 0.585 for Jevhakim-0.8B and 0.490 for stock Kev-0.8B. | |
| The probabilities use the calibration temperature stored in each model's `head.pt` ({TEMPERATURES}). Measure the model | |
| on your own data before you set a threshold. | |
| The models do not generate text. They score the options you give them, and nothing else. | |
| Licence CC BY-NC-SA 4.0: non-commercial research only. Several training sources are research-only. The instruction | |
| wording is model-written Modern Standard Arabic that no native speaker has reviewed. Do not use the model for decisions | |
| with legal, medical, financial or employment consequences without human review. | |
| """ | |
| PLACEHOLDER = ('<div style="border:1px dashed var(--border-color-primary);border-radius:10px;padding:28px 16px;' | |
| 'text-align:center;color:var(--body-text-color-subdued);font-size:13px">' | |
| 'The answers appear here: one card per question, with one bar per option.</div>') | |
| CSS = """ | |
| #col-container { max-width: 1180px; margin: 0 auto; } | |
| #decide-btn { min-height: 44px; } | |
| """ | |
| first = EXAMPLES[2] | |
| with gr.Blocks(title="Jevhakim") as demo: | |
| with gr.Column(elem_id="col-container"): | |
| gr.Markdown(INTRO) | |
| model_choice = gr.Radio(list(MODELS), value=DEFAULT, label="Model") | |
| with gr.Tabs(): | |
| with gr.Tab("Questions · أسئلة"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| state_box = gr.Textbox(label="State · الحالة (the text the model reads)", lines=5, max_lines=14, | |
| value=first["state"], rtl=True, | |
| placeholder="رسالة أو مراجعة أو خبر أو كائن JSON") | |
| with gr.Group(): | |
| gr.Markdown("**Choice question** (`choice`)") | |
| with gr.Row(): | |
| choice_id = gr.Textbox(label="Id", value=first["choice"][0], scale=1) | |
| choice_instr = gr.Textbox(label="Instruction · التعليمة", value=first["choice"][1], | |
| scale=3, rtl=True) | |
| choice_opts = gr.Textbox(label="Options, one per line: name: description · الخيارات", | |
| lines=4, value=first["choice"][2], rtl=True) | |
| with gr.Group(): | |
| gr.Markdown("**Yes/no question** (`noul`)") | |
| with gr.Row(): | |
| noul_id = gr.Textbox(label="Id", value=first["noul"][0], scale=1) | |
| noul_instr = gr.Textbox(label="Instruction · التعليمة", value=first["noul"][1], | |
| scale=3, rtl=True) | |
| with gr.Group(): | |
| gr.Markdown("**Score question** (`score`)") | |
| with gr.Row(): | |
| score_id = gr.Textbox(label="Id", value=first["score"][0], scale=1) | |
| score_instr = gr.Textbox(label="Instruction · التعليمة", value=first["score"][1], | |
| scale=3, rtl=True) | |
| score_levels = gr.Textbox(label="Levels, lowest first, one per line · المستويات", lines=3, | |
| value=first["score"][2], rtl=True) | |
| gr.Markdown("Leave an id empty to leave that question out.") | |
| run_btn = gr.Button("Decide · قرِّر", variant="primary", elem_id="decide-btn") | |
| with gr.Column(scale=1): | |
| answers_html = gr.HTML(value=PLACEHOLDER) | |
| with gr.Accordion("Request sent (System One format)", open=False): | |
| request_json = gr.JSON() | |
| with gr.Accordion("Raw response", open=False): | |
| response_json = gr.JSON() | |
| inputs = [state_box, choice_id, choice_instr, choice_opts, noul_id, noul_instr, score_id, score_instr, score_levels] | |
| gr.Examples(examples=[example_row(ex) for ex in EXAMPLES], example_labels=[ex["name"] for ex in EXAMPLES], | |
| inputs=inputs, label="Examples") | |
| run_btn.click(fn=decide, inputs=[model_choice, *inputs], outputs=[answers_html, request_json, response_json], api_name="decide") | |
| with gr.Tab("Raw JSON"): | |
| gr.Markdown("Paste a System One request (`POST /v1/systemone`) as JSON and see the JSON response.") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| raw_in = gr.Code(label="Request (JSON)", language="json", lines=18, value=RAW_EXAMPLE) | |
| raw_btn = gr.Button("Send", variant="primary") | |
| with gr.Column(scale=1): | |
| raw_status = gr.Markdown() | |
| raw_out = gr.JSON(label="Response (JSON)") | |
| raw_btn.click(fn=decide_raw, inputs=[model_choice, raw_in], outputs=[raw_status, raw_out], api_name="systemone") | |
| with gr.Tab("About"): | |
| gr.Markdown(ABOUT) | |
| if __name__ == "__main__": | |
| demo.queue(default_concurrency_limit=1).launch(css=CSS) | |