Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import os | |
| import zipfile | |
| import subprocess | |
| import shutil | |
| import requests | |
| import re | |
| import time | |
| # --- CONFIGURATION --- | |
| API_ENDPOINT = "https://gen.pollinations.ai/v1/chat/completions" | |
| API_KEY = "sk_qDYtDvntvqzjmp23XKVzr9lBXYwYndaR" | |
| AI_MODEL = "claude" | |
| MAX_RETRIES = 10 # AI 10 baar koshish karega haar manne se pehle | |
| st.set_page_config(page_title="AI Terminator Builder", layout="centered") | |
| st.title("π€ AI Terminator Builder") | |
| st.markdown("This AI will **Hunt & Fix** errors recursively until the APK is built.") | |
| # --- HELPER: AI API --- | |
| def ask_claude(system_prompt, user_message): | |
| headers = { | |
| "Content-Type": "application/json", | |
| "Authorization": f"Bearer {API_KEY}" | |
| } | |
| payload = { | |
| "model": AI_MODEL, | |
| "messages": [ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": user_message} | |
| ], | |
| "temperature": 0.1 # High precision | |
| } | |
| try: | |
| response = requests.post(API_ENDPOINT, json=payload, headers=headers, timeout=60) | |
| if response.status_code == 200: | |
| return True, response.json()['choices'][0]['message']['content'] | |
| return False, f"API Error: {response.text}" | |
| except Exception as e: | |
| return False, f"Connection Error: {str(e)}" | |
| # --- HELPER: KEYSTORE --- | |
| def generate_keystore(): | |
| keystore_path = "debug.keystore" | |
| if not os.path.exists(keystore_path): | |
| subprocess.run([ | |
| "keytool", "-genkey", "-v", "-keystore", keystore_path, | |
| "-storepass", "android", "-alias", "androiddebugkey", | |
| "-keypass", "android", "-keyalg", "RSA", "-keysize", "2048", | |
| "-validity", "10000", "-dname", "CN=AndroidDebug,O=Android,C=US" | |
| ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) | |
| return keystore_path | |
| # --- STEP 1: FORCE UPGRADE (Base Setup) --- | |
| def initial_modernization(project_root, status_box): | |
| status_box.write("π οΈ AI is modernizing project engine first...") | |
| # 1. Gradle Wrapper Upgrade to 8.0 | |
| wrapper_props = os.path.join(project_root, "gradle", "wrapper", "gradle-wrapper.properties") | |
| os.makedirs(os.path.dirname(wrapper_props), exist_ok=True) | |
| new_props = """distributionBase=GRADLE_USER_HOME | |
| distributionPath=wrapper/dists | |
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip | |
| zipStoreBase=GRADLE_USER_HOME | |
| zipStorePath=wrapper/dists | |
| """ | |
| with open(wrapper_props, "w") as f: f.write(new_props) | |
| # 2. Root Build.gradle Fix | |
| root_gradle = None | |
| for root, dirs, files in os.walk(project_root): | |
| if "build.gradle" in files and "app" not in root: | |
| root_gradle = os.path.join(root, "build.gradle") | |
| break | |
| if root_gradle: | |
| with open(root_gradle, 'r') as f: content = f.read() | |
| # AI se kaho dependencies update kare | |
| p = "Update 'classpath' dependencies to be compatible with Gradle 8. Use 'com.android.tools.build:gradle:8.0.0'. Output full code." | |
| ok, fix = ask_claude(p, content) | |
| if ok: | |
| clean = fix.replace("```gradle","").replace("```","").strip() | |
| with open(root_gradle, 'w') as f: f.write(clean) | |
| status_box.write("β AI updated root build.gradle configuration.") | |
| # --- STEP 2: THE FIXER LOOP --- | |
| def analyze_and_fix_errors(project_root, error_log, status_box): | |
| # Regex to find file paths in error logs (Java, Kotlin, XML, Gradle) | |
| # Pattern looks for: /path/to/file.ext:line_number: error | |
| match = re.search(r'(/[\w/-]+\.(java|kt|xml|gradle))', error_log) | |
| target_file = None | |
| if match: | |
| extracted_path = match.group(1) | |
| # Verify path exists (Handle relative/absolute mismatch) | |
| if os.path.exists(extracted_path): | |
| target_file = extracted_path | |
| else: | |
| # Try to find by filename | |
| filename = os.path.basename(extracted_path) | |
| for root, dirs, files in os.walk(project_root): | |
| if filename in files: | |
| target_file = os.path.join(root, filename) | |
| break | |
| # Agar Logs se file nahi mili, to fallback 'build.gradle' (App Level) par jao | |
| if not target_file: | |
| for root, dirs, files in os.walk(project_root): | |
| if "build.gradle" in files and "app" in root: | |
| target_file = os.path.join(root, "build.gradle") | |
| break | |
| if target_file: | |
| file_name = os.path.basename(target_file) | |
| status_box.write(f"π§ AI identified issue in: **{file_name}**") | |
| with open(target_file, 'r', errors='ignore') as f: current_code = f.read() | |
| system_prompt = """ | |
| You are an elite Android debugger. | |
| Analyze the Error Log and the File Content. | |
| Fix the code to resolve the build error. | |
| Ensure Java 17 / Gradle 8 compatibility. | |
| RETURN ONLY THE CORRECTED CODE. NO EXPLANATION. | |
| """ | |
| user_message = f""" | |
| ERROR LOG: | |
| {error_log[-2000:]} | |
| FILE CONTENT ({file_name}): | |
| {current_code} | |
| """ | |
| success, ai_fix = ask_claude(system_prompt, user_message) | |
| if success: | |
| # Clean response | |
| cleaned_code = ai_fix.replace("```java", "").replace("```kotlin", "").replace("```xml", "").replace("```gradle", "").replace("```", "").strip() | |
| with open(target_file, 'w', encoding='utf-8') as f: | |
| f.write(cleaned_code) | |
| status_box.write(f"π€ **AI Message:** I have corrected `{file_name}` to fix the reported error.") | |
| return True | |
| else: | |
| status_box.error("AI API Failed to respond.") | |
| return False | |
| else: | |
| status_box.warning("Could not identify specific broken file from logs. Trying generic fix...") | |
| return False | |
| # --- MAIN UI --- | |
| uploaded_file = st.file_uploader("Project ZIP Upload", type=["zip"]) | |
| if uploaded_file: | |
| if st.button("π Start Terminator Build"): | |
| status_box = st.status("Initializing System...", expanded=True) | |
| # 1. EXTRACT | |
| if os.path.exists("project_extract"): shutil.rmtree("project_extract") | |
| os.makedirs("project_extract", exist_ok=True) | |
| with open("uploaded.zip", "wb") as f: f.write(uploaded_file.getbuffer()) | |
| with zipfile.ZipFile("uploaded.zip", "r") as z: z.extractall("project_extract") | |
| project_root = None | |
| for root, dirs, files in os.walk("project_extract"): | |
| if "gradlew" in files: | |
| project_root = root | |
| break | |
| if not project_root: | |
| st.error("Gradlew not found.") | |
| st.stop() | |
| subprocess.run(["chmod", "+x", os.path.join(project_root, "gradlew")]) | |
| # 2. PRE-BUILD MODERNIZATION | |
| initial_modernization(project_root, status_box) | |
| # 3. THE LOOP (MAX RETRIES) | |
| build_success = False | |
| cmd = ["./gradlew", "assembleRelease", "--no-daemon", "--stacktrace"] | |
| for attempt in range(1, MAX_RETRIES + 1): | |
| status_box.write(f"π **Build Attempt {attempt}/{MAX_RETRIES}** is running...") | |
| result = subprocess.run(cmd, cwd=project_root, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) | |
| if result.returncode == 0: | |
| build_success = True | |
| break # Loop khatam, kaam ho gaya | |
| else: | |
| status_box.write(f"β Build Failed (Attempt {attempt}). AI is analyzing logs...") | |
| # AI FIXING LOGIC | |
| fixed = analyze_and_fix_errors(project_root, result.stderr, status_box) | |
| if not fixed: | |
| # Agar AI file nahi dhoond paya, to shayad "clean" ki zaroorat hai | |
| subprocess.run(["./gradlew", "clean"], cwd=project_root) | |
| status_box.write("π§Ή AI cleaned the project cache.") | |
| # Thora saans lene do system ko | |
| time.sleep(1) | |
| # 4. FINAL OUTCOME | |
| if build_success: | |
| status_box.update(label="β Build Successful! Signing APK...", state="complete") | |
| target_apk = None | |
| for root, dirs, files in os.walk(project_root): | |
| for file in files: | |
| if file.endswith(".apk") and "release" in file: | |
| target_apk = os.path.join(root, file) | |
| break | |
| if target_apk: | |
| keystore = generate_keystore() | |
| signed = "AI_Terminator_App.apk" | |
| subprocess.run(["zipalign", "-v", "-p", "4", target_apk, "aligned.apk"], stdout=subprocess.DEVNULL) | |
| subprocess.run(["apksigner", "sign", "--ks", keystore, "--ks-pass", "pass:android", "--out", signed, "aligned.apk"]) | |
| with open(signed, "rb") as f: | |
| st.download_button("β¬οΈ Download Final APK", f, file_name=signed) | |
| else: | |
| status_box.update(label="β Mission Failed", state="error") | |
| st.error(f"AI tried {MAX_RETRIES} times but could not fix all errors.") | |
| st.text_area("Last Error Log", result.stderr[-2000:], height=300) | |
| if os.path.exists("uploaded.zip"): os.remove("uploaded.zip") |