Sina1138 commited on
Commit ยท
8ea388a
1
Parent(s): d8b84f2
Add Expand/Collapse functionality for reviews and rebuttals, enhance HTML rendering for improved formatting, default to light mode
Browse files- interface/Demo.py +257 -88
interface/Demo.py
CHANGED
|
@@ -40,23 +40,163 @@ def _get_context(sentence: str, sentence_lists: list):
|
|
| 40 |
return "", ""
|
| 41 |
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
def _rebuttal_toggle_html() -> str:
|
| 44 |
"""Generate an Expand/Collapse All Responses toggle button with inline JS."""
|
| 45 |
return (
|
| 46 |
-
'<div style="display:flex;justify-content:flex-end;margin-bottom:4px;">'
|
| 47 |
'<button onclick="'
|
| 48 |
"let tab=this.closest('.tabitem')||this.closest('.gradio-container');"
|
| 49 |
-
"let details=tab.querySelectorAll('details');"
|
|
|
|
| 50 |
"let allOpen=Array.from(details).every(d=>d.open);"
|
| 51 |
"details.forEach(d=>d.open=!allOpen);"
|
| 52 |
"this.textContent=allOpen?'Expand All Responses':'Collapse All Responses';"
|
| 53 |
-
'" style="'
|
| 54 |
-
'
|
| 55 |
-
'font-size:0.78em;color:#6b7280;cursor:pointer;white-space:nowrap;'
|
| 56 |
-
'">Expand All Responses</button></div>'
|
| 57 |
)
|
| 58 |
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
def format_summary_cards(
|
| 61 |
sentences: list,
|
| 62 |
scores: dict,
|
|
@@ -287,6 +427,7 @@ def render_agreement_html(
|
|
| 287 |
speaker: Dict[str, Dict[str, float]],
|
| 288 |
num_reviews: int,
|
| 289 |
label: str = "Agreement",
|
|
|
|
| 290 |
) -> str:
|
| 291 |
"""
|
| 292 |
Custom HTML renderer for Agreement mode (replaces gr.HighlightedText).
|
|
@@ -310,21 +451,39 @@ def render_agreement_html(
|
|
| 310 |
'</div>'
|
| 311 |
)
|
| 312 |
|
| 313 |
-
parts = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 314 |
parts.append(legend_html)
|
| 315 |
-
parts.append('<div style="line-height:1.8;font-size:0.95em;">')
|
| 316 |
|
| 317 |
# Compute informativeness threshold: 2 / K (twice uniform baseline)
|
| 318 |
k = max(len(uniqueness), 1)
|
| 319 |
info_threshold = 2.0 / k
|
| 320 |
|
| 321 |
-
for sent in sentences:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
sent_id = _make_sentence_id(sent)
|
| 323 |
score = uniqueness.get(sent)
|
| 324 |
|
|
|
|
|
|
|
|
|
|
| 325 |
if score is None or abs(score) < HIGHLIGHT_THRESHOLD:
|
| 326 |
# No highlight
|
| 327 |
-
parts.append(f'<span id="{sent_id}">{_html.escape(sent)} </span>')
|
| 328 |
continue
|
| 329 |
|
| 330 |
# --- Color and opacity ---
|
|
@@ -388,6 +547,8 @@ def render_agreement_html(
|
|
| 388 |
)
|
| 389 |
|
| 390 |
parts.append("</div>")
|
|
|
|
|
|
|
| 391 |
return "".join(parts)
|
| 392 |
|
| 393 |
|
|
@@ -963,7 +1124,12 @@ details summary::-webkit-details-marker { display: none; }
|
|
| 963 |
details[open] summary span:first-child { display: inline-block; transform: rotate(90deg); }
|
| 964 |
"""
|
| 965 |
|
| 966 |
-
with gr.Blocks(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 967 |
# gr.Markdown("# ReView Interface")
|
| 968 |
|
| 969 |
# TODO: Uncomment this for home/description tab once finished with testing.
|
|
@@ -1121,61 +1287,58 @@ with gr.Blocks(title="ReView", css=CUSTOM_CSS) as demo:
|
|
| 1121 |
if i < number_of_displayed_reviews:
|
| 1122 |
review_item, rebuttal_html = review_items_cache[i]
|
| 1123 |
|
| 1124 |
-
|
| 1125 |
-
|
| 1126 |
-
for sentence, metadata in review_item:
|
| 1127 |
-
polarity = metadata.get("polarity", None)
|
| 1128 |
-
if polarity == 2:
|
| 1129 |
-
label = "โ"
|
| 1130 |
-
elif polarity == 0:
|
| 1131 |
-
label = "โ"
|
| 1132 |
-
else:
|
| 1133 |
-
label = None
|
| 1134 |
-
highlighted.append((sentence, label))
|
| 1135 |
-
elif show_consensuality:
|
| 1136 |
-
highlighted = [(sentence, None) for sentence, _ in review_item]
|
| 1137 |
-
elif show_topic:
|
| 1138 |
-
highlighted = []
|
| 1139 |
-
for sentence, metadata in review_item:
|
| 1140 |
-
topic = metadata.get("topic", None)
|
| 1141 |
-
if topic != "NONE":
|
| 1142 |
-
highlighted.append((sentence, topic))
|
| 1143 |
-
else:
|
| 1144 |
-
highlighted.append((sentence, None))
|
| 1145 |
-
else:
|
| 1146 |
-
highlighted = [
|
| 1147 |
-
(sentence, None)
|
| 1148 |
-
for sentence, _ in review_item
|
| 1149 |
-
]
|
| 1150 |
-
|
| 1151 |
-
# HighlightedText: visible for all modes except agreement
|
| 1152 |
review_updates.append(
|
| 1153 |
gr.update(
|
| 1154 |
-
visible=
|
| 1155 |
-
value=
|
|
|
|
| 1156 |
color_map=color_map,
|
| 1157 |
-
show_legend=legend,
|
| 1158 |
key=f"updated_{score_type}_{i}"
|
| 1159 |
)
|
| 1160 |
)
|
| 1161 |
|
| 1162 |
-
|
| 1163 |
if show_consensuality:
|
| 1164 |
sentences_for_review = [s for s, _ in review_item]
|
| 1165 |
-
|
| 1166 |
sentences_for_review, consensuality_dict,
|
| 1167 |
listener=prep_listener, speaker=prep_speaker,
|
| 1168 |
num_reviews=number_of_displayed_reviews,
|
| 1169 |
-
label=
|
| 1170 |
)
|
| 1171 |
# Append per-review divergent cards (if RSA data available)
|
| 1172 |
if i in divergent_per_review:
|
| 1173 |
-
|
| 1174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1175 |
else:
|
| 1176 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1177 |
|
| 1178 |
-
|
|
|
|
|
|
|
| 1179 |
else:
|
| 1180 |
review_updates.append(
|
| 1181 |
gr.update(
|
|
@@ -1225,33 +1388,44 @@ with gr.Blocks(title="ReView", css=CUSTOM_CSS) as demo:
|
|
| 1225 |
most_common_visibility = gr.update(visible=False, value="")
|
| 1226 |
most_unique_visibility = gr.update(visible=False, value="")
|
| 1227 |
|
| 1228 |
-
# update topic
|
| 1229 |
-
if
|
| 1230 |
-
|
| 1231 |
-
|
| 1232 |
-
|
| 1233 |
-
|
| 1234 |
-
|
| 1235 |
-
|
| 1236 |
-
|
| 1237 |
-
|
| 1238 |
-
|
| 1239 |
-
|
| 1240 |
-
|
| 1241 |
-
|
|
|
|
| 1242 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1243 |
else:
|
| 1244 |
-
topic_color_map_visibility = gr.update(visible=False, value=
|
| 1245 |
|
| 1246 |
-
# Toggle
|
| 1247 |
has_any_rebuttal = any(
|
| 1248 |
-
|
| 1249 |
-
|
| 1250 |
)
|
|
|
|
| 1251 |
if has_any_rebuttal:
|
| 1252 |
-
|
| 1253 |
-
|
| 1254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1255 |
|
| 1256 |
return (
|
| 1257 |
new_review_id,
|
|
@@ -1260,7 +1434,7 @@ with gr.Blocks(title="ReView", css=CUSTOM_CSS) as demo:
|
|
| 1260 |
most_common_visibility,
|
| 1261 |
most_unique_visibility,
|
| 1262 |
topic_color_map_visibility,
|
| 1263 |
-
|
| 1264 |
*rebuttal_updates, # 10 per-review rebuttals
|
| 1265 |
general_rebuttal_update, # General rebuttal section
|
| 1266 |
state
|
|
@@ -1304,20 +1478,12 @@ with gr.Blocks(title="ReView", css=CUSTOM_CSS) as demo:
|
|
| 1304 |
label="Most Divergent Opinions",
|
| 1305 |
)
|
| 1306 |
|
| 1307 |
-
#
|
| 1308 |
-
topic_text_box = gr.
|
| 1309 |
-
label="Topic Labels (Color-Coded)",
|
| 1310 |
-
visible=False,
|
| 1311 |
-
value=[],
|
| 1312 |
-
show_legend=True,
|
| 1313 |
-
)
|
| 1314 |
|
| 1315 |
with gr.Row():
|
| 1316 |
gr.Markdown("### ๐ Reviews", elem_classes=["review-section-header"])
|
| 1317 |
-
|
| 1318 |
-
visible=False, value="",
|
| 1319 |
-
elem_classes=["rebuttal-toggle-container"],
|
| 1320 |
-
)
|
| 1321 |
review1 = gr.HighlightedText(show_legend=False, label="๐ Review 1", visible=number_of_displayed_reviews >= 1, key="initial_review1")
|
| 1322 |
prep_agreement1 = gr.HTML(visible=False, value="")
|
| 1323 |
prep_rebuttal1 = gr.HTML(visible=False, value="")
|
|
@@ -1373,7 +1539,7 @@ with gr.Blocks(title="ReView", css=CUSTOM_CSS) as demo:
|
|
| 1373 |
return update_review_display(state, score_type)
|
| 1374 |
|
| 1375 |
# Hook up the callbacks with the session state.
|
| 1376 |
-
_review_outputs = [review_id, review1, review2, review3, review4, review5, review6, review7, review8, review9, review10, prep_agreement1, prep_agreement2, prep_agreement3, prep_agreement4, prep_agreement5, prep_agreement6, prep_agreement7, prep_agreement8, prep_agreement9, prep_agreement10, most_common_sentences, most_unique_sentences, topic_text_box,
|
| 1377 |
year.change(fn=year_change, inputs=[year, state, score_type], outputs=_review_outputs)
|
| 1378 |
score_type.change(fn=update_review_display, inputs=[state, score_type], outputs=_review_outputs)
|
| 1379 |
next_button.click(fn=next_review, inputs=[state, score_type], outputs=_review_outputs)
|
|
@@ -1533,13 +1699,16 @@ with gr.Blocks(title="ReView", css=CUSTOM_CSS) as demo:
|
|
| 1533 |
general_formatted = format_general_rebuttals(rebuttal or "")
|
| 1534 |
has_general = bool(general_formatted)
|
| 1535 |
|
| 1536 |
-
#
|
| 1537 |
has_any = any(bool(format_rebuttal_for_review(rebuttal or "", i)) for i in range(1, 7)) or has_general
|
|
|
|
| 1538 |
if has_any:
|
| 1539 |
-
|
| 1540 |
-
|
| 1541 |
-
|
| 1542 |
-
|
|
|
|
|
|
|
| 1543 |
|
| 1544 |
return (
|
| 1545 |
gr.update(visible=False), # input_section
|
|
|
|
| 40 |
return "", ""
|
| 41 |
|
| 42 |
|
| 43 |
+
_TOGGLE_BTN_STYLE = (
|
| 44 |
+
'background:none;border:1px solid #d1d5db;border-radius:6px;padding:4px 12px;'
|
| 45 |
+
'font-size:0.78em;color:#6b7280;cursor:pointer;white-space:nowrap;'
|
| 46 |
+
'line-height:1.4;height:28px;box-sizing:border-box;'
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
def _rebuttal_toggle_html() -> str:
|
| 50 |
"""Generate an Expand/Collapse All Responses toggle button with inline JS."""
|
| 51 |
return (
|
|
|
|
| 52 |
'<button onclick="'
|
| 53 |
"let tab=this.closest('.tabitem')||this.closest('.gradio-container');"
|
| 54 |
+
"let details=tab.querySelectorAll('details:not(.review-collapse)');"
|
| 55 |
+
"if(!details.length)return;"
|
| 56 |
"let allOpen=Array.from(details).every(d=>d.open);"
|
| 57 |
"details.forEach(d=>d.open=!allOpen);"
|
| 58 |
"this.textContent=allOpen?'Expand All Responses':'Collapse All Responses';"
|
| 59 |
+
f'" style="{_TOGGLE_BTN_STYLE}"'
|
| 60 |
+
'>Expand All Responses</button>'
|
|
|
|
|
|
|
| 61 |
)
|
| 62 |
|
| 63 |
|
| 64 |
+
def _review_toggle_html() -> str:
|
| 65 |
+
"""Generate a Collapse/Expand All Reviews toggle button with inline JS."""
|
| 66 |
+
return (
|
| 67 |
+
'<button onclick="'
|
| 68 |
+
"let tab=this.closest('.tabitem')||this.closest('.gradio-container');"
|
| 69 |
+
"let details=tab.querySelectorAll('details.review-collapse');"
|
| 70 |
+
"if(!details.length)return;"
|
| 71 |
+
"let allOpen=Array.from(details).every(d=>d.open);"
|
| 72 |
+
"details.forEach(d=>d.open=!allOpen);"
|
| 73 |
+
"this.textContent=allOpen?'Expand All Reviews':'Collapse All Reviews';"
|
| 74 |
+
f'" style="{_TOGGLE_BTN_STYLE}"'
|
| 75 |
+
'>Collapse All Reviews</button>'
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
import re as _re
|
| 80 |
+
|
| 81 |
+
def _should_break_before(sent: str) -> bool:
|
| 82 |
+
"""Detect if a paragraph break should be inserted before this sentence."""
|
| 83 |
+
s = sent.strip()
|
| 84 |
+
# Numbered items: 1), 2., (1), 1:, etc.
|
| 85 |
+
if _re.match(r'^[\(\[]?\d+[\)\]\.:]', s):
|
| 86 |
+
return True
|
| 87 |
+
# Dash/bullet items
|
| 88 |
+
if len(s) > 2 and s[0] in ('-', 'โข', '*', 'โ', 'โ') and s[1] == ' ':
|
| 89 |
+
return True
|
| 90 |
+
# Markdown separators / headers
|
| 91 |
+
if s.startswith('##') or s.startswith('---'):
|
| 92 |
+
return True
|
| 93 |
+
# Common review section headers
|
| 94 |
+
if _re.match(
|
| 95 |
+
r'^\*{0,2}(Rating|Strengths?|Weaknesses?|Questions?|Limitations?|Summary|'
|
| 96 |
+
r'Soundness|Presentation|Contribution|Confidence|Experience|Review Assessment|'
|
| 97 |
+
r'Recommendation|Overall|Minor|Major|Typos?|Suggestions?|Comments?|'
|
| 98 |
+
r'Detailed\s+Comments?|Pros?|Cons?|Flag|Clarity|Significance|Originality)',
|
| 99 |
+
s, _re.IGNORECASE,
|
| 100 |
+
):
|
| 101 |
+
return True
|
| 102 |
+
return False
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def _is_review_header(sent: str) -> bool:
|
| 106 |
+
"""Detect if a sentence is a review metadata header (Rating:, Experience:, etc.)."""
|
| 107 |
+
return bool(_re.match(
|
| 108 |
+
r'^\*{0,2}(Rating|Confidence|Experience|Review Assessment|Recommendation|Flag)\b',
|
| 109 |
+
sent.strip(), _re.IGNORECASE,
|
| 110 |
+
))
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
# ---- Polarity / Topic color maps for HTML rendering ----
|
| 114 |
+
_POLARITY_COLORS = {2: "#d4fcd6", 0: "#fcd6d6"} # positive=green, negative=red
|
| 115 |
+
_TOPIC_HTML_COLORS = {
|
| 116 |
+
"Substance": "#b3e5fc",
|
| 117 |
+
"Clarity": "#c8e6c9",
|
| 118 |
+
"Soundness/Correctness": "#fff9c4",
|
| 119 |
+
"Originality": "#f8bbd0",
|
| 120 |
+
"Motivation/Impact": "#d1c4e9",
|
| 121 |
+
"Meaningful Comparison": "#ffe0b2",
|
| 122 |
+
"Replicability": "#b2dfdb",
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def render_review_html(
|
| 127 |
+
review_items: list,
|
| 128 |
+
mode: str = "plain",
|
| 129 |
+
label: str = "Review",
|
| 130 |
+
wrap: bool = False,
|
| 131 |
+
) -> str:
|
| 132 |
+
"""
|
| 133 |
+
Render a review as HTML with proper paragraph formatting.
|
| 134 |
+
|
| 135 |
+
Args:
|
| 136 |
+
review_items: list of (sentence, metadata_dict) tuples
|
| 137 |
+
mode: "plain", "polarity", or "topic"
|
| 138 |
+
label: header label
|
| 139 |
+
wrap: if False, return bare content (caller handles outer wrapper)
|
| 140 |
+
"""
|
| 141 |
+
if not review_items:
|
| 142 |
+
return ""
|
| 143 |
+
|
| 144 |
+
parts = []
|
| 145 |
+
if wrap:
|
| 146 |
+
parts.append(
|
| 147 |
+
'<details open class="review-collapse" style="border:1px solid #e5e7eb;border-radius:8px;padding:12px 16px;margin-bottom:10px;">'
|
| 148 |
+
f'<summary style="font-weight:600;font-size:0.85em;color:#374151;cursor:pointer;'
|
| 149 |
+
f'list-style:none;display:flex;align-items:center;gap:6px;">'
|
| 150 |
+
f'<span style="transition:transform 0.2s;font-size:0.7em;">โถ</span> '
|
| 151 |
+
f'{_html.escape(label)}</summary>'
|
| 152 |
+
)
|
| 153 |
+
else:
|
| 154 |
+
if label:
|
| 155 |
+
parts.append(f'<div style="font-weight:600;font-size:0.85em;color:#374151;margin-bottom:4px;">{_html.escape(label)}</div>')
|
| 156 |
+
parts.append('<div style="line-height:1.8;font-size:0.95em;margin-top:6px;">')
|
| 157 |
+
|
| 158 |
+
for i, (sent, metadata) in enumerate(review_items):
|
| 159 |
+
# Paragraph break detection
|
| 160 |
+
if i > 0 and _should_break_before(sent):
|
| 161 |
+
parts.append('<br>')
|
| 162 |
+
|
| 163 |
+
# Header styling (Rating:, Experience:, etc.)
|
| 164 |
+
is_header = _is_review_header(sent)
|
| 165 |
+
|
| 166 |
+
bg = ""
|
| 167 |
+
label_text = ""
|
| 168 |
+
if mode == "polarity":
|
| 169 |
+
polarity = metadata.get("polarity")
|
| 170 |
+
if polarity in _POLARITY_COLORS:
|
| 171 |
+
bg = f"background:{_POLARITY_COLORS[polarity]};"
|
| 172 |
+
elif mode == "topic":
|
| 173 |
+
topic = metadata.get("topic")
|
| 174 |
+
if topic and topic != "NONE" and topic in _TOPIC_HTML_COLORS:
|
| 175 |
+
bg = f"background:{_TOPIC_HTML_COLORS[topic]};"
|
| 176 |
+
label_text = topic
|
| 177 |
+
|
| 178 |
+
style = f"padding:1px 3px;border-radius:3px;{bg}"
|
| 179 |
+
if is_header:
|
| 180 |
+
style += "font-weight:600;color:#92400e;"
|
| 181 |
+
|
| 182 |
+
sent_id = _make_sentence_id(sent)
|
| 183 |
+
escaped = _html.escape(sent)
|
| 184 |
+
|
| 185 |
+
if label_text:
|
| 186 |
+
# Show topic label as a small tag
|
| 187 |
+
parts.append(
|
| 188 |
+
f'<span id="{sent_id}" style="{style}" title="{_html.escape(label_text)}">'
|
| 189 |
+
f'{escaped} </span>'
|
| 190 |
+
)
|
| 191 |
+
else:
|
| 192 |
+
parts.append(f'<span id="{sent_id}" style="{style}">{escaped} </span>')
|
| 193 |
+
|
| 194 |
+
parts.append('</div>')
|
| 195 |
+
if wrap:
|
| 196 |
+
parts.append('</details>')
|
| 197 |
+
return "".join(parts)
|
| 198 |
+
|
| 199 |
+
|
| 200 |
def format_summary_cards(
|
| 201 |
sentences: list,
|
| 202 |
scores: dict,
|
|
|
|
| 427 |
speaker: Dict[str, Dict[str, float]],
|
| 428 |
num_reviews: int,
|
| 429 |
label: str = "Agreement",
|
| 430 |
+
wrap: bool = False,
|
| 431 |
) -> str:
|
| 432 |
"""
|
| 433 |
Custom HTML renderer for Agreement mode (replaces gr.HighlightedText).
|
|
|
|
| 451 |
'</div>'
|
| 452 |
)
|
| 453 |
|
| 454 |
+
parts = []
|
| 455 |
+
if wrap:
|
| 456 |
+
parts.append(
|
| 457 |
+
'<details open class="review-collapse" style="border:1px solid #e5e7eb;border-radius:8px;padding:12px 16px;margin-bottom:10px;">'
|
| 458 |
+
f'<summary style="font-weight:600;font-size:0.85em;color:#374151;cursor:pointer;'
|
| 459 |
+
f'list-style:none;display:flex;align-items:center;gap:6px;">'
|
| 460 |
+
f'<span style="transition:transform 0.2s;font-size:0.7em;">โถ</span> '
|
| 461 |
+
f'{_html.escape(label)}</summary>'
|
| 462 |
+
)
|
| 463 |
+
else:
|
| 464 |
+
if label:
|
| 465 |
+
parts.append(f'<div style="font-weight:600;font-size:0.85em;color:#374151;margin-bottom:4px;">{_html.escape(label)}</div>')
|
| 466 |
parts.append(legend_html)
|
| 467 |
+
parts.append('<div style="line-height:1.8;font-size:0.95em;margin-top:6px;">')
|
| 468 |
|
| 469 |
# Compute informativeness threshold: 2 / K (twice uniform baseline)
|
| 470 |
k = max(len(uniqueness), 1)
|
| 471 |
info_threshold = 2.0 / k
|
| 472 |
|
| 473 |
+
for idx, sent in enumerate(sentences):
|
| 474 |
+
# Paragraph break detection
|
| 475 |
+
if idx > 0 and _should_break_before(sent):
|
| 476 |
+
parts.append('<br>')
|
| 477 |
+
|
| 478 |
sent_id = _make_sentence_id(sent)
|
| 479 |
score = uniqueness.get(sent)
|
| 480 |
|
| 481 |
+
# Header styling (Rating:, Experience:, etc.)
|
| 482 |
+
header_style = "font-weight:600;color:#92400e;" if _is_review_header(sent) else ""
|
| 483 |
+
|
| 484 |
if score is None or abs(score) < HIGHLIGHT_THRESHOLD:
|
| 485 |
# No highlight
|
| 486 |
+
parts.append(f'<span id="{sent_id}" style="{header_style}">{_html.escape(sent)} </span>')
|
| 487 |
continue
|
| 488 |
|
| 489 |
# --- Color and opacity ---
|
|
|
|
| 547 |
)
|
| 548 |
|
| 549 |
parts.append("</div>")
|
| 550 |
+
if wrap:
|
| 551 |
+
parts.append("</details>")
|
| 552 |
return "".join(parts)
|
| 553 |
|
| 554 |
|
|
|
|
| 1124 |
details[open] summary span:first-child { display: inline-block; transform: rotate(90deg); }
|
| 1125 |
"""
|
| 1126 |
|
| 1127 |
+
with gr.Blocks(
|
| 1128 |
+
title="ReView",
|
| 1129 |
+
css=CUSTOM_CSS,
|
| 1130 |
+
theme=gr.themes.Default(),
|
| 1131 |
+
js="() => { document.querySelector('body').classList.remove('dark'); }",
|
| 1132 |
+
) as demo:
|
| 1133 |
# gr.Markdown("# ReView Interface")
|
| 1134 |
|
| 1135 |
# TODO: Uncomment this for home/description tab once finished with testing.
|
|
|
|
| 1287 |
if i < number_of_displayed_reviews:
|
| 1288 |
review_item, rebuttal_html = review_items_cache[i]
|
| 1289 |
|
| 1290 |
+
# All modes now use HTML rendering for proper paragraph formatting.
|
| 1291 |
+
# HighlightedText is always hidden; prep_agreement HTML is always shown.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1292 |
review_updates.append(
|
| 1293 |
gr.update(
|
| 1294 |
+
visible=False,
|
| 1295 |
+
value=[],
|
| 1296 |
+
show_legend=False,
|
| 1297 |
color_map=color_map,
|
|
|
|
| 1298 |
key=f"updated_{score_type}_{i}"
|
| 1299 |
)
|
| 1300 |
)
|
| 1301 |
|
| 1302 |
+
review_label = f"Review {i + 1}"
|
| 1303 |
if show_consensuality:
|
| 1304 |
sentences_for_review = [s for s, _ in review_item]
|
| 1305 |
+
inner_html = render_agreement_html(
|
| 1306 |
sentences_for_review, consensuality_dict,
|
| 1307 |
listener=prep_listener, speaker=prep_speaker,
|
| 1308 |
num_reviews=number_of_displayed_reviews,
|
| 1309 |
+
label="",
|
| 1310 |
)
|
| 1311 |
# Append per-review divergent cards (if RSA data available)
|
| 1312 |
if i in divergent_per_review:
|
| 1313 |
+
inner_html += divergent_per_review[i]
|
| 1314 |
+
elif show_polarity:
|
| 1315 |
+
inner_html = render_review_html(
|
| 1316 |
+
review_item, mode="polarity", label="",
|
| 1317 |
+
)
|
| 1318 |
+
elif show_topic:
|
| 1319 |
+
inner_html = render_review_html(
|
| 1320 |
+
review_item, mode="topic", label="",
|
| 1321 |
+
)
|
| 1322 |
else:
|
| 1323 |
+
inner_html = render_review_html(
|
| 1324 |
+
review_item, mode="plain", label="",
|
| 1325 |
+
)
|
| 1326 |
+
|
| 1327 |
+
# Wrap review content + rebuttal in a single collapsible card
|
| 1328 |
+
html_content = (
|
| 1329 |
+
'<details open class="review-collapse" style="border:1px solid #e5e7eb;'
|
| 1330 |
+
'border-radius:8px;padding:12px 16px;margin-bottom:10px;">'
|
| 1331 |
+
f'<summary style="font-weight:600;font-size:0.9em;color:#374151;cursor:pointer;'
|
| 1332 |
+
f'list-style:none;display:flex;align-items:center;gap:6px;">'
|
| 1333 |
+
f'<span style="transition:transform 0.2s;font-size:0.7em;">โถ</span> '
|
| 1334 |
+
f'{_html.escape(review_label)}</summary>'
|
| 1335 |
+
f'{inner_html}{rebuttal_html}'
|
| 1336 |
+
'</details>'
|
| 1337 |
+
)
|
| 1338 |
|
| 1339 |
+
agreement_updates.append(gr.update(visible=True, value=html_content))
|
| 1340 |
+
# Rebuttal is now embedded in the review card, so hide the separate component
|
| 1341 |
+
rebuttal_updates.append(gr.update(visible=False, value=""))
|
| 1342 |
else:
|
| 1343 |
review_updates.append(
|
| 1344 |
gr.update(
|
|
|
|
| 1388 |
most_common_visibility = gr.update(visible=False, value="")
|
| 1389 |
most_unique_visibility = gr.update(visible=False, value="")
|
| 1390 |
|
| 1391 |
+
# update color legend (topic or polarity)
|
| 1392 |
+
if show_polarity:
|
| 1393 |
+
polarity_legend = (
|
| 1394 |
+
'<div style="display:flex;gap:12px;align-items:center;padding:8px 0;font-size:0.8em;">'
|
| 1395 |
+
'<span style="background:#d4fcd6;padding:2px 8px;border-radius:4px;">Positive</span>'
|
| 1396 |
+
'<span style="background:#fcd6d6;padding:2px 8px;border-radius:4px;">Negative</span>'
|
| 1397 |
+
'<span style="color:#9ca3af;">Neutral (no highlight)</span>'
|
| 1398 |
+
'</div>'
|
| 1399 |
+
)
|
| 1400 |
+
topic_color_map_visibility = gr.update(visible=True, value=polarity_legend)
|
| 1401 |
+
elif show_topic:
|
| 1402 |
+
legend_items = " ".join(
|
| 1403 |
+
f'<span style="background:{color};padding:2px 8px;border-radius:4px;'
|
| 1404 |
+
f'font-size:0.8em;margin-right:4px;">{_html.escape(name)}</span>'
|
| 1405 |
+
for name, color in _TOPIC_HTML_COLORS.items()
|
| 1406 |
)
|
| 1407 |
+
topic_legend_html = (
|
| 1408 |
+
f'<div style="display:flex;flex-wrap:wrap;gap:4px;align-items:center;'
|
| 1409 |
+
f'padding:8px 0;">{legend_items}</div>'
|
| 1410 |
+
)
|
| 1411 |
+
topic_color_map_visibility = gr.update(visible=True, value=topic_legend_html)
|
| 1412 |
else:
|
| 1413 |
+
topic_color_map_visibility = gr.update(visible=False, value="")
|
| 1414 |
|
| 1415 |
+
# Toggle bar: review collapse + rebuttal expand buttons
|
| 1416 |
has_any_rebuttal = any(
|
| 1417 |
+
rebuttal_html
|
| 1418 |
+
for _, rebuttal_html in review_items_cache
|
| 1419 |
)
|
| 1420 |
+
toggle_buttons = [_review_toggle_html()]
|
| 1421 |
if has_any_rebuttal:
|
| 1422 |
+
toggle_buttons.append(_rebuttal_toggle_html())
|
| 1423 |
+
toggle_bar_html = (
|
| 1424 |
+
'<div style="display:flex;justify-content:flex-end;gap:8px;">'
|
| 1425 |
+
+ "".join(toggle_buttons)
|
| 1426 |
+
+ '</div>'
|
| 1427 |
+
)
|
| 1428 |
+
toggle_bar_update = gr.update(visible=True, value=toggle_bar_html)
|
| 1429 |
|
| 1430 |
return (
|
| 1431 |
new_review_id,
|
|
|
|
| 1434 |
most_common_visibility,
|
| 1435 |
most_unique_visibility,
|
| 1436 |
topic_color_map_visibility,
|
| 1437 |
+
toggle_bar_update, # Review collapse + rebuttal expand buttons
|
| 1438 |
*rebuttal_updates, # 10 per-review rebuttals
|
| 1439 |
general_rebuttal_update, # General rebuttal section
|
| 1440 |
state
|
|
|
|
| 1478 |
label="Most Divergent Opinions",
|
| 1479 |
)
|
| 1480 |
|
| 1481 |
+
# Topic color legend (HTML version)
|
| 1482 |
+
topic_text_box = gr.HTML(visible=False, value="")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1483 |
|
| 1484 |
with gr.Row():
|
| 1485 |
gr.Markdown("### ๐ Reviews", elem_classes=["review-section-header"])
|
| 1486 |
+
prep_toggle_bar = gr.HTML(visible=False, value="")
|
|
|
|
|
|
|
|
|
|
| 1487 |
review1 = gr.HighlightedText(show_legend=False, label="๐ Review 1", visible=number_of_displayed_reviews >= 1, key="initial_review1")
|
| 1488 |
prep_agreement1 = gr.HTML(visible=False, value="")
|
| 1489 |
prep_rebuttal1 = gr.HTML(visible=False, value="")
|
|
|
|
| 1539 |
return update_review_display(state, score_type)
|
| 1540 |
|
| 1541 |
# Hook up the callbacks with the session state.
|
| 1542 |
+
_review_outputs = [review_id, review1, review2, review3, review4, review5, review6, review7, review8, review9, review10, prep_agreement1, prep_agreement2, prep_agreement3, prep_agreement4, prep_agreement5, prep_agreement6, prep_agreement7, prep_agreement8, prep_agreement9, prep_agreement10, most_common_sentences, most_unique_sentences, topic_text_box, prep_toggle_bar, prep_rebuttal1, prep_rebuttal2, prep_rebuttal3, prep_rebuttal4, prep_rebuttal5, prep_rebuttal6, prep_rebuttal7, prep_rebuttal8, prep_rebuttal9, prep_rebuttal10, prep_general_rebuttal, state]
|
| 1543 |
year.change(fn=year_change, inputs=[year, state, score_type], outputs=_review_outputs)
|
| 1544 |
score_type.change(fn=update_review_display, inputs=[state, score_type], outputs=_review_outputs)
|
| 1545 |
next_button.click(fn=next_review, inputs=[state, score_type], outputs=_review_outputs)
|
|
|
|
| 1699 |
general_formatted = format_general_rebuttals(rebuttal or "")
|
| 1700 |
has_general = bool(general_formatted)
|
| 1701 |
|
| 1702 |
+
# Toggle bar: review collapse + rebuttal expand
|
| 1703 |
has_any = any(bool(format_rebuttal_for_review(rebuttal or "", i)) for i in range(1, 7)) or has_general
|
| 1704 |
+
toggle_buttons = [_review_toggle_html()]
|
| 1705 |
if has_any:
|
| 1706 |
+
toggle_buttons.append(_rebuttal_toggle_html())
|
| 1707 |
+
toggle_bar = (
|
| 1708 |
+
'<div style="display:flex;justify-content:flex-end;gap:8px;">'
|
| 1709 |
+
+ "".join(toggle_buttons) + '</div>'
|
| 1710 |
+
)
|
| 1711 |
+
toggle_update = gr.update(visible=True, value=toggle_bar)
|
| 1712 |
|
| 1713 |
return (
|
| 1714 |
gr.update(visible=False), # input_section
|