Spaces:
Sleeping
Sleeping
Rajan Sharma
commited on
Update app.py
Browse files
app.py
CHANGED
|
@@ -47,13 +47,12 @@ from huggingface_hub import login
|
|
| 47 |
|
| 48 |
from safety import safety_filter, refusal_reply
|
| 49 |
from retriever import init_retriever, retrieve_context
|
| 50 |
-
from decision_math import compute_operational_numbers
|
| 51 |
from prompt_templates import build_system_preamble
|
| 52 |
from upload_ingest import extract_text_from_files
|
| 53 |
from session_rag import SessionRAG
|
| 54 |
-
from mdsi_analysis import capacity_projection, cost_estimate, outcomes_summary
|
| 55 |
|
| 56 |
-
# NEW: dynamic data
|
| 57 |
from data_registry import DataRegistry
|
| 58 |
from schema_mapper import map_concepts, build_phase1_questions
|
| 59 |
from auto_metrics import build_data_findings_markdown
|
|
@@ -68,23 +67,21 @@ USE_HOSTED_COHERE = bool(COHERE_API_KEY and _HAS_COHERE)
|
|
| 68 |
# Larger output budget for Phase 2
|
| 69 |
MAX_NEW_TOKENS = int(os.getenv("MAX_NEW_TOKENS", "2048"))
|
| 70 |
|
| 71 |
-
# ---------- System
|
| 72 |
SYSTEM_MASTER = """
|
| 73 |
SYSTEM ROLE
|
| 74 |
-
You are
|
| 75 |
Absolute rules:
|
| 76 |
- Use ONLY information provided in this conversation (scenario text + uploaded files + user answers).
|
| 77 |
- Never invent data. If something required is missing after clarifications, write the literal token: INSUFFICIENT_DATA.
|
| 78 |
-
-
|
| 79 |
-
|
| 80 |
-
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
5. ClarityOps Top 3 Recommendations
|
| 87 |
-
- End with a brief “Provenance” mapping outputs to scenario text, uploaded files, and answers.
|
| 88 |
""".strip()
|
| 89 |
|
| 90 |
# ---------- Helpers ----------
|
|
@@ -122,10 +119,21 @@ def _sanitize_text(s: str) -> str:
|
|
| 122 |
return re2.sub(r'[\p{C}--[\n\t]]+', '', s)
|
| 123 |
|
| 124 |
def is_scenario_triggered(text: str, uploaded_files_paths) -> bool:
|
|
|
|
| 125 |
t = (text or "").lower()
|
| 126 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
has_files = bool(uploaded_files_paths)
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
# ---------- Cohere first ----------
|
| 131 |
def cohere_chat(message, history):
|
|
@@ -206,39 +214,20 @@ def local_generate(model, tokenizer, input_ids, max_new_tokens=MAX_NEW_TOKENS):
|
|
| 206 |
|
| 207 |
# ---------- Snapshot & retrieval ----------
|
| 208 |
def _load_snapshot(path=SNAPSHOT_PATH):
|
|
|
|
| 209 |
try:
|
| 210 |
with open(path, "r", encoding="utf-8") as f:
|
| 211 |
return json.load(f)
|
| 212 |
except Exception:
|
| 213 |
-
return {
|
| 214 |
-
"timestamp": None, "beds_total": 400, "staffed_ratio": 1.0, "occupied_pct": 0.97,
|
| 215 |
-
"ed_census": 62, "ed_admits_waiting": 19, "avg_ed_wait_hours": 8,
|
| 216 |
-
"discharge_ready_today": 11, "discharge_barriers": {"allied_health": 7, "placement": 4},
|
| 217 |
-
"rn_shortfall": {"med_ward_A": 1, "med_ward_B": 1},
|
| 218 |
-
"forecast_admits_next_24h": {"respiratory": 14, "other": 9},
|
| 219 |
-
"isolation_needs_waiting": {"contact": 3, "airborne": 1}, "telemetry_needed_waiting": 5
|
| 220 |
-
}
|
| 221 |
|
| 222 |
init_retriever()
|
| 223 |
_session_rag = SessionRAG()
|
| 224 |
|
| 225 |
-
# ---------- Executive pre-compute (MDSi block) ----------
|
| 226 |
-
def _mdsi_block():
|
| 227 |
-
base_capacity = capacity_projection(18, 48, 6)
|
| 228 |
-
cons_capacity = capacity_projection(12, 48, 6)
|
| 229 |
-
opt_capacity = capacity_projection(24, 48, 6)
|
| 230 |
-
cost_1200 = cost_estimate(1200, 74.0, 75000.0)
|
| 231 |
-
outcomes = outcomes_summary()
|
| 232 |
-
return json.dumps({
|
| 233 |
-
"capacity_projection": {"conservative": cons_capacity, "base": base_capacity, "optimistic": opt_capacity},
|
| 234 |
-
"cost_for_1200": cost_1200,
|
| 235 |
-
"outcomes_summary": outcomes
|
| 236 |
-
}, indent=2)
|
| 237 |
-
|
| 238 |
# NEW: session-scoped data registry
|
| 239 |
_data_registry = DataRegistry()
|
| 240 |
|
| 241 |
-
# ---------- Core chat logic (
|
| 242 |
def clarityops_reply(user_msg, history, tz, uploaded_files_paths, awaiting_answers=False):
|
| 243 |
try:
|
| 244 |
log_event("user_message", None, {"sizes": {"chars": len(user_msg or "")}})
|
|
@@ -249,10 +238,10 @@ def clarityops_reply(user_msg, history, tz, uploaded_files_paths, awaiting_answe
|
|
| 249 |
return history + [(user_msg, ans)], awaiting_answers
|
| 250 |
|
| 251 |
if is_identity_query(safe_in, history):
|
| 252 |
-
ans = "I am
|
| 253 |
return history + [(user_msg, ans)], awaiting_answers
|
| 254 |
|
| 255 |
-
# 1) Ingest uploads into RAG AND DataRegistry
|
| 256 |
artifacts = []
|
| 257 |
if uploaded_files_paths:
|
| 258 |
ing = extract_text_from_files(uploaded_files_paths)
|
|
@@ -269,7 +258,7 @@ def clarityops_reply(user_msg, history, tz, uploaded_files_paths, awaiting_answe
|
|
| 269 |
"chunks": len(chunks), "artifacts": len(artifacts), "tables": len(_data_registry.names())
|
| 270 |
})
|
| 271 |
|
| 272 |
-
#
|
| 273 |
if re.search(r"\b(columns?|headers?)\b", (safe_in or "").lower()):
|
| 274 |
cols = _session_rag.get_latest_csv_columns()
|
| 275 |
if cols:
|
|
@@ -302,12 +291,12 @@ def clarityops_reply(user_msg, history, tz, uploaded_files_paths, awaiting_answe
|
|
| 302 |
})
|
| 303 |
return history + [(user_msg, safe_out)], awaiting_answers
|
| 304 |
|
| 305 |
-
# ---------- Scenario Mode ----------
|
| 306 |
# 3) Build dynamic concept mapping from scenario + data
|
| 307 |
mapping = map_concepts(safe_in, _data_registry)
|
| 308 |
|
| 309 |
if not awaiting_answers:
|
| 310 |
-
# PHASE 1: ask
|
| 311 |
phase1 = build_phase1_questions(scenario_text=safe_in, registry=_data_registry, mapping=mapping)
|
| 312 |
phase1 = _sanitize_text(phase1)
|
| 313 |
log_event("assistant_reply", None, {
|
|
@@ -318,56 +307,52 @@ def clarityops_reply(user_msg, history, tz, uploaded_files_paths, awaiting_answe
|
|
| 318 |
})
|
| 319 |
return history + [(user_msg, phase1)], True
|
| 320 |
|
| 321 |
-
# PHASE 2: compute data
|
| 322 |
data_findings_md, missing_keys = build_data_findings_markdown(_data_registry, mapping)
|
| 323 |
|
| 324 |
-
#
|
| 325 |
-
|
| 326 |
if missing_keys:
|
| 327 |
-
|
| 328 |
-
"\n\
|
| 329 |
+ ", ".join(sorted(set(missing_keys)))
|
| 330 |
-
+ ".
|
| 331 |
)
|
| 332 |
|
| 333 |
-
#
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
|
|
|
| 338 |
snapshot = _load_snapshot()
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
|
| 344 |
-
|
| 345 |
-
mdsi_extra = ""
|
| 346 |
-
if any(k in user_lower for k in ["diabetes", "mdsi", "mobile screening"]):
|
| 347 |
-
mdsi_extra = _mdsi_block()
|
| 348 |
-
|
| 349 |
-
# Build artifact + table summary for the prompt
|
| 350 |
registry_summary = _data_registry.summarize_for_prompt()
|
| 351 |
-
artifact_block = "Uploaded Data Files
|
| 352 |
|
| 353 |
scenario_block = safe_in if len((safe_in or "")) > 0 else ""
|
| 354 |
system_preamble = build_system_preamble(
|
| 355 |
snapshot=snapshot,
|
| 356 |
policy_context=policy_context,
|
| 357 |
-
computed_numbers=
|
| 358 |
-
scenario_text=scenario_block + f"\n\n{artifact_block}\n\n{data_findings_md}" +
|
| 359 |
session_snips=session_snips
|
| 360 |
)
|
| 361 |
|
| 362 |
directive = (
|
| 363 |
-
"\n\n[INSTRUCTION
|
| 364 |
-
"
|
| 365 |
-
"
|
| 366 |
-
"
|
| 367 |
-
"
|
| 368 |
)
|
| 369 |
|
| 370 |
-
augmented_user = SYSTEM_MASTER + "\n\n" + system_preamble + "\n\
|
| 371 |
|
| 372 |
out = cohere_chat(augmented_user, history)
|
| 373 |
if not out:
|
|
@@ -402,10 +387,28 @@ def clarityops_reply(user_msg, history, tz, uploaded_files_paths, awaiting_answe
|
|
| 402 |
pass
|
| 403 |
return history + [(user_msg, err)], awaiting_answers
|
| 404 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
# ---------- Theme & CSS ----------
|
| 406 |
theme = gr.themes.Soft(primary_hue="teal", neutral_hue="slate", radius_size=gr.themes.sizes.radius_lg)
|
| 407 |
custom_css = """
|
| 408 |
-
:root { --brand-bg: #0f172a; --brand-accent: #0d9488; --brand-text: #0f172a; --brand-text-light: #ffffff; }
|
| 409 |
html, body, .gradio-container { height: 100vh; }
|
| 410 |
.gradio-container { background: var(--brand-bg); display: flex; flex-direction: column; }
|
| 411 |
|
|
@@ -433,33 +436,33 @@ textarea, input, .gr-input { border-radius: 12px !important; }
|
|
| 433 |
|
| 434 |
# ---------- UI ----------
|
| 435 |
with gr.Blocks(theme=theme, css=custom_css, analytics_enabled=False) as demo:
|
| 436 |
-
# --- HERO (initial
|
| 437 |
with gr.Column(elem_id="hero-wrap", visible=True) as hero_wrap:
|
| 438 |
with gr.Column(elem_id="hero"):
|
| 439 |
-
gr.HTML("<h2>What can I
|
| 440 |
with gr.Row(elem_classes="search-row"):
|
| 441 |
hero_msg = gr.Textbox(
|
| 442 |
-
placeholder="
|
| 443 |
show_label=False,
|
| 444 |
lines=1,
|
| 445 |
elem_classes="hero-box"
|
| 446 |
)
|
| 447 |
hero_send = gr.Button("➤", scale=0, elem_id="hero-send")
|
| 448 |
-
gr.Markdown('<div class="hint">
|
| 449 |
|
| 450 |
# --- MAIN APP (hidden until first message) ---
|
| 451 |
with gr.Column(elem_id="chat-container", visible=False) as app_wrap:
|
| 452 |
chat = gr.Chatbot(label="", show_label=False, height="80vh")
|
| 453 |
with gr.Row():
|
| 454 |
uploads = gr.Files(
|
| 455 |
-
label="Upload
|
| 456 |
file_types=["file"], file_count="multiple", height=68
|
| 457 |
)
|
| 458 |
with gr.Row(elem_id="chat-input-row"):
|
| 459 |
msg = gr.Textbox(
|
| 460 |
label="",
|
| 461 |
show_label=False,
|
| 462 |
-
placeholder="Continue
|
| 463 |
scale=10,
|
| 464 |
elem_id="chat-msg",
|
| 465 |
lines=1,
|
|
@@ -529,8 +532,9 @@ with gr.Blocks(theme=theme, css=custom_css, analytics_enabled=False) as demo:
|
|
| 529 |
concurrency_limit=2, queue=True)
|
| 530 |
|
| 531 |
def _on_clear():
|
| 532 |
-
#
|
| 533 |
_data_registry.clear()
|
|
|
|
| 534 |
return (
|
| 535 |
[], "", [], False,
|
| 536 |
gr.update(visible=True),
|
|
@@ -542,7 +546,7 @@ with gr.Blocks(theme=theme, css=custom_css, analytics_enabled=False) as demo:
|
|
| 542 |
|
| 543 |
if __name__ == "__main__":
|
| 544 |
port = int(os.environ.get("PORT", "7860"))
|
| 545 |
-
demo.launch(server_name="0.0.0.0", server_port=port, show_api=False, max_threads=8)
|
| 546 |
|
| 547 |
|
| 548 |
|
|
|
|
| 47 |
|
| 48 |
from safety import safety_filter, refusal_reply
|
| 49 |
from retriever import init_retriever, retrieve_context
|
| 50 |
+
from decision_math import compute_operational_numbers
|
| 51 |
from prompt_templates import build_system_preamble
|
| 52 |
from upload_ingest import extract_text_from_files
|
| 53 |
from session_rag import SessionRAG
|
|
|
|
| 54 |
|
| 55 |
+
# NEW: dynamic data analysis framework
|
| 56 |
from data_registry import DataRegistry
|
| 57 |
from schema_mapper import map_concepts, build_phase1_questions
|
| 58 |
from auto_metrics import build_data_findings_markdown
|
|
|
|
| 67 |
# Larger output budget for Phase 2
|
| 68 |
MAX_NEW_TOKENS = int(os.getenv("MAX_NEW_TOKENS", "2048"))
|
| 69 |
|
| 70 |
+
# ---------- Generic System Prompt ----------
|
| 71 |
SYSTEM_MASTER = """
|
| 72 |
SYSTEM ROLE
|
| 73 |
+
You are an AI analytical system that provides data-driven insights for any scenario.
|
| 74 |
Absolute rules:
|
| 75 |
- Use ONLY information provided in this conversation (scenario text + uploaded files + user answers).
|
| 76 |
- Never invent data. If something required is missing after clarifications, write the literal token: INSUFFICIENT_DATA.
|
| 77 |
+
- Provide clear analysis with calculations, evidence, and reasoning.
|
| 78 |
+
- Maintain privacy safeguards (aggregate data; suppress small cohorts <10).
|
| 79 |
+
- Adapt your analysis approach to the specific scenario and data provided.
|
| 80 |
+
|
| 81 |
+
Formatting rules for structured analysis:
|
| 82 |
+
- Start with the header: "Structured Analysis"
|
| 83 |
+
- Organize analysis into logical sections based on the scenario requirements
|
| 84 |
+
- End with concrete recommendations and a brief "Provenance" mapping outputs to scenario text, uploaded files, and answers.
|
|
|
|
|
|
|
| 85 |
""".strip()
|
| 86 |
|
| 87 |
# ---------- Helpers ----------
|
|
|
|
| 119 |
return re2.sub(r'[\p{C}--[\n\t]]+', '', s)
|
| 120 |
|
| 121 |
def is_scenario_triggered(text: str, uploaded_files_paths) -> bool:
|
| 122 |
+
"""Detect if this should be treated as a scenario analysis request."""
|
| 123 |
t = (text or "").lower()
|
| 124 |
+
|
| 125 |
+
# Scenario keywords
|
| 126 |
+
scenario_keywords = [
|
| 127 |
+
"scenario", "analysis", "analyze", "assess", "evaluate", "recommendation",
|
| 128 |
+
"strategy", "plan", "solution", "decision", "priority", "allocate", "resource"
|
| 129 |
+
]
|
| 130 |
+
|
| 131 |
+
has_keyword = any(keyword in t for keyword in scenario_keywords)
|
| 132 |
has_files = bool(uploaded_files_paths)
|
| 133 |
+
|
| 134 |
+
# If files are uploaded, assume scenario mode
|
| 135 |
+
# If certain analytical keywords are present, assume scenario mode
|
| 136 |
+
return has_files or has_keyword
|
| 137 |
|
| 138 |
# ---------- Cohere first ----------
|
| 139 |
def cohere_chat(message, history):
|
|
|
|
| 214 |
|
| 215 |
# ---------- Snapshot & retrieval ----------
|
| 216 |
def _load_snapshot(path=SNAPSHOT_PATH):
|
| 217 |
+
"""Load operational snapshot if available."""
|
| 218 |
try:
|
| 219 |
with open(path, "r", encoding="utf-8") as f:
|
| 220 |
return json.load(f)
|
| 221 |
except Exception:
|
| 222 |
+
return {} # Return empty dict if no snapshot available
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
|
| 224 |
init_retriever()
|
| 225 |
_session_rag = SessionRAG()
|
| 226 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
# NEW: session-scoped data registry
|
| 228 |
_data_registry = DataRegistry()
|
| 229 |
|
| 230 |
+
# ---------- Core chat logic (generic scenario handling) ----------
|
| 231 |
def clarityops_reply(user_msg, history, tz, uploaded_files_paths, awaiting_answers=False):
|
| 232 |
try:
|
| 233 |
log_event("user_message", None, {"sizes": {"chars": len(user_msg or "")}})
|
|
|
|
| 238 |
return history + [(user_msg, ans)], awaiting_answers
|
| 239 |
|
| 240 |
if is_identity_query(safe_in, history):
|
| 241 |
+
ans = "I am an AI analytical system designed to help you analyze scenarios and make data-driven decisions."
|
| 242 |
return history + [(user_msg, ans)], awaiting_answers
|
| 243 |
|
| 244 |
+
# 1) Ingest uploads into RAG AND DataRegistry
|
| 245 |
artifacts = []
|
| 246 |
if uploaded_files_paths:
|
| 247 |
ing = extract_text_from_files(uploaded_files_paths)
|
|
|
|
| 258 |
"chunks": len(chunks), "artifacts": len(artifacts), "tables": len(_data_registry.names())
|
| 259 |
})
|
| 260 |
|
| 261 |
+
# Quick helper for column inspection
|
| 262 |
if re.search(r"\b(columns?|headers?)\b", (safe_in or "").lower()):
|
| 263 |
cols = _session_rag.get_latest_csv_columns()
|
| 264 |
if cols:
|
|
|
|
| 291 |
})
|
| 292 |
return history + [(user_msg, safe_out)], awaiting_answers
|
| 293 |
|
| 294 |
+
# ---------- Generic Scenario Analysis Mode ----------
|
| 295 |
# 3) Build dynamic concept mapping from scenario + data
|
| 296 |
mapping = map_concepts(safe_in, _data_registry)
|
| 297 |
|
| 298 |
if not awaiting_answers:
|
| 299 |
+
# PHASE 1: ask for missing/ambiguous information
|
| 300 |
phase1 = build_phase1_questions(scenario_text=safe_in, registry=_data_registry, mapping=mapping)
|
| 301 |
phase1 = _sanitize_text(phase1)
|
| 302 |
log_event("assistant_reply", None, {
|
|
|
|
| 307 |
})
|
| 308 |
return history + [(user_msg, phase1)], True
|
| 309 |
|
| 310 |
+
# PHASE 2: compute data analysis and generate structured response
|
| 311 |
data_findings_md, missing_keys = build_data_findings_markdown(_data_registry, mapping)
|
| 312 |
|
| 313 |
+
# Build context for analysis
|
| 314 |
+
insufficient_data_note = ""
|
| 315 |
if missing_keys:
|
| 316 |
+
insufficient_data_note = (
|
| 317 |
+
"\n\nData limitations: Missing or uncomputable: "
|
| 318 |
+ ", ".join(sorted(set(missing_keys)))
|
| 319 |
+
+ ". Where these are essential to analysis, write INSUFFICIENT_DATA."
|
| 320 |
)
|
| 321 |
|
| 322 |
+
# Get relevant context from uploaded documents
|
| 323 |
+
# Extract key terms from scenario to improve retrieval
|
| 324 |
+
scenario_terms = _extract_key_terms_from_scenario(safe_in)
|
| 325 |
+
session_snips = "\n---\n".join(_session_rag.retrieve(scenario_terms, k=6))
|
| 326 |
+
|
| 327 |
+
# Load any available operational data
|
| 328 |
snapshot = _load_snapshot()
|
| 329 |
+
computed_numbers = compute_operational_numbers(snapshot) if snapshot else {}
|
| 330 |
+
|
| 331 |
+
# Get general policy/context if available
|
| 332 |
+
policy_context = retrieve_context(scenario_terms)
|
| 333 |
|
| 334 |
+
# Build comprehensive data summary for analysis
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
registry_summary = _data_registry.summarize_for_prompt()
|
| 336 |
+
artifact_block = "Uploaded Data Files:\n" + registry_summary if registry_summary else "No data files uploaded."
|
| 337 |
|
| 338 |
scenario_block = safe_in if len((safe_in or "")) > 0 else ""
|
| 339 |
system_preamble = build_system_preamble(
|
| 340 |
snapshot=snapshot,
|
| 341 |
policy_context=policy_context,
|
| 342 |
+
computed_numbers=computed_numbers,
|
| 343 |
+
scenario_text=scenario_block + f"\n\n{artifact_block}\n\n{data_findings_md}" + insufficient_data_note,
|
| 344 |
session_snips=session_snips
|
| 345 |
)
|
| 346 |
|
| 347 |
directive = (
|
| 348 |
+
"\n\n[ANALYSIS INSTRUCTION]\n"
|
| 349 |
+
"Provide a structured analysis appropriate to this scenario. Begin with 'Structured Analysis' and "
|
| 350 |
+
"organize your response into logical sections based on what the scenario requires. Use the data "
|
| 351 |
+
"provided as ground truth. When information is missing, write INSUFFICIENT_DATA. Show your reasoning "
|
| 352 |
+
"and calculations. End with concrete recommendations and a brief Provenance section.\n"
|
| 353 |
)
|
| 354 |
|
| 355 |
+
augmented_user = SYSTEM_MASTER + "\n\n" + system_preamble + "\n\nScenario and context:\n" + safe_in + directive
|
| 356 |
|
| 357 |
out = cohere_chat(augmented_user, history)
|
| 358 |
if not out:
|
|
|
|
| 387 |
pass
|
| 388 |
return history + [(user_msg, err)], awaiting_answers
|
| 389 |
|
| 390 |
+
def _extract_key_terms_from_scenario(scenario_text: str) -> str:
|
| 391 |
+
"""Extract key terms from scenario text for better context retrieval."""
|
| 392 |
+
if not scenario_text:
|
| 393 |
+
return ""
|
| 394 |
+
|
| 395 |
+
# Simple extraction of important words (remove common stop words)
|
| 396 |
+
stop_words = {
|
| 397 |
+
'the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by',
|
| 398 |
+
'is', 'are', 'was', 'were', 'be', 'been', 'have', 'has', 'had', 'do', 'does', 'did',
|
| 399 |
+
'a', 'an', 'this', 'that', 'these', 'those', 'i', 'you', 'he', 'she', 'it', 'we', 'they'
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
words = re.findall(r'\b[a-zA-Z]{3,}\b', scenario_text.lower())
|
| 403 |
+
key_terms = [word for word in words if word not in stop_words]
|
| 404 |
+
|
| 405 |
+
# Return first 10-15 key terms
|
| 406 |
+
return ' '.join(key_terms[:15])
|
| 407 |
+
|
| 408 |
# ---------- Theme & CSS ----------
|
| 409 |
theme = gr.themes.Soft(primary_hue="teal", neutral_hue="slate", radius_size=gr.themes.sizes.radius_lg)
|
| 410 |
custom_css = """
|
| 411 |
+
:root { --brand-bg: #0f172a; --brand-accent: #0d9488; --brand-text: #0f172a; --brand-text-light: #ffffff; }
|
| 412 |
html, body, .gradio-container { height: 100vh; }
|
| 413 |
.gradio-container { background: var(--brand-bg); display: flex; flex-direction: column; }
|
| 414 |
|
|
|
|
| 436 |
|
| 437 |
# ---------- UI ----------
|
| 438 |
with gr.Blocks(theme=theme, css=custom_css, analytics_enabled=False) as demo:
|
| 439 |
+
# --- HERO (initial screen) ---
|
| 440 |
with gr.Column(elem_id="hero-wrap", visible=True) as hero_wrap:
|
| 441 |
with gr.Column(elem_id="hero"):
|
| 442 |
+
gr.HTML("<h2>What scenario can I help you analyze?</h2>")
|
| 443 |
with gr.Row(elem_classes="search-row"):
|
| 444 |
hero_msg = gr.Textbox(
|
| 445 |
+
placeholder="Describe your scenario or ask any question (upload files for data analysis)…",
|
| 446 |
show_label=False,
|
| 447 |
lines=1,
|
| 448 |
elem_classes="hero-box"
|
| 449 |
)
|
| 450 |
hero_send = gr.Button("➤", scale=0, elem_id="hero-send")
|
| 451 |
+
gr.Markdown('<div class="hint">Upload files and describe your scenario for comprehensive analysis. The system will ask clarifying questions, then provide structured insights.</div>')
|
| 452 |
|
| 453 |
# --- MAIN APP (hidden until first message) ---
|
| 454 |
with gr.Column(elem_id="chat-container", visible=False) as app_wrap:
|
| 455 |
chat = gr.Chatbot(label="", show_label=False, height="80vh")
|
| 456 |
with gr.Row():
|
| 457 |
uploads = gr.Files(
|
| 458 |
+
label="Upload data files (PDF, DOCX, CSV, PNG, JPG)",
|
| 459 |
file_types=["file"], file_count="multiple", height=68
|
| 460 |
)
|
| 461 |
with gr.Row(elem_id="chat-input-row"):
|
| 462 |
msg = gr.Textbox(
|
| 463 |
label="",
|
| 464 |
show_label=False,
|
| 465 |
+
placeholder="Continue the conversation. Provide additional details or answer clarifying questions.",
|
| 466 |
scale=10,
|
| 467 |
elem_id="chat-msg",
|
| 468 |
lines=1,
|
|
|
|
| 532 |
concurrency_limit=2, queue=True)
|
| 533 |
|
| 534 |
def _on_clear():
|
| 535 |
+
# Clear the in-memory data registry for a fresh scenario
|
| 536 |
_data_registry.clear()
|
| 537 |
+
_session_rag.clear() # Also clear RAG session if available
|
| 538 |
return (
|
| 539 |
[], "", [], False,
|
| 540 |
gr.update(visible=True),
|
|
|
|
| 546 |
|
| 547 |
if __name__ == "__main__":
|
| 548 |
port = int(os.environ.get("PORT", "7860"))
|
| 549 |
+
demo.launch(server_name="0.0.0.0", server_port=port, show_api=False, max_threads=40)ds=8)
|
| 550 |
|
| 551 |
|
| 552 |
|