Spaces:
Sleeping
Sleeping
| 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() | |