Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import requests | |
| import groq | |
| import os | |
| from dotenv import load_dotenv | |
| import urllib.parse | |
| import re | |
| from sumy.parsers.html import HtmlParser | |
| from sumy.nlp.tokenizers import Tokenizer | |
| from sumy.summarizers.lsa import LsaSummarizer | |
| load_dotenv(verbose=True) | |
| # APIキーと検索エンジンID | |
| API_KEY = os.environ["API_KEY"] | |
| SEARCH_ENGINE_ID = os.environ["SEARCH_ENGINE_ID"] | |
| search_active = True # 検索処理の状態を管理するフラグ | |
| html = """ | |
| <!DOCTYPE html> | |
| <html lang="ja"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>プリンス オブ ペルシャ 失われた王冠 - 検索結果</title> | |
| <style> | |
| body { | |
| font-family: 'Arial', sans-serif; | |
| line-height: 1.6; | |
| background-color: #f4f4f4; | |
| color: #333; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| .container { | |
| max-width: 800px; | |
| margin: 40px auto; | |
| padding: 20px; | |
| } | |
| .result-card { | |
| background: white; | |
| padding: 15px; | |
| margin-bottom: 15px; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); | |
| } | |
| .result-card a { | |
| color: #007bff; | |
| text-decoration: none; | |
| font-weight: bold; | |
| } | |
| .result-card a:hover { | |
| text-decoration: underline; | |
| } | |
| h1 { | |
| text-align: center; | |
| color: #444; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>タイトル - 検索結果</h1> | |
| <div class="result-card"> | |
| <a href="https://www.ubisoft.com/ja-jp/game/prince-of-persia/the-lost-crown">プリンス オブ ペルシャ 失われた王冠 | Ubisoft (JP)</a> | |
| </div> | |
| </div> | |
| </body> | |
| </html> | |
| """ | |
| def generate_prompt(keyword: str): | |
| client = groq.Client(api_key=os.environ.get("GROQ_API_KEY")) | |
| completion = client.chat.completions.create( | |
| model="llama3-70b-8192", | |
| messages=[ | |
| {"role": "system", "content": "貴方は優秀なプロンプト・エンジニアです。"}, | |
| {"role": "user", "content": keyword+"に答えてください。合わせて、主語を含むプロンプトも5つ生成してください。 必ず、日本語で答えてください。"} | |
| ], | |
| ) | |
| return completion.choices[0].message.content | |
| def stop_search(): | |
| global search_active | |
| search_active = False | |
| return "検索が停止されました。" | |
| def update_prompt(search_text): | |
| return search_text | |
| def summarize_webpage(url): | |
| try: | |
| parser = HtmlParser.from_url(url, Tokenizer("japanese")) | |
| summarizer = LsaSummarizer() | |
| summary = summarizer(parser.document, 5) # 5文で要約 | |
| return "\n".join(str(sentence) for sentence in summary) | |
| except Exception as e: | |
| return f"エラー: {str(e)}" | |
| # Google Custom Search API を利用して検索する関数 | |
| def google_search(query): | |
| global search_active | |
| search_active = True # 検索開始時にフラグを True にする | |
| yield "" # 初期表示を空にする | |
| if not search_active: | |
| yield "" # 処理中メッセージを空にする | |
| return | |
| yield "<div>処理中...</div>" | |
| if (len(query) == 0): | |
| errmsg = ''' | |
| <!DOCTYPE html> | |
| <html lang="ja"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>エラー - キーワード未入力</title> | |
| <style> | |
| body { | |
| font-family: Arial, sans-serif; | |
| background-color: #f8d7da; | |
| color: #721c24; | |
| text-align: center; | |
| padding: 50px; | |
| } | |
| .error-container { | |
| max-width: 500px; | |
| margin: auto; | |
| background-color: #ffffff; | |
| padding: 20px; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); | |
| border: 2px solid #dc3545; | |
| } | |
| .error-icon { | |
| font-size: 50px; | |
| color: #dc3545; | |
| } | |
| h1 { | |
| margin: 10px 0; | |
| } | |
| p { | |
| font-size: 18px; | |
| } | |
| .btn { | |
| display: inline-block; | |
| padding: 10px 20px; | |
| background-color: #dc3545; | |
| color: #fff; | |
| text-decoration: none; | |
| font-size: 16px; | |
| border-radius: 5px; | |
| margin-top: 15px; | |
| } | |
| .btn:hover { | |
| background-color: #c82333; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="error-container"> | |
| <div class="error-icon">⚠️</div> | |
| <h1>エラー</h1> | |
| <p>キーワードが入力されていません。</p> | |
| </div> | |
| </body> | |
| </html> | |
| ''' | |
| yield errmsg | |
| else: | |
| url = f"https://www.googleapis.com/customsearch/v1?q={query}&key={API_KEY}&cx={SEARCH_ENGINE_ID}&sort=date" | |
| response = requests.get(url) | |
| if response.status_code == 200: | |
| data = response.json() | |
| #print("respdata:",data) | |
| results = [] | |
| for item in data.get("items", []): | |
| title = item.get("title", "No Title") | |
| link = item.get("link", "No Link") | |
| snippet = item.get("htmlSnippet", "No Snippet") | |
| contents = title + "\n" + snippet | |
| results.append(f"{contents}: {link}") | |
| # URLデコードを適用 | |
| decoded_urls = [urllib.parse.unquote(url) for url in results] | |
| decoded ="\n".join(decoded_urls) | |
| # 正規表現を使ってタイトルとURLを抽出 | |
| matches = re.findall(r'(.+?):\s*(https?://[^\s]+)', decoded) | |
| # 結果を表示 | |
| finale = "" | |
| for title, url in matches: | |
| smry = summarize_webpage(url) | |
| finale = finale+f'<div style="background:white;padding:15px;margin-bottom:15px;border-radius:8px;box-shadow:02px5pxrgba(0,0,0,0.1);"><a style="color: #007bff; text-decoration: none; font-weight: bold;" href="{url}" target="_blank" rel="noopener">{title}</a><br />{smry}</div>\n' | |
| generated = f"""<!DOCTYPE html><html lang="ja"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>{query} - 検結果</title></head><body><div style="max-width: 800px; margin: 40px auto; padding: 20px;"><h1>{query} - 検索結果</h1><ul><li style="background:ghostwhite;border-radius:8px;box-shadow:0 2px 5px rgba(0, 0, 0, 0.1);">{finale}</li></ul></div></body></html>""" | |
| yield generated | |
| else: | |
| yield f"検索に失敗しました: {response.status_code}" | |
| with gr.Blocks(css="footer {visibility: hidden;} #custom_button {width: 400px; margin: 0 auto; background-color: #E0E7FF;}", theme=gr.themes.Soft(), title="Google カスタム検索エージェント") as agent: | |
| gr.HTML('''<div style="display: flex; justify-content: center; align-items: center; font-size: 20px; font-weight: bold; font-family: 'Noto Sans JP', 'Yu Gothic', 'ヒラギノ角ゴシック', 'メイリオ', sans-serif;">Google カスタム検索エージェント</div>''') | |
| with gr.Column(): | |
| search_input = gr.Textbox(label="検索キーワードを入力",info="例)著名投資家ウォーレン・バフェット氏が米投資会社バークシャー・ハザウェイの最高経営責任者(CEO)から退く",placeholder="検索するキーワードを入力します。") | |
| search_output = gr.HTML(label="検索結果") | |
| search_btn = gr.Button("検索",elem_id="custom_button") | |
| search_btn.click(google_search, inputs=search_input, outputs=search_output) | |
| stop_output = gr.Textbox(label="ステータス") | |
| stop_btn = gr.Button("停止",elem_id="custom_button") | |
| stop_btn.click(stop_search, inputs=None, outputs=stop_output) | |
| with gr.Column(): | |
| prompt_input = gr.Textbox( | |
| label="検索キーワードを入力", | |
| info="例)著名投資家ウォーレン・バフェット氏が米投資会社バークシャー・ハザウェイの最高経営責任者(CEO)から退く", | |
| placeholder="検索するキーワードを入力します。", | |
| ) | |
| prompt_output = gr.Textbox(label="推薦プロンプト") | |
| prompt_btn = gr.Button("推薦プロンプト",elem_id="custom_button") | |
| prompt_btn.click(generate_prompt, inputs=prompt_input, outputs=prompt_output) | |
| # search_input の変更を監視し、prompt_input に反映 | |
| search_input.change(update_prompt, inputs=search_input, outputs=prompt_input) | |
| agent.launch(favicon_path='favicon.ico') | |