| | """ |
| | 审稿人推荐系统主应用 |
| | 基于AgentReview框架风格实现的Gradio界面 |
| | """ |
| |
|
| | import json |
| | import os |
| | import time |
| | from datetime import datetime |
| | from typing import List, Dict, Any |
| |
|
| | import gradio as gr |
| |
|
| | from reviewer_recommendation import ( |
| | PaperInfo, Reviewer, RecommendationRequest, RecommendationResponse, AppState, |
| | AcademicSearcher, DynamicAcademicSearcher, LLMRecommendationEngine, OpenAlexSearcher |
| | ) |
| |
|
| | |
| | |
| | openai_api_key = os.environ.get('OPENAI_API_KEY', 'sk-cnaekqCE8TTvhJrY8q4GljfzLiHHbgR5nlzwgc14FG8iH3uj') |
| | os.environ['OPENAI_API_KEY'] = openai_api_key |
| |
|
| | |
| | css = """ |
| | /* 调整列间距 */ |
| | .gradio-container .row { |
| | gap: 40px !important; /* 可以调整这个数值来改变间距 */ |
| | } |
| | |
| | /* 或者更精确地控制特定行的间距 */ |
| | .gradio-container .row > div { |
| | margin-right: 20px !important; /* 右侧边距 */ |
| | } |
| | |
| | .gradio-container .row > div:last-child { |
| | margin-right: 0 !important; /* 最后一列不需要右边距 */ |
| | } |
| | |
| | /* 对齐推荐结果标题和内容 */ |
| | .gradio-container .row > div:last-child h2 { |
| | margin-top: 0 !important; /* 移除标题顶部边距 */ |
| | } |
| | |
| | .gradio-container .row > div:last-child .html-content { |
| | margin-top: 0 !important; /* 移除内容顶部边距 */ |
| | } |
| | |
| | /* 隐藏标题下方的默认边框 */ |
| | .gradio-container h1 { |
| | border-bottom: none !important; |
| | margin-bottom: 20px !important; |
| | } |
| | |
| | /* 隐藏可能的默认分隔线 */ |
| | .gradio-container hr { |
| | display: none !important; |
| | } |
| | """ |
| |
|
| | |
| | app_state = AppState() |
| |
|
| |
|
| | def format_reviewers_output(reviewers: List[Dict[str, Any]]) -> str: |
| | """格式化审稿人输出""" |
| | if not reviewers: |
| | return "<p style='font-size: 14px;'>未找到合适的审稿人推荐,请尝试调整关键词或查询条件。</p>" |
| | |
| | output_lines = [] |
| | for i, reviewer in enumerate(reviewers, 1): |
| | |
| | if isinstance(reviewer, dict): |
| | name = reviewer.get('name', 'N/A') |
| | affiliation = reviewer.get('affiliation', 'N/A') |
| | email = reviewer.get('email', 'N/A') |
| | expertise_areas = reviewer.get('expertise_areas', []) |
| | reason = reviewer.get('reason', 'N/A') |
| | relevance_score = reviewer.get('relevance_score', 0) |
| | else: |
| | |
| | name = getattr(reviewer, 'name', 'N/A') |
| | affiliation = getattr(reviewer, 'affiliation', 'N/A') |
| | email = getattr(reviewer, 'email', 'N/A') |
| | expertise_areas = getattr(reviewer, 'expertise_areas', []) |
| | reason = getattr(reviewer, 'reason', 'N/A') |
| | relevance_score = getattr(reviewer, 'relevance_score', 0) |
| | |
| | output_lines.append(f"<h3 style='font-size: 16px; margin: 20px 0 10px 0;'>审稿人 {i}</h3>") |
| | output_lines.append(f"<p style='font-size: 14px; margin: 5px 0;'><strong>姓名</strong>: {name}</p>") |
| | output_lines.append(f"<p style='font-size: 14px; margin: 5px 0;'><strong>单位</strong>: {affiliation}</p>") |
| | output_lines.append(f"<p style='font-size: 14px; margin: 5px 0;'><strong>邮箱</strong>: {email}</p>") |
| | if expertise_areas: |
| | output_lines.append(f"<p style='font-size: 14px; margin: 5px 0;'><strong>专业领域</strong>: {', '.join(expertise_areas)}</p>") |
| | output_lines.append(f"<p style='font-size: 14px; margin: 5px 0;'><strong>推荐理由</strong>: {reason}</p>") |
| | |
| | try: |
| | score_display = f"{float(relevance_score):.2f}" |
| | except (ValueError, TypeError): |
| | score_display = "0.00" |
| | output_lines.append(f"<p style='font-size: 14px; margin: 5px 0;'><strong>匹配度</strong>: {score_display}</p>") |
| | output_lines.append("<hr style='margin: 15px 0;'>") |
| | |
| | return "".join(output_lines) |
| |
|
| |
|
| | def recommend_reviewers(paper_title: str, paper_abstract: str, paper_authors: str, paper_affiliations: str, reviewer_count) -> str: |
| | """推荐审稿人的主要函数""" |
| | try: |
| | |
| | app_state.is_processing = True |
| | app_state.last_error = None |
| | |
| | |
| | try: |
| | reviewer_count = int(reviewer_count) |
| | except (ValueError, TypeError): |
| | return "错误:推荐审稿人数量必须是有效的数字。" |
| | |
| | |
| | if not paper_title.strip() or not paper_abstract.strip(): |
| | return "错误:论文标题和摘要不能为空。" |
| | |
| | if reviewer_count < 1 or reviewer_count > 10: |
| | return "错误:推荐审稿人数量应在1-10之间。" |
| | |
| | |
| | authors = [author.strip() for author in paper_authors.split(',') if author.strip()] |
| | affiliations = [aff.strip() for aff in paper_affiliations.split(',') if aff.strip()] |
| | |
| | |
| | paper = PaperInfo( |
| | title=paper_title.strip(), |
| | abstract=paper_abstract.strip(), |
| | keywords=[], |
| | authors=authors, |
| | affiliations=affiliations |
| | ) |
| | |
| | |
| | request = RecommendationRequest( |
| | paper=paper, |
| | reviewer_count=reviewer_count |
| | ) |
| | |
| | app_state.current_request = request |
| | |
| | |
| | start_time = time.time() |
| | |
| | |
| | print("初始化推荐系统...") |
| | |
| | openalex_searcher = OpenAlexSearcher(limit=reviewer_count * 3) |
| | dynamic_searcher = DynamicAcademicSearcher(openalex_searcher=openalex_searcher) |
| | engine = LLMRecommendationEngine() |
| | |
| | |
| | print("开始双数据源检索候选文献...") |
| | |
| | years_after = 3 |
| | |
| | channel1_candidates, channel2_candidates, channel3_candidates = dynamic_searcher.search_with_dynamic_queries( |
| | paper=paper, |
| | num_reviewers=reviewer_count, |
| | years_after=years_after |
| | ) |
| | |
| | if not channel1_candidates and not channel2_candidates and not channel3_candidates: |
| | return "未找到相关候选文献,请尝试调整关键词或查询条件。" |
| | |
| | print(f"找到候选文献:{len(channel1_candidates)} 篇,开始分析...") |
| | |
| | |
| | found_reviewers = engine.analyze_candidates(paper, channel1_candidates, reviewer_count) |
| | |
| | |
| | search_time = time.time() - start_time |
| | |
| | |
| | response = RecommendationResponse( |
| | reviewers=found_reviewers[:reviewer_count], |
| | search_time=search_time, |
| | total_candidates=len(channel1_candidates) + len(channel2_candidates) + len(channel3_candidates), |
| | success=True |
| | ) |
| | |
| | app_state.current_response = response |
| | app_state.is_processing = False |
| | |
| | |
| | output = format_reviewers_output(response.reviewers) |
| | |
| | return output |
| | |
| | except Exception as e: |
| | app_state.is_processing = False |
| | app_state.last_error = str(e) |
| | error_msg = f"推荐过程中发生错误: {str(e)}" |
| | print(error_msg) |
| | return error_msg |
| |
|
| |
|
| | def clear_form(): |
| | """清空表单""" |
| | return "", "", "", "", "3" |
| |
|
| |
|
| | |
| | def create_interface(): |
| | """创建Gradio界面""" |
| | |
| | with gr.Blocks(css=css, title="审稿人推荐系统") as demo: |
| | |
| | |
| | gr.Markdown("# 审稿人推荐系统") |
| | |
| | with gr.Row(): |
| | with gr.Column(scale=1): |
| | |
| | gr.Markdown("## 论文信息") |
| | |
| | paper_title = gr.Textbox( |
| | lines=2, |
| | label="论文标题", |
| | placeholder="请输入论文标题..." |
| | ) |
| | |
| | paper_abstract = gr.Textbox( |
| | lines=8, |
| | label="论文摘要", |
| | placeholder="请输入论文摘要..." |
| | ) |
| | |
| | |
| | paper_authors = gr.Textbox( |
| | lines=2, |
| | label="作者姓名(用逗号分隔)", |
| | placeholder="必填" |
| | ) |
| | |
| | paper_affiliations = gr.Textbox( |
| | lines=2, |
| | label="作者所属单位(用逗号分隔)", |
| | placeholder="必填" |
| | ) |
| | |
| | reviewer_count = gr.Dropdown( |
| | choices=["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], |
| | value="3", |
| | label="推荐审稿人数量" |
| | ) |
| | |
| | with gr.Row(): |
| | recommend_btn = gr.Button("开始推荐", variant="primary") |
| | clear_btn = gr.Button("清空表单") |
| | |
| | with gr.Column(scale=1): |
| | |
| | gr.Markdown("## 推荐结果") |
| | |
| | output_text = gr.HTML( |
| | value="<p style='font-size: 14px; text-align: center; color: #666; margin-top: 0;'>请输入论文信息并点击'开始推荐'按钮</p>", |
| | label="推荐结果" |
| | ) |
| | |
| | |
| | recommend_btn.click( |
| | fn=recommend_reviewers, |
| | inputs=[paper_title, paper_abstract, paper_authors, paper_affiliations, reviewer_count], |
| | outputs=[output_text] |
| | ) |
| | |
| | clear_btn.click( |
| | fn=clear_form, |
| | outputs=[paper_title, paper_abstract, paper_authors, paper_affiliations, reviewer_count] |
| | ) |
| | |
| | return demo |
| |
|
| |
|
| | if __name__ == "__main__": |
| | |
| | demo = create_interface() |
| | demo.launch( |
| | server_name="0.0.0.0", |
| | |
| | debug=False, |
| | show_error=True, |
| | quiet=False |
| | ) |