Spaces:
Sleeping
Sleeping
| # llm_utils.py | |
| import os | |
| import requests | |
| import re | |
| import json | |
| from typing import Optional, List, Dict, Any | |
| from github import Github | |
| def generate_app_files(brief: str, checks: List[str], attachments: Optional[List[Dict[str, str]]] = None, round: int = 1, task: str = None) -> Dict[str, Any]: | |
| api_key = os.getenv("AI_API_KEY") | |
| github_token = os.getenv("GITHUB_TOKEN") | |
| if not api_key: | |
| raise RuntimeError("AI_API_KEY environment variable is required") | |
| # Get existing code for rounds > 1 | |
| existing_files = {} | |
| if round > 1 and github_token and task: | |
| try: | |
| g = Github(github_token) | |
| user = g.get_user() | |
| # Just use the task name as repo name, don't include user login | |
| repo = g.get_repo(f"{user.login}/{task}") | |
| for filename in ["index.html", "README.md"]: | |
| try: | |
| file_content = repo.get_contents(filename) | |
| existing_files[filename] = file_content.decoded_content.decode('utf-8') | |
| except Exception as e: | |
| print(f"Failed to fetch {filename}: {str(e)}") | |
| except Exception as e: | |
| print(f"Failed to access repository: {str(e)}") | |
| # Modify system message to include context for updates | |
| system_msg = ( | |
| 'Create a single-page web application that implements the requirements.\n\n' | |
| 'Output format - JSON object with:\n' | |
| '- "index": Complete HTML file with implementation\n' | |
| '- "README": Documentation markdown file following this structure:\n' | |
| ' 1. Summary/Overview of the application\n' | |
| ' 2. Setup instructions\n' | |
| ' 3. Usage guide with examples\n' | |
| ' 4. Code explanation and architecture\n' | |
| ' 5. License information (MIT License)\n\n' | |
| 'Technical requirements:\n' | |
| '1. Process data client-side\n' | |
| '2. CDN requirements:\n' | |
| ' - Use cdnjs.cloudflare.com as primary CDN provider\n' | |
| ' - Use latest stable versions with specific version numbers\n' | |
| ' - Preferred format: https://cdnjs.cloudflare.com/ajax/libs/LIBRARY/VERSION/FILE.min.js\n' | |
| '3. Base64 handling:\n' | |
| ' - Keep ${...} strings in data URIs as-is\n' | |
| ' - Do not try to decode template literals\n' | |
| ' - Example: data:text/csv;base64,${someBase64} should be used directly\n' | |
| '4. Match exact IDs from brief\n' | |
| '5. Handle all test conditions\n\n' | |
| 'Best practices:\n' | |
| '- Process encoded data at runtime\n' | |
| '- Keep template literals intact\n' | |
| '- Include error handling\n' | |
| '- Verify all test checks pass' | |
| ) | |
| if round > 1 and existing_files: | |
| system_msg += ( | |
| '\n\nUpdate mode:\n' | |
| '- Use existing files as base\n' | |
| '- Preserve working features\n' | |
| '- Add new requirements\n' | |
| '- Maintain code structure\n' | |
| '- Use the attachments if provided\n' | |
| ) | |
| # Prepare messages and make direct request to OpenRouter | |
| headers = { | |
| "Authorization": f"Bearer {api_key}", | |
| "Content-Type": "application/json" | |
| } | |
| # Prepare user message with clearer context | |
| user_payload = { | |
| "brief": brief, | |
| "checks": checks, | |
| "attachments": attachments, | |
| "round": round, | |
| "note": "Important: Keep ${...} template literals intact in data URIs" | |
| } | |
| if existing_files: | |
| user_payload["existing_files"] = existing_files | |
| payload = { | |
| "model": "qwen/qwen3-coder", | |
| "messages": [ | |
| {"role": "system", "content": system_msg}, | |
| {"role": "user", "content": json.dumps(user_payload, ensure_ascii=False, indent=2)} | |
| ] | |
| } | |
| resp = requests.post( | |
| "https://aipipe.org/openrouter/v1/chat/completions", | |
| headers=headers, | |
| json=payload | |
| ) | |
| if not resp.ok: | |
| raise RuntimeError(f"API request failed: {resp.status_code} {resp.text}") | |
| # Extract content from OpenRouter response format | |
| try: | |
| content = resp.json()["choices"][0]["message"]["content"] | |
| except Exception as e: | |
| content = str(resp.text) | |
| # Attempt to extract JSON substring if wrapped in markdown or extra text | |
| json_obj = None | |
| try: | |
| json_obj = json.loads(content) | |
| except Exception: | |
| # Try to find the first {...} JSON block (non-greedy to avoid trailing text) | |
| m = re.search(r"\{(?:[^{}]|(?R))*\}", content, re.S) if hasattr(re, 'R') else re.search(r"\{.*\}", content, re.S) | |
| if m: | |
| try: | |
| json_obj = json.loads(m.group(0)) | |
| except Exception: | |
| json_obj = None | |
| # If parsing succeeded and has required keys, normalize and return it | |
| if isinstance(json_obj, dict) and "index" in json_obj and "README" in json_obj: | |
| result = {"index": json_obj["index"], "README": json_obj["README"]} | |
| assets = json_obj.get("assets") | |
| if isinstance(assets, dict): | |
| # Ensure asset keys and values are strings | |
| sanitized_assets: Dict[str, str] = {} | |
| for k, v in assets.items(): | |
| if isinstance(k, str) and (isinstance(v, str) or v is None): | |
| sanitized_assets[k] = v or "" | |
| if sanitized_assets: | |
| result["assets"] = sanitized_assets | |
| return result | |
| # Fallback: return the assistant output as the index.html and a structured README | |
| readme_template = f"""# {brief} | |
| ## Summary | |
| A web application that {brief.lower()} | |
| ## Setup | |
| 1. Clone the repository | |
| 2. Open index.html in a web browser | |
| 3. No additional setup required as all dependencies are loaded via CDN | |
| ## Usage | |
| {content} | |
| ## Code Explanation | |
| The application is built using vanilla JavaScript and processes data client-side. | |
| Please refer to the code comments in index.html for detailed implementation details. | |
| ## License | |
| MIT License | |
| Copyright (c) {os.getenv('GITHUB_USER', '2024')} | |
| Permission is hereby granted, free of charge, to any person obtaining a copy | |
| of this software and associated documentation files (the "Software"), to deal | |
| in the Software without restriction, including without limitation the rights | |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| copies of the Software, and to permit persons to whom the Software is | |
| furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in all | |
| copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| SOFTWARE.""" | |
| return { | |
| "index": content, | |
| "README": readme_template, | |
| } |