Spaces:
Running
Running
| import os | |
| from datetime import datetime | |
| import tempfile | |
| import time | |
| import pandas as pd | |
| from io import StringIO | |
| import fitz | |
| import gradio as gr | |
| from google import genai | |
| from google.genai import types | |
| # ===================================== | |
| # Gemini | |
| # ===================================== | |
| API_KEY = os.getenv("GEMINI_API_KEY") | |
| if not API_KEY: | |
| raise ValueError("GEMINI_API_KEY が設定されていません") | |
| client = genai.Client(api_key=API_KEY) | |
| # ===================================== | |
| # Folder | |
| # ===================================== | |
| PDF_FOLDER = "papers" | |
| os.makedirs(PDF_FOLDER, exist_ok=True) | |
| # ===================================== | |
| # PDF・論文データ処理 | |
| # ===================================== | |
| def extract_pdf_text(filepath): | |
| text = "" | |
| try: | |
| pdf = fitz.open(filepath) | |
| for page in pdf: | |
| text += page.get_text() | |
| except Exception as e: | |
| print(e) | |
| return text | |
| def load_papers(): | |
| texts = [] | |
| files = [] | |
| for file in os.listdir(PDF_FOLDER): | |
| if file.lower().endswith(".pdf"): | |
| files.append(file) | |
| path = os.path.join(PDF_FOLDER, file) | |
| paper_text = extract_pdf_text(path) | |
| texts.append(f"\n\n### {file}\n{paper_text}") | |
| return files, "\n".join(texts) | |
| def show_paper_list(): | |
| files, _ = load_papers() | |
| if len(files) == 0: | |
| return "📚 登録論文なし (上の「論文を追加する」からPDFをドロップしてください)" | |
| return "【読込済み】\n" + "\n".join([f"・ {f}" for f in files]) | |
| def add_pdfs(files): | |
| if files is None: | |
| return "PDFが選択されていません" | |
| count = 0 | |
| for file in files: | |
| dst = os.path.join(PDF_FOLDER, os.path.basename(file.name)) | |
| with open(file.name, "rb") as src, open(dst, "wb") as out: | |
| out.write(src.read()) | |
| count += 1 | |
| return f"✅ {count}件の論文を新しく登録しました!" | |
| # ===================================== | |
| # 解析・プロンプトコア | |
| # ===================================== | |
| def ask_paper(question, mode): | |
| files, knowledge = load_papers() | |
| if len(files) == 0: | |
| return "⚠️ 論文が登録されていません。まずは最上部の管理パネルからPDFを登録してください。" | |
| if mode == "手法": | |
| instruction = "論文中の手法について、概要、特徴、利点、数式、初心者向け解説を整理してください。" | |
| elif mode == "実験": | |
| instruction = "論文中の実験内容について、データセット、評価指標、比較手法、結果、考察を抽出してください。" | |
| elif mode == "表形式": | |
| instruction = """論文からユーザーの指示に合う情報を抽出し、結果を必ずCSV形式のみで出力してください。 | |
| 列名は「項目,内容」とし、余計な挨拶、説明、コードブロックのバッククォート(```)などは一切出力しないでください。純粋なCSVの文字列のみを返してください。""" | |
| else: | |
| instruction = "論文内容の全体像を要約してください(研究背景、目的、提案手法、結果、今後の課題)。" | |
| prompt = f"{instruction}\n\n【論文情報】\n{knowledge[:120000]}\n\n【ユーザーからの指示・質問】\n{question}" | |
| for i in range(3): | |
| try: | |
| response = client.models.generate_content( | |
| model="gemini-2.5-flash", | |
| contents=prompt | |
| ) | |
| return response.text | |
| except Exception as e: | |
| err_msg = str(e) | |
| if "429" in err_msg or "503" in err_msg: | |
| if i < 2: | |
| time.sleep(3) | |
| continue | |
| return f"⚠️ APIが混雑しています。少し待ってから再実行してください。" | |
| return f"エラー: {err_msg}" | |
| # ===================================== | |
| # バックエンド制御 | |
| # ===================================== | |
| def run_text_analysis(question, mode): | |
| """テキスト系モード(手法、実験、要約)の解析処理""" | |
| if not question.strip(): | |
| return "⚠️ 質問を入力してください。" | |
| return ask_paper(question, mode) | |
| def run_table_analysis(question): | |
| """表形式モード専用の解析処理""" | |
| if not question.strip(): | |
| return pd.DataFrame({"エラー": ["⚠️ 指示を入力してください。"]}) | |
| result = ask_paper(question, "表形式") | |
| if "⚠️" in result or "エラー" in result: | |
| return pd.DataFrame({"エラー": [result]}) | |
| try: | |
| df = pd.read_csv(StringIO(result.strip())) | |
| return df | |
| except: | |
| return pd.DataFrame({"エラー": ["CSV表への自動変換に失敗しました。指示内容を具体的にするか、もう一度お試しください。"]}) | |
| def handle_download(mode, question, txt_answer, df_answer=None): | |
| if not question.strip(): | |
| return None | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| filename = f"paper_analysis_{mode}_{timestamp}.txt" | |
| temp_dir = tempfile.gettempdir() | |
| file_path = os.path.join(temp_dir, filename) | |
| now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| with open(file_path, "w", encoding="utf-8") as f: | |
| f.write("=" * 50 + "\n") | |
| f.write(f" 論文インサイト・ログ ({mode}分析)\n") | |
| f.write("=" * 50 + "\n\n") | |
| f.write(f"実行日時: {now}\n") | |
| f.write(f"入力質問: {question}\n\n") | |
| f.write("-" * 50 + "\n") | |
| if mode == "表形式" and df_answer is not None: | |
| f.write("【抽出データ】\n") | |
| f.write(df_answer.to_string(index=False)) | |
| else: | |
| f.write(f"【AI回答】\n{txt_answer}") | |
| f.write("\n") | |
| return file_path | |
| # ===================================== | |
| # UI 構築 | |
| # ===================================== | |
| with gr.Blocks(theme=gr.themes.Soft()) as app: | |
| gr.Markdown("## 🎓 PaperInsight Mini") | |
| gr.Markdown("論文から欲しい情報だけを即座に抜き出すコンパクトツール") | |
| # 1. 論文管理エリア | |
| with gr.Accordion("📚 文献ライブラリ管理(PDF登録・確認)", open=False): | |
| with gr.Row(): | |
| upload_files = gr.File(file_count="multiple", file_types=[".pdf"], label="論文を追加する") | |
| with gr.Column(): | |
| upload_btn = gr.Button("📤 ライブラリに登録", variant="secondary") | |
| upload_result = gr.Textbox(label="処理ステータス", placeholder="結果がここに表示されます", interactive=False) | |
| paper_list = gr.Textbox(value=show_paper_list(), label="現在読み込み済みの文献", lines=4, interactive=False) | |
| upload_btn.click(add_pdfs, inputs=upload_files, outputs=upload_result).then( | |
| show_paper_list, outputs=paper_list | |
| ) | |
| # 2. メイン解析エリア (タブによる完全独立化) | |
| with gr.Tabs(): | |
| # --- タブ1: 手法解析 --- | |
| with gr.Tab("🔬 手法解析"): | |
| gr.Markdown("*提案手法のアルゴリズム、理論、数式的な背景にフォーカスして詳しく解説します。*") | |
| q_method = gr.Textbox( | |
| label="📝 論文の手法に関する質問", | |
| placeholder="例:提案されているアルゴリズムや数式の意味は?", | |
| lines=2 | |
| ) | |
| btn_method = gr.Button("🚀 手法を解析する", variant="primary") | |
| ans_method = gr.Textbox(label="💡 AIの分析結果", lines=12, interactive=False) | |
| with gr.Row(): | |
| dl_btn_method = gr.Button("💾 この内容を保存") | |
| dl_file_method = gr.File(label="📥 ダウンロード", visible=True) | |
| btn_method.click( | |
| fn=lambda q: run_text_analysis(q, "手法"), | |
| inputs=q_method, | |
| outputs=ans_method | |
| ) | |
| dl_btn_method.click( | |
| fn=lambda q, a: handle_download("手法", q, a), | |
| inputs=[q_method, ans_method], | |
| outputs=dl_file_method | |
| ) | |
| # --- タブ2: 実験解析 --- | |
| with gr.Tab("📊 実験解析"): | |
| gr.Markdown("*実験環境、評価指標、データセットの規模、Baselineとの比較結果を深掘りします。*") | |
| q_exp = gr.Textbox( | |
| label="📝 論文の実験に関する質問", | |
| placeholder="例:使用されたデータセットについてまとめてください。", | |
| lines=2 | |
| ) | |
| btn_exp = gr.Button("🚀 実験を解析する", variant="primary") | |
| ans_exp = gr.Textbox(label="💡 AIの分析結果", lines=12, interactive=False) | |
| with gr.Row(): | |
| dl_btn_exp = gr.Button("💾 この内容を保存") | |
| dl_file_exp = gr.File(label="📥 ダウンロード", visible=True) | |
| btn_exp.click( | |
| fn=lambda q: run_text_analysis(q, "実験"), | |
| inputs=q_exp, | |
| outputs=ans_exp | |
| ) | |
| dl_btn_exp.click( | |
| fn=lambda q, a: handle_download("実験", q, a), | |
| inputs=[q_exp, ans_exp], | |
| outputs=dl_file_exp | |
| ) | |
| # --- タブ3: 全体要約 --- | |
| with gr.Tab("📝 全体要約"): | |
| gr.Markdown("*研究背景、目的、アプローチ、結果、今後の課題までを1つのプロットに要約します。*") | |
| q_summary = gr.Textbox( | |
| label="📝 要約の指示・フォーカスしたい点", | |
| placeholder="例:この論文の全体像(背景、目的、手法、結果、課題)を分かりやすく要約してください。", | |
| lines=2 | |
| ) | |
| btn_summary = gr.Button("🚀 要約を生成する", variant="primary") | |
| ans_summary = gr.Textbox(label="💡 AIの分析結果", lines=12, interactive=False) | |
| with gr.Row(): | |
| dl_btn_summary = gr.Button("💾 この内容を保存") | |
| dl_file_summary = gr.File(label="📥 ダウンロード", visible=True) | |
| btn_summary.click( | |
| fn=lambda q: run_text_analysis(q, "要約"), | |
| inputs=q_summary, | |
| outputs=ans_summary | |
| ) | |
| dl_btn_summary.click( | |
| fn=lambda q, a: handle_download("要約", q, a), | |
| inputs=[q_summary, ans_summary], | |
| outputs=dl_file_summary | |
| ) | |
| # --- タブ4: 表形式データ抽出 --- | |
| with gr.Tab("📈 表形式"): | |
| gr.Markdown("*指定した条件に沿って、AIが論文内の数値を抽出し、構造化されたデータテーブルを出力します。*") | |
| q_table = gr.Textbox( | |
| label="📝 テーブル化したい項目・指示", | |
| placeholder="例:主要な実験結果を、データセット、評価指標、精度の項目でテーブルにして。", | |
| lines=2 | |
| ) | |
| btn_table = gr.Button("🚀 表形式で抽出する", variant="primary") | |
| ans_table = gr.Dataframe(label="📊 抽出データ(データフレーム)") | |
| with gr.Row(): | |
| dl_btn_table = gr.Button("💾 この表を保存") | |
| dl_file_table = gr.File(label="📥 ダウンロード", visible=True) | |
| btn_table.click( | |
| fn=run_table_analysis, | |
| inputs=q_table, | |
| outputs=ans_table | |
| ) | |
| dl_btn_table.click( | |
| fn=lambda q, df: handle_download("表形式", q, "", df), | |
| inputs=[q_table, ans_table], | |
| outputs=dl_file_table | |
| ) | |
| if __name__ == "__main__": | |
| app.launch() |