Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -2,9 +2,10 @@ import os
|
|
| 2 |
import json
|
| 3 |
import gradio as gr
|
| 4 |
from fastapi import FastAPI
|
|
|
|
| 5 |
|
| 6 |
|
| 7 |
-
# ---- 遅延インポート
|
| 8 |
def _lazy_imports_for_main():
|
| 9 |
from modules.utils import ensure_dirs # noqa: F401
|
| 10 |
from modules.rag_indexer import index_files_and_urls
|
|
@@ -14,6 +15,36 @@ def _lazy_imports_for_main():
|
|
| 14 |
return index_files_and_urls, run_full_workflow
|
| 15 |
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
# ---- UI コールバック ----
|
| 18 |
def ui_company_score_and_proposal(
|
| 19 |
company_name,
|
|
@@ -21,32 +52,33 @@ def ui_company_score_and_proposal(
|
|
| 21 |
lead_email,
|
| 22 |
objective,
|
| 23 |
urls_text,
|
| 24 |
-
files, # gr.File(
|
| 25 |
):
|
| 26 |
"""
|
| 27 |
戻り値順:
|
| 28 |
score_json(str), contexts_text(str), proposal_text(str),
|
| 29 |
-
|
| 30 |
"""
|
| 31 |
index_files_and_urls, run_full_workflow = _lazy_imports_for_main()
|
| 32 |
|
| 33 |
-
# URL & ファイル
|
| 34 |
urls = [u.strip() for u in (urls_text or "").splitlines() if u.strip()]
|
| 35 |
file_paths = []
|
| 36 |
if files:
|
|
|
|
| 37 |
if isinstance(files, list):
|
| 38 |
-
file_paths = [getattr(f, "name", None) or f for f in files if f]
|
| 39 |
else:
|
| 40 |
-
file_paths = [getattr(files, "name", None) or files]
|
| 41 |
file_paths = [p for p in file_paths if p]
|
| 42 |
|
| 43 |
-
# インデックス(失敗しても
|
| 44 |
try:
|
| 45 |
index_report = index_files_and_urls(file_paths=file_paths, urls=urls)
|
| 46 |
except Exception as e:
|
| 47 |
index_report = f"Index error:\n\n{e}"
|
| 48 |
|
| 49 |
-
# メイン処理
|
| 50 |
result = run_full_workflow(
|
| 51 |
company_name=company_name or "(未入力)",
|
| 52 |
company_website=company_website or "",
|
|
@@ -60,24 +92,26 @@ def ui_company_score_and_proposal(
|
|
| 60 |
proposal_text = result.get("proposal_markdown", "")
|
| 61 |
next_actions = result.get("next_actions", "")
|
| 62 |
|
| 63 |
-
#
|
| 64 |
docx_path = result.get("exports", {}).get("docx_path", "")
|
| 65 |
pptx_path = result.get("exports", {}).get("pptx_path", "")
|
|
|
|
|
|
|
| 66 |
|
| 67 |
return (
|
| 68 |
score_json,
|
| 69 |
contexts_text,
|
| 70 |
proposal_text,
|
| 71 |
-
|
| 72 |
-
|
| 73 |
next_actions,
|
| 74 |
index_report,
|
| 75 |
)
|
| 76 |
|
| 77 |
|
| 78 |
-
# ---- Gradio UI ----
|
| 79 |
-
with gr.Blocks(title="営業自動化 Agent Studio", theme=gr.themes.Soft()) as demo:
|
| 80 |
-
gr.Markdown("## 営業自動化 Agent Studio(Space内完結・ダウンロード
|
| 81 |
|
| 82 |
with gr.Row():
|
| 83 |
with gr.Column():
|
|
@@ -91,36 +125,37 @@ with gr.Blocks(title="営業自動化 Agent Studio", theme=gr.themes.Soft()) as
|
|
| 91 |
lines=4,
|
| 92 |
placeholder="https://example.com\nhttps://example.com/blog/post1",
|
| 93 |
)
|
| 94 |
-
#
|
| 95 |
-
|
| 96 |
|
| 97 |
run_btn = gr.Button("ワークフロー実行", variant="primary")
|
| 98 |
|
| 99 |
with gr.Column():
|
| 100 |
-
# Code/Markdown ではなく Textbox に統一してスキーマ安定化
|
| 101 |
score_json = gr.Textbox(label="✅ 企業スコア(JSON)", lines=10, interactive=False)
|
| 102 |
contexts_text = gr.Textbox(label="🧠 抽出コンテキスト(上位)", lines=8, interactive=False)
|
| 103 |
proposal_text = gr.Textbox(label="✍️ 提案ドラフト(Markdownテキスト)", lines=16)
|
| 104 |
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
pptx_file = gr.File(label="PPTX ダウンロード")
|
| 109 |
|
| 110 |
next_actions = gr.Textbox(label="🤖 次アクション提案", lines=4, interactive=False)
|
| 111 |
index_report = gr.Textbox(label="🧩 インデックス更新ログ", lines=4, interactive=False)
|
| 112 |
|
| 113 |
run_btn.click(
|
| 114 |
fn=ui_company_score_and_proposal,
|
| 115 |
-
inputs=[company_name, company_website, lead_email, objective, urls_text,
|
| 116 |
-
outputs=[score_json, contexts_text, proposal_text,
|
| 117 |
)
|
| 118 |
|
| 119 |
-
|
|
|
|
| 120 |
app = FastAPI()
|
|
|
|
|
|
|
|
|
|
| 121 |
app = gr.mount_gradio_app(app, demo.queue(), path="/")
|
| 122 |
|
| 123 |
if __name__ == "__main__":
|
| 124 |
import uvicorn
|
| 125 |
-
|
| 126 |
uvicorn.run(app, host="0.0.0.0", port=int(os.getenv("PORT", "7860")))
|
|
|
|
| 2 |
import json
|
| 3 |
import gradio as gr
|
| 4 |
from fastapi import FastAPI
|
| 5 |
+
from starlette.staticfiles import StaticFiles
|
| 6 |
|
| 7 |
|
| 8 |
+
# ---- 遅延インポート ----
|
| 9 |
def _lazy_imports_for_main():
|
| 10 |
from modules.utils import ensure_dirs # noqa: F401
|
| 11 |
from modules.rag_indexer import index_files_and_urls
|
|
|
|
| 15 |
return index_files_and_urls, run_full_workflow
|
| 16 |
|
| 17 |
|
| 18 |
+
# ---- ユーティリティ ----
|
| 19 |
+
EXPORT_DIR = "/tmp/agent_studio/exports"
|
| 20 |
+
os.makedirs(EXPORT_DIR, exist_ok=True) # 静的配信用に事前作成
|
| 21 |
+
|
| 22 |
+
def _link_or_note(path_str):
|
| 23 |
+
"""
|
| 24 |
+
生成されたファイルパスから /files の相対URLを返す。
|
| 25 |
+
存在しなければ理由を文字列で返す。
|
| 26 |
+
"""
|
| 27 |
+
if not path_str:
|
| 28 |
+
return "(まだ生成されていません)"
|
| 29 |
+
fname = os.path.basename(path_str)
|
| 30 |
+
full = os.path.join(EXPORT_DIR, fname)
|
| 31 |
+
if os.path.exists(full):
|
| 32 |
+
# Hugging Face Space 上では相対リンクでOK
|
| 33 |
+
return f"/files/{fname}"
|
| 34 |
+
# modules 側が別ディレクトリで作った場合もあるので存在チェック
|
| 35 |
+
if os.path.exists(path_str):
|
| 36 |
+
# もし別パスにあるなら公開用にコピーしてリンク化
|
| 37 |
+
try:
|
| 38 |
+
import shutil
|
| 39 |
+
dst = os.path.join(EXPORT_DIR, os.path.basename(path_str))
|
| 40 |
+
if path_str != dst:
|
| 41 |
+
shutil.copy2(path_str, dst)
|
| 42 |
+
return f"/files/{os.path.basename(path_str)}"
|
| 43 |
+
except Exception as e:
|
| 44 |
+
return f"(生成済みだが公開に失敗: {e})"
|
| 45 |
+
return "(パスが無効です)"
|
| 46 |
+
|
| 47 |
+
|
| 48 |
# ---- UI コールバック ----
|
| 49 |
def ui_company_score_and_proposal(
|
| 50 |
company_name,
|
|
|
|
| 52 |
lead_email,
|
| 53 |
objective,
|
| 54 |
urls_text,
|
| 55 |
+
files, # gr.File(multiple=True) は使わないが、値は受ける
|
| 56 |
):
|
| 57 |
"""
|
| 58 |
戻り値順:
|
| 59 |
score_json(str), contexts_text(str), proposal_text(str),
|
| 60 |
+
docx_link(str), pptx_link(str), next_actions(str), index_report(str)
|
| 61 |
"""
|
| 62 |
index_files_and_urls, run_full_workflow = _lazy_imports_for_main()
|
| 63 |
|
| 64 |
+
# URL & ファイルパス抽出(SpaceのUI都合で来ることがあるため防御的に扱う)
|
| 65 |
urls = [u.strip() for u in (urls_text or "").splitlines() if u.strip()]
|
| 66 |
file_paths = []
|
| 67 |
if files:
|
| 68 |
+
# ここには通常来ないが、互換のため残す(path or tempfileオブジェクト)
|
| 69 |
if isinstance(files, list):
|
| 70 |
+
file_paths = [getattr(f, "name", None) or str(f) for f in files if f]
|
| 71 |
else:
|
| 72 |
+
file_paths = [getattr(files, "name", None) or str(files)]
|
| 73 |
file_paths = [p for p in file_paths if p]
|
| 74 |
|
| 75 |
+
# インデックス更新(失敗しても継続)
|
| 76 |
try:
|
| 77 |
index_report = index_files_and_urls(file_paths=file_paths, urls=urls)
|
| 78 |
except Exception as e:
|
| 79 |
index_report = f"Index error:\n\n{e}"
|
| 80 |
|
| 81 |
+
# メイン処理(提案ドラフトなど)
|
| 82 |
result = run_full_workflow(
|
| 83 |
company_name=company_name or "(未入力)",
|
| 84 |
company_website=company_website or "",
|
|
|
|
| 92 |
proposal_text = result.get("proposal_markdown", "")
|
| 93 |
next_actions = result.get("next_actions", "")
|
| 94 |
|
| 95 |
+
# 生成物を /files で配信できる相対リンクに変換(テキストで返す)
|
| 96 |
docx_path = result.get("exports", {}).get("docx_path", "")
|
| 97 |
pptx_path = result.get("exports", {}).get("pptx_path", "")
|
| 98 |
+
docx_link = _link_or_note(docx_path)
|
| 99 |
+
pptx_link = _link_or_note(pptx_path)
|
| 100 |
|
| 101 |
return (
|
| 102 |
score_json,
|
| 103 |
contexts_text,
|
| 104 |
proposal_text,
|
| 105 |
+
docx_link,
|
| 106 |
+
pptx_link,
|
| 107 |
next_actions,
|
| 108 |
index_report,
|
| 109 |
)
|
| 110 |
|
| 111 |
|
| 112 |
+
# ---- Gradio UI(すべて Textbox / Button のみ)----
|
| 113 |
+
with gr.Blocks(title="営業自動化 Agent Studio", analytics_enabled=False, theme=gr.themes.Soft()) as demo:
|
| 114 |
+
gr.Markdown("## 営業自動化 Agent Studio(Space内完結・リンクでダウンロード)")
|
| 115 |
|
| 116 |
with gr.Row():
|
| 117 |
with gr.Column():
|
|
|
|
| 125 |
lines=4,
|
| 126 |
placeholder="https://example.com\nhttps://example.com/blog/post1",
|
| 127 |
)
|
| 128 |
+
# NOTICE: ファイル型コンポーネントはスキーマ問題回避のため配置しない
|
| 129 |
+
dummy_files = gr.Textbox(visible=False) # 互換のためダミー
|
| 130 |
|
| 131 |
run_btn = gr.Button("ワークフロー実行", variant="primary")
|
| 132 |
|
| 133 |
with gr.Column():
|
|
|
|
| 134 |
score_json = gr.Textbox(label="✅ 企業スコア(JSON)", lines=10, interactive=False)
|
| 135 |
contexts_text = gr.Textbox(label="🧠 抽出コンテキスト(上位)", lines=8, interactive=False)
|
| 136 |
proposal_text = gr.Textbox(label="✍️ 提案ドラフト(Markdownテキスト)", lines=16)
|
| 137 |
|
| 138 |
+
# ここはテキストでリンクを表示(/files/xxx)
|
| 139 |
+
docx_link = gr.Textbox(label="📎 DOCX ダウンロードリンク", interactive=False)
|
| 140 |
+
pptx_link = gr.Textbox(label="📎 PPTX ダウンロードリンク", interactive=False)
|
|
|
|
| 141 |
|
| 142 |
next_actions = gr.Textbox(label="🤖 次アクション提案", lines=4, interactive=False)
|
| 143 |
index_report = gr.Textbox(label="🧩 インデックス更新ログ", lines=4, interactive=False)
|
| 144 |
|
| 145 |
run_btn.click(
|
| 146 |
fn=ui_company_score_and_proposal,
|
| 147 |
+
inputs=[company_name, company_website, lead_email, objective, urls_text, dummy_files],
|
| 148 |
+
outputs=[score_json, contexts_text, proposal_text, docx_link, pptx_link, next_actions, index_report],
|
| 149 |
)
|
| 150 |
|
| 151 |
+
|
| 152 |
+
# ---- FastAPI / 静的ファイル公開(/files -> /tmp/agent_studio/exports)----
|
| 153 |
app = FastAPI()
|
| 154 |
+
# 生成物を静的公開。相対リンク /files/xxx でダウンロード可能。
|
| 155 |
+
app.mount("/files", StaticFiles(directory=EXPORT_DIR), name="files")
|
| 156 |
+
# Gradio をルートにマウント
|
| 157 |
app = gr.mount_gradio_app(app, demo.queue(), path="/")
|
| 158 |
|
| 159 |
if __name__ == "__main__":
|
| 160 |
import uvicorn
|
|
|
|
| 161 |
uvicorn.run(app, host="0.0.0.0", port=int(os.getenv("PORT", "7860")))
|