From 0eb46e4255ff7f9b3c5fb0a7615d0a1a1e8f3d07 Mon Sep 17 00:00:00 2001 From: Damir Date: Tue, 7 Apr 2026 22:00:04 +0300 Subject: [PATCH] Refactor: improve run.sh startup logic and ensure proper permissions --- backend/main.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ frontend/ui.py | 58 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 backend/main.py create mode 100644 frontend/ui.py diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..698ea93 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +import os + +import requests +import yaml +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel + +app = FastAPI(title="AI CI/CD Assistant") + + +def _env(name: str, default: str) -> str: + value = os.getenv(name) + return value if value else default + + +# Example for local Ollama: http://localhost:11434/api/generate +OLLAMA_API_URL = _env("OLLAMA_API_URL", "http://host.docker.internal:11434/api/generate") + + +class GenerationRequest(BaseModel): + description: str + platform: str = "GitHub Actions" # or GitLab CI + model: str = "codellama" + + +def validate_config(config_text: str): + try: + yaml.safe_load(config_text) + return True, "Success" + except Exception as e: + return False, str(e) + + +@app.get("/health") +async def health(): + return {"status": "ok"} + + +@app.post("/generate") +async def generate_pipeline(req: GenerationRequest): + prompt = ( + f"Write only a valid {req.platform} YAML configuration for: {req.description}. " + "No explanations, just code." + ) + + payload = {"model": req.model, "prompt": prompt, "stream": False} + + try: + response = requests.post(OLLAMA_API_URL, json=payload, timeout=60) + response.raise_for_status() + raw_text = response.json().get("response", "") + + clean_code = raw_text.replace("```yaml", "").replace("```", "").strip() + is_valid, error_msg = validate_config(clean_code) + + return { + "status": "ok" if is_valid else "error", + "config": clean_code, + "validation_error": error_msg if not is_valid else None, + } + except Exception as e: + raise HTTPException(status_code=500, detail=f"Ollama error: {str(e)}") + + +if __name__ == "__main__": + import uvicorn + + uvicorn.run(app, host="0.0.0.0", port=int(_env("PORT", "8000"))) diff --git a/frontend/ui.py b/frontend/ui.py new file mode 100644 index 0000000..d81bcc4 --- /dev/null +++ b/frontend/ui.py @@ -0,0 +1,58 @@ +import os + +import requests +import streamlit as st + +st.set_page_config(page_title="DevOps AI Assistant", page_icon="πŸš€") + +st.title("πŸ€– AI CI/CD Pipeline Generator") +st.markdown("ΠŸΡ€ΠΎΡ‚ΠΎΡ‚ΠΈΠΏ сСрвиса для Π³Π΅Π½Π΅Ρ€Π°Ρ†ΠΈΠΈ ΠΈ Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΉ") + + +def _env(name: str, default: str) -> str: + value = os.getenv(name) + return value if value else default + + +default_backend_url = _env("BACKEND_URL", "http://backend:8000/generate") + +with st.sidebar: + st.header("Настройки") + api_url = st.text_input("Backend URL", default_backend_url) + model = st.selectbox( + "LLM МодСль", + ["deepseek-r1:1.5b", "qwen2.5-coder:1.5b", "llama3.1:8b", "Arena Model"], + ) + platform = st.radio("ΠŸΠ»Π°Ρ‚Ρ„ΠΎΡ€ΠΌΠ°", ["GitHub Actions", "GitLab CI"]) + +user_input = st.text_area( + "ΠžΠΏΠΈΡˆΠΈΡ‚Π΅ ΠΏΠ°ΠΉΠΏΠ»Π°ΠΉΠ½ (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€: 'Python app with pytest and docker build')", height=150 +) + +if st.button("Π‘Π³Π΅Π½Π΅Ρ€ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ", type="primary"): + if user_input: + with st.spinner("LLM Π΄ΡƒΠΌΠ°Π΅Ρ‚..."): + try: + res = requests.post( + api_url, + json={"description": user_input, "platform": platform, "model": model}, + timeout=90, + ) + res.raise_for_status() + data = res.json() + + if data.get("status") == "ok": + st.success("βœ… ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ Π²Π°Π»ΠΈΠ΄Π½Π°!") + st.code(data.get("config", ""), language="yaml") + st.download_button( + "Π‘ΠΊΠ°Ρ‡Π°Ρ‚ΡŒ .yml", + data.get("config", ""), + file_name="pipeline.yml", + ) + else: + st.error(f"❌ Ошибка Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ: {data.get('validation_error')}") + st.code(data.get("config", ""), language="yaml") + except Exception as e: + st.error(f"Ошибка ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ ΠΊ Π±Π΅ΠΊΠ΅Π½Π΄Ρƒ: {e}") + else: + st.warning("ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π°, Π²Π²Π΅Π΄ΠΈΡ‚Π΅ описаниС.")