pr_ir_onepass / app.py
Corin1998's picture
Update app.py
0fe9399 verified
import os, tempfile, datetime
import gradio as gr
from dotenv import load_dotenv
from models import SeedInput, CompanyInfo
from llm_flow import generate_narrative
from exporters import get_jinja_env, render_all, make_zip_from_outputs, export_to_notion, export_to_google_docs
from ingest import fetch_url_text, extract_pdf_file
load_dotenv() # optional for local dev
TEMPLATES_DIR = os.path.join(os.path.dirname(__file__), "templates")
env = get_jinja_env(TEMPLATES_DIR)
DESCRIPTION = """
### 企業PR/IR素材ワンパス生成器
1つの元ネタ(テキスト/URL/PDF)から、プレス/LP/SNSスレッド/メディア向けピッチ、ESG/IRテンプレを一括生成します。
**LLM: OpenAI**(環境変数 `OPENAI_API_KEY` を設定してください)
"""
def _pdf_path_from_input(pdf_file):
"""gr.File(type='filepath') は str、場合により file-like を返すことがあるので両対応"""
if pdf_file is None:
return None
if isinstance(pdf_file, str):
return pdf_file
return getattr(pdf_file, "name", None) or getattr(pdf_file, "path", None)
def choose_source_text(source_text, url, pdf_file):
log = ""
text = (source_text or "").strip()
# 1) PDF優先
pdf_path = _pdf_path_from_input(pdf_file)
if pdf_path:
try:
text, lg = extract_pdf_file(pdf_path)
log = f"[PDF] {lg}"
return text, log
except Exception as e:
log = f"[PDF] Error: {e}"
# 2) URL
if url and url.strip():
try:
text, lg = fetch_url_text(url.strip())
log = f"[URL] {lg}"
return text, log
except Exception as e:
log = f"[URL] Error: {e}"
# 3) テキスト貼り付け
if text:
return text, "[TEXT] Using pasted text."
raise ValueError("No valid source provided. Paste text, set a URL, or upload a PDF.")
def run(company_name, brand_voice, region, language, tone, event_type, announce_date,
goals_text, source_text, url, pdf_file, include_esg, include_ir, do_notion, do_gdocs):
try:
goals = [g.strip() for g in (goals_text or "").split("\n") if g.strip()]
chosen_text, ingest_log = choose_source_text(source_text, url, pdf_file)
# 入力長の安全上限
MAX_CHARS = int(os.environ.get("MAX_SOURCE_CHARS", "20000"))
chosen_text = chosen_text[:MAX_CHARS]
seed = SeedInput(
event_type=event_type,
announce_date=announce_date or None,
source_text=chosen_text,
goals=goals,
tone=tone,
company=CompanyInfo(
company_name=company_name,
brand_voice=brand_voice or None,
region=region or None,
language=language
),
include_esg=include_esg,
include_ir=include_ir,
)
narrative = generate_narrative(seed)
outputs = render_all(env, narrative, seed.company)
ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
zip_path = os.path.join(tempfile.gettempdir(), f"onepass_outputs_{ts}.zip")
make_zip_from_outputs(outputs, zip_path)
notion_result = export_to_notion(outputs) if do_notion else None
gdocs_result = export_to_google_docs(outputs) if do_gdocs else None
press = outputs.get("press_release.md", "")
lp = outputs.get("lp_section.md", "")
sns = outputs.get("sns_thread.md", "")
pitch = outputs.get("media_pitch.md", "")
esg = outputs.get("esg_template.md", "")
ir = outputs.get("ir_template.md", "")
export_msg = ""
if ingest_log:
export_msg += f"{ingest_log}\n"
if notion_result:
export_msg += f"Notion: {notion_result}\n"
if gdocs_result:
export_msg += f"Google Docs: {gdocs_result}\n"
return press, lp, sns, pitch, esg, ir, zip_path, export_msg, chosen_text
except Exception as e:
# ここでスタックの要約を返して原因を掴みやすく
err = f"Error: {type(e).__name__}: {e}"
return (err,) + ("",)*5 + (None, err) + ("",)
with gr.Blocks(title="One-Pass PR/IR Generator") as demo:
gr.Markdown(DESCRIPTION)
with gr.Row():
with gr.Column():
company_name = gr.Textbox(label="会社名", placeholder="例)SFM株式会社", value="SFM株式会社")
brand_voice = gr.Textbox(label="ブランドボイス(任意)", placeholder="信頼性 / 革新性 / 先進性 など")
region = gr.Textbox(label="地域/市場(任意)", placeholder="JP / US / APAC など")
language = gr.Dropdown(["ja", "en"], value="ja", label="出力言語")
tone = gr.Dropdown(["formal", "friendly", "excited", "neutral"], value="formal", label="トーン")
event_type = gr.Dropdown(["product_update", "earnings_summary"], value="product_update", label="イベント種別")
announce_date = gr.Textbox(label="発表日(任意)", placeholder="YYYY-MM-DD")
with gr.Column():
gr.Markdown("#### 情報ソース(いずれか)")
source_text = gr.Textbox(label="テキスト貼り付け(任意)", placeholder="製品更新・決算要旨を貼り付け", lines=8)
url = gr.Textbox(label="URL(任意:WebページまたはPDF)", placeholder="https://...")
pdf_file = gr.File(label="PDFアップロード(任意)", file_types=[".pdf"], type="filepath")
goals_text = gr.Textbox(label="目的(KGI/KPIなど)", placeholder="1行につき1つ", lines=4)
include_esg = gr.Checkbox(label="ESGテンプレを出力", value=True)
include_ir = gr.Checkbox(label="IRテンプレを出力", value=True)
do_notion = gr.Checkbox(label="Notionへエクスポート", value=False)
do_gdocs = gr.Checkbox(label="Google Docsへエクスポート", value=False)
run_btn = gr.Button("一括生成", variant="primary")
with gr.Tab("プレスリリース"):
press_md = gr.Markdown("")
with gr.Tab("LPセクション"):
lp_md = gr.Markdown("")
with gr.Tab("SNSスレッド"):
sns_md = gr.Markdown("")
with gr.Tab("メディア向けピッチ"):
pitch_md = gr.Markdown("")
with gr.Tab("ESGテンプレ"):
esg_md = gr.Markdown("")
with gr.Tab("IRテンプレ"):
ir_md = gr.Markdown("")
zip_file = gr.File(label="生成結果ZIP", interactive=False)
ingest_status = gr.Textbox(label="インポートログ / Export結果", interactive=False)
preview_src = gr.Textbox(label="投入された元テキスト(プレビュー)", lines=10, interactive=False)
run_btn.click(
fn=run,
inputs=[company_name, brand_voice, region, language, tone, event_type, announce_date,
goals_text, source_text, url, pdf_file, include_esg, include_ir, do_notion, do_gdocs],
outputs=[press_md, lp_md, sns_md, pitch_md, esg_md, ir_md, zip_file, ingest_status, preview_src],
)
if __name__ == "__main__":
demo.launch()