Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -338,20 +338,73 @@ body, .gradio-container {
|
|
| 338 |
border-bottom-color: var(--accent) !important;
|
| 339 |
}
|
| 340 |
|
| 341 |
-
/* ββ Dataframe ββ */
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
|
|
|
|
|
|
| 345 |
}
|
| 346 |
-
.
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
font-
|
| 351 |
-
|
| 352 |
-
text-transform: uppercase !important;
|
| 353 |
}
|
| 354 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
|
| 356 |
/* ββ File upload ββ */
|
| 357 |
.file-drop {
|
|
@@ -477,14 +530,20 @@ def build_app():
|
|
| 477 |
|
| 478 |
# ββ Tab A: Review Table ββββββββββββββββββββββββββββββββββββββ
|
| 479 |
with gr.Tab("π Review Table"):
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
label="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
)
|
| 489 |
submit_review_btn = gr.Button(
|
| 490 |
"Submit Review β",
|
|
@@ -533,7 +592,7 @@ def build_app():
|
|
| 533 |
def on_send(message, history, csv_path, charts_state, downloads_state):
|
| 534 |
"""Stream agent responses and update all UI elements."""
|
| 535 |
if not message.strip():
|
| 536 |
-
yield history, "", phase_html("idle"),
|
| 537 |
return
|
| 538 |
|
| 539 |
# Accumulate streamed updates
|
|
@@ -548,38 +607,109 @@ def build_app():
|
|
| 548 |
(last_history, last_phase,
|
| 549 |
last_charts, last_downloads, new_topics) = result
|
| 550 |
|
|
|
|
| 551 |
yield (
|
| 552 |
last_history,
|
| 553 |
"", # clear input
|
| 554 |
phase_html(last_phase),
|
| 555 |
-
|
|
|
|
| 556 |
gr.update(), # downloads updated below
|
| 557 |
last_charts,
|
| 558 |
last_downloads,
|
| 559 |
)
|
| 560 |
|
| 561 |
# Final yield with downloads
|
|
|
|
| 562 |
yield (
|
| 563 |
last_history,
|
| 564 |
"",
|
| 565 |
phase_html(last_phase),
|
| 566 |
-
|
|
|
|
| 567 |
last_downloads if last_downloads else None,
|
| 568 |
last_charts,
|
| 569 |
last_downloads,
|
| 570 |
)
|
| 571 |
|
| 572 |
-
def
|
|
|
|
| 573 |
if not rows:
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 577 |
|
| 578 |
send_outputs = [
|
| 579 |
chatbot,
|
| 580 |
user_input,
|
| 581 |
phase_display,
|
| 582 |
-
|
|
|
|
| 583 |
download_files,
|
| 584 |
state_charts,
|
| 585 |
state_downloads,
|
|
@@ -597,9 +727,12 @@ def build_app():
|
|
| 597 |
)
|
| 598 |
|
| 599 |
# ββ Submit review βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 600 |
-
def on_submit_review(
|
| 601 |
"""Pass edited table back to the agent as a review submission."""
|
| 602 |
-
|
|
|
|
|
|
|
|
|
|
| 603 |
|
| 604 |
last_history = history
|
| 605 |
last_phase = "review"
|
|
@@ -628,7 +761,7 @@ def build_app():
|
|
| 628 |
|
| 629 |
submit_review_btn.click(
|
| 630 |
fn=on_submit_review,
|
| 631 |
-
inputs=[
|
| 632 |
outputs=[chatbot, phase_display, download_files, state_charts, state_downloads],
|
| 633 |
)
|
| 634 |
|
|
|
|
| 338 |
border-bottom-color: var(--accent) !important;
|
| 339 |
}
|
| 340 |
|
| 341 |
+
/* ββ Review Table (custom HTML β replaces gr.Dataframe) ββ */
|
| 342 |
+
#review-table-wrap {
|
| 343 |
+
overflow-x: auto;
|
| 344 |
+
width: 100%;
|
| 345 |
+
border-radius: var(--radius);
|
| 346 |
+
border: 1px solid var(--border);
|
| 347 |
}
|
| 348 |
+
.rt-table {
|
| 349 |
+
width: 100%;
|
| 350 |
+
min-width: 960px;
|
| 351 |
+
border-collapse: collapse;
|
| 352 |
+
font-family: var(--mono);
|
| 353 |
+
font-size: .78rem;
|
|
|
|
| 354 |
}
|
| 355 |
+
.rt-table th {
|
| 356 |
+
background: #1a1f2e;
|
| 357 |
+
color: var(--accent);
|
| 358 |
+
font-family: var(--display);
|
| 359 |
+
font-size: .68rem;
|
| 360 |
+
letter-spacing: .1em;
|
| 361 |
+
text-transform: uppercase;
|
| 362 |
+
padding: .65rem .8rem;
|
| 363 |
+
text-align: left;
|
| 364 |
+
border-bottom: 2px solid var(--border);
|
| 365 |
+
white-space: nowrap;
|
| 366 |
+
position: sticky;
|
| 367 |
+
top: 0;
|
| 368 |
+
z-index: 2;
|
| 369 |
+
}
|
| 370 |
+
.rt-table td {
|
| 371 |
+
padding: .55rem .8rem;
|
| 372 |
+
border-bottom: 1px solid var(--border);
|
| 373 |
+
vertical-align: top;
|
| 374 |
+
color: var(--text);
|
| 375 |
+
}
|
| 376 |
+
.rt-table tr:nth-child(even) td { background: rgba(255,255,255,.02); }
|
| 377 |
+
.rt-table tr:hover td { background: rgba(79,142,247,.06); transition: background .15s; }
|
| 378 |
+
.rt-col-num { width: 45px; text-align: center; color: var(--muted); }
|
| 379 |
+
.rt-col-label { min-width: 160px; font-weight: 600; }
|
| 380 |
+
.rt-col-evid { min-width: 240px; line-height: 1.6; color: var(--muted); }
|
| 381 |
+
.rt-col-sent { width: 80px; text-align: center; }
|
| 382 |
+
.rt-col-paper { width: 70px; text-align: center; }
|
| 383 |
+
.rt-col-appr { width: 72px; text-align: center; }
|
| 384 |
+
.rt-col-rename { min-width: 140px; }
|
| 385 |
+
.rt-col-reason { min-width: 200px; }
|
| 386 |
+
.rt-check {
|
| 387 |
+
width: 18px; height: 18px; cursor: pointer;
|
| 388 |
+
accent-color: var(--success);
|
| 389 |
+
}
|
| 390 |
+
.rt-input, .rt-textarea {
|
| 391 |
+
width: 100%; box-sizing: border-box;
|
| 392 |
+
padding: .32rem .55rem;
|
| 393 |
+
background: #0d0f14;
|
| 394 |
+
border: 1px solid var(--border);
|
| 395 |
+
color: var(--text);
|
| 396 |
+
border-radius: 5px;
|
| 397 |
+
font-family: var(--sans);
|
| 398 |
+
font-size: .78rem;
|
| 399 |
+
transition: border-color .15s;
|
| 400 |
+
}
|
| 401 |
+
.rt-input:focus, .rt-textarea:focus {
|
| 402 |
+
border-color: var(--accent);
|
| 403 |
+
outline: none;
|
| 404 |
+
box-shadow: 0 0 0 2px rgba(79,142,247,.18);
|
| 405 |
+
}
|
| 406 |
+
.rt-textarea { resize: vertical; min-height: 46px; }
|
| 407 |
+
.rt-empty { color: var(--muted); padding: 1.5rem; font-style: italic; text-align: center; }
|
| 408 |
|
| 409 |
/* ββ File upload ββ */
|
| 410 |
.file-drop {
|
|
|
|
| 530 |
|
| 531 |
# ββ Tab A: Review Table ββββββββββββββββββββββββββββββββββββββ
|
| 532 |
with gr.Tab("π Review Table"):
|
| 533 |
+
gr.HTML(
|
| 534 |
+
"<p style='color:var(--muted,#7a8299);font-size:.8rem;margin:0 0 .6rem'>"
|
| 535 |
+
"Check β Approve, optionally fill Rename To / Reasoning, then click Submit.</p>"
|
| 536 |
+
)
|
| 537 |
+
review_table_html = gr.HTML(
|
| 538 |
+
value="<p class='rt-empty' style='color:#7a8299;padding:1rem;font-style:italic'>"
|
| 539 |
+
"No topics yet β run topic modelling first.</p>",
|
| 540 |
+
label="",
|
| 541 |
+
)
|
| 542 |
+
# Hidden textbox holds JSON-serialised table rows, kept in sync by JS
|
| 543 |
+
review_json_state = gr.Textbox(
|
| 544 |
+
value="[]",
|
| 545 |
+
visible=False,
|
| 546 |
+
elem_id="review-json-state",
|
| 547 |
)
|
| 548 |
submit_review_btn = gr.Button(
|
| 549 |
"Submit Review β",
|
|
|
|
| 592 |
def on_send(message, history, csv_path, charts_state, downloads_state):
|
| 593 |
"""Stream agent responses and update all UI elements."""
|
| 594 |
if not message.strip():
|
| 595 |
+
yield history, "", phase_html("idle"), gr.update(), gr.update(), None, charts_state, downloads_state
|
| 596 |
return
|
| 597 |
|
| 598 |
# Accumulate streamed updates
|
|
|
|
| 607 |
(last_history, last_phase,
|
| 608 |
last_charts, last_downloads, new_topics) = result
|
| 609 |
|
| 610 |
+
_html, _json = _rows_to_html(new_topics) if new_topics else (gr.update(), gr.update())
|
| 611 |
yield (
|
| 612 |
last_history,
|
| 613 |
"", # clear input
|
| 614 |
phase_html(last_phase),
|
| 615 |
+
_html,
|
| 616 |
+
_json,
|
| 617 |
gr.update(), # downloads updated below
|
| 618 |
last_charts,
|
| 619 |
last_downloads,
|
| 620 |
)
|
| 621 |
|
| 622 |
# Final yield with downloads
|
| 623 |
+
_html, _json = _rows_to_html(new_topics) if new_topics else (gr.update(), gr.update())
|
| 624 |
yield (
|
| 625 |
last_history,
|
| 626 |
"",
|
| 627 |
phase_html(last_phase),
|
| 628 |
+
_html,
|
| 629 |
+
_json,
|
| 630 |
last_downloads if last_downloads else None,
|
| 631 |
last_charts,
|
| 632 |
last_downloads,
|
| 633 |
)
|
| 634 |
|
| 635 |
+
def _rows_to_html(rows):
|
| 636 |
+
"""Convert agent row data β (html_str, json_str) for the custom table."""
|
| 637 |
if not rows:
|
| 638 |
+
html = "<p class='rt-empty'>No topics yet β run topic modelling first.</p>"
|
| 639 |
+
return html, "[]"
|
| 640 |
+
|
| 641 |
+
padded = []
|
| 642 |
+
for r in rows:
|
| 643 |
+
row = list(r) + [""] * max(0, 8 - len(r))
|
| 644 |
+
padded.append(row[:8])
|
| 645 |
+
|
| 646 |
+
json_data = json.dumps(padded)
|
| 647 |
+
|
| 648 |
+
rows_html = ""
|
| 649 |
+
for i, row in enumerate(padded):
|
| 650 |
+
num, label, evidence, sentences, papers, approve, rename, reasoning = row
|
| 651 |
+
checked = "checked" if str(approve).lower() in ("true", "1", "yes", "β") else ""
|
| 652 |
+
# Escape for HTML attribute safety
|
| 653 |
+
rename_esc = str(rename).replace('"', """)
|
| 654 |
+
reason_esc = str(reasoning).replace("<", "<").replace(">", ">")
|
| 655 |
+
evidence_esc = str(evidence).replace("<", "<").replace(">", ">")
|
| 656 |
+
label_esc = str(label).replace("<", "<").replace(">", ">")
|
| 657 |
+
rows_html += f"""
|
| 658 |
+
<tr data-row="{i}">
|
| 659 |
+
<td class="rt-col-num">{num}</td>
|
| 660 |
+
<td class="rt-col-label">{label_esc}</td>
|
| 661 |
+
<td class="rt-col-evid">{evidence_esc}</td>
|
| 662 |
+
<td class="rt-col-sent">{sentences}</td>
|
| 663 |
+
<td class="rt-col-paper">{papers}</td>
|
| 664 |
+
<td class="rt-col-appr">
|
| 665 |
+
<input type="checkbox" class="rt-check" {checked}
|
| 666 |
+
onchange="rtUpdate({i},5,this.checked)">
|
| 667 |
+
</td>
|
| 668 |
+
<td class="rt-col-rename">
|
| 669 |
+
<input type="text" class="rt-input" value="{rename_esc}"
|
| 670 |
+
oninput="rtUpdate({i},6,this.value)" placeholder="new labelβ¦">
|
| 671 |
+
</td>
|
| 672 |
+
<td class="rt-col-reason">
|
| 673 |
+
<textarea class="rt-textarea" rows="2"
|
| 674 |
+
oninput="rtUpdate({i},7,this.value)" placeholder="notesβ¦">{reason_esc}</textarea>
|
| 675 |
+
</td>
|
| 676 |
+
</tr>"""
|
| 677 |
+
|
| 678 |
+
html = f"""<div id="review-table-wrap">
|
| 679 |
+
<script>
|
| 680 |
+
(function(){{
|
| 681 |
+
window._rtData = {json_data};
|
| 682 |
+
window.rtUpdate = function(idx, col, val) {{
|
| 683 |
+
window._rtData[idx][col] = val;
|
| 684 |
+
var el = document.querySelector('#review-json-state textarea');
|
| 685 |
+
if (!el) return;
|
| 686 |
+
var setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype,'value').set;
|
| 687 |
+
setter.call(el, JSON.stringify(window._rtData));
|
| 688 |
+
el.dispatchEvent(new Event('input',{{bubbles:true}}));
|
| 689 |
+
}};
|
| 690 |
+
}})();
|
| 691 |
+
</script>
|
| 692 |
+
<table class="rt-table">
|
| 693 |
+
<thead><tr>
|
| 694 |
+
<th class="rt-col-num">#</th>
|
| 695 |
+
<th class="rt-col-label">Topic Label</th>
|
| 696 |
+
<th class="rt-col-evid">Top Evidence</th>
|
| 697 |
+
<th class="rt-col-sent">Sentences</th>
|
| 698 |
+
<th class="rt-col-paper">Papers</th>
|
| 699 |
+
<th class="rt-col-appr">Approve</th>
|
| 700 |
+
<th class="rt-col-rename">Rename To</th>
|
| 701 |
+
<th class="rt-col-reason">Reasoning</th>
|
| 702 |
+
</tr></thead>
|
| 703 |
+
<tbody>{rows_html}</tbody>
|
| 704 |
+
</table></div>"""
|
| 705 |
+
return html, json_data
|
| 706 |
|
| 707 |
send_outputs = [
|
| 708 |
chatbot,
|
| 709 |
user_input,
|
| 710 |
phase_display,
|
| 711 |
+
review_table_html,
|
| 712 |
+
review_json_state,
|
| 713 |
download_files,
|
| 714 |
state_charts,
|
| 715 |
state_downloads,
|
|
|
|
| 727 |
)
|
| 728 |
|
| 729 |
# ββ Submit review βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 730 |
+
def on_submit_review(json_str, history, charts_state, downloads_state):
|
| 731 |
"""Pass edited table back to the agent as a review submission."""
|
| 732 |
+
try:
|
| 733 |
+
rows = json.loads(json_str) if json_str and json_str.strip() not in ("", "[]") else []
|
| 734 |
+
except Exception:
|
| 735 |
+
rows = []
|
| 736 |
|
| 737 |
last_history = history
|
| 738 |
last_phase = "review"
|
|
|
|
| 761 |
|
| 762 |
submit_review_btn.click(
|
| 763 |
fn=on_submit_review,
|
| 764 |
+
inputs=[review_json_state, chatbot, state_charts, state_downloads],
|
| 765 |
outputs=[chatbot, phase_display, download_files, state_charts, state_downloads],
|
| 766 |
)
|
| 767 |
|