import streamlit as st from pathlib import Path import time from config import settings from core import rag from agent import agent, AgentMode from ollama_client import ollama, LLMClient, PROVIDER_BASE_URLS from logger import get_logger log = get_logger("app") from document_processor import DocumentProcessor SUPPORTED_EXTENSIONS = list(DocumentProcessor.SUPPORTED_EXTENSIONS.keys()) PROVIDER_MODELS = { "ollama": ["gemma2:9b", "llama3.2", "llama3.1", "mistral"], "opencode": ["claude-3.5-sonnet", "gpt-4o", "gemini-2.0-flash", "deepseek-r1", "llama-3.3-70b"], "openrouter": ["openrouter/auto", "anthropic/claude-3.5-sonnet", "google/gemini-pro-1.5", "meta-llama/llama-3.1-70b-instruct"], "groq": ["llama-3.1-70b-versatile", "llama-3.1-8b-instant", "mixtral-8x7b-32768"], "openai": ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo"] } st.set_page_config( page_title="RAGGER", page_icon="πŸ“š", layout="wide" ) def save_settings(provider, model, api_key, base_url, ollama_host, ollama_model): env_path = Path(".env") lines = [] if env_path.exists(): for line in env_path.read_text().splitlines(): if line.startswith("LLM_PROVIDER="): lines.append(f"LLM_PROVIDER={provider}") elif line.startswith("LLM_MODEL="): lines.append(f"LLM_MODEL={model}") elif line.startswith("LLM_API_KEY="): lines.append(f"LLM_API_KEY={api_key}") elif line.startswith("LLM_BASE_URL="): lines.append(f"LLM_BASE_URL={base_url}") elif line.startswith("OLLAMA_HOST="): lines.append(f"OLLAMA_HOST={ollama_host}") elif line.startswith("OLLAMA_MODEL="): lines.append(f"OLLAMA_MODEL={ollama_model}") else: lines.append(line) else: lines = [ f"LLM_PROVIDER={provider}", f"LLM_MODEL={model}", f"LLM_API_KEY={api_key}", f"LLM_BASE_URL={base_url}", f"OLLAMA_HOST={ollama_host}", f"OLLAMA_MODEL={ollama_model}", ] env_path.write_text("\n".join(lines) + "\n") log.info(f"Settings saved: provider={provider}, model={model}") def check_health(): log.debug("Running startup health check") issues = [] if not ollama.is_available(): issues.append("Ollama non raggiungibile") log.warning("Ollama not available at startup") return issues startup_issues = check_health() if startup_issues: st.warning("⚠️ Problemi rilevati: " + ", ".join(startup_issues)) def show_logs(): """Mostra i log dell'applicazione""" log_file = Path("logs/ragger.log") if log_file.exists(): lines = log_file.read_text().splitlines() last_lines = lines[-50:] if len(lines) > 50 else lines return "\n".join(last_lines) return "Nessun log disponibile" def check_ollama_status(): """Verifica se Ollama sta elaborando qualcosa""" try: import httpx resp = httpx.get(f"{settings.OLLAMA_HOST}/api/tags", timeout=5) return resp.status_code == 200 except: return False st.title("πŸ“š RAGGER") st.caption("Sistema RAG con Agent Intelligente") with st.sidebar: st.header("βš™οΈ ModalitΓ ") mode = st.selectbox( "Seleziona modalitΓ ", options=["chat", "lettera", "progetto", "impostazioni"], index=0, format_func=lambda x: { "chat": "πŸ’¬ Chat", "lettera": "βœ‰οΈ Lettera", "progetto": "πŸ“‹ Progetto", "impostazioni": "βš™οΈ Impostazioni" }[x] ) st.divider() with st.expander("πŸ“œ Log Applicazione", expanded=True): log_content = show_logs() st.text_area("", log_content, height=200, key="log_viewer", disabled=True) col_log1, col_log2 = st.columns(2) with col_log1: if st.button("πŸ”„ Aggiorna Log", use_container_width=True): st.rerun() with col_log2: if st.button("πŸ“‹ Copia Log", use_container_width=True): st.write("Log copiato!") ollama_status = check_ollama_status() if ollama_status: st.success("🟒 Ollama connesso") else: st.error("πŸ”΄ Ollama non risponde") if mode == "impostazioni": st.divider() st.header("Configurazione") st.write(f"**Ollama:** {settings.OLLAMA_HOST}") st.write(f"**Modello:** {settings.OLLAMA_MODEL}") st.write(f"**Embedding:** {settings.OLLAMA_EMBED_MODEL}") if st.button("Test Connessione Ollama"): with st.spinner("Testing..."): if ollama.is_available(): st.success("βœ… Ollama connesso!") else: st.error("❌ Ollama non raggiungibile") stats = rag.get_stats() st.metric("Documenti indicizzati", stats["total_chunks"]) st.divider() tab_imp, _ = st.tabs(["βš™οΈ Impostazioni"]) with tab_imp: st.header("βš™οΈ Impostazioni Provider LLM") current_provider = settings.LLM_PROVIDER all_providers = ["ollama", "opencode", "openrouter", "groq", "openai"] provider = st.selectbox( "Provider", options=all_providers, index=all_providers.index(current_provider) if current_provider in all_providers else 0 ) if provider == "ollama": st.info("🏠 **Ollama** - Locale/On-premise") ollama_host = st.text_input("Ollama Host", value=settings.OLLAMA_HOST) ollama_models = ollama.list_models() or PROVIDER_MODELS["ollama"] model = st.selectbox("Modello", options=ollama_models, index=0) api_key = "" base_url = "" elif provider == "opencode": st.info("🧘 **OpenCode Zen**") api_key = st.text_input("API Key", value=settings.LLM_API_KEY or "", type="password") base_url = st.text_input("Base URL", value=settings.LLM_BASE_URL or PROVIDER_BASE_URLS["opencode"]) models = PROVIDER_MODELS.get(provider, []) model = st.selectbox("Modello", options=models, index=0) ollama_host = settings.OLLAMA_HOST else: st.info(f"🌐 **{provider.capitalize()}**") api_key = st.text_input("API Key", value=settings.LLM_API_KEY or "", type="password") default_url = PROVIDER_BASE_URLS.get(provider, "") base_url = st.text_input("Base URL", value=settings.LLM_BASE_URL or default_url) models = PROVIDER_MODELS.get(provider, []) model = st.selectbox("Modello", options=models, index=0) ollama_host = settings.OLLAMA_HOST col1, col2 = st.columns(2) with col1: if st.button("πŸ’Ύ Salva", type="primary"): save_settings(provider, model, api_key, base_url, ollama_host, model if provider == "ollama" else settings.OLLAMA_MODEL) st.success("βœ… Salvato!") with col2: if st.button("πŸ”„ Ricarica"): st.rerun() st.json({ "provider": settings.LLM_PROVIDER, "model": settings.LLM_MODEL or settings.OLLAMA_MODEL, }) if mode == "chat": with st.sidebar: st.divider() st.header("πŸ“„ Documenti") uploaded = st.file_uploader( "Carica documento", type=SUPPORTED_EXTENSIONS, accept_multiple_files=True ) if uploaded: for file in uploaded: if st.button(f"Indicizza: {file.name}", key=f"idx_{file.name}"): save_path = Path(settings.UPLOAD_DIR) / file.name save_path.write_bytes(file.getbuffer()) with st.spinner(f"Indicizzando {file.name}..."): try: result = rag.ingest_document(save_path) st.success(f"βœ… {result['chunks_created']} chunks") except Exception as e: st.error(f"❌ {e}") stats = rag.get_stats() st.metric("Chunks indicizzati", stats["total_chunks"]) st.header("πŸ’¬ Chat con i tuoi documenti") if "messages" not in st.session_state: st.session_state.messages = [] for msg in st.session_state.messages: with st.chat_message(msg["role"]): st.write(msg["content"]) if "sources" in msg: with st.expander("πŸ“š Fonti"): for src in msg["sources"]: st.write(f"- **{src['source']}**" + (f" (pag. {src['page']})" if src['page'] else "")) if prompt := st.chat_input("Fai una domanda..."): log.info(f"Chat prompt: {prompt[:50]}...") st.session_state.messages.append({"role": "user", "content": prompt}) with st.chat_message("user"): st.write(prompt) with st.chat_message("assistant"): with st.spinner("Elaborazione in corso..."): result = agent.process(prompt, mode="chat") st.write(result["answer"]) if result.get("sources"): with st.expander("πŸ“š Fonti"): for src in result["sources"]: st.write(f"- **{src['source']}**" + (f" (pag. {src['page']})" if src['page'] else "") + f" - Score: {src['score']:.2f}") st.caption(src["excerpt"]) st.session_state.messages.append({ "role": "assistant", "content": result["answer"], "sources": result.get("sources", []) }) elif mode == "lettera": with st.sidebar: st.divider() st.header("πŸ“„ Documenti") uploaded = st.file_uploader( "Carica documento", type=SUPPORTED_EXTENSIONS, accept_multiple_files=True ) if uploaded: for file in uploaded: if st.button(f"Indicizza: {file.name}", key=f"idx_{file.name}"): save_path = Path(settings.UPLOAD_DIR) / file.name save_path.write_bytes(file.getbuffer()) with st.spinner(f"Indicizzando {file.name}..."): try: result = rag.ingest_document(save_path) st.success(f"βœ… {result['chunks_created']} chunks") except Exception as e: st.error(f"❌ {e}") stats = rag.get_stats() st.metric("Chunks indicizzati", stats["total_chunks"]) st.header("βœ‰οΈ Genera Lettera") topic = st.text_area("Argomento della lettera", placeholder="Es: Richiesta di patrocinio per evento annuale...") context = st.text_area("Contesto aggiuntivo (opzionale)", placeholder="Dettagli specifici, destinatario, tono...") col1, col2 = st.columns([1, 1]) with col1: if st.button("βœ‰οΈ Genera Lettera", type="primary", use_container_width=True): if not topic: st.warning("Inserisci un argomento") else: with st.spinner("Generazione in corso..."): result = agent.process(topic, mode="lettera", context=context) st.session_state["last_letter"] = result["answer"] with col2: if st.button("πŸ—‘οΈ Pulisci", use_container_width=True): if "last_letter" in st.session_state: del st.session_state["last_letter"] st.rerun() if "last_letter" in st.session_state: st.divider() st.subheader("Lettera generata") st.text_area("", st.session_state["last_letter"], height=400, key="letter_output") c1, c2 = st.columns(2) with c1: st.button("πŸ“‹ Copia", on_click=lambda: st.write("Copiato!")) with c2: st.button("πŸ’Ύ Salva") elif mode == "progetto": with st.sidebar: st.divider() st.header("πŸ“„ Documenti") uploaded = st.file_uploader( "Carica documento", type=SUPPORTED_EXTENSIONS, accept_multiple_files=True ) if uploaded: for file in uploaded: if st.button(f"Indicizza: {file.name}", key=f"idx_{file.name}"): save_path = Path(settings.UPLOAD_DIR) / file.name save_path.write_bytes(file.getbuffer()) with st.spinner(f"Indicizzando {file.name}..."): try: result = rag.ingest_document(save_path) st.success(f"βœ… {result['chunks_created']} chunks") except Exception as e: st.error(f"❌ {e}") stats = rag.get_stats() st.metric("Chunks indicizzati", stats["total_chunks"]) st.header("πŸ“‹ Genera Proposta Progetto") title = st.text_input("Titolo del progetto", placeholder="Es: Insieme senza confini") description = st.text_area("Descrizione", placeholder="Descrivi l'obiettivo, il target, le attivitΓ  previste...") col1, col2 = st.columns([1, 1]) with col1: if st.button("πŸ“‹ Genera Proposta", type="primary", use_container_width=True): if not title or not description: st.warning("Inserisci titolo e descrizione") else: with st.spinner("Generazione in corso..."): result = agent.process(description, mode="progetto", context=title) st.session_state["last_project"] = result["answer"] with col2: if st.button("πŸ—‘οΈ Pulisci", use_container_width=True): if "last_project" in st.session_state: del st.session_state["last_project"] st.rerun() if "last_project" in st.session_state: st.divider() st.subheader("Proposta generata") st.text_area("", st.session_state["last_project"], height=500, key="project_output") c1, c2, c3 = st.columns(3) with c1: st.button("πŸ“‹ Copia") with c2: st.button("πŸ’Ύ Salva") with c3: st.button("πŸ“§ Email")