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()