Spaces:
Sleeping
Sleeping
Commit ·
5cee72e
1
Parent(s): 7dfbd5c
Redesign merchant tools and exports
Browse files
app.py
CHANGED
|
@@ -8,6 +8,7 @@ import re
|
|
| 8 |
import traceback
|
| 9 |
from functools import lru_cache
|
| 10 |
from pathlib import Path
|
|
|
|
| 11 |
from typing import Any, Dict, List, Tuple
|
| 12 |
from urllib.parse import quote
|
| 13 |
|
|
@@ -267,6 +268,15 @@ def _report_image(name: str):
|
|
| 267 |
return str(path) if path.exists() else None
|
| 268 |
|
| 269 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
@lru_cache(maxsize=1)
|
| 271 |
def _predictor() -> AspectPredictor:
|
| 272 |
return AspectPredictor(checkpoint_dir=CHECKPOINT_DIR)
|
|
@@ -662,6 +672,63 @@ def merchant_product_scores(metric: str) -> List[List[Any]]:
|
|
| 662 |
return rows or [["No rows", "Try Refresh", metric, "-", 0.0, 0.0, 0.0]]
|
| 663 |
|
| 664 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 665 |
def _metadata_risks(features: str, categories: str, price: Any, rating: Any, count: Any) -> Dict[str, str]:
|
| 666 |
text = f"{features} {categories}".lower()
|
| 667 |
risks = {}
|
|
@@ -702,6 +769,36 @@ def screen_new_product(features: str, categories: str, price: Any, rating: Any,
|
|
| 702 |
return f'<div class="note-card"><b>New product risk focus:</b> {_esc(summary)}<br><span class="muted">This is a metadata screening tool, not a replacement for real review evaluation.</span></div>', rows
|
| 703 |
|
| 704 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 705 |
def external_review_predict(review: str, features: str, categories: str, price: Any, rating: Any, count: Any, selected_aspect: str) -> Tuple[str, str, List[List[Any]]]:
|
| 706 |
try:
|
| 707 |
result = _predict_custom(review, features, categories, price, rating, count)
|
|
@@ -710,6 +807,37 @@ def external_review_predict(review: str, features: str, categories: str, price:
|
|
| 710 |
return _overall_html(result), _aspect_cards(result, review or "", selected_aspect), _aspect_rows(result, review or "")
|
| 711 |
|
| 712 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 713 |
def _status_html() -> str:
|
| 714 |
missing = []
|
| 715 |
if not CHECKPOINT_PATH.exists() or CHECKPOINT_PATH.stat().st_size < 1024 * 1024:
|
|
@@ -776,7 +904,15 @@ mark { background:#fde68a; color:#111827; border-radius:4px; padding:1px 3px; }
|
|
| 776 |
.kv-table td { border-bottom:1px solid #e2e8f0; padding:9px 12px; }
|
| 777 |
.kv-table td:first-child { width:220px; color:#334155; font-weight:700; background:#f8fafc; }
|
| 778 |
.compact-note { color:#475569; font-size:13px; margin:4px 0 10px; }
|
| 779 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 780 |
"""
|
| 781 |
|
| 782 |
|
|
@@ -818,45 +954,64 @@ def build_app() -> gr.Blocks:
|
|
| 818 |
filter_btn.click(filter_products, [filter_aspect, filter_sentiment, filter_category, filter_tags, min_rating], [filter_table, filter_summary])
|
| 819 |
|
| 820 |
with gr.Tab("Merchant Interface"):
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
with gr.Column(scale=6):
|
| 826 |
-
gr.Markdown("### Product Score Monitor")
|
| 827 |
-
merchant_metric = gr.Dropdown(["Overall"] + ASPECTS, value="Overall", label="Choose overall or aspect")
|
| 828 |
-
merchant_scores = gr.Dataframe(headers=["Product", "Category", "Metric", "Prediction", "Confidence", "Price", "Rating"], value=[["Click Refresh Product Scores", "", "", "", 0.0, 0.0, 0.0]], datatype=["str", "str", "str", "str", "number", "number", "number"], interactive=False)
|
| 829 |
-
refresh_scores = gr.Button("Refresh Product Scores", variant="primary")
|
| 830 |
-
with gr.Accordion("New Product Metadata Risk Screening", open=False):
|
| 831 |
with gr.Row():
|
| 832 |
-
|
| 833 |
-
|
|
|
|
|
|
|
| 834 |
with gr.Row():
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
screen_btn = gr.Button("Predict Metadata Risk", variant="primary")
|
| 840 |
-
risk_summary = gr.HTML()
|
| 841 |
-
risk_table = gr.Dataframe(headers=["Aspect", "Risk Level", "Model Signal", "Confidence", "Reason"], value=[["Click Predict Metadata Risk", "", "", 0.0, ""]], datatype=["str", "str", "str", "number", "str"], interactive=False)
|
| 842 |
-
with gr.Accordion("External Review Prediction", open=False):
|
| 843 |
-
external_review = gr.Textbox("The fabric is soft and the color looks good, but it runs small and the zipper feels weak.", label="External customer review", lines=4)
|
| 844 |
with gr.Row():
|
| 845 |
-
|
| 846 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 847 |
with gr.Row():
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 858 |
screen_btn.click(screen_new_product, [new_features, new_categories, new_price, new_rating, new_count, new_focus], [risk_summary, risk_table])
|
|
|
|
|
|
|
| 859 |
external_btn.click(external_review_predict, [external_review, ext_features, ext_categories, ext_price, ext_rating, ext_count, ext_aspect], [ext_overall, ext_aspects, ext_table])
|
|
|
|
| 860 |
|
| 861 |
with gr.Tab("Research Metrics"):
|
| 862 |
gr.Markdown("Metrics are loaded from the updated 10W experiment reports. Tables use CSV outputs when available, with JSON fallback.")
|
|
@@ -879,7 +1034,8 @@ def build_app() -> gr.Blocks:
|
|
| 879 |
refresh_research = gr.Button("Refresh Research Metrics", variant="primary")
|
| 880 |
refresh_research.click(refresh_research_outputs, outputs=[research_cards, overall_table, aspect_table, ablation_table, meta_source_table])
|
| 881 |
demo.load(consumer_product_view, [product_select, consumer_aspect], [product_detail, aspect_html, evidence_html, consumer_table])
|
| 882 |
-
demo.load(
|
|
|
|
| 883 |
demo.load(filter_products, [filter_aspect, filter_sentiment, filter_category, filter_tags, min_rating], [filter_table, filter_summary])
|
| 884 |
return demo
|
| 885 |
|
|
|
|
| 8 |
import traceback
|
| 9 |
from functools import lru_cache
|
| 10 |
from pathlib import Path
|
| 11 |
+
import tempfile
|
| 12 |
from typing import Any, Dict, List, Tuple
|
| 13 |
from urllib.parse import quote
|
| 14 |
|
|
|
|
| 268 |
return str(path) if path.exists() else None
|
| 269 |
|
| 270 |
|
| 271 |
+
def _write_csv_download(name: str, headers: List[str], rows: List[List[Any]]):
|
| 272 |
+
path = Path(tempfile.gettempdir()) / name
|
| 273 |
+
with path.open("w", encoding="utf-8-sig", newline="") as fh:
|
| 274 |
+
writer = csv.writer(fh)
|
| 275 |
+
writer.writerow(headers)
|
| 276 |
+
writer.writerows(rows or [])
|
| 277 |
+
return str(path)
|
| 278 |
+
|
| 279 |
+
|
| 280 |
@lru_cache(maxsize=1)
|
| 281 |
def _predictor() -> AspectPredictor:
|
| 282 |
return AspectPredictor(checkpoint_dir=CHECKPOINT_DIR)
|
|
|
|
| 672 |
return rows or [["No rows", "Try Refresh", metric, "-", 0.0, 0.0, 0.0]]
|
| 673 |
|
| 674 |
|
| 675 |
+
def merchant_score_filter(aspect: str, prediction: str, category: str) -> List[List[Any]]:
|
| 676 |
+
rows = []
|
| 677 |
+
for product in PRODUCTS:
|
| 678 |
+
if category != "All" and product.get("category") != category:
|
| 679 |
+
continue
|
| 680 |
+
try:
|
| 681 |
+
result = _predict_product(product["name"])
|
| 682 |
+
except Exception:
|
| 683 |
+
continue
|
| 684 |
+
d = result.get("overall", {}) if aspect == "Overall" else result.get("aspect_details", {}).get(aspect, {})
|
| 685 |
+
label = d.get("label", "Unknown")
|
| 686 |
+
if prediction != "Any" and label != prediction:
|
| 687 |
+
continue
|
| 688 |
+
rows.append([
|
| 689 |
+
product["name"], product["category"], aspect, label,
|
| 690 |
+
round(_safe_float(d.get("confidence")), 4),
|
| 691 |
+
product["price"], product["average_rating"],
|
| 692 |
+
])
|
| 693 |
+
return rows or [["No matching products", category, aspect, prediction, 0.0, 0.0, 0.0]]
|
| 694 |
+
|
| 695 |
+
|
| 696 |
+
def export_merchant_scores(rows):
|
| 697 |
+
return _write_csv_download(
|
| 698 |
+
"merchant_product_scores.csv",
|
| 699 |
+
["Product", "Category", "Metric", "Prediction", "Confidence", "Price", "Rating"],
|
| 700 |
+
rows,
|
| 701 |
+
)
|
| 702 |
+
|
| 703 |
+
|
| 704 |
+
def negative_product_spotlight(limit: int = 6) -> str:
|
| 705 |
+
items = []
|
| 706 |
+
for product in PRODUCTS:
|
| 707 |
+
try:
|
| 708 |
+
result = _predict_product(product["name"])
|
| 709 |
+
except Exception:
|
| 710 |
+
continue
|
| 711 |
+
for aspect in ASPECTS:
|
| 712 |
+
d = result.get("aspect_details", {}).get(aspect, {})
|
| 713 |
+
if d.get("label") != "Negative":
|
| 714 |
+
continue
|
| 715 |
+
hits = _keyword_hits(product.get("review", ""), aspect)
|
| 716 |
+
reason = ", ".join(hits) if hits else _short_text(product.get("review") or product.get("features"), 90)
|
| 717 |
+
items.append((_safe_float(d.get("confidence")), aspect, product, reason))
|
| 718 |
+
items.sort(key=lambda x: x[0], reverse=True)
|
| 719 |
+
cards = []
|
| 720 |
+
for conf, aspect, product, reason in items[:limit]:
|
| 721 |
+
img = f'<img class="mini-product-img" src="{_esc(_product_image_url(product))}" alt="{_esc(product["name"])}">'
|
| 722 |
+
cards.append(
|
| 723 |
+
f'<div class="negative-card">{img}<div><b>{_esc(product["name"])}</b>'
|
| 724 |
+
f'<div class="small-label">{_esc(product["category"])} · {aspect} Negative · conf {conf:.2f}</div>'
|
| 725 |
+
f'<p>{_esc(reason)}</p></div></div>'
|
| 726 |
+
)
|
| 727 |
+
if not cards:
|
| 728 |
+
return '<div class="note-card">No highly negative product found in the current catalog sample.</div>'
|
| 729 |
+
return '<div class="negative-grid">' + "".join(cards) + '</div>'
|
| 730 |
+
|
| 731 |
+
|
| 732 |
def _metadata_risks(features: str, categories: str, price: Any, rating: Any, count: Any) -> Dict[str, str]:
|
| 733 |
text = f"{features} {categories}".lower()
|
| 734 |
risks = {}
|
|
|
|
| 769 |
return f'<div class="note-card"><b>New product risk focus:</b> {_esc(summary)}<br><span class="muted">This is a metadata screening tool, not a replacement for real review evaluation.</span></div>', rows
|
| 770 |
|
| 771 |
|
| 772 |
+
def import_new_product_payload(file_obj):
|
| 773 |
+
if not file_obj:
|
| 774 |
+
return gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
|
| 775 |
+
path = Path(getattr(file_obj, "name", file_obj))
|
| 776 |
+
try:
|
| 777 |
+
if path.suffix.lower() == ".json":
|
| 778 |
+
data = json.loads(path.read_text(encoding="utf-8"))
|
| 779 |
+
else:
|
| 780 |
+
with path.open("r", encoding="utf-8-sig", newline="") as fh:
|
| 781 |
+
data = next(csv.DictReader(fh), {})
|
| 782 |
+
except Exception:
|
| 783 |
+
data = {}
|
| 784 |
+
return (
|
| 785 |
+
data.get("features") or data.get("features_text") or "",
|
| 786 |
+
data.get("categories") or data.get("categories_text") or "",
|
| 787 |
+
_safe_float(data.get("price"), 0.0),
|
| 788 |
+
_safe_float(data.get("average_rating") or data.get("rating"), 4.0),
|
| 789 |
+
_safe_float(data.get("rating_number") or data.get("rating_count"), 0.0),
|
| 790 |
+
data.get("focus_aspect") or data.get("focus") or "All",
|
| 791 |
+
)
|
| 792 |
+
|
| 793 |
+
|
| 794 |
+
def export_risk_rows(rows):
|
| 795 |
+
return _write_csv_download(
|
| 796 |
+
"new_product_metadata_risk.csv",
|
| 797 |
+
["Aspect", "Risk Level", "Model Signal", "Confidence", "Reason"],
|
| 798 |
+
rows,
|
| 799 |
+
)
|
| 800 |
+
|
| 801 |
+
|
| 802 |
def external_review_predict(review: str, features: str, categories: str, price: Any, rating: Any, count: Any, selected_aspect: str) -> Tuple[str, str, List[List[Any]]]:
|
| 803 |
try:
|
| 804 |
result = _predict_custom(review, features, categories, price, rating, count)
|
|
|
|
| 807 |
return _overall_html(result), _aspect_cards(result, review or "", selected_aspect), _aspect_rows(result, review or "")
|
| 808 |
|
| 809 |
|
| 810 |
+
def import_external_review_payload(file_obj):
|
| 811 |
+
if not file_obj:
|
| 812 |
+
return gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
|
| 813 |
+
path = Path(getattr(file_obj, "name", file_obj))
|
| 814 |
+
try:
|
| 815 |
+
if path.suffix.lower() == ".json":
|
| 816 |
+
data = json.loads(path.read_text(encoding="utf-8"))
|
| 817 |
+
else:
|
| 818 |
+
with path.open("r", encoding="utf-8-sig", newline="") as fh:
|
| 819 |
+
data = next(csv.DictReader(fh), {})
|
| 820 |
+
except Exception:
|
| 821 |
+
data = {}
|
| 822 |
+
return (
|
| 823 |
+
data.get("review") or data.get("review_text") or "",
|
| 824 |
+
data.get("features") or data.get("features_text") or "",
|
| 825 |
+
data.get("categories") or data.get("categories_text") or "",
|
| 826 |
+
_safe_float(data.get("price"), 0.0),
|
| 827 |
+
_safe_float(data.get("average_rating") or data.get("rating"), 4.0),
|
| 828 |
+
_safe_float(data.get("rating_number") or data.get("rating_count"), 0.0),
|
| 829 |
+
data.get("highlight_aspect") or data.get("aspect") or "All",
|
| 830 |
+
)
|
| 831 |
+
|
| 832 |
+
|
| 833 |
+
def export_external_rows(rows):
|
| 834 |
+
return _write_csv_download(
|
| 835 |
+
"external_review_prediction.csv",
|
| 836 |
+
["Aspect", "Prediction", "Confidence", "Key review evidence"],
|
| 837 |
+
rows,
|
| 838 |
+
)
|
| 839 |
+
|
| 840 |
+
|
| 841 |
def _status_html() -> str:
|
| 842 |
missing = []
|
| 843 |
if not CHECKPOINT_PATH.exists() or CHECKPOINT_PATH.stat().st_size < 1024 * 1024:
|
|
|
|
| 904 |
.kv-table td { border-bottom:1px solid #e2e8f0; padding:9px 12px; }
|
| 905 |
.kv-table td:first-child { width:220px; color:#334155; font-weight:700; background:#f8fafc; }
|
| 906 |
.compact-note { color:#475569; font-size:13px; margin:4px 0 10px; }
|
| 907 |
+
.module-head { display:flex; justify-content:space-between; align-items:center; gap:12px; margin:0 0 10px; }
|
| 908 |
+
.info-tip { position:relative; display:inline-flex; align-items:center; justify-content:center; width:24px; height:24px; border-radius:999px; border:1px solid #bfdbfe; background:#eff6ff; color:#1e40af; font-weight:800; cursor:help; }
|
| 909 |
+
.info-tip .tip-content { display:none; position:absolute; right:0; top:30px; z-index:20; width:420px; max-width:80vw; background:white; border:1px solid var(--line); border-radius:8px; padding:10px; box-shadow:0 12px 30px rgba(15,23,42,.16); }
|
| 910 |
+
.info-tip:hover .tip-content { display:block; }
|
| 911 |
+
.negative-grid { display:grid; grid-template-columns:repeat(3, minmax(0, 1fr)); gap:10px; margin:8px 0 14px; }
|
| 912 |
+
.negative-card { display:grid; grid-template-columns:64px 1fr; gap:10px; border:1px solid #fecaca; border-left:4px solid #ef4444; border-radius:8px; padding:10px; background:#fffafa; }
|
| 913 |
+
.negative-card p { margin:5px 0 0; color:#334155; font-size:13px; }
|
| 914 |
+
.mini-product-img { width:64px; height:76px; object-fit:cover; border-radius:6px; border:1px solid var(--line); background:#f8fafc; }
|
| 915 |
+
@media (max-width:860px) { .aspect-grid, .metric-grid, .product-layout, .decision-layout, .negative-grid { grid-template-columns:1fr; } .product-img { width:100%; height:180px; } }
|
| 916 |
"""
|
| 917 |
|
| 918 |
|
|
|
|
| 954 |
filter_btn.click(filter_products, [filter_aspect, filter_sentiment, filter_category, filter_tags, min_rating], [filter_table, filter_summary])
|
| 955 |
|
| 956 |
with gr.Tab("Merchant Interface"):
|
| 957 |
+
gr.HTML('<div class="module-head"><h3>Product Score Monitor</h3><span class="info-tip">i<span class="tip-content">' + model_info_html() + '</span></span></div>')
|
| 958 |
+
gr.HTML('<div class="small-label">Most negative products across the six aspects</div>')
|
| 959 |
+
negative_spotlight = gr.HTML('<div class="note-card">Loading most negative products...</div>')
|
| 960 |
+
with gr.Accordion("Score filters and export", open=True):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 961 |
with gr.Row():
|
| 962 |
+
merchant_metric = gr.Dropdown(["Overall"] + ASPECTS, value="Overall", label="Aspect")
|
| 963 |
+
merchant_prediction = gr.Dropdown(["Any", "Positive", "Negative", "Not_Mentioned", "Neutral"], value="Any", label="Prediction")
|
| 964 |
+
merchant_category = gr.Dropdown(categories, value="All", label="Category")
|
| 965 |
+
merchant_scores = gr.Dataframe(headers=["Product", "Category", "Metric", "Prediction", "Confidence", "Price", "Rating"], value=[["Click Apply Score Filter", "", "", "", 0.0, 0.0, 0.0]], datatype=["str", "str", "str", "str", "number", "number", "number"], interactive=False)
|
| 966 |
with gr.Row():
|
| 967 |
+
refresh_scores = gr.Button("Apply Score Filter", variant="primary")
|
| 968 |
+
export_scores = gr.Button("Export Score List")
|
| 969 |
+
merchant_scores_file = gr.File(label="Downloaded score CSV", interactive=False)
|
| 970 |
+
with gr.Accordion("New Product Metadata Risk Screening", open=False):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 971 |
with gr.Row():
|
| 972 |
+
with gr.Column(scale=1):
|
| 973 |
+
new_import = gr.File(label="Import product metadata JSON/CSV")
|
| 974 |
+
new_features = gr.Textbox("Cotton Polyester Blend, Slim Fit, Graphic Print, Machine Wash", label="New product features", lines=4)
|
| 975 |
+
new_categories = gr.Textbox("Clothing > Women > Tops > T-Shirts", label="New product categories")
|
| 976 |
+
new_price = gr.Number(29.99, label="Price")
|
| 977 |
+
new_rating = gr.Number(4.1, label="Expected or early average rating")
|
| 978 |
+
new_count = gr.Number(35, label="Expected or early rating count")
|
| 979 |
+
new_focus = gr.Dropdown(["All"] + ASPECTS, value="All", label="Focus aspect")
|
| 980 |
+
screen_btn = gr.Button("Predict Metadata Risk", variant="primary")
|
| 981 |
+
with gr.Column(scale=1):
|
| 982 |
+
risk_summary = gr.HTML()
|
| 983 |
+
risk_table = gr.Dataframe(headers=["Aspect", "Risk Level", "Model Signal", "Confidence", "Reason"], value=[["Click Predict Metadata Risk", "", "", 0.0, ""]], datatype=["str", "str", "str", "number", "str"], interactive=False)
|
| 984 |
+
export_risk = gr.Button("Export Risk Result")
|
| 985 |
+
risk_file = gr.File(label="Downloaded risk CSV", interactive=False)
|
| 986 |
+
with gr.Accordion("External Review Prediction", open=False):
|
| 987 |
with gr.Row():
|
| 988 |
+
with gr.Column(scale=1):
|
| 989 |
+
ext_import = gr.File(label="Import review metadata JSON/CSV")
|
| 990 |
+
external_review = gr.Textbox("The fabric is soft and the color looks good, but it runs small and the zipper feels weak.", label="External customer review", lines=5)
|
| 991 |
+
ext_features = gr.Textbox("Cotton Blend, Slim Fit, Zipper Closure", label="Product features", lines=3)
|
| 992 |
+
ext_categories = gr.Textbox("Clothing > Women > Jackets", label="Product categories")
|
| 993 |
+
ext_price = gr.Number(39.99, label="Price")
|
| 994 |
+
ext_rating = gr.Number(4.2, label="Average rating")
|
| 995 |
+
ext_count = gr.Number(312, label="Rating count")
|
| 996 |
+
ext_aspect = gr.Dropdown(["All"] + ASPECTS, value="All", label="Highlight aspect")
|
| 997 |
+
external_btn = gr.Button("Analyze External Review", variant="primary")
|
| 998 |
+
with gr.Column(scale=1):
|
| 999 |
+
ext_overall = gr.HTML()
|
| 1000 |
+
ext_aspects = gr.HTML()
|
| 1001 |
+
ext_table = gr.Dataframe(headers=["Aspect", "Prediction", "Confidence", "Key review evidence"], value=[["Click Analyze External Review", "", 0.0, ""]], datatype=["str", "str", "number", "str"], interactive=False)
|
| 1002 |
+
export_ext = gr.Button("Export Review Result")
|
| 1003 |
+
ext_file = gr.File(label="Downloaded review CSV", interactive=False)
|
| 1004 |
+
refresh_scores.click(merchant_score_filter, [merchant_metric, merchant_prediction, merchant_category], merchant_scores)
|
| 1005 |
+
export_scores.click(export_merchant_scores, merchant_scores, merchant_scores_file)
|
| 1006 |
+
merchant_metric.change(merchant_score_filter, [merchant_metric, merchant_prediction, merchant_category], merchant_scores)
|
| 1007 |
+
merchant_prediction.change(merchant_score_filter, [merchant_metric, merchant_prediction, merchant_category], merchant_scores)
|
| 1008 |
+
merchant_category.change(merchant_score_filter, [merchant_metric, merchant_prediction, merchant_category], merchant_scores)
|
| 1009 |
+
new_import.change(import_new_product_payload, new_import, [new_features, new_categories, new_price, new_rating, new_count, new_focus])
|
| 1010 |
screen_btn.click(screen_new_product, [new_features, new_categories, new_price, new_rating, new_count, new_focus], [risk_summary, risk_table])
|
| 1011 |
+
export_risk.click(export_risk_rows, risk_table, risk_file)
|
| 1012 |
+
ext_import.change(import_external_review_payload, ext_import, [external_review, ext_features, ext_categories, ext_price, ext_rating, ext_count, ext_aspect])
|
| 1013 |
external_btn.click(external_review_predict, [external_review, ext_features, ext_categories, ext_price, ext_rating, ext_count, ext_aspect], [ext_overall, ext_aspects, ext_table])
|
| 1014 |
+
export_ext.click(export_external_rows, ext_table, ext_file)
|
| 1015 |
|
| 1016 |
with gr.Tab("Research Metrics"):
|
| 1017 |
gr.Markdown("Metrics are loaded from the updated 10W experiment reports. Tables use CSV outputs when available, with JSON fallback.")
|
|
|
|
| 1034 |
refresh_research = gr.Button("Refresh Research Metrics", variant="primary")
|
| 1035 |
refresh_research.click(refresh_research_outputs, outputs=[research_cards, overall_table, aspect_table, ablation_table, meta_source_table])
|
| 1036 |
demo.load(consumer_product_view, [product_select, consumer_aspect], [product_detail, aspect_html, evidence_html, consumer_table])
|
| 1037 |
+
demo.load(negative_product_spotlight, outputs=negative_spotlight)
|
| 1038 |
+
demo.load(merchant_score_filter, [merchant_metric, merchant_prediction, merchant_category], merchant_scores)
|
| 1039 |
demo.load(filter_products, [filter_aspect, filter_sentiment, filter_category, filter_tags, min_rating], [filter_table, filter_summary])
|
| 1040 |
return demo
|
| 1041 |
|