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'''
''', 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('', 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()