File size: 10,419 Bytes
6f00bfe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
import os, json
import gradio as gr
from transformers import pipeline

# ---------------- Model (unchanged from your demo) ----------------
MODEL_NAME = os.getenv("HF_MODEL_GENERATION", "distilgpt2")
_pipe = None
def _get_pipe():
    global _pipe
    if _pipe is None:
        _pipe = pipeline("text-generation", model=MODEL_NAME)
    return _pipe

def model_generate(message, max_new_tokens=128, temperature=0.8, top_p=0.95):
    pipe = _get_pipe()
    out = pipe(
        message,
        max_new_tokens=int(max_new_tokens),
        do_sample=True,
        temperature=float(temperature),
        top_p=float(top_p),
        pad_token_id=50256,
    )
    return out[0]["generated_text"]

# ---------------- Storefront knowledge (prefers your helper module) ----------------
# First try to import your module (recommended location)
STORE_DATA = None
USE_HELPERS = False
try:
    from agenticcore.storefront_rules import (
        load_storefront, answer_faq, get_parking_rules, get_venue_rules, search_products
    )
    STORE_DATA = load_storefront()      # loads agenticcore/storefront_data.json by default
    USE_HELPERS = True
except Exception:
    # Fallback: try JSON next to this file or under agenticcore/
    CANDIDATE_JSON = [
        os.path.join(os.path.dirname(__file__), "storefront_data.json"),
        os.path.join(os.path.dirname(__file__), "agenticcore", "storefront_data.json"),
    ]
    for p in CANDIDATE_JSON:
        if os.path.exists(p):
            with open(p, "r", encoding="utf-8") as f:
                STORE_DATA = json.load(f)
            break

# Defaults (used when no JSON/module found)
DEFAULT_PRODUCTS = [
    {"SKU": "CG-SET", "Name": "Cap & Gown Set", "Price": 59.00, "Notes": "Tassel included; ship until 10 days before event"},
    {"SKU": "PK-1",   "Name": "Parking Pass",   "Price": 10.00, "Notes": "Multiple passes allowed per student"},
]
DEFAULT_PARKING = ["No double parking.", "Vehicles parked in handicap will be towed."]
DEFAULT_VENUE   = ["Formal attire recommended (not required).", "No muscle shirts.", "No sagging pants."]

if STORE_DATA:
    # Build right-panel tables from JSON schema
    try:
        DEFAULT_PRODUCTS = []
        for p in STORE_DATA.get("products", []):
            DEFAULT_PRODUCTS.append({
                "SKU": p.get("sku",""),
                "Name": p.get("name",""),
                "Price": p.get("price_usd", ""),
                "Notes": (p.get("description") or "")[:120],
            })
        pr = STORE_DATA.get("policies", {}).get("parking_rules", [])
        vr = STORE_DATA.get("policies", {}).get("venue_rules", [])
        DEFAULT_PARKING = pr or DEFAULT_PARKING
        DEFAULT_VENUE = vr or DEFAULT_VENUE
    except Exception:
        pass

def storefront_qna(text: str) -> str | None:
    """Try to answer with storefront data (FAQ, rules, products)."""
    t = (text or "").lower().strip()
    if not t:
        return None

    # Use your helper functions if available (preferred)
    if USE_HELPERS and STORE_DATA:
        a = answer_faq(STORE_DATA, t)
        if a:
            return a
        if "parking rule" in t or ("parking" in t and "rule" in t):
            rules = get_parking_rules(STORE_DATA)
            if rules:
                return "Parking rules:\n- " + "\n- ".join(rules)
        if "venue rule" in t or "dress code" in t or "attire" in t:
            rules = get_venue_rules(STORE_DATA)
            if rules:
                return "Venue rules:\n- " + "\n- ".join(rules)
        hits = search_products(STORE_DATA, t)
        if hits:
            lines = []
            for p in hits:
                price = p.get("price_usd")
                price_str = f"${price:.2f}" if isinstance(price, (int, float)) else str(price)
                lines.append(f"{p.get('name','Item')}{price_str}: {p.get('description','')}")
            return "\n".join(lines)
        return None

    # Fallback logic (no helper module)
    if "parking" in t and ("more than one" in t or "multiple" in t or "extra" in t):
        return "Yes, multiple parking passes are allowed per student."
    if "parking rule" in t or ("parking" in t and "rule" in t):
        return "Parking rules:\n- " + "\n- ".join(DEFAULT_PARKING)
    if "venue rule" in t or "dress code" in t or "attire" in t:
        return "Venue rules:\n- " + "\n- ".join(DEFAULT_VENUE)
    if "cap" in t or "gown" in t:
        lines = []
        for p in DEFAULT_PRODUCTS:
            price = p.get("Price")
            price_str = f"${price:.2f}" if isinstance(price, (int, float)) else str(price)
            lines.append(f"{p.get('Name','Item')}{price_str}: {p.get('Notes','')}")
        return "\n".join(lines)
    return None

def chat_pipeline(message, max_new_tokens=128, temperature=0.8, top_p=0.95):
    sf = storefront_qna(message)
    if sf:
        return sf
    return model_generate(message, max_new_tokens, temperature, top_p)

# ---------------- Gradio UI (storefront look) ----------------
CUSTOM_CSS = """
:root { --bg:#0b0d12; --panel:#0f172a; --panel-2:#111827; --border:#1f2940; --text:#e5e7eb; --muted:#9ca3af; }
.gradio-container { background: var(--bg) !important; color: var(--text) !important; }
#header, .panel { border:1px solid var(--border); border-radius:16px; background:var(--panel); }
.small { font-size:12px; color: var(--muted); }
.badge { font-size:12px; opacity:.9; padding:4px 8px; border:1px solid var(--border); border-radius:999px; background: rgba(255,255,255,.04); }
"""

with gr.Blocks(title="Storefront Chat • Cap & Gown + Parking", css=CUSTOM_CSS) as demo:
    with gr.Row(elem_id="header"):
        gr.Markdown("## Storefront Chat • Cap & Gown + Parking")
        gr.Markdown("<div class='badge'>Gradio • LLM • Storefront Q&A</div>", elem_classes=["small"])

    with gr.Row():
        with gr.Column(scale=3):
            with gr.Group(elem_classes=["panel"]):
                with gr.Row():
                    health_btn = gr.Button("Health", variant="secondary")
                    caps_btn = gr.Button("Capabilities", variant="secondary")
                    status_md = gr.Markdown("Status: not checked", elem_classes=["small"])

                chatbot = gr.Chatbot(value=[], height=360, bubble_full_width=False, avatar_images=(None, None), label="Chat")

                with gr.Row():
                    msg = gr.Textbox(placeholder="Ask about cap & gown sizes, parking rules, refunds, etc…", scale=5)
                    send = gr.Button("Send", scale=1)

                # Quick chips
                with gr.Row():
                    chip1 = gr.Button("Parking rules", variant="secondary")
                    chip2 = gr.Button("Multiple passes", variant="secondary")
                    chip3 = gr.Button("Attire", variant="secondary")
                    chip4 = gr.Button("Sizing", variant="secondary")
                    chip5 = gr.Button("Refunds", variant="secondary")
                    chip6 = gr.Button("Shipping cutoff", variant="secondary")

                with gr.Row():
                    max_new = gr.Slider(32, 512, 128, 1, label="Max new tokens")
                with gr.Row():
                    temp = gr.Slider(0.1, 1.5, 0.8, 0.05, label="Temperature")
                with gr.Row():
                    topp = gr.Slider(0.1, 1.0, 0.95, 0.05, label="Top-p")

        with gr.Column(scale=2):
            with gr.Group(elem_classes=["panel"]):
                gr.Markdown("### Products")
                cols = list(DEFAULT_PRODUCTS[0].keys()) if DEFAULT_PRODUCTS else ["SKU","Name","Price","Notes"]
                data = [[p.get(c,"") for c in cols] for p in DEFAULT_PRODUCTS]
                products_tbl = gr.Dataframe(headers=cols, value=data, interactive=False, wrap=True)

            with gr.Group(elem_classes=["panel"]):
                gr.Markdown("### Rules (Venue & Parking)")
                rules_md = gr.Markdown("- " + "\n- ".join(DEFAULT_VENUE + DEFAULT_PARKING))

            with gr.Group(elem_classes=["panel"]):
                gr.Markdown("### Logistics")
                gr.Markdown(
                    "- Shipping available until 10 days before event (typ. 3–5 business days)\n"
                    "- Pickup: Student Center Bookstore during week prior to event\n"
                    "- Graduates arrive 90 minutes early; guests 60 minutes early\n"
                    "- Lots A & B open 2 hours before; overflow as needed\n"
                    "\n*Try: “What time should I arrive?” or “Where do I pick up the gown?”*"
                )

    # ---- wiring ----
    def on_send(history, message, max_new_tokens, temperature, top_p):
        message = (message or "").strip()
        if not message:
            return history, ""
        history = history + [[message, None]]
        reply = chat_pipeline(message, max_new_tokens, temperature, top_p)
        history[-1][1] = reply
        return history, ""

    send.click(on_send, [chatbot, msg, max_new, temp, topp], [chatbot, msg])
    msg.submit(on_send, [chatbot, msg, max_new, temp, topp], [chatbot, msg])

    # Quick chips → prefill
    chip1.click(lambda: "What are the parking rules?", outputs=msg)
    chip2.click(lambda: "Can I buy multiple parking passes?", outputs=msg)
    chip3.click(lambda: "Is formal attire required?", outputs=msg)
    chip4.click(lambda: "How do I pick a cap & gown size?", outputs=msg)
    chip5.click(lambda: "What is the refund policy?", outputs=msg)
    chip6.click(lambda: "When is the shipping cutoff for cap & gown?", outputs=msg)

    # Health / capabilities
    def ui_health_check():
        return (f"### Status: ✅ Healthy\n- Model: `{MODEL_NAME}`\n"
                f"- Storefront module: {'yes' if USE_HELPERS else 'no'}\n"
                f"- Storefront JSON: {'loaded' if bool(STORE_DATA) else 'not found'}")
    def ui_capabilities():
        caps = [
            "Chat (LLM – text-generation)",
            "Quick storefront Q&A (parking rules, attire, products)",
            "Adjustable: max_new_tokens, temperature, top-p",
        ]
        return "### Capabilities\n- " + "\n- ".join(caps)

    def _health():
        return ui_health_check(), "Status: ✅ Healthy"

    health_btn.click(_health, outputs=[chatbot, status_md])
    caps_btn.click(ui_capabilities, outputs=chatbot)

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", "7860")))