Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import os | |
| from dotenv import load_dotenv | |
| from google import genai | |
| load_dotenv(verbose=True) | |
| google_api_key = os.environ.get("GEMINI_API_KEY") | |
| client = genai.Client(api_key=google_api_key) | |
| template = """ | |
| <!DOCTYPE html> | |
| <html lang="ja"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Executive Report - Liquid Glass Edition</title> | |
| <style> | |
| :root { | |
| --glass-bg: rgba(255, 255, 255, 0.4); | |
| --glass-border: rgba(255, 255, 255, 0.6); | |
| --text-main: #2d3436; | |
| --accent-color: #6c5ce7; | |
| } | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| font-family: 'Noto Sans JP', sans-serif; | |
| color: var(--text-main); | |
| background-color: #f0f2f5; | |
| display: flex; | |
| justify-content: center; | |
| overflow-x: hidden; | |
| } | |
| /* 背景のアニメーションオーブ */ | |
| .bg-gradient { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(120deg, #e0c3fc 0%, #8ec5fc 100%); | |
| z-index: -2; | |
| } | |
| .bg-orb { | |
| position: fixed; | |
| border-radius: 50%; | |
| filter: blur(80px); | |
| z-index: -1; | |
| opacity: 0.5; | |
| } | |
| .orb-1 { | |
| width: 400px; | |
| height: 400px; | |
| background: #fab1a0; | |
| top: -100px; | |
| right: -100px; | |
| } | |
| .orb-2 { | |
| width: 600px; | |
| height: 600px; | |
| background: #81ecec; | |
| bottom: -200px; | |
| left: -200px; | |
| } | |
| /* 共通ガラススタイル */ | |
| .glass { | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(15px); | |
| -webkit-backdrop-filter: blur(15px); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 24px; | |
| box-shadow: 0 8px 32px rgba(31, 38, 135, 0.1); | |
| margin-bottom: 24px; | |
| } | |
| /* メインレイアウト */ | |
| .report-wrapper { | |
| width: 90%; | |
| max-width: 800px; | |
| padding: 60px 0; | |
| z-index: 1; | |
| } | |
| /* 各セクションの装飾 */ | |
| .report-header { | |
| padding: 40px; | |
| text-align: center; | |
| } | |
| .badge { | |
| display: inline-block; | |
| padding: 4px 12px; | |
| background: var(--accent-color); | |
| color: white; | |
| border-radius: 50px; | |
| font-size: 0.8rem; | |
| font-weight: bold; | |
| margin-bottom: 16px; | |
| } | |
| h1 { | |
| font-size: 2.5rem; | |
| margin: 0; | |
| line-height: 1.2; | |
| } | |
| .highlight { | |
| color: var(--accent-color); | |
| } | |
| .meta-info { | |
| margin-top: 20px; | |
| font-size: 0.9rem; | |
| opacity: 0.7; | |
| } | |
| .meta-info span { | |
| margin: 0 10px; | |
| } | |
| /* キーメッセージ */ | |
| .key-message { | |
| padding: 30px; | |
| border-left: 8px solid var(--accent-color); | |
| } | |
| .key-message h2 { | |
| margin-top: 0; | |
| font-size: 1.2rem; | |
| color: var(--accent-color); | |
| } | |
| /* 統計カード */ | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 20px; | |
| margin-bottom: 24px; | |
| } | |
| .stat-card { | |
| padding: 24px; | |
| text-align: center; | |
| } | |
| .stat-card .label { | |
| font-size: 0.85rem; | |
| display: block; | |
| margin-bottom: 8px; | |
| } | |
| .stat-card .value { | |
| font-size: 1.8rem; | |
| font-weight: bold; | |
| font-family: 'Montserrat'; | |
| } | |
| .stat-card .trend { | |
| font-size: 0.8rem; | |
| margin-left: 5px; | |
| font-weight: bold; | |
| } | |
| .trend.up { | |
| color: #00b894; | |
| } | |
| .trend.down { | |
| color: #d63031; | |
| } | |
| /* ボディテキスト */ | |
| .report-body { | |
| padding: 40px; | |
| line-height: 1.8; | |
| } | |
| ul { | |
| padding-left: 20px; | |
| } | |
| li { | |
| margin-bottom: 10px; | |
| } | |
| .report-footer { | |
| text-align: center; | |
| font-size: 0.8rem; | |
| opacity: 0.5; | |
| margin-top: 40px; | |
| } | |
| /* スマホ対応 */ | |
| @media (max-width: 600px) { | |
| h1 { | |
| font-size: 1.8rem; | |
| } | |
| .report-wrapper { | |
| padding: 20px 0; | |
| } | |
| } | |
| </style> | |
| <link | |
| href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@300;500;700&family=Montserrat:wght@600&display=swap" | |
| rel="stylesheet"> | |
| </head> | |
| <body> | |
| <div class="bg-gradient"></div> | |
| <div class="bg-orb orb-1"></div> | |
| <div class="bg-orb orb-2"></div> | |
| <main class="report-wrapper"> | |
| <header class="report-header glass"> | |
| <div class="badge" style="color: white;">Quarterly Report 2024</div> | |
| <h1>マーケティング戦略 <br><span class="highlight">進捗報告書</span></h1> | |
| <div class="meta-info"> | |
| <span>社外秘</span> | |
| </div> | |
| </header> | |
| <section class="key-message glass"> | |
| <h2><span class="icon">💡</span> Key Insight</h2> | |
| <p> | |
| 過去3ヶ月の施策により、リード獲得コストは<strong>25%削減</strong>されました。 | |
| 次四半期は、現在のLiquid Glassデザインを導入したLPのABテストを強化し、CVRの更なる向上を目指します。 | |
| </p> | |
| </section> | |
| <div class="stats-grid"> | |
| <div class="stat-card glass"> | |
| <span class="label">総リード数</span> | |
| <span class="value">1,240</span> | |
| <span class="trend up">+12%</span> | |
| </div> | |
| <div class="stat-card glass"> | |
| <span class="label">CPA(獲得単価)</span> | |
| <span class="value">¥4,200</span> | |
| <span class="trend down">-8%</span> | |
| </div> | |
| <div class="stat-card glass"> | |
| <span class="label">成約率</span> | |
| <span class="value">4.8%</span> | |
| <span class="trend up">+1.2%</span> | |
| </div> | |
| </div> | |
| <section class="report-body glass"> | |
| <h3>今後の重点施策</h3> | |
| <p> | |
| 市場のトレンドは「視覚的な透明感」と「信頼性」の両立に移行しています。 | |
| 本報告書で採用しているデザインコンセプトは、ユーザーに清潔感とモダンな印象を与え、滞在時間の延長に寄与します。 | |
| </p> | |
| <ul> | |
| <li>UI/UXの透明化による情報の階層化</li> | |
| <li>モバイルフレンドリーなレスポンシブ対応の強化</li> | |
| <li>インタラクティブなデータビジュアライゼーションの導入</li> | |
| </ul> | |
| </section> | |
| <footer class="report-footer"> | |
| <p>© 2025 RYH International. Confidential</p> | |
| </footer> | |
| </main> | |
| </body> | |
| </html> | |
| """ | |
| meta_prompt = ''' | |
| 思考のガイドライン | |
| 文脈の理解: ユーザーの意図、背景、制約条件を正確に把握してください。 | |
| 推論の先行: 結論を出す前に、必ず「なぜその結論に至るのか」という思考のプロセスを記述してください。 | |
| 多角的視点: 表面的な回答だけでなく、潜在的な課題や代替案、リスクについても考慮してください。 | |
| 論理的整合性: ステップ間のつながりを明確にし、矛盾がないかセルフチェックを行ってください。 | |
| 実行ステップ | |
| 1. タスクの分解と分析 | |
| 実行すべきタスクを最小単位の要素に分解してください。 | |
| 必要な情報、使用すべきトーン、守るべきルールを明確にします。 | |
| 2. 推論と戦略立案(Reasoning) | |
| 結論に至るまでの論理的な道筋を立ててください。 | |
| 複数のアプローチがある場合は、最も適切なものを選択した理由を明記してください。 | |
| 計算や複雑な論理が必要な場合は、ここでステップバイステップで展開してください。 | |
| 3. 検証と洗練 | |
| 生成した解決策が、すべての制約条件を満たしているか確認してください。 | |
| より簡潔に、あるいはより強力にできる部分を修正してください。 | |
| 出力形式 | |
| 以下の構造で回答してください。 | |
| 推論プロセス(Reasoning) | |
| [タスクの分析、論理的な思考、解決までのステップを詳細に記述してください。] | |
| 最終回答(Conclusion/Result) | |
| [推論に基づいた最終的な成果物を出力してください。形式はタスクに応じて最適化してください(JSON、マークダウン、文章など)。] | |
| ''' | |
| def save_report(html_content): | |
| filename = "report_output.html" | |
| with open(filename, "w", encoding="utf-8") as f: | |
| f.write(html_content) | |
| return filename | |
| # --- Gradio UI Logic --- | |
| def generate_response(query): | |
| prompt = f"{meta_prompt}に基づいて{query}に対する答えを必ず、{template}に基づいてHTML形式で出力してください。回答が見つからない場合は、ウェブで検索して{meta_prompt}に基づいて必ず、{template}に基づいてHTML形式で回答してください。" | |
| gresponse = client.models.generate_content( | |
| model="gemini-2.5-flash", | |
| contents=[prompt] | |
| ) | |
| myresp = gresponse.text | |
| part1 = myresp.find('```html') | |
| part2 = myresp.rfind('```') | |
| first_part = myresp[:part1] | |
| second_part = myresp[part1+7:part2] | |
| return first_part, second_part, second_part, gr.update(visible=True), gr.update(visible=True) | |
| # --- Gradio UI setup --- | |
| with gr.Blocks(title="メタプロンプト", theme=gr.themes.Glass(), css="""footer {visibility: hidden;} #header {display: flex; justify-content: space-between; align-items: center; font-size: 24px; font-weight: bold;} #logo {width: 50px; height: 50px;} #html_output_box {max-height: 600px; overflow-y: auto; border: 1px solid #e5e7eb; padding: 10px; border-radius: 8px;}""") as dob: | |
| gr.HTML('<div id="header"><span>🛡️ メタプロンプト</span><img id="logo" src="https://www.ryhintl.com/images/ryhlogo/ryhlogo.png" width="64" height="64" alt="Logo"></div>') | |
| gr.Markdown("メタプロンプトを利用して答えを生成できます。") | |
| html_state = gr.State("") | |
| with gr.Row(): | |
| prompt_input = gr.Textbox( | |
| label="プロンプト", | |
| info="最近のスマートフォンのトレンドや市場動向を考慮して回答してください。" | |
| ) | |
| with gr.Row(): | |
| log_output = gr.Textbox( | |
| label="回答", | |
| lines=20, | |
| interactive=False, | |
| max_lines=5, | |
| show_copy_button=True | |
| ) | |
| html_output = gr.HTML(elem_id="html_output_box") | |
| with gr.Row(): | |
| with gr.Row(): | |
| process_button = gr.Button("🤖 実行", variant="primary") | |
| with gr.Row(): | |
| print_btn = gr.Button("保存", visible=False) | |
| file_output = gr.File(label="ダウンロード用リンク", visible=False) | |
| print_btn.click(fn=save_report, inputs=html_state, outputs=file_output) | |
| process_button.click( | |
| fn=generate_response, | |
| inputs=[prompt_input], | |
| outputs=[log_output, html_output, html_state, print_btn, file_output] | |
| ) | |
| dob.launch(favicon_path="favicon.ico", show_api=False) |