File size: 5,395 Bytes
efb5cee 493bc10 e321e95 493bc10 efb5cee e321e95 efb5cee 493bc10 efb5cee 493bc10 efb5cee 493bc10 efb5cee 493bc10 efb5cee 493bc10 efb5cee 493bc10 efb5cee 493bc10 efb5cee e321e95 493bc10 efb5cee 493bc10 efb5cee e321e95 efb5cee 493bc10 efb5cee 493bc10 efb5cee e321e95 493bc10 efb5cee e321e95 493bc10 efb5cee 493bc10 efb5cee 493bc10 efb5cee | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | """
Molecule Maestro – Category Shuffle Edition (Markdown‑Table Output)
------------------------------------------------------------------
* Six category boxes (🔴 Acids, 🔵 Bases, …).
* **Reshuffle** keeps 50 % of reactants.
* One free‑form **Conditions** textbox.
* **NEW:** GPT now returns a ready‑made **Markdown table** – no JSON parsing required.
```bash
pip install "gradio>=4" "openai>=1.0"
export OPENAI_API_KEY="sk‑…"
python molecule_maestro_gradio.py
```
"""
from __future__ import annotations
import random
from typing import List, Dict
from openai import OpenAI
import gradio as gr
client = OpenAI()
CATEGORIES = [
("acid", "🔴 Acids"),
("base", "🔵 Bases"),
("solvent", "🟢 Solvents"),
("gas", "🟡 Gases"),
("oxidizer", "🟣 Oxidizers"),
("other", "⚪ Others"),
]
SYSTEM_PROMPT_PALETTE = (
"You are a helpful chemistry assistant. Provide a JSON array named 'palette' with exactly 12 reactants commonly used in school labs. "
"Each item must have fields: name (string) and category (acid, base, solvent, gas, oxidizer, or other)."
)
SYSTEM_PROMPT_REACTION = (
"You are a chemistry mentor. The user will supply 1‑5 reactants and an optional conditions string. "
"Respond ONLY with a Markdown table **including the header row**: \n"
"| Reactants | Conditions | Reaction Equation | Observations |\n"
"|---|---|---|---|\n"
"Each subsequent row lists **one** plausible, single‑step, school‑level reaction (max 5 rows total). "
"Observations must indicate exothermic or endothermic and an approximate ΔH in kJ, plus a ≤25‑word note. "
"If no simple reaction is feasible, output the header row followed by a row stating 'No simple reaction found' in the Reaction Equation column and leave other cells blank."
)
def chat(prompt: str, system: str, temp: float = 0.4) -> str:
return client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "system", "content": system}, {"role": "user", "content": prompt}],
temperature=temp,
).choices[0].message.content.strip()
# ---------- Palette helpers ----------
def gen_palette() -> List[Dict[str, str]]:
try:
import json
raw = chat("Give me the palette", SYSTEM_PROMPT_PALETTE)
return json.loads(raw)["palette"]
except Exception:
return [
{"name": "HCl", "category": "acid"}, {"name": "NaOH", "category": "base"}, {"name": "Ethanol", "category": "solvent"},
{"name": "Acetic Acid", "category": "acid"}, {"name": "NH3", "category": "base"}, {"name": "H2O", "category": "solvent"},
{"name": "H2", "category": "gas"}, {"name": "O2", "category": "gas"}, {"name": "KMnO4", "category": "oxidizer"},
{"name": "H2SO4", "category": "acid"}, {"name": "NaCl", "category": "other"}, {"name": "CuSO4", "category": "other"},
]
palette: List[Dict[str, str]] = gen_palette()
def labels_by_cat(cat: str) -> List[str]:
return [item["name"] for item in palette if item["category"] == cat]
# ---------- Reaction evaluation ----------
def evaluate(reactants: List[str], cond: str) -> str:
if not reactants:
return "⚠️ Select at least one reactant."
prompt = f"Reactants: {', '.join(reactants)}. Conditions: {cond or 'none'}."
table = chat(prompt, SYSTEM_PROMPT_REACTION, 0.3)
# Basic sanity check: ensure header present
if "| Reactants |" not in table:
return f"Parse err 🤖\n```\n{table}\n```"
return table
# ---------- Gradio UI ----------
with gr.Blocks() as demo:
gr.Markdown("# 🧪 Molecule Maestro – Category Shuffle Edition")
with gr.Row():
cat_groups = {}
for cat, title in CATEGORIES:
with gr.Column():
cat_groups[cat] = gr.CheckboxGroup(choices=labels_by_cat(cat), label=title)
with gr.Column():
shuffle_btn = gr.Button("🔀 Reshuffle (50 % new)")
conditions_tb = gr.Textbox(label="Conditions (optional)")
run_btn = gr.Button("▶︎ Generate Reactions")
result_md = gr.Markdown("—")
# ---------- Callbacks ----------
def _shuffle():
global palette
keep = random.sample(palette, len(palette)//2)
kept_names = {i['name'] for i in keep}
fresh: List[Dict[str, str]] = []
attempts = 0
while len(fresh) < len(palette)//2 and attempts < 5:
for item in gen_palette():
if item['name'] not in kept_names and item['name'] not in {f['name'] for f in fresh}:
fresh.append(item)
if len(fresh) == len(palette)//2:
break
attempts += 1
palette[:] = keep + fresh
random.shuffle(palette)
updates = [gr.update(choices=labels_by_cat(cat), value=[]) for cat, _ in CATEGORIES]
updates.append("—")
return updates
def _run(*inputs):
*checkbox_lists, cond = inputs
chosen: List[str] = []
for lst in checkbox_lists:
chosen.extend(lst)
return evaluate(chosen, cond)
shuffle_btn.click(_shuffle, None, [cat_groups[c] for c, _ in CATEGORIES] + [result_md])
run_btn.click(_run, [cat_groups[c] for c, _ in CATEGORIES] + [conditions_tb], result_md)
if __name__ == "__main__":
demo.launch()
|