File size: 6,733 Bytes
a996e11
 
cc571ea
dc31d2b
9364bd4
dc31d2b
a996e11
9364bd4
cc571ea
dc31d2b
cc571ea
 
dc31d2b
 
e8cc220
cc571ea
dc31d2b
9364bd4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dc31d2b
a996e11
dc31d2b
 
 
 
 
9364bd4
dc31d2b
a996e11
dc31d2b
e8cc220
9364bd4
a996e11
e8cc220
a996e11
9364bd4
cc571ea
dc31d2b
 
9364bd4
dc31d2b
9364bd4
dc31d2b
9364bd4
dc31d2b
5c349c2
9364bd4
cc571ea
727d601
538a15d
a996e11
 
9364bd4
e8cc220
a996e11
 
 
 
 
 
 
dc31d2b
a996e11
e8cc220
a996e11
 
9364bd4
dc31d2b
 
9364bd4
 
a996e11
 
 
 
e8cc220
9364bd4
 
a996e11
 
 
 
dc31d2b
9364bd4
 
 
a996e11
 
 
 
 
 
 
 
 
 
 
 
 
9364bd4
 
a996e11
 
 
 
31203de
e8cc220
 
a996e11
9364bd4
 
 
a996e11
e8cc220
 
a996e11
 
 
9364bd4
 
a996e11
 
9364bd4
 
ba67af2
9364bd4
 
 
ba67af2
 
a996e11
ba67af2
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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")))