Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| from openai import OpenAI | |
| import os | |
| import json | |
| from urllib.parse import quote | |
| import html | |
| # Initialize OpenAI client with API key and base URL from environment variables | |
| client = OpenAI( | |
| api_key=os.environ["OPENAI_API_KEY"], | |
| base_url=os.environ["OPENAI_BASE_URL"] | |
| ) | |
| # Define constants for pagination | |
| RESULTS_PER_PAGE = 10 | |
| TOTAL_RESULTS = 30 # Generate 30 results to allow pagination | |
| def fetch_search_results(query): | |
| """Fetch search results from the LLM based on the user's query.""" | |
| if not query.strip(): | |
| return None, "Please enter a search query." | |
| prompt = f""" | |
| You are a search engine that provides informative and relevant results. For the given query '{query}', | |
| generate {TOTAL_RESULTS} search results. Each result should include: | |
| - 'title': A concise, descriptive title of the result. | |
| - 'snippet': A short summary (2-3 sentences) of the content. | |
| - 'url': A plausible, clickable URL where the information might be found (e.g., a real or hypothetical website). | |
| Format the response as a JSON array of objects, where each object has 'title', 'snippet', and 'url' fields. | |
| Ensure the results are diverse, relevant to the query, and the URLs are realistic (e.g., https://example.com/page). | |
| """ | |
| try: | |
| response = client.chat.completions.create( | |
| model="gemini-2.0-flash-lite", # Updated model name | |
| messages=[ | |
| {"role": "system", "content": "You are a helpful search engine."}, | |
| {"role": "user", "content": prompt} | |
| ], | |
| response_format={"type": "json_object"} | |
| ) | |
| content = response.choices[0].message.content | |
| results = json.loads(content) | |
| # Handle different possible JSON structures | |
| if isinstance(results, dict) and "results" in results: | |
| results = results["results"] | |
| elif isinstance(results, list): | |
| pass | |
| else: | |
| return None, "Error: Unexpected JSON structure." | |
| return results, None | |
| except Exception as e: | |
| error_msg = str(e) | |
| if "404" in error_msg: | |
| return None, f"Error 404: Model or endpoint not found. Check OPENAI_BASE_URL ({os.environ['OPENAI_BASE_URL']}) and model name." | |
| elif "401" in error_msg: | |
| return None, "Error 401: Invalid API key. Check OPENAI_API_KEY." | |
| else: | |
| return None, f"Error: {error_msg}" | |
| def generate_search_page(query, page=1): | |
| """Generate a full HTML search results page.""" | |
| if not query.strip(): | |
| return """ | |
| <html> | |
| <head><title>LLM Search Engine</title></head> | |
| <body style="font-family: Arial, sans-serif;"> | |
| <h1>LLM Search Engine</h1> | |
| <form method="get" action="/"> | |
| <input type="text" name="query" placeholder="Type your search here..."> | |
| <input type="submit" value="Search"> | |
| <input type="hidden" name="page" value="1"> | |
| </form> | |
| <p>Please enter a search query.</p> | |
| </body> | |
| </html> | |
| """ | |
| results, error = fetch_search_results(query) | |
| if error: | |
| return f""" | |
| <html> | |
| <head><title>LLM Search Engine</title></head> | |
| <body style="font-family: Arial, sans-serif;"> | |
| <h1>LLM Search Engine</h1> | |
| <form method="get" action="/"> | |
| <input type="text" name="query" value="{html.escape(query)}"> | |
| <input type="submit" value="Search"> | |
| <input type="hidden" name="page" value="1"> | |
| </form> | |
| <p style="color: red;">{error}</p> | |
| </body> | |
| </html> | |
| """ | |
| # Calculate pagination boundaries | |
| start_idx = (page - 1) * RESULTS_PER_PAGE | |
| end_idx = start_idx + RESULTS_PER_PAGE | |
| total_pages = (len(results) + RESULTS_PER_PAGE - 1) // RESULTS_PER_PAGE | |
| # Ensure indices are within bounds | |
| if start_idx >= len(results): | |
| return f""" | |
| <html> | |
| <head><title>LLM Search Engine</title></head> | |
| <body style="font-family: Arial, sans-serif;"> | |
| <h1>LLM Search Engine</h1> | |
| <form method="get" action="/"> | |
| <input type="text" name="query" value="{html.escape(query)}"> | |
| <input type="submit" value="Search"> | |
| <input type="hidden" name="page" value="1"> | |
| </form> | |
| <p>No more results to display.</p> | |
| </body> | |
| </html> | |
| """ | |
| paginated_results = results[start_idx:end_idx] | |
| # Generate full HTML page | |
| html_content = f""" | |
| <html> | |
| <head> | |
| <title>LLM Search Engine</title> | |
| <style> | |
| body {{ | |
| font-family: Arial, sans-serif; | |
| margin: 0; | |
| padding: 20px; | |
| max-width: 800px; | |
| margin-left: auto; | |
| margin-right: auto; | |
| }} | |
| .search-box input[type="text"] {{ | |
| width: 70%; | |
| padding: 8px; | |
| font-size: 16px; | |
| border: 1px solid #dfe1e5; | |
| border-radius: 4px; | |
| }} | |
| .search-box input[type="submit"] {{ | |
| padding: 8px 16px; | |
| font-size: 14px; | |
| background-color: #f8f9fa; | |
| border: 1px solid #dfe1e5; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| }} | |
| .search-result {{ | |
| margin-bottom: 20px; | |
| }} | |
| .search-result a {{ | |
| color: #1a0dab; | |
| font-size: 18px; | |
| text-decoration: none; | |
| }} | |
| .search-result a:hover {{ | |
| text-decoration: underline; | |
| }} | |
| .search-result .url {{ | |
| color: #006621; | |
| font-size: 14px; | |
| margin: 2px 0; | |
| }} | |
| .search-result p {{ | |
| color: #545454; | |
| font-size: 14px; | |
| margin: 2px 0; | |
| }} | |
| .pagination {{ | |
| margin-top: 20px; | |
| text-align: center; | |
| }} | |
| .pagination a, .pagination span {{ | |
| margin: 0 10px; | |
| color: #1a0dab; | |
| text-decoration: none; | |
| }} | |
| .pagination a:hover {{ | |
| text-decoration: underline; | |
| }} | |
| </style> | |
| </head> | |
| <body> | |
| <h1>LLM Search Engine</h1> | |
| <form class="search-box" method="get" action="/"> | |
| <input type="text" name="query" value="{html.escape(query)}"> | |
| <input type="submit" value="Search"> | |
| <input type="hidden" name="page" value="1"> | |
| </form> | |
| <h2>Results for '{query}' (Page {page} of {total_pages})</h2> | |
| <div class="results"> | |
| """ | |
| for result in paginated_results: | |
| title = result.get("title", "No title") | |
| snippet = result.get("snippet", "No snippet") | |
| url = result.get("url", "#") | |
| html_content += f""" | |
| <div class="search-result"> | |
| <a href="{url}" target="_blank">{title}</a> | |
| <div class="url">{url}</div> | |
| <p>{snippet}</p> | |
| </div> | |
| """ | |
| # Pagination links | |
| encoded_query = quote(query) | |
| prev_link = f'<a href="/?query={encoded_query}&page={page-1}">Previous</a>' if page > 1 else '<span>Previous</span>' | |
| next_link = f'<a href="/?query={encoded_query}&page={page+1}">Next</a>' if page < total_pages else '<span>Next</span>' | |
| html_content += f""" | |
| </div> | |
| <div class="pagination"> | |
| {prev_link} | |
| <span>Page {page} of {total_pages}</span> | |
| {next_link} | |
| </div> | |
| </body> | |
| </html> | |
| """ | |
| return html_content | |
| # Define the Gradio app with Blocks | |
| with gr.Blocks(title="LLM Search Engine") as app: | |
| def handle_request(request: gr.Request): | |
| """Handle the incoming request and return the HTML response.""" | |
| query = request.query_params.get("query", "") | |
| try: | |
| page = int(request.query_params.get("page", "1")) | |
| except ValueError: | |
| page = 1 | |
| html = generate_search_page(query, page) | |
| return html | |
| # Define the route for the root URL | |
| app.route("/", fn=handle_request) | |
| # Launch the app | |
| app.launch() |