import streamlit as st import os import sys import time sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) from client import JewelryShopEnv from models import JewelryAction from inference import get_model_action from openai import OpenAI from dotenv import load_dotenv load_dotenv() st.set_page_config(page_title="Jewelry Shop AI Agent", page_icon="๐Ÿ’Ž", layout="wide", initial_sidebar_state="expanded") # โ”€โ”€ PREMIUM CSS โ”€โ”€ st.markdown(""" """, unsafe_allow_html=True) # โ”€โ”€ HELPER FUNCTIONS โ”€โ”€ def render_phase_pipeline(current_phase, done): phases = ["market", "warehouse", "showroom"] icons = {"market": "๐Ÿ“ˆ", "warehouse": "๐Ÿญ", "showroom": "๐Ÿ›๏ธ"} labels = {"market": "MARKET", "warehouse": "WAREHOUSE", "showroom": "SHOWROOM"} current_idx = phases.index(current_phase) if current_phase in phases else 0 html = '
' for i, p in enumerate(phases): cls = "phase-node" if done: cls += " done" elif i == current_idx: cls += f" active {p}" elif i < current_idx: cls += " done" html += f'
{icons[p]} {labels[p]}
' if i < len(phases) - 1: html += 'โ†’' html += '
' st.markdown(html, unsafe_allow_html=True) def render_metrics(obs): crafted = [f"{v} {k}" for k, v in obs.inventory.items() if v > 0] items_str = ", ".join(crafted) if crafted else "โ€”" cols = st.columns(5) cards = [ ("phase", "๐Ÿ”„", str(obs.phase).upper(), "CURRENT PHASE", "v-phase"), ("cash", "๐Ÿ’ต", f"${obs.cash:,.2f}", "CASH BALANCE", "v-cash"), ("gold", "๐Ÿช™", f"{obs.gold_oz:.2f} oz", "RAW GOLD", "v-gold"), ("items", "๐Ÿ’", items_str, "CRAFTED ITEMS", "v-items"), ("price", "๐Ÿ“Š", f"${obs.gold_price:.2f}", "GOLD PRICE/OZ", "v-price"), ] for col, (ctype, icon, value, label, vcls) in zip(cols, cards): with col: st.markdown(f'''
{icon}
{value}
{label}
''', unsafe_allow_html=True) def format_log_entry(step, phase, action_str, reward, msg=""): badge_cls = f"badge-{phase}" if reward > 0: r_cls, r_sign = "reward-pos", "+" elif reward < 0: r_cls, r_sign = "reward-neg", "" else: r_cls, r_sign = "reward-zero", "" html = f'''
#{step} {phase.upper()} {action_str} {r_sign}{reward:.3f}
''' if msg: html += f'
๐Ÿ’ฌ {msg}
' return html # โ”€โ”€ MAIN APP โ”€โ”€ def main(): # โ”€โ”€ SIDEBAR โ”€โ”€ with st.sidebar: st.markdown("""
๐Ÿ’Ž
ShopManager AI
Powered by GRPO Fine-tuning
""", unsafe_allow_html=True) st.markdown("---") hf_token = st.text_input("๐Ÿ”‘ HuggingFace Token", type="password", value=os.getenv("HF_TOKEN", ""), help="Your HF API token for model inference") models = [ "hard007ik/shopmanager-grpo-qwen3", "meta-llama/Llama-3.3-70B-Instruct", "Qwen/Qwen2.5-72B-Instruct", ] model_name = st.selectbox("๐Ÿค– AI Model", models) env_url = st.text_input("๐ŸŒ Environment URL", value="https://hard007ik-shopmanagereng.hf.space") task_type = st.selectbox("๐ŸŽฏ Task Focus", [ "market_timing", "demand_crafter", "profit_negotiator" ], help="Controls reward weighting across phases") task_labels = { "market_timing": "Market 60% ยท Craft 20% ยท Sell 20%", "demand_crafter": "Market 20% ยท Craft 60% ยท Sell 20%", "profit_negotiator": "Market 20% ยท Craft 20% ยท Sell 60%", } st.caption(task_labels[task_type]) st.markdown("
", unsafe_allow_html=True) start_btn = st.button("๐Ÿš€ Start New Episode", use_container_width=True) st.markdown("---") st.markdown("""
Model: shopmanager-grpo-qwen3
Env: HuggingFace Spaces
""", unsafe_allow_html=True) # โ”€โ”€ HERO HEADER โ”€โ”€ st.markdown("""
๐Ÿ’Ž Jewelry Shop AI Agent
Watch your fine-tuned AI autonomously manage a jewelry business โ€” buying gold at the right price, crafting high-demand products, and negotiating maximum profit with customers.
""", unsafe_allow_html=True) # โ”€โ”€ PLACEHOLDERS โ”€โ”€ pipeline_ph = st.empty() metrics_ph = st.empty() chart_cols_ph = st.empty() st.markdown('
๐Ÿ“œ Live Event Log
', unsafe_allow_html=True) log_ph = st.empty() result_ph = st.empty() # โ”€โ”€ IDLE STATE โ”€โ”€ if not start_btn: pipeline_ph.markdown("""
๐Ÿ“ˆ MARKET
โ†’
๐Ÿญ WAREHOUSE
โ†’
๐Ÿ›๏ธ SHOWROOM
""", unsafe_allow_html=True) st.info("๐Ÿ‘ˆ Configure settings in the sidebar and click **Start New Episode** to watch the AI run!") return # โ”€โ”€ VALIDATION โ”€โ”€ if not hf_token: st.error("๐Ÿ”‘ Please enter your HuggingFace API Token in the sidebar.") return # โ”€โ”€ INIT ENV โ”€โ”€ with st.spinner("๐Ÿ”Œ Connecting to environment server..."): try: env = JewelryShopEnv(base_url=env_url).sync() result = env.reset(task_id=task_type) obs = result.observation client = OpenAI(base_url="https://router.huggingface.co/v1", api_key=hf_token) except Exception as e: st.error(f"โŒ Failed to connect: {e}") return # โ”€โ”€ TRACKING STATE โ”€โ”€ history = [] rewards_list = [] gold_prices = list(obs.gold_price_history) if obs.gold_price_history else [obs.gold_price] step_num = 1 done = result.done last_reward = result.reward or 0.0 log_html = "" phase_order = [] # Render initial state with pipeline_ph.container(): render_phase_pipeline(obs.phase, False) with metrics_ph.container(): render_metrics(obs) # โ”€โ”€ AUTOMATED EPISODE LOOP โ”€โ”€ while not done and step_num <= 15: # 1. Ask AI for action try: import inference orig_model = inference.MODEL_NAME inference.MODEL_NAME = model_name action, action_str = get_model_action( client=client, step=step_num, obs=obs, last_reward=last_reward, history=history ) inference.MODEL_NAME = orig_model except Exception as e: log_html += f'
โŒ AI Error: {e}
' log_ph.markdown(log_html, unsafe_allow_html=True) break current_phase = obs.phase phase_order.append(current_phase) # 2. Step environment try: result = env.step(action) obs = result.observation reward = result.reward or 0.0 done = result.done rewards_list.append(reward) history.append(f"Step {step_num} ({current_phase}): '{action_str}' -> reward {reward:+.2f}") # Track gold prices if obs.gold_price_history: gold_prices = list(obs.gold_price_history) elif obs.gold_price and obs.gold_price not in gold_prices: gold_prices.append(obs.gold_price) except Exception as e: log_html += f'
โŒ Env Error: {e}
' log_ph.markdown(log_html, unsafe_allow_html=True) break # 3. Update all displays log_html += format_log_entry(step_num, current_phase, action_str, reward, obs.message) log_ph.markdown(log_html, unsafe_allow_html=True) with pipeline_ph.container(): render_phase_pipeline(obs.phase if not done else "showroom", done) with metrics_ph.container(): render_metrics(obs) # 4. Update charts with chart_cols_ph.container(): c1, c2, c3 = st.columns(3) with c1: st.markdown('
', unsafe_allow_html=True) st.caption("๐Ÿ“ˆ Gold Price Trend") if len(gold_prices) > 1: st.line_chart(gold_prices, height=180) else: st.info(f"Current: ${gold_prices[0]:.2f}/oz") st.markdown('
', unsafe_allow_html=True) with c2: st.markdown('
', unsafe_allow_html=True) st.caption("๐Ÿ“Š Demand Forecast") forecast = getattr(obs, "demand_forecast", {}) or {} if forecast: st.bar_chart(forecast, height=180) else: demand = getattr(obs, "demand", {}) or {} if demand: st.bar_chart(demand, height=180) else: st.info("No demand data yet") st.markdown('
', unsafe_allow_html=True) with c3: st.markdown('
', unsafe_allow_html=True) st.caption("๐Ÿ† Cumulative Reward") if rewards_list: cum_rewards = [] running = 0 for r in rewards_list: running += r cum_rewards.append(running) st.area_chart(cum_rewards, height=180) else: st.info("No rewards yet") st.markdown('
', unsafe_allow_html=True) last_reward = reward step_num += 1 time.sleep(1.0) # โ”€โ”€ CLEANUP โ”€โ”€ try: env.close() except Exception: pass # โ”€โ”€ FINAL RESULTS โ”€โ”€ score = float(getattr(obs, "cumulative_reward", sum(rewards_list) if rewards_list else 0.0)) score = min(max(score, 0.0), 1.0) success = score >= 0.01 with result_ph.container(): cls = "success" if success else "fail" color = "#00E09E" if success else "#FF4B6E" icon = "๐ŸŽ‰" if success else "๐Ÿ˜ž" label = "SUCCESS" if success else "FAILED" st.markdown(f"""
{icon}
{label}
{score:.4f}
Steps: {step_num - 1}  |  Task: {task_type}  |  Model: {model_name.split('/')[-1]}
""", unsafe_allow_html=True) if __name__ == "__main__": main()