Files

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