# app.py
# ============================================================
# 类型:界面层(乙负责)
# 功能:Gradio 界面 + 研究方向全景研报格式化
# 用法:python app.py → 浏览器打开 http://127.0.0.1:7860
# 公网:设置 NGROK_AUTH_TOKEN 环境变量后自动生成公网链接
# ============================================================
import sys
import os
import gradio as gr
# 自动加载 .env 文件
_ENV_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
if os.path.isfile(_ENV_PATH):
with open(_ENV_PATH, "r", encoding="utf-8") as _f:
for _line in _f:
_line = _line.strip()
if _line and not _line.startswith("#") and "=" in _line:
_key, _val = _line.split("=", 1)
if _key not in os.environ:
os.environ[_key] = _val.strip().strip('"').strip("'")
try:
from run import run
except (ImportError, ModuleNotFoundError) as e:
print(f"[错误] 无法导入 run.py,请确认甲已交付所有 Workflow 模块。")
print(f" {e}")
sys.exit(1)
def diagnose(arxiv_url: str, progress: gr.Progress = gr.Progress()) -> str:
"""Gradio 回调:用户输入 arXiv URL → 返回 Markdown 研报"""
url = arxiv_url.strip()
if not url:
return "## ⚠️ 请输入 arXiv 论文链接\n\n示例: `https://arxiv.org/abs/2011.08785`"
try:
result = run(url, progress=progress)
except ValueError as e:
return f"## ❌ 输入格式错误\n\n{str(e)}\n\n请确认输入的是有效的 arXiv 链接。"
except RuntimeError as e:
return f"## ❌ API 请求失败\n\n{str(e)}\n\n请检查网络连接或稍后重试。"
except Exception as e:
return f"## ❌ 运行出错\n\n```\n{type(e).__name__}: {str(e)}\n```\n\n请将以上错误信息反馈给开发者。"
# 处理部分失败的情况
if result.get("error") and not result.get("repos"):
paper = result.get("paper", {})
direction = result.get("direction", {})
title = paper.get("title", "未知") if paper else "未知"
md = f"""# ResearchRadar 研究方向全景研报
---
## 论文信息
**{title}**
---
## ⚠️ 未能完成完整评估
**原因**: {result['error']}
"""
if direction:
md += f"""## 研究方向解析(部分结果)
**子领域**: {direction.get('subfield', 'N/A')}
**趋势**: {direction.get('subfield_trend', 'N/A')}
"""
md += """
---
> 该论文方向可能太新或太冷门,暂无高质量开源实现。这恰好帮助你判断——在这个方向开展研究需要从头实现。
"""
return md
return format_report(result)
def format_report(result: dict) -> str:
"""将 run() 的结果格式化为"研究方向全景研报" Markdown。
研报结构:
1. 论文信息
2. 研究方向全景(子领域 + 趋势 + 方法族谱系 + 开源覆盖度)
3. 方法族开源覆盖度(哪些已有实现、哪些是空白)
4. 开源实现评估排名(六维度评分明细)
5. 对比实验推荐总结(按就绪度分组)
"""
paper = result.get("paper", {})
direction = result.get("direction", {})
repos = result.get("repos", [])
# ===== 第一部分:论文信息 =====
arxiv_id = paper.get("arxiv_id", "")
arxiv_url = f"https://arxiv.org/abs/{arxiv_id}" if arxiv_id else ""
md = "# ResearchRadar 研究方向全景研报\n\n---\n\n## 一、论文信息\n\n"
md += "| 项目 | 内容 |\n|------|------|\n"
md += f"| **标题** | {paper.get('title', 'N/A')} |\n"
if arxiv_url:
md += f"| **arXiv** | [{arxiv_id}]({arxiv_url}) |\n"
else:
md += f"| **arXiv ID** | {arxiv_id or 'N/A'} |\n"
md += f"| **作者** | {', '.join(paper.get('authors', ['N/A'])[:5])}"
if len(paper.get("authors", [])) > 5:
md += f" 等 {len(paper.get('authors', []))} 人"
md += " |\n"
published = paper.get("published", "")
if published:
md += f"| **发表时间** | {published[:10]} |\n"
categories = paper.get("categories", [])
if categories:
md += f"| **分类** | {', '.join(categories)} |\n"
# ===== 论文自身代码评估 =====
own_repo = None
other_repos = []
for r in repos:
if r.get("method_family") == "本文代码":
own_repo = r
else:
other_repos.append(r)
if own_repo is not None:
ev = own_repo.get("evaluation", {})
md += "\n---\n\n## 二、本文代码复现评估\n\n"
md += "> 该论文是否提供了官方代码?代码质量如何?能否直接复现论文实验结果?\n\n"
md += f"| 仓库 | Stars | 综合评分 | 可复现性 | 对比实验适配度 |\n"
md += f"|------|:----:|:----:|:----:|:----:|\n"
verdict_icon = {"reproducible": "✅", "partially": "⚠️", "not_reproducible": "❌", "error": "💥"}.get(ev.get("verdict", ""), "❓")
readiness_icon = {"ready": "🟢", "partial": "🟡", "not_ready": "🔴"}.get(ev.get("benchmark_readiness", ""), "⚪")
md += f"| [{own_repo['full_name']}]({own_repo.get('html_url', '#')}) | {own_repo.get('stars', 0)} | **{ev.get('overall_score', 'N/A')}/100** | {verdict_icon} | {readiness_icon} |\n\n"
md += f"### 📋 详细评估\n\n{ev.get('reasoning', 'N/A')}\n\n"
risks = ev.get("risks", [])
if risks:
md += "### ⚠️ 风险点\n\n"
for r_ in risks:
md += f"- {r_}\n"
md += "\n"
md += f"### 💡 使用建议\n\n{ev.get('suggested_use', 'N/A')}\n\n"
else:
md += "\n---\n\n## 二、本文代码复现评估\n\n"
md += "> 🔴 **未找到该论文的官方开源代码。**\n>\n"
md += "> 这意味着复现该论文的实验结果需要从头实现,难度较高。建议在对比实验中使用下面列出的同一方法族的开源实现作为替代。\n\n"
# ===== 第三部分:研究方向全景 =====
md += "\n---\n\n## 三、研究方向全景\n\n"
md += f"### 🎯 子领域定位\n\n**{direction.get('subfield', '未知')}**\n\n"
trend = direction.get('subfield_trend', '暂无分析')
md += f"### 📈 技术趋势与前沿动态\n\n{trend}\n\n"
md += "### 🏗️ 方法族谱系\n\n"
md += "> 以下方法族基于 GitHub 上该领域实际开源仓库的分析归纳,反映了当前学术界的组织结构和开源生态。\n\n"
families = direction.get("method_families", [])
for i, mf in enumerate(families):
family_name = mf.get("family_name", f"方法族 {i+1}")
desc = mf.get("description", "暂无详细描述")
rep = mf.get("representative_work", "暂无代表工作")
matched = mf.get("matched_repos", [])
queries = mf.get("search_queries", [])
md += f"#### {i+1}. {family_name}\n\n"
md += f"**核心特点**: {desc}\n\n"
md += f"**代表工作**: {rep}\n\n"
if matched:
md += f"**开源覆盖**: 🟢 {len(matched)} 个仓库 — {', '.join(matched)}\n\n"
else:
md += "**开源覆盖**: 🔴 暂无开源实现(可能是研究空白)\n\n"
if queries:
md += f"**精准搜索词**: {', '.join(queries)}\n\n"
if not families:
md += "| - | 未能识别出明确的方法族 | - | - |\n"
md += "\n"
# ===== 第四部分:仓库评估排名 =====
md += "\n---\n\n## 四、开源实现评估排名\n\n"
md += "> 评分逻辑:可复现性(80分)评估代码能否被他人成功跑通;对比实验适配度(20分)评估该仓库能否直接用于论文 Experiment section。\n\n"
if not other_repos and not own_repo:
md += "**未找到相关开源仓库。**\n\n"
return md
if not other_repos:
md += "**未找到其他开源仓库(仅找到论文自身代码)。**\n\n"
for i, repo in enumerate(other_repos):
full_name = repo.get("full_name", f"仓库 {i+1}")
html_url = repo.get("html_url", "")
ev = repo.get("evaluation", {})
mf = repo.get("method_family", "")
verdict_icon = {
"reproducible": "✅", "partially": "⚠️",
"not_reproducible": "❌", "error": "💥",
}.get(ev.get("verdict", ""), "❓")
readiness_icon = {
"ready": "🟢", "partial": "🟡", "not_ready": "🔴",
}.get(ev.get("benchmark_readiness", ""), "⚪")
family_tag = f" `[{mf}]`" if mf else ""
md += f"### {i+1}. [{full_name}]({html_url}){family_tag}\n\n"
repro_score = ev.get("reproducibility_score", ev.get("overall_score", "N/A"))
bench_score = ev.get("benchmark_fitness_score", ev.get("benchmark_score", "N/A"))
overall = ev.get("overall_score", "N/A")
# 评分概要
md += f"| 指标 | 评分 |\n"
md += f"|------|:----:|\n"
md += f"| {verdict_icon} 可复现性 | **{repro_score}/80** |\n"
md += f"| {readiness_icon} 对比实验适配度 | **{bench_score}/20** |\n"
md += f"| 📊 综合评分 | **{overall}/100** |\n\n"
# 详细分析(核心内容,放在最显眼位置)
reasoning = ev.get("reasoning", "N/A")
md += f"### 📋 详细分析\n\n{reasoning}\n\n"
# 六维度评分表
md += "📊 六维度评分明细(点击展开)
\n\n"
md += "| 维度 | 得分 | 满分 | 评估标准 |\n"
md += "|------|:----:|:----:|----------|\n"
md += f"| 环境配置 | {_fmt(ev.get('env_score'))} | 15 | 依赖文件完整性 |\n"
md += f"| 文档质量 | {_fmt(ev.get('doc_score'))} | 20 | 安装/训练/数据说明 |\n"
md += f"| 代码可用性 | {_fmt(ev.get('code_score'))} | 20 | 训练+推理脚本 |\n"
md += f"| 社区活跃度 | {_fmt(ev.get('community_score'))} | 10 | Star + 更新频率 |\n"
md += f"| 依赖健康度 | {_fmt(ev.get('dep_score'))} | 15 | 依赖兼容性 |\n"
md += f"| **对比实验** | **{_fmt(ev.get('benchmark_score'))}** | **20** | **benchmark + 预训练权重** |\n"
md += "\n
输入一篇工科论文的 arXiv 链接,系统自动完成:
① 摸清方向 — 该领域有哪些方法族?各自什么特点?当前趋势是什么?
② 找到代码 — GitHub 上哪些开源仓库实现了这些方法?覆盖度如何?
③ 评估可用性 — 每个仓库能不能跑通?能不能直接用于论文对比实验?