JerameeUC commited on
Commit
4e4b89c
·
1 Parent(s): 3a3f10b

Fixed memory.

Browse files
Files changed (1) hide show
  1. storefront_tabs_memory_app.py +286 -0
storefront_tabs_memory_app.py ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # storefront_tabs_memory_app.py
2
+ import os, json
3
+ import gradio as gr
4
+ from transformers import pipeline
5
+
6
+ # ---------------- Model ----------------
7
+ MODEL_NAME = os.getenv("HF_MODEL_GENERATION", "distilgpt2")
8
+ _pipe = None
9
+ def _get_pipe():
10
+ global _pipe
11
+ if _pipe is None:
12
+ _pipe = pipeline("text-generation", model=MODEL_NAME)
13
+ return _pipe
14
+
15
+ def model_generate(prompt, max_new_tokens=128, temperature=0.8, top_p=0.95):
16
+ out = _get_pipe()(
17
+ prompt,
18
+ max_new_tokens=int(max_new_tokens),
19
+ do_sample=True,
20
+ temperature=float(temperature),
21
+ top_p=float(top_p),
22
+ pad_token_id=50256,
23
+ )
24
+ return out[0]["generated_text"]
25
+
26
+
27
+ # ---------------- Storefront knowledge (helper module preferred) ----------------
28
+ STORE_DATA, USE_HELPERS = None, False
29
+ try:
30
+ # Optional helper module under agenticcore/
31
+ from agenticcore.storefront_rules import (
32
+ load_storefront, answer_faq, get_parking_rules, get_venue_rules, search_products
33
+ )
34
+ STORE_DATA = load_storefront()
35
+ USE_HELPERS = True
36
+ except Exception:
37
+ # Fallback: try JSON next to this file or under agenticcore/
38
+ CANDIDATES = [
39
+ os.path.join(os.path.dirname(__file__), "storefront_data.json"),
40
+ os.path.join(os.path.dirname(__file__), "agenticcore", "storefront_data.json"),
41
+ ]
42
+ for p in CANDIDATES:
43
+ if os.path.exists(p):
44
+ with open(p, "r", encoding="utf-8") as f:
45
+ STORE_DATA = json.load(f)
46
+ break
47
+
48
+ # Defaults if JSON/module missing
49
+ DEFAULT_PRODUCTS = [
50
+ {"SKU": "CG-SET", "Name": "Cap & Gown Set", "Price": 59.00, "Notes": "Tassel included; ship until 10 days before event"},
51
+ {"SKU": "PK-1", "Name": "Parking Pass", "Price": 10.00, "Notes": "Multiple passes allowed per student"},
52
+ ]
53
+ DEFAULT_PARKING = ["No double parking.", "Vehicles parked in handicap will be towed."]
54
+ DEFAULT_VENUE = ["Formal attire recommended (not required).", "No muscle shirts.", "No sagging pants."]
55
+
56
+ # Normalize JSON to tables for the UI
57
+ if STORE_DATA:
58
+ try:
59
+ DEFAULT_PRODUCTS = [{
60
+ "SKU": p.get("sku",""),
61
+ "Name": p.get("name",""),
62
+ "Price": p.get("price_usd",""),
63
+ "Notes": (p.get("description") or "")[:120],
64
+ } for p in STORE_DATA.get("products", [])]
65
+ DEFAULT_PARKING = STORE_DATA.get("policies", {}).get("parking_rules", DEFAULT_PARKING) or DEFAULT_PARKING
66
+ DEFAULT_VENUE = STORE_DATA.get("policies", {}).get("venue_rules", DEFAULT_VENUE) or DEFAULT_VENUE
67
+ except Exception:
68
+ pass
69
+
70
+
71
+ # ---------------- Memory seeding ----------------
72
+ def seed_storefront_facts() -> str:
73
+ """Small system-like primer injected ahead of chat history to bias the LLM toward storefront truth."""
74
+ lines = ["You are answering questions about a graduation storefront.",
75
+ "Products:"]
76
+ for p in DEFAULT_PRODUCTS:
77
+ price = p.get("Price")
78
+ if isinstance(price, (int, float)):
79
+ price = f"${price:.2f}"
80
+ lines.append(f"- {p.get('Name','Item')} ({p.get('SKU','')}) — {price}: {p.get('Notes','')}")
81
+ lines.append("Venue rules:")
82
+ for r in DEFAULT_VENUE:
83
+ lines.append(f"- {r}")
84
+ lines.append("Parking rules:")
85
+ for r in DEFAULT_PARKING:
86
+ lines.append(f"- {r}")
87
+ lines.append("Answer concisely using these facts. If unsure, say what’s known from the list above.")
88
+ return "\n".join(lines)
89
+
90
+ SEED_TEXT = seed_storefront_facts()
91
+
92
+ def build_prompt_from_history(history, user_text, k=4):
93
+ """history = [[user, bot], ...] — build a short rolling prompt + seed facts."""
94
+ lines = [SEED_TEXT, "", "Conversation so far:"]
95
+ for u, b in (history or [])[-k:]:
96
+ if u: lines.append(f"User: {u}")
97
+ if b: lines.append(f"Assistant: {b}")
98
+ lines.append(f"User: {user_text}")
99
+ lines.append("Assistant:")
100
+ return "\n".join(lines)
101
+
102
+
103
+ # ---------------- Storefront Q&A router (storefront first, then LLM) ----------------
104
+ def storefront_qna(text: str) -> str | None:
105
+ t = (text or "").lower().strip()
106
+ if not t:
107
+ return None
108
+
109
+ # Single-word catches to avoid LLM drift
110
+ if t in {"parking"}:
111
+ return "Parking rules:\n- " + "\n- ".join(DEFAULT_PARKING)
112
+ if t in {"venue", "attire", "dress", "dress code"}:
113
+ return "Venue rules:\n- " + "\n- ".join(DEFAULT_VENUE)
114
+ if t in {"passes", "parking pass", "parking passes"}:
115
+ return "Yes, multiple parking passes are allowed per student."
116
+
117
+ # Prefer helper functions if available
118
+ if USE_HELPERS and STORE_DATA:
119
+ a = answer_faq(STORE_DATA, t)
120
+ if a:
121
+ return a
122
+ if "parking" in t and "rule" in t:
123
+ r = get_parking_rules(STORE_DATA)
124
+ if r:
125
+ return "Parking rules:\n- " + "\n- ".join(r)
126
+ if ("venue" in t and "rule" in t) or "attire" in t or "dress code" in t:
127
+ r = get_venue_rules(STORE_DATA)
128
+ if r:
129
+ return "Venue rules:\n- " + "\n- ".join(r)
130
+ # Specific timing phrasing to avoid hallucinated dates
131
+ if "parking" in t and ("hours" in t or "time" in t or "open" in t):
132
+ return "Parking lots open 2 hours before the ceremony."
133
+ hits = search_products(STORE_DATA, t)
134
+ if hits:
135
+ return "\n".join(
136
+ f"{p.get('name','Item')} — ${p.get('price_usd',0):.2f}: {p.get('description','')}"
137
+ for p in hits
138
+ )
139
+ return None
140
+
141
+ # Fallback rules (no helper module)
142
+ if "parking" in t and ("more than one" in t or "multiple" in t or "extra" in t):
143
+ return "Yes, multiple parking passes are allowed per student."
144
+ if "parking" in t and "rule" in t:
145
+ return "Parking rules:\n- " + "\n- ".join(DEFAULT_PARKING)
146
+ if "parking" in t and ("hours" in t or "time" in t or "open" in t):
147
+ return "Parking lots open 2 hours before the ceremony."
148
+ if "attire" in t or "dress code" in t or ("venue" in t and "rule" in t):
149
+ return "Venue rules:\n- " + "\n- ".join(DEFAULT_VENUE)
150
+ if "cap" in t or "gown" in t:
151
+ return "\n".join(
152
+ f"{p['Name']} — ${p['Price']:.2f}: {p['Notes']}"
153
+ for p in DEFAULT_PRODUCTS
154
+ )
155
+ return None
156
+
157
+
158
+ def chat_pipeline(history, message, max_new_tokens=128, temperature=0.8, top_p=0.95):
159
+ # 1) Try storefront knowledge first
160
+ sf = storefront_qna(message)
161
+ if sf:
162
+ return sf
163
+ # 2) Memory-aware model prompt
164
+ prompt = build_prompt_from_history(history, message, k=4)
165
+ return model_generate(prompt, max_new_tokens, temperature, top_p)
166
+
167
+
168
+ # ---------------- Gradio UI (Tabs + Accordion) ----------------
169
+ CSS = """
170
+ :root { --bg:#0b0d12; --panel:#0f172a; --border:#1f2940; --text:#e5e7eb; --muted:#9ca3af; }
171
+ .gradio-container { background: var(--bg) !important; color: var(--text) !important; }
172
+ .panel { border:1px solid var(--border); border-radius:16px; background:var(--panel); }
173
+ .small { font-size:12px; color: var(--muted); }
174
+ """
175
+
176
+ with gr.Blocks(title="Storefront Chat", css=CSS) as demo:
177
+ gr.Markdown("## Storefront Chat")
178
+
179
+ # Keep a single source of truth for chat history (pairs of [user, bot])
180
+ history_state = gr.State([])
181
+
182
+ with gr.Tabs():
183
+ # --- TAB 1: Chat (sliders tucked into an accordion) ---
184
+ with gr.TabItem("Chat"):
185
+ with gr.Group(elem_classes=["panel"]):
186
+ chat = gr.Chatbot(height=360, bubble_full_width=False, label="Chat")
187
+
188
+ with gr.Row():
189
+ msg = gr.Textbox(placeholder="Ask about parking rules, attire, cap & gown, pickup times…", scale=5)
190
+ send = gr.Button("Send", scale=1)
191
+
192
+ # Quick chips
193
+ with gr.Row():
194
+ chip1 = gr.Button("Parking rules", variant="secondary")
195
+ chip2 = gr.Button("Multiple passes", variant="secondary")
196
+ chip3 = gr.Button("Attire", variant="secondary")
197
+ chip4 = gr.Button("When do lots open?", variant="secondary")
198
+
199
+ # Advanced options hidden
200
+ with gr.Accordion("Advanced chat options", open=False):
201
+ max_new = gr.Slider(32, 512, 128, 1, label="Max new tokens")
202
+ temp = gr.Slider(0.1, 1.5, 0.8, 0.05, label="Temperature")
203
+ topp = gr.Slider(0.1, 1.0, 0.95, 0.05, label="Top-p")
204
+
205
+ # Small utilities
206
+ with gr.Row():
207
+ health_btn = gr.Button("Health", variant="secondary")
208
+ caps_btn = gr.Button("Capabilities", variant="secondary")
209
+ status_md = gr.Markdown("Status: not checked", elem_classes=["small"])
210
+
211
+ # --- TAB 2: Products ---
212
+ with gr.TabItem("Products"):
213
+ gr.Markdown("### Available Items")
214
+ cols = list(DEFAULT_PRODUCTS[0].keys()) if DEFAULT_PRODUCTS else ["SKU","Name","Price","Notes"]
215
+ data = [[p.get(c,"") for c in cols] for p in DEFAULT_PRODUCTS]
216
+ _products_tbl = gr.Dataframe(headers=cols, value=data, interactive=False, wrap=True, label="Products")
217
+
218
+ # --- TAB 3: Rules ---
219
+ with gr.TabItem("Rules"):
220
+ gr.Markdown("### Venue rules")
221
+ gr.Markdown("- " + "\n- ".join(DEFAULT_VENUE))
222
+ gr.Markdown("### Parking rules")
223
+ gr.Markdown("- " + "\n- ".join(DEFAULT_PARKING))
224
+
225
+ # --- TAB 4: Logistics ---
226
+ with gr.TabItem("Logistics"):
227
+ gr.Markdown(
228
+ "### Event Logistics\n"
229
+ "- Shipping available until 10 days before event (typ. 3–5 business days)\n"
230
+ "- Pickup: Student Center Bookstore during the week prior to event\n"
231
+ "- Graduates arrive 90 minutes early; guests 60 minutes early\n"
232
+ "- Lots A & B open 2 hours before; overflow as needed\n"
233
+ "\n*Try asking the bot:* “What time should I arrive?” • “Where do I pick up the gown?”"
234
+ )
235
+
236
+ # ---------- Helpers that keep Chatbot history valid (list of [u,b]) ----------
237
+ def _append_bot_md(history, md_text):
238
+ """Append a bot markdown message without breaking the [user, bot] format."""
239
+ history = history or []
240
+ return history + [[None, md_text]]
241
+
242
+ # ---------- Callbacks ----------
243
+ def on_send(history, message, max_new_tokens, temperature, top_p):
244
+ t = (message or "").strip()
245
+ if not t:
246
+ return history, history, "" # no-op
247
+ history = (history or []) + [[t, None]]
248
+ reply = chat_pipeline(history[:-1], t, max_new_tokens, temperature, top_p)
249
+ history[-1][1] = reply
250
+ # Return updated state AND what the Chatbot should render
251
+ return history, history, ""
252
+
253
+ def _health_cb(history):
254
+ md = (f"### Status: ✅ Healthy\n"
255
+ f"- Model: `{MODEL_NAME}`\n"
256
+ f"- Storefront module: {'yes' if USE_HELPERS else 'no'}\n"
257
+ f"- Storefront JSON: {'loaded' if bool(STORE_DATA) else 'not found'}")
258
+ new_hist = _append_bot_md(history, md)
259
+ return new_hist, new_hist, "Status: ✅ Healthy"
260
+
261
+ def _caps_cb(history):
262
+ caps = [
263
+ "Chat (LLM text-generation, memory-aware prompt)",
264
+ "Storefront Q&A (parking, attire, products, logistics)",
265
+ "Adjustable: max_new_tokens, temperature, top-p",
266
+ ]
267
+ md = "### Capabilities\n- " + "\n- ".join(caps)
268
+ new_hist = _append_bot_md(history, md)
269
+ return new_hist, new_hist
270
+
271
+ # Wire up (note: always update both the state and the Chatbot value)
272
+ send.click(on_send, [history_state, msg, max_new, temp, topp], [history_state, chat, msg])
273
+ msg.submit(on_send, [history_state, msg, max_new, temp, topp], [history_state, chat, msg])
274
+
275
+ # Chips
276
+ chip1.click(lambda: "What are the parking rules?", outputs=msg)
277
+ chip2.click(lambda: "Can I buy multiple parking passes?", outputs=msg)
278
+ chip3.click(lambda: "Is formal attire required?", outputs=msg)
279
+ chip4.click(lambda: "What time do the parking lots open?", outputs=msg)
280
+
281
+ # Health / capabilities (append to chat, keep valid tuple format)
282
+ health_btn.click(_health_cb, inputs=[history_state], outputs=[history_state, chat, status_md])
283
+ caps_btn.click(_caps_cb, inputs=[history_state], outputs=[history_state, chat])
284
+
285
+ if __name__ == "__main__":
286
+ demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", "7860")))