Team Ai
Apppublic

samrat-doi/LLM-Code-Deployment-Github

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
main.py342 linesDownload Raw Back to root
1from fastapi import FastAPI, Request, HTTPException2import requests3import os4import base645import time6# from dotenv import load_dotenv 7import traceback8import json9 10# load_dotenv()11 12GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")13AIPIPE_API_KEY = os.getenv("AIPIPE_API_KEY") 14GITHUB_USERNAME = os.getenv("GITHUB_USERNAME")15SECRET = os.getenv("SECRET")16 17if not all([GITHUB_TOKEN, AIPIPE_API_KEY, GITHUB_USERNAME, SECRET]):18    print("FATAL: Missing one or more essential environment variables.")19 20task_registry = {}21 22app = FastAPI()23 24def validate_secret(secret: str) -> bool:25    return secret == SECRET26 27def get_repo_info(repo_name: str):28    headers = {"Authorization": f"Bearer {GITHUB_TOKEN}", "Accept": "application/vnd.github+json"}29    response = requests.get(f"https://api.github.com/repos/{GITHUB_USERNAME}/{repo_name}", headers=headers)30    if response.status_code == 200:31        print(f"Repository {repo_name} already exists. Using existing one.")32        return response.json()33    return None34 35def create_github_repo(repo_name: str):36    print(f"Attempting to create or find repository: {repo_name}")37    repo_info = get_repo_info(repo_name)38    if repo_info:39        return repo_info40 41    payload = {"name": repo_name, "private": False, "auto_init": False, "license_template": "mit"}42    headers = {"Authorization": f"Bearer {GITHUB_TOKEN}", "Accept": "application/vnd.github+json"}43    response = requests.post("https://api.github.com/user/repos", headers=headers, json=payload)44 45    if response.status_code == 201:46        print(f"Successfully created new repository: {repo_name}")47        return response.json()48    if response.status_code == 422:49        repo_info = get_repo_info(repo_name)50        if repo_info:51            return repo_info52        else:53            raise Exception(f"Repo creation failed (422) AND subsequent GET failed: {response.content.decode('utf-8')}")54    55    raise Exception(f"Failed to create repository with status {response.status_code}: {response.content.decode('utf-8')}")56 57def enable_github_pages(repo_name: str):58    headers = {"Authorization": f"Bearer {GITHUB_TOKEN}", "Accept": "application/vnd.github+json"}59    payload = {"source": {"branch": "main", "path": "/"}}60    61    response = requests.post(f"https://api.github.com/repos/{GITHUB_USERNAME}/{repo_name}/pages", headers=headers, json=payload)62    63    if response.status_code not in [201, 409]:64        raise Exception(f"Failed to enable GitHub Pages (Status {response.status_code}): {response.content.decode('utf-8')}")65    66    print(f"GitHub Pages enabled for {repo_name}.")67    68    pages_response = requests.get(f"https://api.github.com/repos/{GITHUB_USERNAME}/{repo_name}/pages", headers=headers)69    if pages_response.status_code == 200:70        return pages_response.json().get('html_url')71    72    return f"https://{GITHUB_USERNAME}.github.io/{repo_name}/" 73 74def get_sha_of_latest_commit(repo_name: str, branch: str = "main") -> str:75    response = requests.get(76        f"https://api.github.com/repos/{GITHUB_USERNAME}/{repo_name}/commits/{branch}",77        headers={"Authorization": f"Bearer {GITHUB_TOKEN}", "Accept": "application/vnd.github+json"}78    )79    if response.status_code != 200:80        if response.status_code == 404:81            return None82        raise Exception(f"Failed to get latest commit SHA (Status {response.status_code}): {response.content.decode('utf-8')}")83    return response.json().get("sha")84 85def push_files_to_repo(repo_name: str, files: list, deployment_round: int):86    headers = {"Authorization": f"Bearer {GITHUB_TOKEN}", "Accept": "application/vnd.github+json"}87    88    for file in files:89        file_name = file.get("name")90        file_content = file.get("content")91        92        if isinstance(file_content, bytes):93            content_b64 = base64.b64encode(file_content).decode()94        elif isinstance(file_content, str):95            content_b64 = base64.b64encode(file_content.encode('utf-8')).decode()96        else:97            raise TypeError(f"File content for {file_name} must be bytes or str.")98 99        payload = {100            "message": f"[Round {deployment_round}] Update {file_name}",101            "content": content_b64,102            "branch": "main" 103        }104 105        file_info_response = requests.get(106            f"https://api.github.com/repos/{GITHUB_USERNAME}/{repo_name}/contents/{file_name}?ref=main",107            headers=headers108        )109        110        if file_info_response.status_code == 200:111            payload["sha"] = file_info_response.json().get("sha")112        113        response = requests.put(114            f"https://api.github.com/repos/{GITHUB_USERNAME}/{repo_name}/contents/{file_name}",115            headers=headers,116            json=payload117        )118        119        if response.status_code not in [200, 201]:120            raise Exception(f"Failed to push/update {file_name} (Status {response.status_code}): {response.content.decode('utf-8')}")121        122        print(f"Successfully pushed {file_name}. Commit SHA: {response.json().get('commit', {}).get('sha')}")123 124def generate_app_code(brief: str, attachments: list):125    system_instruction = (126        "You are an expert software engineer and code generator. "127        "Your only job is to generate a single, complete HTML file (index.html) based on the user's request. "128        "Do NOT include any external explanation, markdown delimiters (```), or comments in the final output. "129        "The output must be the raw, ready-to-use HTML code only."130    )131    user_prompt = (132        f"Generate a minimal, complete, and functional web application as a single index.html file "133        f"based on this brief: '{brief}'. "134        f"Context/Attachments provided: {attachments}. "135        "Ensure the HTML file is valid and complete."136    )137    138    combined_prompt = f"{system_instruction}\n\nUser Request: {user_prompt}"139 140    if not AIPIPE_API_KEY:141        raise Exception("AIPIPE_API_KEY environment variable is not set.")142 143    headers = {"Authorization": f"Bearer {AIPIPE_API_KEY}", "Content-Type": "application/json"}144    payload = {"model": "openai/gpt-4o-mini", "input": combined_prompt}145    146    aipipe_url = "https://aipipe.org/openrouter/v1/responses"147    print(f"Calling Ai pipe API at {aipipe_url}...")148    149    response = requests.post(aipipe_url, headers=headers, json=payload, timeout=60)150    151    if response.status_code != 200:152        try:153            error_details = response.json().get('error', {})154            error_message = error_details.get('message', f"Unknown error (Status {response.status_code})")155        except json.JSONDecodeError:156            error_message = response.content.decode('utf-8')157        raise Exception(f"Ai pipe API error: {error_message}")158        159    result = response.json()160    161    try:162        code = result['output'][0]['content'][0]['text'].strip()163    except (KeyError, IndexError, TypeError):164        print("ERROR: Could not parse AI response. Received structure:")165        print(json.dumps(result, indent=2))166        raise Exception("Failed to parse generated code from AI response.")167 168    return code169 170def generate_readme(data: dict, brief: str) -> str:171    task_name = data['task']172    return f"""# {task_name}173 174## Summary175 176This application was automatically generated based on the following brief:177> {brief}178 179## Setup180 181This is a static HTML application hosted on GitHub Pages. No setup is required. Simply visit the GitHub Pages URL to view the live application.182 183## Usage184 185Open the `index.html` file in your browser or visit the live GitHub Pages link.186 187## Code Explanation188 189The application consists of a single `index.html` file. It was generated by an AI model to fulfill the requirements of the project brief.190 191## License192 193This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.194"""195 196def generate_license() -> str:197    return """MIT License198 199Copyright (c) [2024] [Your Name or GitHub Username]200 201Permission is hereby granted, free of charge, to any person obtaining a copy202of this software and associated documentation files (the "Software"), to deal203in the Software without restriction, including without limitation the rights204to use, copy, modify, merge, publish, distribute, sublicense, and/or sell205copies of the Software, and to permit persons to whom the Software is206furnished to do so, subject to the following conditions:207 208The above copyright notice and this permission notice shall be included in all209copies or substantial portions of the Software.210 211THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR212IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,213FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE214AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER215LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,216OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE217SOFTWARE.218"""219 220def deploy_app(data, deployment_round: int):221    brief = data['brief']222    attachments = data.get('attachments', [])223    224    if deployment_round == 2:225        round2_data = data.get('round2', [])226        if round2_data and isinstance(round2_data, list):227            update_data = round2_data[0] 228            brief = update_data.get('brief', brief)229            if update_data.get('attachments'):230                attachments = update_data['attachments'] 231 232    task_name = data['task'].replace(' ', '-').lower() 233    repo_name = f"{task_name}_{data['nonce']}" 234    235    create_github_repo(repo_name)236    237    code_str = generate_app_code(brief, attachments)238    readme_content = generate_readme(data, brief)239    license_content = generate_license()240 241    files = [242        {"name": "index.html", "content": code_str},243        {"name": "README.md", "content": readme_content},244        {"name": "LICENSE", "content": license_content}245    ]246    247    push_files_to_repo(repo_name, files, deployment_round) 248    pages_url = enable_github_pages(repo_name)249    repo_url = f"https://github.com/{GITHUB_USERNAME}/{repo_name}"250    251    return repo_url, pages_url, repo_name252 253def post_evaluation(data, repo_url, sha, pages_url, round):254    payload = {255        "email": data["email"], "task": data["task"], "round": round, "nonce": data["nonce"],256        "repo_url": repo_url, "commit_sha": sha, "pages_url": pages_url257    }258    headers = {"Content-Type": "application/json"}259    260    evaluation_url = data.get("evaluation_url")261    if not evaluation_url:262         print("CRITICAL: evaluation_url is missing from the request data. Cannot post results.")263         return264 265    max_retries = 5266    retry_delay = 1267 268    for attempt in range(max_retries):269        print(f"Posting evaluation for Round {round} to {evaluation_url}... (Attempt {attempt + 1}/{max_retries})")270        try:271            response = requests.post(evaluation_url, headers=headers, json=payload, timeout=10)272            if response.status_code == 200:273                print("Successfully posted evaluation.")274                return275            print(f"Evaluation post failed with status {response.status_code}: {response.content.decode('utf-8')}")276        except requests.exceptions.RequestException as e:277            print(f"Evaluation post failed due to network error: {e}")278 279        if attempt < max_retries - 1:280            print(f"Retrying in {retry_delay} seconds...")281            time.sleep(retry_delay)282            retry_delay *= 2283    284    print("CRITICAL: All evaluation post attempts failed.")285 286def handle_round1(data):287    repo_url, pages_url, repo_name = deploy_app(data, deployment_round=1)288    sha = get_sha_of_latest_commit(repo_name)289    task_registry[data['nonce']] = repo_name290    post_evaluation(data, repo_url, sha, pages_url, round=1)291 292def handle_round2(data):293    nonce = data['nonce']294    known_repo = task_registry.get(nonce)295    296    if known_repo and get_repo_info(known_repo):297        repo_url, pages_url, repo_name = deploy_app(data, deployment_round=2)298        sha = get_sha_of_latest_commit(repo_name)299        post_evaluation(data, repo_url, sha, pages_url, round=2)300    else:301        repo_url, pages_url, repo_name = deploy_app(data, deployment_round=2)302        task_registry[nonce] = repo_name303        print(f"Repo not in registry or deleted. Used/Created repo {repo_name} and deployed.")304        sha = get_sha_of_latest_commit(repo_name)305        post_evaluation(data, repo_url, sha, pages_url, round=2)306 307@app.post("/handle_task")308async def handle_task(request: Request):309    try:310        data = await request.json()311    except json.JSONDecodeError:312        raise HTTPException(status_code=400, detail="Invalid JSON format.")313 314    if not validate_secret(data.get("secret")):315        raise HTTPException(status_code=401, detail="Invalid secret.")316    317    required_keys = ["round", "nonce", "task", "email", "evaluation_url"] 318    if not all(key in data for key in required_keys):319         missing = [key for key in required_keys if key not in data]320         raise HTTPException(status_code=400, detail=f"Missing required data keys: {', '.join(missing)}.")321 322    try:323        round_number = data.get("round")324        task_name = data['task'].replace(' ', '-').lower() 325        repo_name = f"{task_name}_{data['nonce']}" 326 327        if round_number == 1:328            handle_round1(data)329            return {"message": "Round 1 completed successfully", "repo_name": repo_name, "status": "success"}330        elif round_number == 2:331            handle_round2(data)332            return {"message": "Round 2 completed successfully", "repo_name": repo_name, "status": "success"}333        else:334            raise HTTPException(status_code=400, detail="Invalid round number. Must be 1 or 2.")335            336    except Exception as e:337        print(traceback.format_exc())338        raise HTTPException(status_code=500, detail=f"Deployment Error: {str(e)}")339 340if __name__ == "__main__":341    import uvicorn342    uvicorn.run(app, host="0.0.0.0", port=8000)