Files
devops-portal/frontend/ui.py
2026-04-07 22:45:45 +03:00

160 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
from datetime import datetime, timezone
import requests
import streamlit as st
import yaml
st.set_page_config(page_title="DevOps AI Assistant", page_icon="🚀")
st.title("DevOps AI Assistant")
st.markdown("Веб-интерфейс для генерации и проверки CI/CD конфигураций.")
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("Платформа", ["PV DOT CI", "PV DOT CD", "DPM CI", "DPM CD"])
if "history" not in st.session_state:
st.session_state.history = []
def _push_history(*, platform: str, model: str, prompt: str, status: str, config: str, error: str | None):
st.session_state.history.insert(
0,
{
"ts": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC"),
"platform": platform,
"model": model,
"prompt": prompt,
"status": status,
"error": error,
"config": config,
},
)
st.session_state.history = st.session_state.history[:50]
tab_overview, tab_generator, tab_validator, tab_history = st.tabs(
["Обзор", "Pipeline generator", "Проверка YAML", "История"]
)
with tab_overview:
st.subheader("Что умеет сервис")
st.markdown(
"- **Pipeline generator**: генерирует YAML через backend (FastAPI → Ollama).\n"
"- **Проверка YAML**: локально проверяет синтаксис YAML, чтобы быстро править конфиг.\n"
"- **История**: хранит последние результаты в рамках сессии браузера."
)
st.divider()
st.caption(f"Backend URL: {api_url}")
with tab_generator:
st.subheader("AI CI/CD Pipeline Generator")
user_input = st.text_area(
"Опишите пайплайн (например: 'Python app with pytest and docker build')",
height=150,
key="generator_prompt",
)
col_a, col_b = st.columns([1, 3])
with col_a:
generate_clicked = st.button("Сгенерировать конфигурацию", type="primary")
with col_b:
st.caption("Подсказка: чем конкретнее требования, тем валиднее YAML.")
if generate_clicked:
if user_input.strip():
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()
status = data.get("status", "error")
config = data.get("config", "")
err = data.get("validation_error")
_push_history(
platform=platform,
model=model,
prompt=user_input,
status=status,
config=config,
error=err,
)
if status == "ok":
st.success("Конфигурация валидна.")
else:
st.error(f"Ошибка валидации: {err}")
st.code(config, language="yaml")
st.download_button("Скачать .yml", config, file_name="pipeline.yml")
except Exception as e:
st.error(f"Ошибка подключения к бекенду: {e}")
else:
st.warning("Пожалуйста, введите описание.")
with tab_validator:
st.subheader("Локальная проверка YAML")
yaml_text = st.text_area("Вставьте YAML для проверки", height=240, key="validator_yaml")
c1, c2 = st.columns([1, 1])
with c1:
validate_clicked = st.button("Проверить YAML")
with c2:
if st.button("Вставить последний результат", disabled=(len(st.session_state.history) == 0)):
st.session_state.validator_yaml = (st.session_state.history[0].get("config") or "")
st.rerun()
if validate_clicked:
if yaml_text.strip():
try:
yaml.safe_load(yaml_text)
st.success("YAML синтаксически валиден.")
except Exception as e:
st.error(f"Ошибка YAML: {e}")
else:
st.warning("Вставьте YAML.")
with tab_history:
st.subheader("История (последние 50)")
if not st.session_state.history:
st.info("Пока пусто. Сгенерируйте конфигурацию во вкладке Pipeline generator.")
else:
if st.button("Очистить историю"):
st.session_state.history = []
st.rerun()
for idx, item in enumerate(st.session_state.history):
title = f"[{item['ts']}] {item['platform']} · {item['model']} · {item['status']}"
with st.expander(title, expanded=(idx == 0)):
st.caption(item.get("prompt", ""))
if item.get("error"):
st.error(f"Validation error: {item['error']}")
st.code(item.get("config", ""), language="yaml")
st.download_button(
"Скачать .yml",
item.get("config", ""),
file_name=f"pipeline-{idx+1}.yml",
key=f"download_{idx}",
)