Spaces:
Sleeping
Sleeping
| import os | |
| import json | |
| import gradio as gr | |
| from fastapi import FastAPI | |
| from starlette.staticfiles import StaticFiles | |
| # ---- 遅延インポート ---- | |
| def _lazy_imports_for_main(): | |
| from modules.utils import ensure_dirs # noqa: F401 | |
| from modules.rag_indexer import index_files_and_urls | |
| from modules.workflow import run_full_workflow | |
| ensure_dirs() | |
| return index_files_and_urls, run_full_workflow | |
| # ---- ユーティリティ ---- | |
| EXPORT_DIR = "/tmp/agent_studio/exports" | |
| os.makedirs(EXPORT_DIR, exist_ok=True) # 静的配信用に事前作成 | |
| def _link_or_note(path_str): | |
| """ | |
| 生成されたファイルパスから /files の相対URLを返す。 | |
| 存在しなければ理由を文字列で返す。 | |
| """ | |
| if not path_str: | |
| return "(まだ生成されていません)" | |
| fname = os.path.basename(path_str) | |
| full = os.path.join(EXPORT_DIR, fname) | |
| if os.path.exists(full): | |
| # Hugging Face Space 上では相対リンクでOK | |
| return f"/files/{fname}" | |
| # modules 側が別ディレクトリで作った場合もあるので存在チェック | |
| if os.path.exists(path_str): | |
| # もし別パスにあるなら公開用にコピーしてリンク化 | |
| try: | |
| import shutil | |
| dst = os.path.join(EXPORT_DIR, os.path.basename(path_str)) | |
| if path_str != dst: | |
| shutil.copy2(path_str, dst) | |
| return f"/files/{os.path.basename(path_str)}" | |
| except Exception as e: | |
| return f"(生成済みだが公開に失敗: {e})" | |
| return "(パスが無効です)" | |
| # ---- UI コールバック ---- | |
| def ui_company_score_and_proposal( | |
| company_name, | |
| company_website, | |
| lead_email, | |
| objective, | |
| urls_text, | |
| files, # gr.File(multiple=True) は使わないが、値は受ける | |
| ): | |
| """ | |
| 戻り値順: | |
| score_json(str), contexts_text(str), proposal_text(str), | |
| docx_link(str), pptx_link(str), next_actions(str), index_report(str) | |
| """ | |
| index_files_and_urls, run_full_workflow = _lazy_imports_for_main() | |
| # URL & ファイルパス抽出(SpaceのUI都合で来ることがあるため防御的に扱う) | |
| urls = [u.strip() for u in (urls_text or "").splitlines() if u.strip()] | |
| file_paths = [] | |
| if files: | |
| # ここには通常来ないが、互換のため残す(path or tempfileオブジェクト) | |
| if isinstance(files, list): | |
| file_paths = [getattr(f, "name", None) or str(f) for f in files if f] | |
| else: | |
| file_paths = [getattr(files, "name", None) or str(files)] | |
| file_paths = [p for p in file_paths if p] | |
| # インデックス更新(失敗しても継続) | |
| try: | |
| index_report = index_files_and_urls(file_paths=file_paths, urls=urls) | |
| except Exception as e: | |
| index_report = f"Index error:\n\n{e}" | |
| # メイン処理(提案ドラフトなど) | |
| result = run_full_workflow( | |
| company_name=company_name or "(未入力)", | |
| company_website=company_website or "", | |
| lead_email=lead_email or "", | |
| objective=objective or "", | |
| temperature=0.3, | |
| ) | |
| score_json = json.dumps(result.get("score", {}), ensure_ascii=False, indent=2) | |
| contexts_text = "\n\n---\n\n".join(result.get("top_contexts") or []) | |
| proposal_text = result.get("proposal_markdown", "") | |
| next_actions = result.get("next_actions", "") | |
| # 生成物を /files で配信できる相対リンクに変換(テキストで返す) | |
| docx_path = result.get("exports", {}).get("docx_path", "") | |
| pptx_path = result.get("exports", {}).get("pptx_path", "") | |
| docx_link = _link_or_note(docx_path) | |
| pptx_link = _link_or_note(pptx_path) | |
| return ( | |
| score_json, | |
| contexts_text, | |
| proposal_text, | |
| docx_link, | |
| pptx_link, | |
| next_actions, | |
| index_report, | |
| ) | |
| # ---- Gradio UI(すべて Textbox / Button のみ)---- | |
| with gr.Blocks(title="営業自動化 Agent Studio", analytics_enabled=False, theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("## 営業自動化 Agent Studio(Space内完結・リンクでダウンロード)") | |
| with gr.Row(): | |
| with gr.Column(): | |
| company_name = gr.Textbox(label="企業名", value="トヨタ自動車") | |
| company_website = gr.Textbox(label="企業Webサイト", value="https://toyota.jp/index.html") | |
| lead_email = gr.Textbox(label="リードのメールアドレス(任意)", value="") | |
| objective = gr.Textbox(label="提案目的(任意)", value="商談化のための初回提案") | |
| urls_text = gr.Textbox( | |
| label="クロールするURL(1行1件、任意)", | |
| lines=4, | |
| placeholder="https://example.com\nhttps://example.com/blog/post1", | |
| ) | |
| # NOTICE: ファイル型コンポーネントはスキーマ問題回避のため配置しない | |
| dummy_files = gr.Textbox(visible=False) # 互換のためダミー | |
| run_btn = gr.Button("ワークフロー実行", variant="primary") | |
| with gr.Column(): | |
| score_json = gr.Textbox(label="✅ 企業スコア(JSON)", lines=10, interactive=False) | |
| contexts_text = gr.Textbox(label="🧠 抽出コンテキスト(上位)", lines=8, interactive=False) | |
| proposal_text = gr.Textbox(label="✍️ 提案ドラフト(Markdownテキスト)", lines=16) | |
| # ここはテキストでリンクを表示(/files/xxx) | |
| docx_link = gr.Textbox(label="📎 DOCX ダウンロードリンク", interactive=False) | |
| pptx_link = gr.Textbox(label="📎 PPTX ダウンロードリンク", interactive=False) | |
| next_actions = gr.Textbox(label="🤖 次アクション提案", lines=4, interactive=False) | |
| index_report = gr.Textbox(label="🧩 インデックス更新ログ", lines=4, interactive=False) | |
| run_btn.click( | |
| fn=ui_company_score_and_proposal, | |
| inputs=[company_name, company_website, lead_email, objective, urls_text, dummy_files], | |
| outputs=[score_json, contexts_text, proposal_text, docx_link, pptx_link, next_actions, index_report], | |
| ) | |
| # ---- FastAPI / 静的ファイル公開(/files -> /tmp/agent_studio/exports)---- | |
| app = FastAPI() | |
| # 生成物を静的公開。相対リンク /files/xxx でダウンロード可能。 | |
| app.mount("/files", StaticFiles(directory=EXPORT_DIR), name="files") | |
| # Gradio をルートにマウント | |
| app = gr.mount_gradio_app(app, demo.queue(), path="/") | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=int(os.getenv("PORT", "7860"))) | |