Files
RAGGER/app.py
T

401 lines
15 KiB
Python

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")