Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -30,14 +30,13 @@ from typing import Optional, Tuple, Dict, Any
|
|
| 30 |
from agent import PAJAISResearchAgent, AnalysisConfig
|
| 31 |
from tools import (
|
| 32 |
load_journal_csv, validate_dataframe,
|
| 33 |
-
PAJAIS_THEMES, export_all_artifacts
|
| 34 |
-
)
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
split_large_clusters,
|
| 39 |
get_cluster_summary,
|
| 40 |
-
|
| 41 |
run_agentic_council,
|
| 42 |
)
|
| 43 |
|
|
@@ -57,6 +56,7 @@ OUTPUTS_DIR.mkdir(exist_ok=True)
|
|
| 57 |
# ---------------------------------------------------------------------------
|
| 58 |
MISTRAL_API_KEY = os.environ.get("MISTRAL_API_KEY", "")
|
| 59 |
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
|
|
|
|
| 60 |
|
| 61 |
# ---------------------------------------------------------------------------
|
| 62 |
# Custom CSS — Light, readable theme that works on HuggingFace Spaces
|
|
@@ -1461,40 +1461,57 @@ with gr.Blocks(
|
|
| 1461 |
# ==================================================================
|
| 1462 |
# TAB A — DBSCAN Clusters (Phase 2.5)
|
| 1463 |
# ==================================================================
|
| 1464 |
-
with gr.Tab("🔵
|
| 1465 |
-
gr.Markdown("## Phase 2.5: Semantic Clustering via
|
| 1466 |
gr.Markdown(
|
| 1467 |
-
"
|
| 1468 |
-
"
|
| 1469 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1470 |
)
|
| 1471 |
|
| 1472 |
with gr.Accordion("⚙️ Clustering Parameters", open=False):
|
| 1473 |
with gr.Row():
|
| 1474 |
-
|
| 1475 |
-
|
|
|
|
|
|
|
| 1476 |
)
|
| 1477 |
-
|
| 1478 |
-
|
|
|
|
|
|
|
| 1479 |
)
|
| 1480 |
with gr.Row():
|
| 1481 |
-
|
| 1482 |
-
|
|
|
|
|
|
|
| 1483 |
)
|
| 1484 |
-
|
| 1485 |
-
|
|
|
|
|
|
|
| 1486 |
)
|
| 1487 |
with gr.Row():
|
| 1488 |
-
|
| 1489 |
-
|
|
|
|
|
|
|
| 1490 |
)
|
| 1491 |
-
|
| 1492 |
-
|
|
|
|
|
|
|
| 1493 |
)
|
| 1494 |
|
| 1495 |
with gr.Row():
|
| 1496 |
-
btn_run_dbscan = gr.Button("▶ Run
|
| 1497 |
-
btn_llm_label = gr.Button("🤖 Label Clusters
|
| 1498 |
|
| 1499 |
dbscan_status = gr.Markdown("*Run DBSCAN or use the full pipeline from Tab 1.*")
|
| 1500 |
|
|
@@ -1527,7 +1544,7 @@ with gr.Blocks(
|
|
| 1527 |
|
| 1528 |
def handle_run_dbscan(
|
| 1529 |
df, existing_cluster_df, existing_summary,
|
| 1530 |
-
|
| 1531 |
progress=gr.Progress(track_tqdm=True)
|
| 1532 |
):
|
| 1533 |
if existing_cluster_df is not None and not existing_cluster_df.empty:
|
|
@@ -1536,7 +1553,7 @@ with gr.Blocks(
|
|
| 1536 |
saved_docs = _safe_save_csv(existing_cluster_df, "cluster_documents.csv")
|
| 1537 |
saved_sum = _safe_save_csv(summary, "cluster_summary.csv")
|
| 1538 |
return (
|
| 1539 |
-
"<div class='success-box'>✅ Loaded existing
|
| 1540 |
summary, existing_cluster_df, summary,
|
| 1541 |
fig_sz, fig_noise,
|
| 1542 |
gr.update(value=saved_docs), gr.update(value=saved_sum), gr.update(),
|
|
@@ -1552,19 +1569,26 @@ with gr.Blocks(
|
|
| 1552 |
|
| 1553 |
try:
|
| 1554 |
_ensure_output_dir()
|
| 1555 |
-
progress(0.
|
| 1556 |
-
|
| 1557 |
-
|
| 1558 |
-
|
| 1559 |
-
|
| 1560 |
-
|
| 1561 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1562 |
)
|
| 1563 |
-
progress(0.
|
| 1564 |
-
cdf = enforce_min_membership(cdf, min_members=int(min_m))
|
| 1565 |
-
progress(0.7, desc="Splitting large clusters…")
|
| 1566 |
-
cdf = split_large_clusters(cdf, max_cluster_size=int(max_sz))
|
| 1567 |
-
progress(0.9, desc="Summarising…")
|
| 1568 |
summary = get_cluster_summary(cdf)
|
| 1569 |
progress(1.0, desc="Done!")
|
| 1570 |
|
|
@@ -1572,8 +1596,8 @@ with gr.Blocks(
|
|
| 1572 |
saved_docs = _safe_save_csv(cdf, "cluster_documents.csv")
|
| 1573 |
saved_sum = _safe_save_csv(summary, "cluster_summary.csv")
|
| 1574 |
|
| 1575 |
-
n_c = len(set(cdf[
|
| 1576 |
-
n_n = int(cdf[
|
| 1577 |
return (
|
| 1578 |
f"<div class='success-box'>✅ {n_c} clusters found, {n_n} noise docs.</div>",
|
| 1579 |
summary, cdf, summary,
|
|
@@ -1591,21 +1615,36 @@ with gr.Blocks(
|
|
| 1591 |
def handle_llm_label(cluster_df, cluster_summary, progress=gr.Progress(track_tqdm=True)):
|
| 1592 |
if cluster_df is None or cluster_df.empty:
|
| 1593 |
return (
|
| 1594 |
-
"<div class='error-box'>❌ Run
|
| 1595 |
cluster_summary, gr.update()
|
| 1596 |
)
|
| 1597 |
try:
|
| 1598 |
_ensure_output_dir()
|
| 1599 |
-
|
| 1600 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1601 |
cluster_df=cluster_df,
|
| 1602 |
-
cluster_summary_df=cluster_summary.copy() if cluster_summary is not None
|
| 1603 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1604 |
)
|
| 1605 |
progress(1.0, desc="Done!")
|
| 1606 |
saved = _safe_save_csv(labeled, "cluster_labels.csv")
|
| 1607 |
return (
|
| 1608 |
-
"<div class='success-box'>✅ Clusters labeled by
|
| 1609 |
labeled,
|
| 1610 |
gr.update(value=saved),
|
| 1611 |
)
|
|
@@ -1619,9 +1658,9 @@ with gr.Blocks(
|
|
| 1619 |
fn=handle_run_dbscan,
|
| 1620 |
inputs=[
|
| 1621 |
state_df, state_cluster_df, state_cluster_summary,
|
| 1622 |
-
|
| 1623 |
-
|
| 1624 |
-
|
| 1625 |
],
|
| 1626 |
outputs=[
|
| 1627 |
dbscan_status,
|
|
@@ -1653,9 +1692,10 @@ with gr.Blocks(
|
|
| 1653 |
with gr.Tab("🧠 Agentic Council"):
|
| 1654 |
gr.Markdown("## Phase 6.5: Dual-Model Research Council")
|
| 1655 |
gr.Markdown(
|
| 1656 |
-
"
|
| 1657 |
"- **Mistral** (Panel A) — pragmatic applied IS perspective\n"
|
| 1658 |
-
"- **Gemini** (Panel B) — broad technology futures perspective\n
|
|
|
|
| 1659 |
"API keys are loaded automatically from HuggingFace Secrets "
|
| 1660 |
"(`MISTRAL_API_KEY`, `GEMINI_API_KEY`). "
|
| 1661 |
"Configure them in your Space under **Settings → Variables and Secrets**."
|
|
@@ -1664,8 +1704,9 @@ with gr.Blocks(
|
|
| 1664 |
# Key-status indicator — shows which secrets are present at load time
|
| 1665 |
_key_lines = ["**🔑 Secret Status (loaded at startup):**"]
|
| 1666 |
for _label, _val in [
|
| 1667 |
-
("MISTRAL_API_KEY",
|
| 1668 |
-
("GEMINI_API_KEY",
|
|
|
|
| 1669 |
]:
|
| 1670 |
_icon = "✅ present" if _val else "❌ missing"
|
| 1671 |
_key_lines.append(f"- `{_label}`: {_icon}")
|
|
@@ -1691,6 +1732,14 @@ with gr.Blocks(
|
|
| 1691 |
interactive=False,
|
| 1692 |
show_label=True,
|
| 1693 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1694 |
|
| 1695 |
dl_council = gr.DownloadButton("⬇ council_report.json", value=None)
|
| 1696 |
|
|
@@ -1721,6 +1770,7 @@ with gr.Blocks(
|
|
| 1721 |
topic_df=topic_df,
|
| 1722 |
mistral_api_key=MISTRAL_API_KEY,
|
| 1723 |
gemini_api_key=GEMINI_API_KEY,
|
|
|
|
| 1724 |
)
|
| 1725 |
progress(0.9, desc="Saving report…")
|
| 1726 |
saved = _safe_save_json(result, "council_report.json")
|
|
@@ -1731,18 +1781,19 @@ with gr.Blocks(
|
|
| 1731 |
status,
|
| 1732 |
result.get("mistral", ""),
|
| 1733 |
result.get("gemini", ""),
|
|
|
|
| 1734 |
gr.update(value=saved),
|
| 1735 |
)
|
| 1736 |
except Exception as e:
|
| 1737 |
return (
|
| 1738 |
f"<div class='error-box'>❌ Council failed: {e}</div>",
|
| 1739 |
-
"", "", gr.update()
|
| 1740 |
)
|
| 1741 |
|
| 1742 |
btn_run_council.click(
|
| 1743 |
fn=handle_run_council,
|
| 1744 |
inputs=[state_taxonomy_map, state_topic_df],
|
| 1745 |
-
outputs=[council_status, mistral_output, gemini_output, dl_council]
|
| 1746 |
)
|
| 1747 |
|
| 1748 |
# Auto-fill if council already ran (e.g. via full pipeline)
|
|
@@ -1750,9 +1801,10 @@ with gr.Blocks(
|
|
| 1750 |
fn=lambda cr: (
|
| 1751 |
cr.get("mistral", "") if cr else "",
|
| 1752 |
cr.get("gemini", "") if cr else "",
|
|
|
|
| 1753 |
),
|
| 1754 |
inputs=[state_council_result],
|
| 1755 |
-
outputs=[mistral_output, gemini_output]
|
| 1756 |
)
|
| 1757 |
|
| 1758 |
# ==================================================================
|
|
|
|
| 30 |
from agent import PAJAISResearchAgent, AnalysisConfig
|
| 31 |
from tools import (
|
| 32 |
load_journal_csv, validate_dataframe,
|
| 33 |
+
PAJAIS_THEMES, export_all_artifacts,
|
| 34 |
+
# Unified clustering pipeline (all now in tools.py)
|
| 35 |
+
build_title_abstract_column,
|
| 36 |
+
embed_with_specter2,
|
| 37 |
+
specter2_hdbscan_cluster_topics,
|
|
|
|
| 38 |
get_cluster_summary,
|
| 39 |
+
label_clusters_3llm,
|
| 40 |
run_agentic_council,
|
| 41 |
)
|
| 42 |
|
|
|
|
| 56 |
# ---------------------------------------------------------------------------
|
| 57 |
MISTRAL_API_KEY = os.environ.get("MISTRAL_API_KEY", "")
|
| 58 |
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
|
| 59 |
+
DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
|
| 60 |
|
| 61 |
# ---------------------------------------------------------------------------
|
| 62 |
# Custom CSS — Light, readable theme that works on HuggingFace Spaces
|
|
|
|
| 1461 |
# ==================================================================
|
| 1462 |
# TAB A — DBSCAN Clusters (Phase 2.5)
|
| 1463 |
# ==================================================================
|
| 1464 |
+
with gr.Tab("🔵 SPECTER2 Clusters"):
|
| 1465 |
+
gr.Markdown("## Phase 2.5: Semantic Clustering via SPECTER2 → UMAP → HDBSCAN")
|
| 1466 |
gr.Markdown(
|
| 1467 |
+
"Each paper is represented by **one 768-dim SPECTER2 vector** computed from its "
|
| 1468 |
+
"combined Title + Abstract column (DOI-keyed). "
|
| 1469 |
+
"UMAP reduces dimensions (cosine metric, 50D), then HDBSCAN clusters with an "
|
| 1470 |
+
"automatic parameter sweep to land in the **15–30 cluster** target range. "
|
| 1471 |
+
"Clusters with fewer than 5 or more than 100 papers are automatically merged/split. "
|
| 1472 |
+
"Intra-cluster cosine similarity is kept in the **0.50–0.60** band. "
|
| 1473 |
+
"The 3 most representative paper titles per cluster are sent to "
|
| 1474 |
+
"**Mistral + Gemini + HuggingFace** (all free) for labeling — majority vote wins."
|
| 1475 |
)
|
| 1476 |
|
| 1477 |
with gr.Accordion("⚙️ Clustering Parameters", open=False):
|
| 1478 |
with gr.Row():
|
| 1479 |
+
min_cs_slider = gr.Slider(
|
| 1480 |
+
2, 20, value=5, step=1,
|
| 1481 |
+
label="Min Cluster Size (papers)",
|
| 1482 |
+
info="Papers < this → merged into nearest cluster"
|
| 1483 |
)
|
| 1484 |
+
max_cs_slider = gr.Slider(
|
| 1485 |
+
20, 200, value=100, step=5,
|
| 1486 |
+
label="Max Cluster Size (papers)",
|
| 1487 |
+
info="Papers > this → cluster is split"
|
| 1488 |
)
|
| 1489 |
with gr.Row():
|
| 1490 |
+
target_min_slider = gr.Slider(
|
| 1491 |
+
5, 20, value=15, step=1,
|
| 1492 |
+
label="Target Min Clusters",
|
| 1493 |
+
info="HDBSCAN sweep lower bound"
|
| 1494 |
)
|
| 1495 |
+
target_max_slider = gr.Slider(
|
| 1496 |
+
15, 40, value=30, step=1,
|
| 1497 |
+
label="Target Max Clusters",
|
| 1498 |
+
info="HDBSCAN sweep upper bound"
|
| 1499 |
)
|
| 1500 |
with gr.Row():
|
| 1501 |
+
sim_low_slider = gr.Slider(
|
| 1502 |
+
0.30, 0.70, value=0.50, step=0.01,
|
| 1503 |
+
label="Min Cosine Similarity (cluster quality)",
|
| 1504 |
+
info="Clusters below this are dissolved to noise"
|
| 1505 |
)
|
| 1506 |
+
umap_neighbors_slider = gr.Slider(
|
| 1507 |
+
5, 50, value=15, step=1,
|
| 1508 |
+
label="UMAP n_neighbors",
|
| 1509 |
+
info="Controls local vs global structure"
|
| 1510 |
)
|
| 1511 |
|
| 1512 |
with gr.Row():
|
| 1513 |
+
btn_run_dbscan = gr.Button("▶ Run SPECTER2 → UMAP → HDBSCAN", variant="primary")
|
| 1514 |
+
btn_llm_label = gr.Button("🤖 Label Clusters (3 LLMs)", variant="secondary")
|
| 1515 |
|
| 1516 |
dbscan_status = gr.Markdown("*Run DBSCAN or use the full pipeline from Tab 1.*")
|
| 1517 |
|
|
|
|
| 1544 |
|
| 1545 |
def handle_run_dbscan(
|
| 1546 |
df, existing_cluster_df, existing_summary,
|
| 1547 |
+
min_cs, max_cs, target_min, target_max, sim_low, umap_n,
|
| 1548 |
progress=gr.Progress(track_tqdm=True)
|
| 1549 |
):
|
| 1550 |
if existing_cluster_df is not None and not existing_cluster_df.empty:
|
|
|
|
| 1553 |
saved_docs = _safe_save_csv(existing_cluster_df, "cluster_documents.csv")
|
| 1554 |
saved_sum = _safe_save_csv(summary, "cluster_summary.csv")
|
| 1555 |
return (
|
| 1556 |
+
"<div class='success-box'>✅ Loaded existing results.</div>",
|
| 1557 |
summary, existing_cluster_df, summary,
|
| 1558 |
fig_sz, fig_noise,
|
| 1559 |
gr.update(value=saved_docs), gr.update(value=saved_sum), gr.update(),
|
|
|
|
| 1569 |
|
| 1570 |
try:
|
| 1571 |
_ensure_output_dir()
|
| 1572 |
+
progress(0.05, desc="Building title+abstract column…")
|
| 1573 |
+
df_ta = build_title_abstract_column(df)
|
| 1574 |
+
|
| 1575 |
+
progress(0.15, desc="Generating SPECTER2 embeddings (may take 2-5 min)…")
|
| 1576 |
+
texts = df_ta['title_abstract'].tolist()
|
| 1577 |
+
embs = embed_with_specter2(texts, cache_dir='outputs/specter_cache')
|
| 1578 |
+
|
| 1579 |
+
progress(0.60, desc="UMAP + HDBSCAN clustering…")
|
| 1580 |
+
cdf = specter2_hdbscan_cluster_topics(
|
| 1581 |
+
df=df_ta,
|
| 1582 |
+
embeddings=embs,
|
| 1583 |
+
min_cluster_size=int(min_cs),
|
| 1584 |
+
max_cluster_size=int(max_cs),
|
| 1585 |
+
target_min_clusters=int(target_min),
|
| 1586 |
+
target_max_clusters=int(target_max),
|
| 1587 |
+
cosine_sim_low=float(sim_low),
|
| 1588 |
+
cosine_sim_high=float(sim_low) + 0.10,
|
| 1589 |
+
umap_n_neighbors=int(umap_n),
|
| 1590 |
)
|
| 1591 |
+
progress(0.85, desc="Summarising clusters…")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1592 |
summary = get_cluster_summary(cdf)
|
| 1593 |
progress(1.0, desc="Done!")
|
| 1594 |
|
|
|
|
| 1596 |
saved_docs = _safe_save_csv(cdf, "cluster_documents.csv")
|
| 1597 |
saved_sum = _safe_save_csv(summary, "cluster_summary.csv")
|
| 1598 |
|
| 1599 |
+
n_c = len(set(cdf['cluster_final']) - {-1})
|
| 1600 |
+
n_n = int(cdf['is_noise'].sum())
|
| 1601 |
return (
|
| 1602 |
f"<div class='success-box'>✅ {n_c} clusters found, {n_n} noise docs.</div>",
|
| 1603 |
summary, cdf, summary,
|
|
|
|
| 1615 |
def handle_llm_label(cluster_df, cluster_summary, progress=gr.Progress(track_tqdm=True)):
|
| 1616 |
if cluster_df is None or cluster_df.empty:
|
| 1617 |
return (
|
| 1618 |
+
"<div class='error-box'>❌ Run clustering first.</div>",
|
| 1619 |
cluster_summary, gr.update()
|
| 1620 |
)
|
| 1621 |
try:
|
| 1622 |
_ensure_output_dir()
|
| 1623 |
+
# Load cached embeddings if available
|
| 1624 |
+
import glob
|
| 1625 |
+
cache_files = glob.glob('outputs/specter_cache/*.npy')
|
| 1626 |
+
if not cache_files:
|
| 1627 |
+
return (
|
| 1628 |
+
"<div class='error-box'>❌ No SPECTER2 cache found. Run clustering tab first.</div>",
|
| 1629 |
+
cluster_summary, gr.update()
|
| 1630 |
+
)
|
| 1631 |
+
embs = np.load(sorted(cache_files)[-1]) # most recent cache
|
| 1632 |
+
|
| 1633 |
+
progress(0.2, desc="Sending clusters to LLMs…")
|
| 1634 |
+
labeled = label_clusters_3llm(
|
| 1635 |
cluster_df=cluster_df,
|
| 1636 |
+
cluster_summary_df=cluster_summary.copy() if cluster_summary is not None
|
| 1637 |
+
else get_cluster_summary(cluster_df),
|
| 1638 |
+
embeddings=embs,
|
| 1639 |
+
mistral_api_key=MISTRAL_API_KEY,
|
| 1640 |
+
gemini_api_key=GEMINI_API_KEY,
|
| 1641 |
+
deepseek_api_key=DEEPSEEK_API_KEY,
|
| 1642 |
+
max_clusters=30,
|
| 1643 |
)
|
| 1644 |
progress(1.0, desc="Done!")
|
| 1645 |
saved = _safe_save_csv(labeled, "cluster_labels.csv")
|
| 1646 |
return (
|
| 1647 |
+
"<div class='success-box'>✅ Clusters labeled by 3 LLMs (majority vote).</div>",
|
| 1648 |
labeled,
|
| 1649 |
gr.update(value=saved),
|
| 1650 |
)
|
|
|
|
| 1658 |
fn=handle_run_dbscan,
|
| 1659 |
inputs=[
|
| 1660 |
state_df, state_cluster_df, state_cluster_summary,
|
| 1661 |
+
min_cs_slider, max_cs_slider,
|
| 1662 |
+
target_min_slider, target_max_slider,
|
| 1663 |
+
sim_low_slider, umap_neighbors_slider,
|
| 1664 |
],
|
| 1665 |
outputs=[
|
| 1666 |
dbscan_status,
|
|
|
|
| 1692 |
with gr.Tab("🧠 Agentic Council"):
|
| 1693 |
gr.Markdown("## Phase 6.5: Dual-Model Research Council")
|
| 1694 |
gr.Markdown(
|
| 1695 |
+
"Three AI models independently assess the PAJAIS research gap findings:\n"
|
| 1696 |
"- **Mistral** (Panel A) — pragmatic applied IS perspective\n"
|
| 1697 |
+
"- **Gemini** (Panel B) — broad technology futures perspective\n"
|
| 1698 |
+
"- **DeepSeek** (Panel C) — deep analytical synthesis\n\n"
|
| 1699 |
"API keys are loaded automatically from HuggingFace Secrets "
|
| 1700 |
"(`MISTRAL_API_KEY`, `GEMINI_API_KEY`). "
|
| 1701 |
"Configure them in your Space under **Settings → Variables and Secrets**."
|
|
|
|
| 1704 |
# Key-status indicator — shows which secrets are present at load time
|
| 1705 |
_key_lines = ["**🔑 Secret Status (loaded at startup):**"]
|
| 1706 |
for _label, _val in [
|
| 1707 |
+
("MISTRAL_API_KEY", MISTRAL_API_KEY),
|
| 1708 |
+
("GEMINI_API_KEY", GEMINI_API_KEY),
|
| 1709 |
+
("DEEPSEEK_API_KEY", DEEPSEEK_API_KEY),
|
| 1710 |
]:
|
| 1711 |
_icon = "✅ present" if _val else "❌ missing"
|
| 1712 |
_key_lines.append(f"- `{_label}`: {_icon}")
|
|
|
|
| 1732 |
interactive=False,
|
| 1733 |
show_label=True,
|
| 1734 |
)
|
| 1735 |
+
with gr.Column():
|
| 1736 |
+
gr.Markdown("### 🟣 Panel C — DeepSeek")
|
| 1737 |
+
deepseek_output = gr.Textbox(
|
| 1738 |
+
label="DeepSeek Assessment",
|
| 1739 |
+
lines=18,
|
| 1740 |
+
interactive=False,
|
| 1741 |
+
show_label=True,
|
| 1742 |
+
)
|
| 1743 |
|
| 1744 |
dl_council = gr.DownloadButton("⬇ council_report.json", value=None)
|
| 1745 |
|
|
|
|
| 1770 |
topic_df=topic_df,
|
| 1771 |
mistral_api_key=MISTRAL_API_KEY,
|
| 1772 |
gemini_api_key=GEMINI_API_KEY,
|
| 1773 |
+
deepseek_api_key=DEEPSEEK_API_KEY,
|
| 1774 |
)
|
| 1775 |
progress(0.9, desc="Saving report…")
|
| 1776 |
saved = _safe_save_json(result, "council_report.json")
|
|
|
|
| 1781 |
status,
|
| 1782 |
result.get("mistral", ""),
|
| 1783 |
result.get("gemini", ""),
|
| 1784 |
+
result.get("deepseek", ""),
|
| 1785 |
gr.update(value=saved),
|
| 1786 |
)
|
| 1787 |
except Exception as e:
|
| 1788 |
return (
|
| 1789 |
f"<div class='error-box'>❌ Council failed: {e}</div>",
|
| 1790 |
+
"", "", "", gr.update()
|
| 1791 |
)
|
| 1792 |
|
| 1793 |
btn_run_council.click(
|
| 1794 |
fn=handle_run_council,
|
| 1795 |
inputs=[state_taxonomy_map, state_topic_df],
|
| 1796 |
+
outputs=[council_status, mistral_output, gemini_output, deepseek_output, dl_council]
|
| 1797 |
)
|
| 1798 |
|
| 1799 |
# Auto-fill if council already ran (e.g. via full pipeline)
|
|
|
|
| 1801 |
fn=lambda cr: (
|
| 1802 |
cr.get("mistral", "") if cr else "",
|
| 1803 |
cr.get("gemini", "") if cr else "",
|
| 1804 |
+
cr.get("deepseek", "") if cr else "",
|
| 1805 |
),
|
| 1806 |
inputs=[state_council_result],
|
| 1807 |
+
outputs=[mistral_output, gemini_output, deepseek_output]
|
| 1808 |
)
|
| 1809 |
|
| 1810 |
# ==================================================================
|