diff --git a/.chainlit/config.toml b/.chainlit/config.toml
index f192c501..7b288140 100644
--- a/.chainlit/config.toml
+++ b/.chainlit/config.toml
@@ -24,7 +24,7 @@ allow_origins = ["*"]
[features]
# Process and display HTML in messages. This can be a security risk (see https://stackoverflow.com/questions/19603097/why-is-it-dangerous-to-render-user-generated-html-or-javascript)
-unsafe_allow_html = false
+unsafe_allow_html = true
# Process and display mathematical expressions. This can clash with "$" characters in messages.
latex = false
@@ -57,7 +57,7 @@ reaction_on_message_received = false
# 3. For specific file extensions:
# accept = { "application/octet-stream" = [".xyz", ".pdb"] }
# Note: Using "*/*" is not recommended as it may cause browser warnings
- accept = ["*/*"]
+ accept = ["*"]
max_files = 20
max_size_mb = 500
@@ -86,11 +86,11 @@ reaction_on_message_received = false
[UI]
# Name of the assistant.
-name = "Assistant"
+name = "Ai Station DFFM"
-# default_theme = "dark"
+default_theme = "dark"
-# layout = "wide"
+layout = "wide"
default_sidebar_state = "open"
@@ -104,6 +104,14 @@ cot = "full"
# The CSS file can be served from the public directory or via an external link.
# custom_css = "/public/test.css"
+# CSS personalizzato
+custom_css = "/public/custom.css"
+
+# Logo custom
+[UI.theme]
+primary_color = "#0066CC" # Colore brand
+background_color = "#1a1a1a"
+
# Specify additional attributes for a custom CSS file
# custom_css_attributes = "media=\"print\""
diff --git a/.files/0df80e24-55a7-4d81-b20e-c048d4dc9de3/6329ce5b-e0b9-409e-bf1b-740f22412f61 b/.files/0df80e24-55a7-4d81-b20e-c048d4dc9de3/6329ce5b-e0b9-409e-bf1b-740f22412f61
new file mode 100644
index 00000000..d8dfb5c4
Binary files /dev/null and b/.files/0df80e24-55a7-4d81-b20e-c048d4dc9de3/6329ce5b-e0b9-409e-bf1b-740f22412f61 differ
diff --git a/.files/3656e301-423c-41a3-95cc-aa9c1d0a6d60/9ea4f57e-fd04-417a-a467-f5594c405972 b/.files/3656e301-423c-41a3-95cc-aa9c1d0a6d60/9ea4f57e-fd04-417a-a467-f5594c405972
new file mode 100644
index 00000000..1e116396
Binary files /dev/null and b/.files/3656e301-423c-41a3-95cc-aa9c1d0a6d60/9ea4f57e-fd04-417a-a467-f5594c405972 differ
diff --git a/.files/47dcfcf5-095b-4b0f-8895-b52859a242cf/ac4be05d-3c02-4f6c-8ac7-bdbfdcb437c7.pdf b/.files/47dcfcf5-095b-4b0f-8895-b52859a242cf/ac4be05d-3c02-4f6c-8ac7-bdbfdcb437c7.pdf
new file mode 100644
index 00000000..562d9231
Binary files /dev/null and b/.files/47dcfcf5-095b-4b0f-8895-b52859a242cf/ac4be05d-3c02-4f6c-8ac7-bdbfdcb437c7.pdf differ
diff --git a/.files/6e654425-7858-497f-ba4c-dcc53af4d26d/42a80328-c46c-4168-bf1e-6326d354b9d9 b/.files/6e654425-7858-497f-ba4c-dcc53af4d26d/42a80328-c46c-4168-bf1e-6326d354b9d9
new file mode 100644
index 00000000..d8dfb5c4
Binary files /dev/null and b/.files/6e654425-7858-497f-ba4c-dcc53af4d26d/42a80328-c46c-4168-bf1e-6326d354b9d9 differ
diff --git a/.files/88144b6d-1339-4122-823c-f79b205993ae/16115c1a-aad0-4bc7-bf60-057af05361fb.pdf b/.files/88144b6d-1339-4122-823c-f79b205993ae/16115c1a-aad0-4bc7-bf60-057af05361fb.pdf
new file mode 100644
index 00000000..562d9231
Binary files /dev/null and b/.files/88144b6d-1339-4122-823c-f79b205993ae/16115c1a-aad0-4bc7-bf60-057af05361fb.pdf differ
diff --git a/.files/88144b6d-1339-4122-823c-f79b205993ae/87b7394a-6fc7-4459-b17d-855b5de71630.pdf b/.files/88144b6d-1339-4122-823c-f79b205993ae/87b7394a-6fc7-4459-b17d-855b5de71630.pdf
new file mode 100644
index 00000000..ff70ae8a
Binary files /dev/null and b/.files/88144b6d-1339-4122-823c-f79b205993ae/87b7394a-6fc7-4459-b17d-855b5de71630.pdf differ
diff --git a/.files/9fe2fbbc-be5f-4b83-b69c-aedfb9df25a8/01ffba51-a4ca-40fb-b732-80f5725a1fb9.pdf b/.files/9fe2fbbc-be5f-4b83-b69c-aedfb9df25a8/01ffba51-a4ca-40fb-b732-80f5725a1fb9.pdf
new file mode 100644
index 00000000..00b03c2c
Binary files /dev/null and b/.files/9fe2fbbc-be5f-4b83-b69c-aedfb9df25a8/01ffba51-a4ca-40fb-b732-80f5725a1fb9.pdf differ
diff --git a/.files/9fe2fbbc-be5f-4b83-b69c-aedfb9df25a8/f10b54f7-8dca-4665-b442-9918c40bba39 b/.files/9fe2fbbc-be5f-4b83-b69c-aedfb9df25a8/f10b54f7-8dca-4665-b442-9918c40bba39
new file mode 100644
index 00000000..5b9189d0
Binary files /dev/null and b/.files/9fe2fbbc-be5f-4b83-b69c-aedfb9df25a8/f10b54f7-8dca-4665-b442-9918c40bba39 differ
diff --git a/.files/aa8e686d-0d34-421e-be02-0ec587b14adc/3306e6a4-fb7b-4cb7-8518-8b237a712667.pdf b/.files/aa8e686d-0d34-421e-be02-0ec587b14adc/3306e6a4-fb7b-4cb7-8518-8b237a712667.pdf
new file mode 100644
index 00000000..562d9231
Binary files /dev/null and b/.files/aa8e686d-0d34-421e-be02-0ec587b14adc/3306e6a4-fb7b-4cb7-8518-8b237a712667.pdf differ
diff --git a/.files/ac1bab01-7ff6-4e50-ab1d-30ddf6cd3d59/17b9be9b-c319-4406-936d-674589ad63a7 b/.files/ac1bab01-7ff6-4e50-ab1d-30ddf6cd3d59/17b9be9b-c319-4406-936d-674589ad63a7
new file mode 100644
index 00000000..2de9199b
Binary files /dev/null and b/.files/ac1bab01-7ff6-4e50-ab1d-30ddf6cd3d59/17b9be9b-c319-4406-936d-674589ad63a7 differ
diff --git a/.files/b7e1daf3-eca7-44c4-a1b1-32f3d0bf03f6/6d477977-149d-4c97-ad86-2ced01cdc811.pdf b/.files/b7e1daf3-eca7-44c4-a1b1-32f3d0bf03f6/6d477977-149d-4c97-ad86-2ced01cdc811.pdf
new file mode 100644
index 00000000..ff70ae8a
Binary files /dev/null and b/.files/b7e1daf3-eca7-44c4-a1b1-32f3d0bf03f6/6d477977-149d-4c97-ad86-2ced01cdc811.pdf differ
diff --git a/.files/bf4072da-0c48-4d57-840e-ab845afa5ff0/4577193b-9fe2-443f-8587-ab59faa2c122.png b/.files/bf4072da-0c48-4d57-840e-ab845afa5ff0/4577193b-9fe2-443f-8587-ab59faa2c122.png
new file mode 100644
index 00000000..88b07644
Binary files /dev/null and b/.files/bf4072da-0c48-4d57-840e-ab845afa5ff0/4577193b-9fe2-443f-8587-ab59faa2c122.png differ
diff --git a/.files/d8a2ce01-1792-4d49-b580-57aa8216d112/55078a22-7b82-4e20-80c8-79a8d53f37f8.pdf b/.files/d8a2ce01-1792-4d49-b580-57aa8216d112/55078a22-7b82-4e20-80c8-79a8d53f37f8.pdf
new file mode 100644
index 00000000..562d9231
Binary files /dev/null and b/.files/d8a2ce01-1792-4d49-b580-57aa8216d112/55078a22-7b82-4e20-80c8-79a8d53f37f8.pdf differ
diff --git a/.files/f49178c6-be52-47f5-93c1-9114b9a67a29/d65f66a2-2673-4cc2-9b84-2ad316a17f75 b/.files/f49178c6-be52-47f5-93c1-9114b9a67a29/d65f66a2-2673-4cc2-9b84-2ad316a17f75
new file mode 100644
index 00000000..1e116396
Binary files /dev/null and b/.files/f49178c6-be52-47f5-93c1-9114b9a67a29/d65f66a2-2673-4cc2-9b84-2ad316a17f75 differ
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/013a0fb6-459d-462f-827c-e0d9c227bfa5/Fatture 2025.xlsx b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/013a0fb6-459d-462f-827c-e0d9c227bfa5/Fatture 2025.xlsx
new file mode 100644
index 00000000..1e116396
Binary files /dev/null and b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/013a0fb6-459d-462f-827c-e0d9c227bfa5/Fatture 2025.xlsx differ
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/16115c1a-aad0-4bc7-bf60-057af05361fb/esempio manuale Omron.pdf b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/16115c1a-aad0-4bc7-bf60-057af05361fb/esempio manuale Omron.pdf
new file mode 100644
index 00000000..562d9231
Binary files /dev/null and b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/16115c1a-aad0-4bc7-bf60-057af05361fb/esempio manuale Omron.pdf differ
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/3306e6a4-fb7b-4cb7-8518-8b237a712667/esempio manuale Omron.pdf b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/3306e6a4-fb7b-4cb7-8518-8b237a712667/esempio manuale Omron.pdf
new file mode 100644
index 00000000..562d9231
Binary files /dev/null and b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/3306e6a4-fb7b-4cb7-8518-8b237a712667/esempio manuale Omron.pdf differ
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/3f168cf3-0444-48da-b16c-90c095f159c7/b71188b4-ae12-4321-8ada-e3f858d851e4.png b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/3f168cf3-0444-48da-b16c-90c095f159c7/b71188b4-ae12-4321-8ada-e3f858d851e4.png
new file mode 100644
index 00000000..8e1034cf
Binary files /dev/null and b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/3f168cf3-0444-48da-b16c-90c095f159c7/b71188b4-ae12-4321-8ada-e3f858d851e4.png differ
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/42a80328-c46c-4168-bf1e-6326d354b9d9/Alarms Table TCO2307.xlsx b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/42a80328-c46c-4168-bf1e-6326d354b9d9/Alarms Table TCO2307.xlsx
new file mode 100644
index 00000000..d8dfb5c4
Binary files /dev/null and b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/42a80328-c46c-4168-bf1e-6326d354b9d9/Alarms Table TCO2307.xlsx differ
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/55078a22-7b82-4e20-80c8-79a8d53f37f8/esempio manuale Omron.pdf b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/55078a22-7b82-4e20-80c8-79a8d53f37f8/esempio manuale Omron.pdf
new file mode 100644
index 00000000..562d9231
Binary files /dev/null and b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/55078a22-7b82-4e20-80c8-79a8d53f37f8/esempio manuale Omron.pdf differ
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/6329ce5b-e0b9-409e-bf1b-740f22412f61/Alarms Table TCO2307.xlsx b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/6329ce5b-e0b9-409e-bf1b-740f22412f61/Alarms Table TCO2307.xlsx
new file mode 100644
index 00000000..d8dfb5c4
Binary files /dev/null and b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/6329ce5b-e0b9-409e-bf1b-740f22412f61/Alarms Table TCO2307.xlsx differ
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/6d477977-149d-4c97-ad86-2ced01cdc811/Dichiarazion_Degenza.pdf b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/6d477977-149d-4c97-ad86-2ced01cdc811/Dichiarazion_Degenza.pdf
new file mode 100644
index 00000000..ff70ae8a
Binary files /dev/null and b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/6d477977-149d-4c97-ad86-2ced01cdc811/Dichiarazion_Degenza.pdf differ
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/7a4ce70a-2f2d-4980-83e9-1ff64ca67195/Alarms Table TCO2307.xlsx b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/7a4ce70a-2f2d-4980-83e9-1ff64ca67195/Alarms Table TCO2307.xlsx
new file mode 100644
index 00000000..d8dfb5c4
Binary files /dev/null and b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/7a4ce70a-2f2d-4980-83e9-1ff64ca67195/Alarms Table TCO2307.xlsx differ
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/87141a69-74d3-48ac-9e11-33ce513fb189/b71188b4-ae12-4321-8ada-e3f858d851e4.png b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/87141a69-74d3-48ac-9e11-33ce513fb189/b71188b4-ae12-4321-8ada-e3f858d851e4.png
new file mode 100644
index 00000000..8e1034cf
Binary files /dev/null and b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/87141a69-74d3-48ac-9e11-33ce513fb189/b71188b4-ae12-4321-8ada-e3f858d851e4.png differ
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/87b7394a-6fc7-4459-b17d-855b5de71630/Dichiarazion_Degenza.pdf b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/87b7394a-6fc7-4459-b17d-855b5de71630/Dichiarazion_Degenza.pdf
new file mode 100644
index 00000000..ff70ae8a
Binary files /dev/null and b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/87b7394a-6fc7-4459-b17d-855b5de71630/Dichiarazion_Degenza.pdf differ
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/9a46bb8a-bf71-4fc5-9ec6-ba1408e6f8ce/Alarms Table TCO2307.xlsx b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/9a46bb8a-bf71-4fc5-9ec6-ba1408e6f8ce/Alarms Table TCO2307.xlsx
new file mode 100644
index 00000000..5367ad53
Binary files /dev/null and b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/9a46bb8a-bf71-4fc5-9ec6-ba1408e6f8ce/Alarms Table TCO2307.xlsx differ
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/9ea4f57e-fd04-417a-a467-f5594c405972/Fatture 2025.xlsx b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/9ea4f57e-fd04-417a-a467-f5594c405972/Fatture 2025.xlsx
new file mode 100644
index 00000000..1e116396
Binary files /dev/null and b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/9ea4f57e-fd04-417a-a467-f5594c405972/Fatture 2025.xlsx differ
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/a98d6f7b-5275-452d-92c1-7e3968dcb526/app.py b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/a98d6f7b-5275-452d-92c1-7e3968dcb526/app.py
new file mode 100644
index 00000000..73f2cb95
--- /dev/null
+++ b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/a98d6f7b-5275-452d-92c1-7e3968dcb526/app.py
@@ -0,0 +1,463 @@
+import os
+import re
+import uuid
+import shutil
+import requests
+import time
+import json
+from datetime import datetime
+from typing import Optional, Dict, List, Any
+import chainlit as cl
+import ollama
+from docling.document_converter import DocumentConverter
+from qdrant_client import AsyncQdrantClient
+# CORREZIONE IMPORT: Importiamo le classi necessarie direttamente dalla libreria
+from qdrant_client.models import PointStruct, Distance, VectorParams, SparseVectorParams, Prefetch
+from chainlit.data.sql_alchemy import SQLAlchemyDataLayer
+from chainlit.types import ThreadDict
+from functools import lru_cache
+
+# === FIX IMPORT ROBUSTO ===
+try:
+ from chainlit.data.storage_clients import BaseStorageClient
+except ImportError:
+ try:
+ from chainlit.data.base import BaseStorageClient
+ except ImportError:
+ from chainlit.data.storage_clients.base import BaseStorageClient
+
+# === CONFIGURAZIONE ===
+DATABASE_URL = os.getenv("DATABASE_URL", "postgresql+asyncpg://ai_user:secure_password_here@postgres:5432/ai_station")
+OLLAMA_URL = os.getenv("OLLAMA_URL", "http://192.168.1.243:11434")
+QDRANT_URL = os.getenv("QDRANT_URL", "http://qdrant:6333")
+BGE_API_URL = os.getenv("BGE_API_URL", "http://192.168.1.243:8001/embed")
+
+VISION_MODEL = "minicpm-v"
+DEFAULT_TEXT_MODEL = "glm-4.6:cloud"
+
+WORKSPACES_DIR = "./workspaces"
+STORAGE_DIR = "./.files"
+
+os.makedirs(STORAGE_DIR, exist_ok=True)
+os.makedirs(WORKSPACES_DIR, exist_ok=True)
+
+# === MAPPING UTENTI ===
+USER_PROFILES = {
+ "giuseppe@defranceschi.pro": { "role": "admin", "name": "Giuseppe", "workspace": "admin_workspace", "rag_collection": "admin_docs", "capabilities": ["debug", "all"], "show_code": True },
+ "giuseppe.defranceschi@gmail.com": { "role": "admin", "name": "Giuseppe", "workspace": "admin_workspace", "rag_collection": "admin_docs", "capabilities": ["debug", "all"], "show_code": True },
+ "federica.tecchio@gmail.com": { "role": "business", "name": "Federica", "workspace": "business_workspace", "rag_collection": "contabilita", "capabilities": ["basic_chat"], "show_code": False },
+ "riccardob545@gmail.com": { "role": "engineering", "name": "Riccardo", "workspace": "engineering_workspace", "rag_collection": "engineering_docs", "capabilities": ["code"], "show_code": True },
+ "giuliadefranceschi05@gmail.com": { "role": "architecture", "name": "Giulia", "workspace": "architecture_workspace", "rag_collection": "architecture_manuals", "capabilities": ["visual"], "show_code": False }
+}
+
+# === STORAGE CLIENT ===
+class LocalStorageClient(BaseStorageClient):
+ def __init__(self, storage_path: str):
+ self.storage_path = storage_path
+ os.makedirs(storage_path, exist_ok=True)
+ async def upload_file(self, object_key: str, data: bytes, mime: str = "application/octet-stream", overwrite: bool = True) -> Dict[str, str]:
+ file_path = os.path.join(self.storage_path, object_key)
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
+ with open(file_path, "wb") as f: f.write(data)
+ return {"object_key": object_key, "url": f"/files/{object_key}"}
+ async def get_read_url(self, object_key: str) -> str: return f"/files/{object_key}"
+ async def delete_file(self, object_key: str) -> bool:
+ path = os.path.join(self.storage_path, object_key)
+ if os.path.exists(path): os.remove(path); return True
+ return False
+ async def close(self): pass
+
+@cl.data_layer
+def get_data_layer():
+ return SQLAlchemyDataLayer(conninfo=DATABASE_URL, storage_provider=LocalStorageClient(STORAGE_DIR))
+
+# === OAUTH & UTILS ===
+@cl.oauth_callback
+def oauth_callback(provider_id: str, token: str, raw_user_data: Dict[str, str], default_user: cl.User) -> Optional[cl.User]:
+ if provider_id == "google":
+ email = raw_user_data.get("email", "").lower()
+ profile = USER_PROFILES.get(email, USER_PROFILES.get("guest", {"role": "guest", "name": "Guest", "workspace": "guest", "rag_collection": "public", "show_code": False}))
+ default_user.metadata.update({"role": profile["role"], "workspace": profile["workspace"], "rag_collection": profile["rag_collection"], "show_code": profile["show_code"], "display_name": profile["name"]})
+ return default_user
+ return default_user
+
+def create_workspace(workspace_name: str) -> str:
+ path = os.path.join(WORKSPACES_DIR, workspace_name)
+ os.makedirs(path, exist_ok=True)
+ return path
+
+
+# === CORE: DOCLING ===
+def process_file_with_docling(file_path: str) -> str:
+ try:
+ converter = DocumentConverter()
+ result = converter.convert(file_path)
+ return result.document.export_to_markdown()
+ except Exception as e:
+ print(f"❌ Docling Error: {e}")
+ return ""
+
+# === CORE: BGE-M3 CLIENT ===
+def get_bge_embeddings(text: str) -> Optional[Dict[str, Any]]:
+ try:
+ payload = {"texts": [text[:8000]]}
+ response = requests.post(BGE_API_URL, json=payload, timeout=30)
+ response.raise_for_status()
+ data = response.json().get("data", [])
+ if data:
+ return data[0]
+ return None
+ except Exception as e:
+ print(f"❌ BGE API Error: {e}")
+ return None
+
+# === CORE: QDRANT ===
+async def ensure_collection(collection_name: str):
+ client = AsyncQdrantClient(url=QDRANT_URL)
+ if not await client.collection_exists(collection_name):
+ await client.create_collection(
+ collection_name=collection_name,
+ vectors_config={"dense": VectorParams(size=1024, distance=Distance.COSINE)},
+ sparse_vectors_config={"sparse": SparseVectorParams()}
+ )
+
+async def index_document(file_name: str, content: str, collection_name: str):
+ await ensure_collection(collection_name)
+ client = AsyncQdrantClient(url=QDRANT_URL)
+
+ chunk_size = 2000
+ overlap = 200
+
+ points = []
+ for i in range(0, len(content), chunk_size - overlap):
+ chunk = content[i : i + chunk_size]
+ embedding_data = get_bge_embeddings(chunk)
+
+ if embedding_data:
+ points.append(PointStruct(
+ id=str(uuid.uuid4()),
+ vector={
+ "dense": embedding_data["dense"],
+ "sparse": embedding_data["sparse"]
+ },
+ payload={
+ "file_name": file_name,
+ "content": chunk,
+ "indexed_at": datetime.now().isoformat()
+ }
+ ))
+
+ if points:
+ await client.upsert(collection_name=collection_name, points=points)
+ return len(points)
+ return 0
+
+async def search_hybrid(query: str, collection_name: str, limit: int = 4) -> str:
+ client = AsyncQdrantClient(url=QDRANT_URL)
+ if not await client.collection_exists(collection_name): return ""
+
+ query_emb = get_bge_embeddings(query)
+ if not query_emb: return ""
+
+ # CORREZIONE QUI: Usiamo l'oggetto Prefetch importato correttamente
+ results = await client.query_points(
+ collection_name=collection_name,
+ prefetch=[
+ Prefetch(
+ query=query_emb["sparse"],
+ using="sparse",
+ limit=limit * 2
+ )
+ ],
+ query=query_emb["dense"],
+ using="dense",
+ limit=limit
+ )
+
+ context = []
+ for hit in results.points:
+ context.append(f"--- DA {hit.payload['file_name']} ---\n{hit.payload['content']}")
+
+ return "\n\n".join(context)
+
+# === Caching Embeddings ===
+@lru_cache(maxsize=1000)
+def get_bge_embeddings_cached(text: str):
+ """Cache per query ripetute"""
+ return get_bge_embeddings(text)
+
+# === CHAINLIT HANDLERS ===
+@cl.on_chat_start
+async def start():
+ # 1. Profilo utente
+ user = cl.user_session.get("user")
+ email = user.identifier if user else "guest"
+ profile = USER_PROFILES.get(email, USER_PROFILES["giuseppe@defranceschi.pro"])
+
+ cl.user_session.set("profile", profile)
+ create_workspace(profile["workspace"])
+
+ # 2. Badge HTML personalizzato
+ role_color = {
+ "admin": "#e74c3c",
+ "engineering": "#3498db",
+ "business": "#2ecc71",
+ "architecture": "#9b59b6",
+ }.get(profile["role"], "#95a5a6")
+
+ badge_html = f"""
+
+ 👤 {profile['name']} | 🔧 {profile['role'].upper()} | 📁 {profile['workspace']}
+
+ """
+ await cl.Message(content=badge_html).send()
+
+ # 3. Settings UI
+ settings = await cl.ChatSettings(
+ [
+ cl.input_widget.Slider(
+ id="top_k",
+ label="Numero Documenti RAG",
+ initial=4,
+ min=1,
+ max=10,
+ step=1,
+ ),
+ cl.input_widget.Select(
+ id="vision_detail",
+ label="Dettaglio Analisi Immagini",
+ values=["auto", "low", "high"],
+ initial_value="auto",
+ ),
+ cl.input_widget.TextInput(
+ id="system_instruction",
+ label="Istruzione Sistema Custom (opzionale)",
+ initial="",
+ placeholder="Es: Rispondi sempre in formato tecnico...",
+ ),
+ cl.input_widget.Select(
+ id="model",
+ label="Modello di Ragionamento",
+ values=[DEFAULT_TEXT_MODEL, "llama3.2", "mistral", "qwen2.5-coder:32b"],
+ initial_value=DEFAULT_TEXT_MODEL,
+ ),
+ cl.input_widget.Slider(
+ id="temperature",
+ label="Creatività (Temperatura)",
+ initial=0.3,
+ min=0,
+ max=1,
+ step=0.1,
+ ),
+ cl.input_widget.Switch(
+ id="rag_enabled",
+ label="Usa Conoscenza Documenti (RAG)",
+ initial=True,
+ ),
+ ]
+ ).send()
+
+ cl.user_session.set("settings", settings)
+
+ # 4. Messaggio iniziale (opzionale)
+ await cl.Message(
+ content=(
+ f"🚀 **Vision-RAG Hybrid System Online**\n"
+ f"Utente: {profile['name']} | Workspace: {profile['workspace']}\n"
+ f"Engine: Docling + BGE-M3 + {VISION_MODEL}"
+ )
+ ).send()
+
+
+ cl.user_session.set("settings", settings)
+
+ await cl.Message(f"🚀 **Vision-RAG Hybrid System Online**\nUtente: {profile['name']} | Workspace: {profile['workspace']}\nEngine: Docling + BGE-M3 + {VISION_MODEL}").send()
+
+@cl.on_settings_update
+async def setup_agent(settings):
+ cl.user_session.set("settings", settings)
+ await cl.Message(content=f"✅ Impostazioni aggiornate: Modello {settings['model']}").send()
+
+async def log_metrics(metrics: dict):
+ # Versione minima: log su stdout
+ print("[METRICS]", metrics)
+
+ # In futuro puoi:
+ # - salvarle in Postgres
+ # - mandarle a Prometheus / Grafana
+ # - scriverle su file JSON per analisi settimanale
+
+# - Resume Chat Handler
+
+@cl.on_chat_resume
+async def on_chat_resume(thread: ThreadDict):
+ """
+ Viene chiamato quando l'utente clicca 'Riprendi' su una chat archiviata.
+ Chainlit carica già i messaggi nella UI, qui puoi solo ripristinare la sessione.
+ """
+ # Se vuoi, puoi recuperare l'identifier dell’utente dal thread
+ user_identifier = thread.get("userIdentifier")
+ profile = USER_PROFILES.get(
+ user_identifier,
+ USER_PROFILES["giuseppe@defranceschi.pro"],
+ )
+ cl.user_session.set("profile", profile)
+
+ # Puoi anche ripristinare eventuale stato custom (es: impostazioni di default)
+ # oppure semplicemente salutare l’utente
+ await cl.Message(
+ content="👋 Bentornato! Possiamo riprendere da questa conversazione."
+ ).send()
+
+@cl.on_message
+async def main(message: cl.Message):
+ start_time = time.time()
+
+ profile = cl.user_session.get("profile")
+ settings = cl.user_session.get("settings", {})
+
+ selected_model = settings.get("model", DEFAULT_TEXT_MODEL)
+ temperature = settings.get("temperature", 0.3)
+ rag_enabled = settings.get("rag_enabled", True)
+
+ workspace = create_workspace(profile["workspace"])
+
+ images_for_vision = []
+ doc_context = ""
+ rag_context = "" # ← la inizializzi qui, così esiste sempre
+
+ # 1. GESTIONE FILE
+ if message.elements:
+ for element in message.elements:
+ file_path = os.path.join(workspace, element.name)
+ shutil.copy(element.path, file_path)
+
+ if "image" in element.mime:
+ images_for_vision.append(file_path)
+ msg_img = cl.Message(
+ content=f"👁️ Analizzo immagine **{element.name}** con {VISION_MODEL}..."
+ )
+ await msg_img.send()
+
+ with open(file_path, "rb") as img_file:
+ img_bytes = img_file.read()
+
+ client_sync = ollama.Client(host=OLLAMA_URL)
+ res = client_sync.chat(
+ model=VISION_MODEL,
+ messages=[{
+ "role": "user",
+ "content": (
+ "Analizza questa immagine tecnica. Trascrivi testi, codici "
+ "e descrivi diagrammi o tabelle in dettaglio."
+ ),
+ "images": [img_bytes],
+ }],
+ )
+ desc = res["message"]["content"]
+ doc_context += f"\n\n[DESCRIZIONE IMMAGINE {element.name}]:\n{desc}"
+ msg_img.content = f"✅ Immagine analizzata:\n{desc[:200]}..."
+ await msg_img.update()
+
+ elif element.name.endswith((".pdf", ".docx")):
+ msg_doc = cl.Message(
+ content=f"📄 Leggo **{element.name}** con Docling (tabelle/formule)..."
+ )
+ await msg_doc.send()
+
+ markdown_content = process_file_with_docling(file_path)
+ if markdown_content:
+ chunks = await index_document(
+ element.name, markdown_content, profile["rag_collection"]
+ )
+ msg_doc.content = (
+ f"✅ **{element.name}**: Convertito e salvato {chunks} "
+ "frammenti nel DB vettoriale."
+ )
+ doc_context += (
+ f"\n\n[CONTENUTO FILE {element.name}]:\n"
+ f"{markdown_content[:1000]}..."
+ )
+ else:
+ msg_doc.content = f"❌ Errore lettura {element.name}"
+ await msg_doc.update()
+
+ # 2. RAG RETRIEVAL
+ if rag_enabled and not images_for_vision:
+ rag_context = await search_hybrid(
+ message.content, profile["rag_collection"]
+ )
+
+ final_context = ""
+ if rag_context:
+ final_context += f"CONTESTO RAG:\n{rag_context}\n"
+ if doc_context:
+ final_context += f"CONTESTO SESSIONE CORRENTE:\n{doc_context}\n"
+
+ system_prompt = (
+ "Sei un assistente tecnico esperto. Usa il contesto fornito "
+ "(incluso Markdown di tabelle e descrizioni immagini) per "
+ "rispondere con precisione. Cita i documenti fonte."
+ )
+
+ msg = cl.Message(content="")
+ await msg.send()
+
+ error = None
+
+ # 3. GENERAZIONE
+ try:
+ client_async = ollama.AsyncClient(host=OLLAMA_URL)
+ stream = await client_async.chat(
+ model=selected_model,
+ messages=[
+ {"role": "system", "content": system_prompt},
+ {
+ "role": "user",
+ "content": f"Domanda: {message.content}\n\n{final_context}",
+ },
+ ],
+ options={"temperature": temperature},
+ stream=True,
+ )
+
+ async for chunk in stream:
+ content = chunk["message"]["content"]
+ await msg.stream_token(content)
+ await msg.update()
+ except Exception as e:
+ error = str(e)
+ await msg.stream_token(f"❌ Errore AI: {error}")
+ await msg.update()
+
+ # 4. SALVATAGGIO CODICE
+ if profile["show_code"]:
+ code_blocks = re.findall(r"``````", msg.content, re.DOTALL)
+ if code_blocks:
+ for i, code in enumerate(code_blocks):
+ fname = f"script_{datetime.now().strftime('%H%M%S')}_{i}.py"
+ with open(os.path.join(workspace, fname), "w") as f:
+ f.write(code.strip())
+ await cl.Message(
+ content=f"💾 Script salvato: `{fname}`"
+ ).send()
+
+ # 5. METRICHE (ALLA FINE)
+ elapsed = time.time() - start_time
+
+ # Se rag_context è una stringa concatenata, puoi stimare i "rag_hits"
+ # contando i separatori che usi in search_hybrid (es. '--- DA ')
+ if rag_context:
+ rag_hits = rag_context.count("--- DA ")
+ else:
+ rag_hits = 0
+
+ metrics = {
+ "response_time": elapsed,
+ "rag_hits": rag_hits,
+ "model": selected_model,
+ "user_role": profile["role"],
+ "error": error,
+ }
+
+ await log_metrics(metrics)
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/cb54f22c-d8c6-406b-9290-00e62e42b515/Alarms Table TCO2307.xlsx b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/cb54f22c-d8c6-406b-9290-00e62e42b515/Alarms Table TCO2307.xlsx
new file mode 100644
index 00000000..d8dfb5c4
Binary files /dev/null and b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/cb54f22c-d8c6-406b-9290-00e62e42b515/Alarms Table TCO2307.xlsx differ
diff --git a/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/f8c60587-20a4-4927-be0f-a487f707ae17/Alarms Table TCO2307.xlsx b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/f8c60587-20a4-4927-be0f-a487f707ae17/Alarms Table TCO2307.xlsx
new file mode 100644
index 00000000..d8dfb5c4
Binary files /dev/null and b/.files/fe508c80-f90f-471d-a7f8-fc98d4a018d6/f8c60587-20a4-4927-be0f-a487f707ae17/Alarms Table TCO2307.xlsx differ
diff --git a/.files/unknown/ac4be05d-3c02-4f6c-8ac7-bdbfdcb437c7/esempio manuale Omron.pdf b/.files/unknown/ac4be05d-3c02-4f6c-8ac7-bdbfdcb437c7/esempio manuale Omron.pdf
new file mode 100644
index 00000000..562d9231
Binary files /dev/null and b/.files/unknown/ac4be05d-3c02-4f6c-8ac7-bdbfdcb437c7/esempio manuale Omron.pdf differ
diff --git a/.files/unknown/d65f66a2-2673-4cc2-9b84-2ad316a17f75/Fatture 2025.xlsx b/.files/unknown/d65f66a2-2673-4cc2-9b84-2ad316a17f75/Fatture 2025.xlsx
new file mode 100644
index 00000000..1e116396
Binary files /dev/null and b/.files/unknown/d65f66a2-2673-4cc2-9b84-2ad316a17f75/Fatture 2025.xlsx differ
diff --git a/Dockerfile b/Dockerfile
index 0ff6ac6b..6d8ae60e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,9 +3,12 @@ FROM python:3.11-slim
WORKDIR /app
# Installa dipendenze sistema
+# Aggiunte libgl1 e libglib2.0-0 per il supporto Docling/CV2
RUN apt-get update && apt-get install -y \
gcc \
postgresql-client \
+ libgl1 \
+ libglib2.0-0 \
&& rm -rf /var/lib/apt/lists/*
# Copia requirements e installa
diff --git a/app.py b/app.py
index 230d6b51..73f2cb95 100644
--- a/app.py
+++ b/app.py
@@ -2,17 +2,22 @@ import os
import re
import uuid
import shutil
+import requests
+import time
+import json
from datetime import datetime
-from typing import Optional, Dict, List
+from typing import Optional, Dict, List, Any
import chainlit as cl
import ollama
-import fitz # PyMuPDF
+from docling.document_converter import DocumentConverter
from qdrant_client import AsyncQdrantClient
-from qdrant_client.models import PointStruct, Distance, VectorParams
+# CORREZIONE IMPORT: Importiamo le classi necessarie direttamente dalla libreria
+from qdrant_client.models import PointStruct, Distance, VectorParams, SparseVectorParams, Prefetch
from chainlit.data.sql_alchemy import SQLAlchemyDataLayer
+from chainlit.types import ThreadDict
+from functools import lru_cache
# === FIX IMPORT ROBUSTO ===
-# Gestisce le differenze tra le versioni di Chainlit 2.x
try:
from chainlit.data.storage_clients import BaseStorageClient
except ImportError:
@@ -25,320 +30,434 @@ except ImportError:
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql+asyncpg://ai_user:secure_password_here@postgres:5432/ai_station")
OLLAMA_URL = os.getenv("OLLAMA_URL", "http://192.168.1.243:11434")
QDRANT_URL = os.getenv("QDRANT_URL", "http://qdrant:6333")
+BGE_API_URL = os.getenv("BGE_API_URL", "http://192.168.1.243:8001/embed")
+
+VISION_MODEL = "minicpm-v"
+DEFAULT_TEXT_MODEL = "glm-4.6:cloud"
+
WORKSPACES_DIR = "./workspaces"
STORAGE_DIR = "./.files"
os.makedirs(STORAGE_DIR, exist_ok=True)
os.makedirs(WORKSPACES_DIR, exist_ok=True)
-# === MAPPING UTENTI E RUOLI ===
+# === MAPPING UTENTI ===
USER_PROFILES = {
- "giuseppe@defranceschi.pro": {
- "role": "admin",
- "name": "Giuseppe",
- "workspace": "admin_workspace",
- "rag_collection": "admin_docs",
- "capabilities": ["debug", "system_prompts", "user_management", "all_models"],
- "show_code": True
- },
- "federica.tecchio@gmail.com": {
- "role": "business",
- "name": "Federica",
- "workspace": "business_workspace",
- "rag_collection": "contabilita",
- "capabilities": ["pdf_upload", "basic_chat"],
- "show_code": False
- },
- "giuseppe.defranceschi@gmail.com": {
- "role": "admin",
- "name": "Giuseppe",
- "workspace": "admin_workspace",
- "rag_collection": "admin_docs",
- "capabilities": ["debug", "system_prompts", "user_management", "all_models"],
- "show_code": True
- },
- "riccardob545@gmail.com": {
- "role": "engineering",
- "name": "Riccardo",
- "workspace": "engineering_workspace",
- "rag_collection": "engineering_docs",
- "capabilities": ["code_execution", "data_viz", "advanced_chat"],
- "show_code": True
- },
- "giuliadefranceschi05@gmail.com": {
- "role": "architecture",
- "name": "Giulia",
- "workspace": "architecture_workspace",
- "rag_collection": "architecture_manuals",
- "capabilities": ["visual_chat", "pdf_upload", "image_gen"],
- "show_code": False
- }
+ "giuseppe@defranceschi.pro": { "role": "admin", "name": "Giuseppe", "workspace": "admin_workspace", "rag_collection": "admin_docs", "capabilities": ["debug", "all"], "show_code": True },
+ "giuseppe.defranceschi@gmail.com": { "role": "admin", "name": "Giuseppe", "workspace": "admin_workspace", "rag_collection": "admin_docs", "capabilities": ["debug", "all"], "show_code": True },
+ "federica.tecchio@gmail.com": { "role": "business", "name": "Federica", "workspace": "business_workspace", "rag_collection": "contabilita", "capabilities": ["basic_chat"], "show_code": False },
+ "riccardob545@gmail.com": { "role": "engineering", "name": "Riccardo", "workspace": "engineering_workspace", "rag_collection": "engineering_docs", "capabilities": ["code"], "show_code": True },
+ "giuliadefranceschi05@gmail.com": { "role": "architecture", "name": "Giulia", "workspace": "architecture_workspace", "rag_collection": "architecture_manuals", "capabilities": ["visual"], "show_code": False }
}
-# === CUSTOM LOCAL STORAGE CLIENT (FIXED) ===# Questa classe ora implementa tutti i metodi astratti richiesti da Chainlit 2.8.3
+# === STORAGE CLIENT ===
class LocalStorageClient(BaseStorageClient):
- """Storage locale su filesystem per file/elementi"""
-
def __init__(self, storage_path: str):
self.storage_path = storage_path
os.makedirs(storage_path, exist_ok=True)
-
- async def upload_file(
- self,
- object_key: str,
- data: bytes,
- mime: str = "application/octet-stream",
- overwrite: bool = True,
- ) -> Dict[str, str]:
+ async def upload_file(self, object_key: str, data: bytes, mime: str = "application/octet-stream", overwrite: bool = True) -> Dict[str, str]:
file_path = os.path.join(self.storage_path, object_key)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
- with open(file_path, "wb") as f:
- f.write(data)
+ with open(file_path, "wb") as f: f.write(data)
return {"object_key": object_key, "url": f"/files/{object_key}"}
-
- # Implementazione metodi obbligatori mancanti nella versione precedente
- async def get_read_url(self, object_key: str) -> str:
- return f"/files/{object_key}"
-
+ async def get_read_url(self, object_key: str) -> str: return f"/files/{object_key}"
async def delete_file(self, object_key: str) -> bool:
- file_path = os.path.join(self.storage_path, object_key)
- if os.path.exists(file_path):
- os.remove(file_path)
- return True
+ path = os.path.join(self.storage_path, object_key)
+ if os.path.exists(path): os.remove(path); return True
return False
+ async def close(self): pass
- async def close(self):
- pass
-
-# === DATA LAYER ===
@cl.data_layer
def get_data_layer():
- return SQLAlchemyDataLayer(
- conninfo=DATABASE_URL,
- user_thread_limit=1000,
- storage_provider=LocalStorageClient(storage_path=STORAGE_DIR)
- )
+ return SQLAlchemyDataLayer(conninfo=DATABASE_URL, storage_provider=LocalStorageClient(STORAGE_DIR))
-# === OAUTH CALLBACK ===
+# === OAUTH & UTILS ===
@cl.oauth_callback
-def oauth_callback(
- provider_id: str,
- token: str,
- raw_user_data: Dict[str, str],
- default_user: cl.User,
-) -> Optional[cl.User]:
+def oauth_callback(provider_id: str, token: str, raw_user_data: Dict[str, str], default_user: cl.User) -> Optional[cl.User]:
if provider_id == "google":
email = raw_user_data.get("email", "").lower()
-
- # Verifica se utente è autorizzato (opzionale: blocca se non in lista)
- # if email not in USER_PROFILES:
- # return None
-
- # Recupera profilo o usa default Guest
- profile = USER_PROFILES.get(email, get_user_profile("guest"))
-
- default_user.metadata.update({
- "picture": raw_user_data.get("picture", ""),
- "role": profile["role"],
- "workspace": profile["workspace"],
- "rag_collection": profile["rag_collection"],
- "capabilities": profile["capabilities"],
- "show_code": profile["show_code"],
- "display_name": profile["name"]
- })
+ profile = USER_PROFILES.get(email, USER_PROFILES.get("guest", {"role": "guest", "name": "Guest", "workspace": "guest", "rag_collection": "public", "show_code": False}))
+ default_user.metadata.update({"role": profile["role"], "workspace": profile["workspace"], "rag_collection": profile["rag_collection"], "show_code": profile["show_code"], "display_name": profile["name"]})
return default_user
return default_user
-# === UTILITY FUNCTIONS ===
-def get_user_profile(user_email: str) -> Dict:
- return USER_PROFILES.get(user_email.lower(), {
- "role": "guest",
- "name": "Ospite",
- "workspace": "guest_workspace",
- "rag_collection": "documents",
- "capabilities": [],
- "show_code": False
- })
-
def create_workspace(workspace_name: str) -> str:
path = os.path.join(WORKSPACES_DIR, workspace_name)
os.makedirs(path, exist_ok=True)
return path
-def save_code_to_file(code: str, workspace: str) -> str:
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
- file_name = f"code_{timestamp}.py"
- file_path = os.path.join(WORKSPACES_DIR, workspace, file_name)
- with open(file_path, "w", encoding="utf-8") as f:
- f.write(code)
- return file_path
-def extract_text_from_pdf(pdf_path: str) -> str:
+# === CORE: DOCLING ===
+def process_file_with_docling(file_path: str) -> str:
try:
- doc = fitz.open(pdf_path)
- text = "\n".join([page.get_text() for page in doc])
- doc.close()
- return text
- except Exception:
+ converter = DocumentConverter()
+ result = converter.convert(file_path)
+ return result.document.export_to_markdown()
+ except Exception as e:
+ print(f"❌ Docling Error: {e}")
return ""
-# === QDRANT FUNCTIONS ===
-async def get_qdrant_client() -> AsyncQdrantClient:
- return AsyncQdrantClient(url=QDRANT_URL)
+# === CORE: BGE-M3 CLIENT ===
+def get_bge_embeddings(text: str) -> Optional[Dict[str, Any]]:
+ try:
+ payload = {"texts": [text[:8000]]}
+ response = requests.post(BGE_API_URL, json=payload, timeout=30)
+ response.raise_for_status()
+ data = response.json().get("data", [])
+ if data:
+ return data[0]
+ return None
+ except Exception as e:
+ print(f"❌ BGE API Error: {e}")
+ return None
+# === CORE: QDRANT ===
async def ensure_collection(collection_name: str):
- client = await get_qdrant_client()
+ client = AsyncQdrantClient(url=QDRANT_URL)
if not await client.collection_exists(collection_name):
await client.create_collection(
collection_name=collection_name,
- vectors_config=VectorParams(size=768, distance=Distance.COSINE)
+ vectors_config={"dense": VectorParams(size=1024, distance=Distance.COSINE)},
+ sparse_vectors_config={"sparse": SparseVectorParams()}
)
-async def get_embeddings(text: str) -> list:
- client = ollama.Client(host=OLLAMA_URL)
- try:
- response = client.embed(model='nomic-embed-text', input=text[:2000])
- if 'embeddings' in response: return response['embeddings'][0]
- return response.get('embedding', [])
- except: return []
-
-async def index_document(file_name: str, content: str, collection_name: str) -> bool:
- try:
- await ensure_collection(collection_name)
- embedding = await get_embeddings(content)
- if not embedding: return False
+async def index_document(file_name: str, content: str, collection_name: str):
+ await ensure_collection(collection_name)
+ client = AsyncQdrantClient(url=QDRANT_URL)
+
+ chunk_size = 2000
+ overlap = 200
+
+ points = []
+ for i in range(0, len(content), chunk_size - overlap):
+ chunk = content[i : i + chunk_size]
+ embedding_data = get_bge_embeddings(chunk)
- qdrant = await get_qdrant_client()
- await qdrant.upsert(
- collection_name=collection_name,
- points=[PointStruct(
+ if embedding_data:
+ points.append(PointStruct(
id=str(uuid.uuid4()),
- vector=embedding,
- payload={"file_name": file_name, "content": content[:3000], "indexed_at": datetime.now().isoformat()}
- )]
- )
- return True
- except: return False
+ vector={
+ "dense": embedding_data["dense"],
+ "sparse": embedding_data["sparse"]
+ },
+ payload={
+ "file_name": file_name,
+ "content": chunk,
+ "indexed_at": datetime.now().isoformat()
+ }
+ ))
+
+ if points:
+ await client.upsert(collection_name=collection_name, points=points)
+ return len(points)
+ return 0
-async def search_qdrant(query: str, collection: str) -> str:
- try:
- client = await get_qdrant_client()
- if not await client.collection_exists(collection): return ""
- emb = await get_embeddings(query)
- if not emb: return ""
- res = await client.query_points(collection_name=collection, query=emb, limit=3)
- return "\n\n".join([hit.payload['content'] for hit in res.points if hit.payload])
- except: return ""
+async def search_hybrid(query: str, collection_name: str, limit: int = 4) -> str:
+ client = AsyncQdrantClient(url=QDRANT_URL)
+ if not await client.collection_exists(collection_name): return ""
+
+ query_emb = get_bge_embeddings(query)
+ if not query_emb: return ""
+
+ # CORREZIONE QUI: Usiamo l'oggetto Prefetch importato correttamente
+ results = await client.query_points(
+ collection_name=collection_name,
+ prefetch=[
+ Prefetch(
+ query=query_emb["sparse"],
+ using="sparse",
+ limit=limit * 2
+ )
+ ],
+ query=query_emb["dense"],
+ using="dense",
+ limit=limit
+ )
+
+ context = []
+ for hit in results.points:
+ context.append(f"--- DA {hit.payload['file_name']} ---\n{hit.payload['content']}")
+
+ return "\n\n".join(context)
+
+# === Caching Embeddings ===
+@lru_cache(maxsize=1000)
+def get_bge_embeddings_cached(text: str):
+ """Cache per query ripetute"""
+ return get_bge_embeddings(text)
# === CHAINLIT HANDLERS ===
-
@cl.on_chat_start
-async def on_chat_start():
+async def start():
+ # 1. Profilo utente
user = cl.user_session.get("user")
+ email = user.identifier if user else "guest"
+ profile = USER_PROFILES.get(email, USER_PROFILES["giuseppe@defranceschi.pro"])
- if not user:
- # Fallback locale se non c'è auth
- user_email = "guest@local"
- profile = get_user_profile(user_email)
- else:
- user_email = user.identifier
- # I metadati sono già popolati dalla callback oauth
- profile = USER_PROFILES.get(user_email, get_user_profile("guest"))
-
- # Salva in sessione
- cl.user_session.set("email", user_email)
- cl.user_session.set("role", profile["role"])
- cl.user_session.set("workspace", profile["workspace"])
- cl.user_session.set("rag_collection", profile["rag_collection"])
- cl.user_session.set("show_code", profile["show_code"])
-
+ cl.user_session.set("profile", profile)
create_workspace(profile["workspace"])
- # === SETTINGS WIDGETS ===
- settings_widgets = [
- cl.input_widget.Select(
- id="model",
- label="Modello AI",
- values=["glm-4.6:cloud", "llama3.2", "mistral", "qwen2.5-coder:32b"],
- initial_value="glm-4.6:cloud",
- ),
- cl.input_widget.Slider(
- id="temperature",
- label="Temperatura",
- initial=0.7, min=0, max=2, step=0.1,
- ),
- ]
- if profile["role"] == "admin":
- settings_widgets.append(cl.input_widget.Switch(id="rag_enabled", label="Abilita RAG", initial=True))
+ # 2. Badge HTML personalizzato
+ role_color = {
+ "admin": "#e74c3c",
+ "engineering": "#3498db",
+ "business": "#2ecc71",
+ "architecture": "#9b59b6",
+ }.get(profile["role"], "#95a5a6")
- await cl.ChatSettings(settings_widgets).send()
-
- await cl.Message(
- content=f"👋 Ciao **{profile['name']}**!\n"
- f"Ruolo: `{profile['role']}` | Workspace: `{profile['workspace']}`\n"
+ badge_html = f"""
+
+ 👤 {profile['name']} | 🔧 {profile['role'].upper()} | 📁 {profile['workspace']}
+
+ """
+ await cl.Message(content=badge_html).send()
+
+ # 3. Settings UI
+ settings = await cl.ChatSettings(
+ [
+ cl.input_widget.Slider(
+ id="top_k",
+ label="Numero Documenti RAG",
+ initial=4,
+ min=1,
+ max=10,
+ step=1,
+ ),
+ cl.input_widget.Select(
+ id="vision_detail",
+ label="Dettaglio Analisi Immagini",
+ values=["auto", "low", "high"],
+ initial_value="auto",
+ ),
+ cl.input_widget.TextInput(
+ id="system_instruction",
+ label="Istruzione Sistema Custom (opzionale)",
+ initial="",
+ placeholder="Es: Rispondi sempre in formato tecnico...",
+ ),
+ cl.input_widget.Select(
+ id="model",
+ label="Modello di Ragionamento",
+ values=[DEFAULT_TEXT_MODEL, "llama3.2", "mistral", "qwen2.5-coder:32b"],
+ initial_value=DEFAULT_TEXT_MODEL,
+ ),
+ cl.input_widget.Slider(
+ id="temperature",
+ label="Creatività (Temperatura)",
+ initial=0.3,
+ min=0,
+ max=1,
+ step=0.1,
+ ),
+ cl.input_widget.Switch(
+ id="rag_enabled",
+ label="Usa Conoscenza Documenti (RAG)",
+ initial=True,
+ ),
+ ]
).send()
-@cl.on_settings_update
-async def on_settings_update(settings):
cl.user_session.set("settings", settings)
- await cl.Message(content="✅ Impostazioni aggiornate").send()
+
+ # 4. Messaggio iniziale (opzionale)
+ await cl.Message(
+ content=(
+ f"🚀 **Vision-RAG Hybrid System Online**\n"
+ f"Utente: {profile['name']} | Workspace: {profile['workspace']}\n"
+ f"Engine: Docling + BGE-M3 + {VISION_MODEL}"
+ )
+ ).send()
+
+
+ cl.user_session.set("settings", settings)
+
+ await cl.Message(f"🚀 **Vision-RAG Hybrid System Online**\nUtente: {profile['name']} | Workspace: {profile['workspace']}\nEngine: Docling + BGE-M3 + {VISION_MODEL}").send()
+
+@cl.on_settings_update
+async def setup_agent(settings):
+ cl.user_session.set("settings", settings)
+ await cl.Message(content=f"✅ Impostazioni aggiornate: Modello {settings['model']}").send()
+
+async def log_metrics(metrics: dict):
+ # Versione minima: log su stdout
+ print("[METRICS]", metrics)
+
+ # In futuro puoi:
+ # - salvarle in Postgres
+ # - mandarle a Prometheus / Grafana
+ # - scriverle su file JSON per analisi settimanale
+
+# - Resume Chat Handler
+
+@cl.on_chat_resume
+async def on_chat_resume(thread: ThreadDict):
+ """
+ Viene chiamato quando l'utente clicca 'Riprendi' su una chat archiviata.
+ Chainlit carica già i messaggi nella UI, qui puoi solo ripristinare la sessione.
+ """
+ # Se vuoi, puoi recuperare l'identifier dell’utente dal thread
+ user_identifier = thread.get("userIdentifier")
+ profile = USER_PROFILES.get(
+ user_identifier,
+ USER_PROFILES["giuseppe@defranceschi.pro"],
+ )
+ cl.user_session.set("profile", profile)
+
+ # Puoi anche ripristinare eventuale stato custom (es: impostazioni di default)
+ # oppure semplicemente salutare l’utente
+ await cl.Message(
+ content="👋 Bentornato! Possiamo riprendere da questa conversazione."
+ ).send()
@cl.on_message
-async def on_message(message: cl.Message):
- workspace = cl.user_session.get("workspace")
- rag_collection = cl.user_session.get("rag_collection")
- user_role = cl.user_session.get("role")
- show_code = cl.user_session.get("show_code")
-
+async def main(message: cl.Message):
+ start_time = time.time()
+
+ profile = cl.user_session.get("profile")
settings = cl.user_session.get("settings", {})
- model = settings.get("model", "glm-4.6:cloud")
- temperature = settings.get("temperature", 0.7)
- rag_enabled = settings.get("rag_enabled", True) if user_role == "admin" else True
+
+ selected_model = settings.get("model", DEFAULT_TEXT_MODEL)
+ temperature = settings.get("temperature", 0.3)
+ rag_enabled = settings.get("rag_enabled", True)
+
+ workspace = create_workspace(profile["workspace"])
+
+ images_for_vision = []
+ doc_context = ""
+ rag_context = "" # ← la inizializzi qui, così esiste sempre
# 1. GESTIONE FILE
if message.elements:
for element in message.elements:
- dest = os.path.join(WORKSPACES_DIR, workspace, element.name)
- shutil.copy(element.path, dest)
- if element.name.endswith(".pdf"):
- text = extract_text_from_pdf(dest)
- if text:
- await index_document(element.name, text, rag_collection)
- await cl.Message(content=f"✅ **{element.name}** indicizzato.").send()
+ file_path = os.path.join(workspace, element.name)
+ shutil.copy(element.path, file_path)
+
+ if "image" in element.mime:
+ images_for_vision.append(file_path)
+ msg_img = cl.Message(
+ content=f"👁️ Analizzo immagine **{element.name}** con {VISION_MODEL}..."
+ )
+ await msg_img.send()
+
+ with open(file_path, "rb") as img_file:
+ img_bytes = img_file.read()
+
+ client_sync = ollama.Client(host=OLLAMA_URL)
+ res = client_sync.chat(
+ model=VISION_MODEL,
+ messages=[{
+ "role": "user",
+ "content": (
+ "Analizza questa immagine tecnica. Trascrivi testi, codici "
+ "e descrivi diagrammi o tabelle in dettaglio."
+ ),
+ "images": [img_bytes],
+ }],
+ )
+ desc = res["message"]["content"]
+ doc_context += f"\n\n[DESCRIZIONE IMMAGINE {element.name}]:\n{desc}"
+ msg_img.content = f"✅ Immagine analizzata:\n{desc[:200]}..."
+ await msg_img.update()
+
+ elif element.name.endswith((".pdf", ".docx")):
+ msg_doc = cl.Message(
+ content=f"📄 Leggo **{element.name}** con Docling (tabelle/formule)..."
+ )
+ await msg_doc.send()
+
+ markdown_content = process_file_with_docling(file_path)
+ if markdown_content:
+ chunks = await index_document(
+ element.name, markdown_content, profile["rag_collection"]
+ )
+ msg_doc.content = (
+ f"✅ **{element.name}**: Convertito e salvato {chunks} "
+ "frammenti nel DB vettoriale."
+ )
+ doc_context += (
+ f"\n\n[CONTENUTO FILE {element.name}]:\n"
+ f"{markdown_content[:1000]}..."
+ )
+ else:
+ msg_doc.content = f"❌ Errore lettura {element.name}"
+ await msg_doc.update()
+
+ # 2. RAG RETRIEVAL
+ if rag_enabled and not images_for_vision:
+ rag_context = await search_hybrid(
+ message.content, profile["rag_collection"]
+ )
+
+ final_context = ""
+ if rag_context:
+ final_context += f"CONTESTO RAG:\n{rag_context}\n"
+ if doc_context:
+ final_context += f"CONTESTO SESSIONE CORRENTE:\n{doc_context}\n"
+
+ system_prompt = (
+ "Sei un assistente tecnico esperto. Usa il contesto fornito "
+ "(incluso Markdown di tabelle e descrizioni immagini) per "
+ "rispondere con precisione. Cita i documenti fonte."
+ )
- # 2. RAG
- context = ""
- if rag_enabled:
- context = await search_qdrant(message.content, rag_collection)
-
- system_prompt = "Sei un assistente esperto."
- if context: system_prompt += f"\n\nCONTESTO:\n{context}"
-
- # 3. GENERAZIONE
- client = ollama.AsyncClient(host=OLLAMA_URL)
msg = cl.Message(content="")
await msg.send()
-
- stream = await client.chat(
- model=model,
- messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": message.content}],
- options={"temperature": temperature},
- stream=True
- )
-
- full_resp = ""
- async for chunk in stream:
- token = chunk['message']['content']
- full_resp += token
- await msg.stream_token(token)
- await msg.update()
+
+ error = None
+
+ # 3. GENERAZIONE
+ try:
+ client_async = ollama.AsyncClient(host=OLLAMA_URL)
+ stream = await client_async.chat(
+ model=selected_model,
+ messages=[
+ {"role": "system", "content": system_prompt},
+ {
+ "role": "user",
+ "content": f"Domanda: {message.content}\n\n{final_context}",
+ },
+ ],
+ options={"temperature": temperature},
+ stream=True,
+ )
+
+ async for chunk in stream:
+ content = chunk["message"]["content"]
+ await msg.stream_token(content)
+ await msg.update()
+ except Exception as e:
+ error = str(e)
+ await msg.stream_token(f"❌ Errore AI: {error}")
+ await msg.update()
# 4. SALVATAGGIO CODICE
- if show_code:
- blocks = re.findall(r"``````", full_resp, re.DOTALL)
- elements = []
- for code in blocks:
- path = save_code_to_file(code.strip(), workspace)
- elements.append(cl.File(name=os.path.basename(path), path=path, display="inline"))
- if elements:
- await cl.Message(content="💾 Codice salvato", elements=elements).send()
\ No newline at end of file
+ if profile["show_code"]:
+ code_blocks = re.findall(r"``````", msg.content, re.DOTALL)
+ if code_blocks:
+ for i, code in enumerate(code_blocks):
+ fname = f"script_{datetime.now().strftime('%H%M%S')}_{i}.py"
+ with open(os.path.join(workspace, fname), "w") as f:
+ f.write(code.strip())
+ await cl.Message(
+ content=f"💾 Script salvato: `{fname}`"
+ ).send()
+
+ # 5. METRICHE (ALLA FINE)
+ elapsed = time.time() - start_time
+
+ # Se rag_context è una stringa concatenata, puoi stimare i "rag_hits"
+ # contando i separatori che usi in search_hybrid (es. '--- DA ')
+ if rag_context:
+ rag_hits = rag_context.count("--- DA ")
+ else:
+ rag_hits = 0
+
+ metrics = {
+ "response_time": elapsed,
+ "rag_hits": rag_hits,
+ "model": selected_model,
+ "user_role": profile["role"],
+ "error": error,
+ }
+
+ await log_metrics(metrics)
diff --git a/Marimo_Multi-User_Hub.md b/bck/Marimo_Multi-User_Hub.md
similarity index 100%
rename from Marimo_Multi-User_Hub.md
rename to bck/Marimo_Multi-User_Hub.md
diff --git a/PROMPT_V2.md b/bck/PROMPT_V2.md
similarity index 100%
rename from PROMPT_V2.md
rename to bck/PROMPT_V2.md
diff --git a/SPEC.md b/bck/SPEC.md
similarity index 100%
rename from SPEC.md
rename to bck/SPEC.md
diff --git a/bck/app-bck212.py b/bck/app-bck212.py
new file mode 100644
index 00000000..230d6b51
--- /dev/null
+++ b/bck/app-bck212.py
@@ -0,0 +1,344 @@
+import os
+import re
+import uuid
+import shutil
+from datetime import datetime
+from typing import Optional, Dict, List
+import chainlit as cl
+import ollama
+import fitz # PyMuPDF
+from qdrant_client import AsyncQdrantClient
+from qdrant_client.models import PointStruct, Distance, VectorParams
+from chainlit.data.sql_alchemy import SQLAlchemyDataLayer
+
+# === FIX IMPORT ROBUSTO ===
+# Gestisce le differenze tra le versioni di Chainlit 2.x
+try:
+ from chainlit.data.storage_clients import BaseStorageClient
+except ImportError:
+ try:
+ from chainlit.data.base import BaseStorageClient
+ except ImportError:
+ from chainlit.data.storage_clients.base import BaseStorageClient
+
+# === CONFIGURAZIONE ===
+DATABASE_URL = os.getenv("DATABASE_URL", "postgresql+asyncpg://ai_user:secure_password_here@postgres:5432/ai_station")
+OLLAMA_URL = os.getenv("OLLAMA_URL", "http://192.168.1.243:11434")
+QDRANT_URL = os.getenv("QDRANT_URL", "http://qdrant:6333")
+WORKSPACES_DIR = "./workspaces"
+STORAGE_DIR = "./.files"
+
+os.makedirs(STORAGE_DIR, exist_ok=True)
+os.makedirs(WORKSPACES_DIR, exist_ok=True)
+
+# === MAPPING UTENTI E RUOLI ===
+USER_PROFILES = {
+ "giuseppe@defranceschi.pro": {
+ "role": "admin",
+ "name": "Giuseppe",
+ "workspace": "admin_workspace",
+ "rag_collection": "admin_docs",
+ "capabilities": ["debug", "system_prompts", "user_management", "all_models"],
+ "show_code": True
+ },
+ "federica.tecchio@gmail.com": {
+ "role": "business",
+ "name": "Federica",
+ "workspace": "business_workspace",
+ "rag_collection": "contabilita",
+ "capabilities": ["pdf_upload", "basic_chat"],
+ "show_code": False
+ },
+ "giuseppe.defranceschi@gmail.com": {
+ "role": "admin",
+ "name": "Giuseppe",
+ "workspace": "admin_workspace",
+ "rag_collection": "admin_docs",
+ "capabilities": ["debug", "system_prompts", "user_management", "all_models"],
+ "show_code": True
+ },
+ "riccardob545@gmail.com": {
+ "role": "engineering",
+ "name": "Riccardo",
+ "workspace": "engineering_workspace",
+ "rag_collection": "engineering_docs",
+ "capabilities": ["code_execution", "data_viz", "advanced_chat"],
+ "show_code": True
+ },
+ "giuliadefranceschi05@gmail.com": {
+ "role": "architecture",
+ "name": "Giulia",
+ "workspace": "architecture_workspace",
+ "rag_collection": "architecture_manuals",
+ "capabilities": ["visual_chat", "pdf_upload", "image_gen"],
+ "show_code": False
+ }
+}
+
+# === CUSTOM LOCAL STORAGE CLIENT (FIXED) ===# Questa classe ora implementa tutti i metodi astratti richiesti da Chainlit 2.8.3
+class LocalStorageClient(BaseStorageClient):
+ """Storage locale su filesystem per file/elementi"""
+
+ def __init__(self, storage_path: str):
+ self.storage_path = storage_path
+ os.makedirs(storage_path, exist_ok=True)
+
+ async def upload_file(
+ self,
+ object_key: str,
+ data: bytes,
+ mime: str = "application/octet-stream",
+ overwrite: bool = True,
+ ) -> Dict[str, str]:
+ file_path = os.path.join(self.storage_path, object_key)
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
+ with open(file_path, "wb") as f:
+ f.write(data)
+ return {"object_key": object_key, "url": f"/files/{object_key}"}
+
+ # Implementazione metodi obbligatori mancanti nella versione precedente
+ async def get_read_url(self, object_key: str) -> str:
+ return f"/files/{object_key}"
+
+ async def delete_file(self, object_key: str) -> bool:
+ file_path = os.path.join(self.storage_path, object_key)
+ if os.path.exists(file_path):
+ os.remove(file_path)
+ return True
+ return False
+
+ async def close(self):
+ pass
+
+# === DATA LAYER ===
+@cl.data_layer
+def get_data_layer():
+ return SQLAlchemyDataLayer(
+ conninfo=DATABASE_URL,
+ user_thread_limit=1000,
+ storage_provider=LocalStorageClient(storage_path=STORAGE_DIR)
+ )
+
+# === OAUTH CALLBACK ===
+@cl.oauth_callback
+def oauth_callback(
+ provider_id: str,
+ token: str,
+ raw_user_data: Dict[str, str],
+ default_user: cl.User,
+) -> Optional[cl.User]:
+ if provider_id == "google":
+ email = raw_user_data.get("email", "").lower()
+
+ # Verifica se utente è autorizzato (opzionale: blocca se non in lista)
+ # if email not in USER_PROFILES:
+ # return None
+
+ # Recupera profilo o usa default Guest
+ profile = USER_PROFILES.get(email, get_user_profile("guest"))
+
+ default_user.metadata.update({
+ "picture": raw_user_data.get("picture", ""),
+ "role": profile["role"],
+ "workspace": profile["workspace"],
+ "rag_collection": profile["rag_collection"],
+ "capabilities": profile["capabilities"],
+ "show_code": profile["show_code"],
+ "display_name": profile["name"]
+ })
+ return default_user
+ return default_user
+
+# === UTILITY FUNCTIONS ===
+def get_user_profile(user_email: str) -> Dict:
+ return USER_PROFILES.get(user_email.lower(), {
+ "role": "guest",
+ "name": "Ospite",
+ "workspace": "guest_workspace",
+ "rag_collection": "documents",
+ "capabilities": [],
+ "show_code": False
+ })
+
+def create_workspace(workspace_name: str) -> str:
+ path = os.path.join(WORKSPACES_DIR, workspace_name)
+ os.makedirs(path, exist_ok=True)
+ return path
+
+def save_code_to_file(code: str, workspace: str) -> str:
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ file_name = f"code_{timestamp}.py"
+ file_path = os.path.join(WORKSPACES_DIR, workspace, file_name)
+ with open(file_path, "w", encoding="utf-8") as f:
+ f.write(code)
+ return file_path
+
+def extract_text_from_pdf(pdf_path: str) -> str:
+ try:
+ doc = fitz.open(pdf_path)
+ text = "\n".join([page.get_text() for page in doc])
+ doc.close()
+ return text
+ except Exception:
+ return ""
+
+# === QDRANT FUNCTIONS ===
+async def get_qdrant_client() -> AsyncQdrantClient:
+ return AsyncQdrantClient(url=QDRANT_URL)
+
+async def ensure_collection(collection_name: str):
+ client = await get_qdrant_client()
+ if not await client.collection_exists(collection_name):
+ await client.create_collection(
+ collection_name=collection_name,
+ vectors_config=VectorParams(size=768, distance=Distance.COSINE)
+ )
+
+async def get_embeddings(text: str) -> list:
+ client = ollama.Client(host=OLLAMA_URL)
+ try:
+ response = client.embed(model='nomic-embed-text', input=text[:2000])
+ if 'embeddings' in response: return response['embeddings'][0]
+ return response.get('embedding', [])
+ except: return []
+
+async def index_document(file_name: str, content: str, collection_name: str) -> bool:
+ try:
+ await ensure_collection(collection_name)
+ embedding = await get_embeddings(content)
+ if not embedding: return False
+
+ qdrant = await get_qdrant_client()
+ await qdrant.upsert(
+ collection_name=collection_name,
+ points=[PointStruct(
+ id=str(uuid.uuid4()),
+ vector=embedding,
+ payload={"file_name": file_name, "content": content[:3000], "indexed_at": datetime.now().isoformat()}
+ )]
+ )
+ return True
+ except: return False
+
+async def search_qdrant(query: str, collection: str) -> str:
+ try:
+ client = await get_qdrant_client()
+ if not await client.collection_exists(collection): return ""
+ emb = await get_embeddings(query)
+ if not emb: return ""
+ res = await client.query_points(collection_name=collection, query=emb, limit=3)
+ return "\n\n".join([hit.payload['content'] for hit in res.points if hit.payload])
+ except: return ""
+
+# === CHAINLIT HANDLERS ===
+
+@cl.on_chat_start
+async def on_chat_start():
+ user = cl.user_session.get("user")
+
+ if not user:
+ # Fallback locale se non c'è auth
+ user_email = "guest@local"
+ profile = get_user_profile(user_email)
+ else:
+ user_email = user.identifier
+ # I metadati sono già popolati dalla callback oauth
+ profile = USER_PROFILES.get(user_email, get_user_profile("guest"))
+
+ # Salva in sessione
+ cl.user_session.set("email", user_email)
+ cl.user_session.set("role", profile["role"])
+ cl.user_session.set("workspace", profile["workspace"])
+ cl.user_session.set("rag_collection", profile["rag_collection"])
+ cl.user_session.set("show_code", profile["show_code"])
+
+ create_workspace(profile["workspace"])
+
+ # === SETTINGS WIDGETS ===
+ settings_widgets = [
+ cl.input_widget.Select(
+ id="model",
+ label="Modello AI",
+ values=["glm-4.6:cloud", "llama3.2", "mistral", "qwen2.5-coder:32b"],
+ initial_value="glm-4.6:cloud",
+ ),
+ cl.input_widget.Slider(
+ id="temperature",
+ label="Temperatura",
+ initial=0.7, min=0, max=2, step=0.1,
+ ),
+ ]
+ if profile["role"] == "admin":
+ settings_widgets.append(cl.input_widget.Switch(id="rag_enabled", label="Abilita RAG", initial=True))
+
+ await cl.ChatSettings(settings_widgets).send()
+
+ await cl.Message(
+ content=f"👋 Ciao **{profile['name']}**!\n"
+ f"Ruolo: `{profile['role']}` | Workspace: `{profile['workspace']}`\n"
+ ).send()
+
+@cl.on_settings_update
+async def on_settings_update(settings):
+ cl.user_session.set("settings", settings)
+ await cl.Message(content="✅ Impostazioni aggiornate").send()
+
+@cl.on_message
+async def on_message(message: cl.Message):
+ workspace = cl.user_session.get("workspace")
+ rag_collection = cl.user_session.get("rag_collection")
+ user_role = cl.user_session.get("role")
+ show_code = cl.user_session.get("show_code")
+
+ settings = cl.user_session.get("settings", {})
+ model = settings.get("model", "glm-4.6:cloud")
+ temperature = settings.get("temperature", 0.7)
+ rag_enabled = settings.get("rag_enabled", True) if user_role == "admin" else True
+
+ # 1. GESTIONE FILE
+ if message.elements:
+ for element in message.elements:
+ dest = os.path.join(WORKSPACES_DIR, workspace, element.name)
+ shutil.copy(element.path, dest)
+ if element.name.endswith(".pdf"):
+ text = extract_text_from_pdf(dest)
+ if text:
+ await index_document(element.name, text, rag_collection)
+ await cl.Message(content=f"✅ **{element.name}** indicizzato.").send()
+
+ # 2. RAG
+ context = ""
+ if rag_enabled:
+ context = await search_qdrant(message.content, rag_collection)
+
+ system_prompt = "Sei un assistente esperto."
+ if context: system_prompt += f"\n\nCONTESTO:\n{context}"
+
+ # 3. GENERAZIONE
+ client = ollama.AsyncClient(host=OLLAMA_URL)
+ msg = cl.Message(content="")
+ await msg.send()
+
+ stream = await client.chat(
+ model=model,
+ messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": message.content}],
+ options={"temperature": temperature},
+ stream=True
+ )
+
+ full_resp = ""
+ async for chunk in stream:
+ token = chunk['message']['content']
+ full_resp += token
+ await msg.stream_token(token)
+ await msg.update()
+
+ # 4. SALVATAGGIO CODICE
+ if show_code:
+ blocks = re.findall(r"``````", full_resp, re.DOTALL)
+ elements = []
+ for code in blocks:
+ path = save_code_to_file(code.strip(), workspace)
+ elements.append(cl.File(name=os.path.basename(path), path=path, display="inline"))
+ if elements:
+ await cl.Message(content="💾 Codice salvato", elements=elements).send()
\ No newline at end of file
diff --git a/app-final.py b/bck/app-final.py
similarity index 100%
rename from app-final.py
rename to bck/app-final.py
diff --git a/app-oauth2.py b/bck/app-oauth2.py
similarity index 100%
rename from app-oauth2.py
rename to bck/app-oauth2.py
diff --git a/app.py.backup b/bck/app.py.backup
similarity index 100%
rename from app.py.backup
rename to bck/app.py.backup
diff --git a/app.py.broken-082810 b/bck/app.py.broken-082810
similarity index 100%
rename from app.py.broken-082810
rename to bck/app.py.broken-082810
diff --git a/app.py.broken-20251229-081214 b/bck/app.py.broken-20251229-081214
similarity index 100%
rename from app.py.broken-20251229-081214
rename to bck/app.py.broken-20251229-081214
diff --git a/bck/cpu.txt b/bck/cpu.txt
new file mode 100644
index 00000000..acc5b76d
--- /dev/null
+++ b/bck/cpu.txt
@@ -0,0 +1,39 @@
+Architecture: x86_64
+CPU op-mode(s): 32-bit, 64-bit
+Address sizes: 40 bits physical, 48 bits virtual
+Byte Order: Little Endian
+CPU(s): 16
+On-line CPU(s) list: 0-15
+Vendor ID: GenuineIntel
+Model name: QEMU Virtual CPU version 2.5+
+CPU family: 15
+Model: 107
+Thread(s) per core: 1
+Core(s) per socket: 4
+Socket(s): 4
+Stepping: 1
+BogoMIPS: 4999.99
+Flags: fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx lm constant_tsc nopl xtopology cpuid tsc_known_freq pni ssse3 cx16 sse4_1 sse4_2 x2apic popcnt aes hypervisor lahf_lm cpuid_fault pti
+Hypervisor vendor: KVM
+Virtualization type: full
+L1d cache: 512 KiB (16 instances)
+L1i cache: 512 KiB (16 instances)
+L2 cache: 64 MiB (16 instances)
+L3 cache: 64 MiB (4 instances)
+NUMA node(s): 1
+NUMA node0 CPU(s): 0-15
+Vulnerability Gather data sampling: Not affected
+Vulnerability Itlb multihit: KVM: Mitigation: VMX unsupported
+Vulnerability L1tf: Mitigation; PTE Inversion
+Vulnerability Mds: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown
+Vulnerability Meltdown: Mitigation; PTI
+Vulnerability Mmio stale data: Unknown: No mitigations
+Vulnerability Reg file data sampling: Not affected
+Vulnerability Retbleed: Not affected
+Vulnerability Spec rstack overflow: Not affected
+Vulnerability Spec store bypass: Vulnerable
+Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
+Vulnerability Spectre v2: Mitigation; Retpolines; STIBP disabled; RSB filling; PBRSB-eIBRS Not affected; BHI Retpoline
+Vulnerability Srbds: Not affected
+Vulnerability Tsx async abort: Not affected
+Vulnerability Vmscape: Not affected
diff --git a/debugchainlit-app.txt b/bck/debugchainlit-app.txt
similarity index 100%
rename from debugchainlit-app.txt
rename to bck/debugchainlit-app.txt
diff --git a/debugchanlit-app.txt b/bck/debugchanlit-app.txt
similarity index 100%
rename from debugchanlit-app.txt
rename to bck/debugchanlit-app.txt
diff --git a/docker.logs b/bck/docker.logs
similarity index 100%
rename from docker.logs
rename to bck/docker.logs
diff --git a/dockerignore b/bck/dockerignore
similarity index 100%
rename from dockerignore
rename to bck/dockerignore
diff --git a/bck/error.log b/bck/error.log
new file mode 100644
index 00000000..9de595d7
--- /dev/null
+++ b/bck/error.log
@@ -0,0 +1,717 @@
+ai-station-app | [INFO] 2025-12-31 10:20:23,724 [RapidOCR] download_file.py:82: Download size: 25.67MB
+ai-station-app | [INFO] 2025-12-31 10:20:27,451 [RapidOCR] download_file.py:95: Successfully saved to: /usr/local/lib/python3.11/site-packages/rapidocr/models/ch_PP-OCRv4_rec_infer.pth
+ai-station-app | [INFO] 2025-12-31 10:20:27,460 [RapidOCR] main.py:50: Using /usr/local/lib/python3.11/site-packages/rapidocr/models/ch_PP-OCRv4_rec_infer.pth
+ai-station-app | 2025-12-31 10:20:28 - Auto OCR model selected rapidocr with torch.
+ai-station-app | 2025-12-31 10:20:28 - Loading plugin 'docling_defaults'
+ai-station-app | 2025-12-31 10:20:28 - Registered layout engines: ['docling_layout_default', 'docling_experimental_table_crops_layout']
+ai-station-app | 2025-12-31 10:20:28 - Accelerator device: 'cpu'
+ai-station-app | 2025-12-31 10:20:59 - Loading plugin 'docling_defaults'
+ai-station-app | 2025-12-31 10:20:59 - Registered table structure engines: ['docling_tableformer']
+ai-station-app | 2025-12-31 10:22:00 - Accelerator device: 'cpu'
+ai-station-app | 2025-12-31 10:22:02 - Processing document esempio manuale Omron.pdf
+ai-station-app | 2025-12-31 10:23:07 - Finished converting document esempio manuale Omron.pdf in 173.75 sec.
+ai-station-app | 2025-12-31 10:23:07 - HTTP Request: GET http://qdrant:6333 "HTTP/1.1 200 OK"
+ai-station-app | 2025-12-31 10:23:07 - An unexpected error occurred:
+ai-station-app | 2025-12-31 10:23:07 - Translation file for it-IT not found. Using default translation en-US.
+ai-station-qdrant | 2025-12-31T10:23:07.266024Z INFO actix_web::middleware::logger: 172.18.0.4 "GET /collections/admin_docs/exists HTTP/1.1" 200 69 "-" "python-client/1.16.2 python/3.11.14" 0.001423
+ai-station-app | 2025-12-31 10:23:07 - HTTP Request: GET http://qdrant:6333/collections/admin_docs/exists "HTTP/1.1 200 OK"
+ai-station-qdrant | 2025-12-31T10:23:07.422109Z INFO storage::content_manager::toc::collection_meta_ops: Creating collection admin_docs
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | 2025-12-31 10:23:07 - Task exception was never retrieved
+ai-station-app | future: exception=ValueError('Session not found')>
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/asyncio/tasks.py", line 277, in __step
+ai-station-app | result = coro.send(None)
+ai-station-app | ^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 607, in _handle_event_internal
+ai-station-app | r = await server._trigger_event(data[0], namespace, sid, *data[1:])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/socketio/async_server.py", line 634, in _trigger_event
+ai-station-app | ret = await handler(*args)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/socket.py", line 323, in window_message
+ai-station-app | session = WebsocketSession.require(sid)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/session.py", line 354, in require
+ai-station-app | raise ValueError("Session not found")
+ai-station-app | ValueError: Session not found
+ai-station-app | post request handler error
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/engineio/async_server.py", line 306, in handle_request
+ai-station-app | await socket.handle_post_request(environ)
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/engineio/async_socket.py", line 109, in handle_post_request
+ai-station-app | p = payload.Payload(encoded_payload=body)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/engineio/payload.py", line 13, in __init__
+ai-station-app | self.decode(encoded_payload)
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/engineio/payload.py", line 44, in decode
+ai-station-app | raise ValueError('Too many packets in payload')
+ai-station-app | ValueError: Too many packets in payload
+ai-station-app | 2025-12-31 10:23:07 - post request handler error
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/engineio/async_server.py", line 306, in handle_request
+ai-station-app | await socket.handle_post_request(environ)
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/engineio/async_socket.py", line 109, in handle_post_request
+ai-station-app | p = payload.Payload(encoded_payload=body)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/engineio/payload.py", line 13, in __init__
+ai-station-app | self.decode(encoded_payload)
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/engineio/payload.py", line 44, in decode
+ai-station-app | raise ValueError('Too many packets in payload')
+ai-station-app | ValueError: Too many packets in payload
+ai-station-qdrant | 2025-12-31T10:23:07.898915Z INFO actix_web::middleware::logger: 172.18.0.4 "PUT /collections/admin_docs HTTP/1.1" 200 57 "-" "python-client/1.16.2 python/3.11.14" 0.478796
+ai-station-app | 2025-12-31 10:23:07 - HTTP Request: PUT http://qdrant:6333/collections/admin_docs "HTTP/1.1 200 OK"
+ai-station-app | 2025-12-31 10:23:08 - HTTP Request: GET http://qdrant:6333 "HTTP/1.1 200 OK"
+ai-station-qdrant | 2025-12-31T10:23:12.256007Z INFO actix_web::middleware::logger: 172.18.0.4 "PUT /collections/admin_docs/points?wait=true HTTP/1.1" 200 84 "-" "python-client/1.16.2 python/3.11.14" 0.128378
+ai-station-app | 2025-12-31 10:23:12 - HTTP Request: PUT http://qdrant:6333/collections/admin_docs/points?wait=true "HTTP/1.1 200 OK"
+ai-station-app | 2025-12-31 10:23:12 - HTTP Request: GET http://qdrant:6333 "HTTP/1.1 200 OK"
+ai-station-qdrant | 2025-12-31T10:23:12.413564Z INFO actix_web::middleware::logger: 172.18.0.4 "GET /collections/admin_docs/exists HTTP/1.1" 200 68 "-" "python-client/1.16.2 python/3.11.14" 0.006418
+ai-station-app | 2025-12-31 10:23:12 - HTTP Request: GET http://qdrant:6333/collections/admin_docs/exists "HTTP/1.1 200 OK"
+ai-station-app | 2025-12-31 10:23:12 - module 'chainlit.data' has no attribute 'qdrant_client'
+ai-station-app | Traceback (most recent call last):
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/utils.py", line 57, in wrapper
+ai-station-app | return await user_function(**params_values)
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/usr/local/lib/python3.11/site-packages/chainlit/callbacks.py", line 161, in with_parent_id
+ai-station-app | await func(message)
+ai-station-app | File "/app/app.py", line 250, in main
+ai-station-app | rag_context = await search_hybrid(message.content, profile["rag_collection"])
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | File "/app/app.py", line 166, in search_hybrid
+ai-station-app | cl.data.qdrant_client.models.Prefetch(
+ai-station-app | ^^^^^^^^^^^^^^^^^^^^^
+ai-station-app | AttributeError: module 'chainlit.data' has no attribute 'qdrant_client'
+ai-station-app | 2025-12-31 10:23:28 - Translation file for it-IT not found. Using default translation en-US.
+ai-station-postgres | 2025-12-31 10:23:35.822 UTC [28] LOG: checkpoint starting: time
+ai-station-postgres | 2025-12-31 10:23:37.312 UTC [28] LOG: checkpoint complete: wrote 17 buffers (0.1%); 0 WAL file(s) added, 0 removed, 0 recycled; write=1.428 s, sync=0.019 s, total=1.491 s; sync files=11, longest=0.009 s, average=0.002 s; distance=68 kB, estimate=68 kB
+^C
+giuseppe@ai-srv:~/ai-station$
\ No newline at end of file
diff --git a/requirements-backup.txt b/bck/requirements-backup.txt
similarity index 100%
rename from requirements-backup.txt
rename to bck/requirements-backup.txt
diff --git a/requirements-oauth2.txt b/bck/requirements-oauth2.txt
similarity index 100%
rename from requirements-oauth2.txt
rename to bck/requirements-oauth2.txt
diff --git a/docker-compose.yml b/docker-compose.yml
index c32439ea..3058954f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,5 +1,3 @@
-version: '3.8'
-
services:
chainlit-app:
build: .
@@ -12,7 +10,7 @@ services:
- DATABASE_URL=postgresql+asyncpg://ai_user:secure_password_here@postgres:5432/ai_station
- OLLAMA_URL=http://192.168.1.243:11434
- QDRANT_URL=http://qdrant:6333
- - BGE_API_URL=http://192.168.1.243:8001
+ - BGE_API_URL=http://192.168.1.243:8001/embed
volumes:
- ./workspaces:/app/workspaces
- ./public:/app/public # ⬅️ VERIFICA QUESTO
diff --git a/error.log b/error.log
deleted file mode 100644
index 5d66a3e8..00000000
--- a/error.log
+++ /dev/null
@@ -1,133 +0,0 @@
-qdrant-1 | _ _
-chainlit-app-1 | 2025-12-25 18:05:12 - INFO - chainlit - Your app is available at http://0.0.0.0:8000
-postgres-1 |
-postgres-1 | PostgreSQL Database directory appears to contain a database; Skipping initialization
-postgres-1 |
-postgres-1 | 2025-12-25 16:38:01.071 UTC [1] LOG: starting PostgreSQL 18.1 (Debian 18.1-1.pgdg13+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
-qdrant-1 | __ _ __| |_ __ __ _ _ __ | |_
-qdrant-1 | / _` |/ _` | '__/ _` | '_ \| __|
-qdrant-1 | | (_| | (_| | | | (_| | | | | |_
-qdrant-1 | \__, |\__,_|_| \__,_|_| |_|\__|
-postgres-1 | 2025-12-25 16:38:01.072 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
-postgres-1 | 2025-12-25 16:38:01.072 UTC [1] LOG: listening on IPv6 address "::", port 5432
-postgres-1 | 2025-12-25 16:38:01.093 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
-postgres-1 | 2025-12-25 16:38:01.126 UTC [32] LOG: database system was shut down at 2025-12-25 14:34:55 UTC
-postgres-1 | 2025-12-25 16:38:01.155 UTC [1] LOG: database system is ready to accept connections
-qdrant-1 | |_|
-qdrant-1 |
-qdrant-1 | Access web UI at https://ui.qdrant.tech/?v=v1.0.0
-qdrant-1 |
-qdrant-1 | [2025-12-25T16:38:00.816Z INFO storage::content_manager::consensus::persistent] Initializing new raft state at ./storage/raft_state
-qdrant-1 | [2025-12-25T16:38:00.861Z INFO qdrant] Distributed mode disabled
-qdrant-1 | [2025-12-25T16:38:00.861Z INFO qdrant] Telemetry reporting enabled, id: e6113e43-627c-471d-8374-0f1b61799d76
-qdrant-1 | [2025-12-25T16:38:00.872Z INFO qdrant::tonic] Qdrant gRPC listening on 6334
-qdrant-1 | [2025-12-25T16:38:00.890Z INFO actix_server::builder] Starting 3 workers
-qdrant-1 | [2025-12-25T16:38:00.890Z INFO actix_server::server] Actix runtime found; starting in Actix runtime
-qdrant-1 | [2025-12-25T16:39:02.504Z INFO actix_server::server] SIGTERM received; starting graceful shutdown
-qdrant-1 | [2025-12-25T16:39:02.505Z INFO actix_server::worker] Shutting down idle worker
-qdrant-1 | [2025-12-25T16:39:02.508Z INFO actix_server::accept] Accept thread stopped
-qdrant-1 | [2025-12-25T16:39:02.508Z INFO actix_server::worker] Shutting down idle worker
-qdrant-1 | [2025-12-25T16:39:02.508Z INFO actix_server::worker] Shutting down idle worker
-qdrant-1 | _ _
-qdrant-1 | __ _ __| |_ __ __ _ _ __ | |_
-qdrant-1 | / _` |/ _` | '__/ _` | '_ \| __|
-qdrant-1 | | (_| | (_| | | | (_| | | | | |_
-postgres-1 | 2025-12-25 16:39:02.495 UTC [1] LOG: received fast shutdown request
-postgres-1 | 2025-12-25 16:39:02.505 UTC [1] LOG: aborting any active transactions
-postgres-1 | 2025-12-25 16:39:02.521 UTC [1] LOG: background worker "logical replication launcher" (PID 35) exited with exit code 1
-postgres-1 | 2025-12-25 16:39:02.521 UTC [30] LOG: shutting down
-postgres-1 | 2025-12-25 16:39:02.533 UTC [30] LOG: checkpoint starting: shutdown immediate
-chainlit-app-1 | 2025-12-25 18:05:25 - INFO - httpx - HTTP Request: GET http://qdrant:6333 "HTTP/1.1 200 OK"
-chainlit-app-1 | /app/app.py:43: UserWarning: Qdrant client version 1.16.2 is incompatible with server version 1.0.0. Major versions should match and minor version difference must not exceed 1. Set check_compatibility=False to skip version check.
-chainlit-app-1 | return QdrantClient(url=QDRANT_URL)
-qdrant-1 | \__, |\__,_|_| \__,_|_| |_|\__|
-chainlit-app-1 | 2025-12-25 18:05:25 - INFO - httpx - HTTP Request: GET http://qdrant:6333/collections/documents "HTTP/1.1 404 Not Found"
-chainlit-app-1 | 2025-12-25 18:06:08 - WARNING - chainlit - Translation file for it-IT not found. Using parent translation it.
-chainlit-app-1 | 2025-12-25 18:06:10 - WARNING - chainlit - Translation file for it-IT not found. Using parent translation it.
-qdrant-1 | |_|
-qdrant-1 |
-qdrant-1 | Access web UI at https://ui.qdrant.tech/?v=v1.0.0
-qdrant-1 |
-qdrant-1 | [2025-12-25T16:43:53.592Z INFO storage::content_manager::consensus::persistent] Loading raft state from ./storage/raft_state
-qdrant-1 | [2025-12-25T16:43:53.612Z INFO qdrant] Distributed mode disabled
-postgres-1 | 2025-12-25 16:39:02.601 UTC [30] LOG: checkpoint complete: wrote 0 buffers (0.0%), wrote 3 SLRU buffers; 0 WAL file(s) added, 0 removed, 0 recycled; write=0.019 s, sync=0.009 s, total=0.079 s; sync files=2, longest=0.005 s, average=0.005 s; distance=0 kB, estimate=0 kB; lsn=0/1BEF980, redo lsn=0/1BEF980
-postgres-1 | 2025-12-25 16:39:02.644 UTC [1] LOG: database system is shut down
-postgres-1 |
-postgres-1 | PostgreSQL Database directory appears to contain a database; Skipping initialization
-postgres-1 |
-postgres-1 | 2025-12-25 16:43:53.946 UTC [1] LOG: starting PostgreSQL 18.1 (Debian 18.1-1.pgdg13+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
-postgres-1 | 2025-12-25 16:43:53.947 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
-postgres-1 | 2025-12-25 16:43:53.947 UTC [1] LOG: listening on IPv6 address "::", port 5432
-qdrant-1 | [2025-12-25T16:43:53.612Z INFO qdrant] Telemetry reporting enabled, id: 2a83356a-9770-47d3-a0bd-638f75769522
-qdrant-1 | [2025-12-25T16:43:53.615Z INFO qdrant::tonic] Qdrant gRPC listening on 6334
-qdrant-1 | [2025-12-25T16:43:53.616Z INFO actix_server::builder] Starting 3 workers
-postgres-1 | 2025-12-25 16:43:53.965 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
-postgres-1 | 2025-12-25 16:43:53.990 UTC [32] LOG: database system was shut down at 2025-12-25 16:39:02 UTC
-postgres-1 | 2025-12-25 16:43:54.013 UTC [1] LOG: database system is ready to accept connections
-postgres-1 | 2025-12-25 16:48:54.089 UTC [30] LOG: checkpoint starting: time
-postgres-1 | 2025-12-25 16:48:54.175 UTC [30] LOG: checkpoint complete: wrote 0 buffers (0.0%), wrote 3 SLRU buffers; 0 WAL file(s) added, 0 removed, 0 recycled; write=0.036 s, sync=0.009 s, total=0.088 s; sync files=2, longest=0.005 s, average=0.005 s; distance=0 kB, estimate=0 kB; lsn=0/1BEFA88, redo lsn=0/1BEFA30
-postgres-1 | 2025-12-25 16:56:42.002 UTC [1] LOG: received fast shutdown request
-postgres-1 | 2025-12-25 16:56:42.018 UTC [1] LOG: aborting any active transactions
-postgres-1 | 2025-12-25 16:56:42.026 UTC [1] LOG: background worker "logical replication launcher" (PID 35) exited with exit code 1
-postgres-1 | 2025-12-25 16:56:42.030 UTC [30] LOG: shutting down
-postgres-1 | 2025-12-25 16:56:42.039 UTC [30] LOG: checkpoint starting: shutdown immediate
-postgres-1 | 2025-12-25 16:56:42.086 UTC [30] LOG: checkpoint complete: wrote 0 buffers (0.0%), wrote 0 SLRU buffers; 0 WAL file(s) added, 0 removed, 0 recycled; write=0.004 s, sync=0.001 s, total=0.057 s; sync files=0, longest=0.000 s, average=0.000 s; distance=0 kB, estimate=0 kB; lsn=0/1BEFB38, redo lsn=0/1BEFB38
-postgres-1 | 2025-12-25 16:56:42.131 UTC [1] LOG: database system is shut down
-postgres-1 |
-postgres-1 | PostgreSQL Database directory appears to contain a database; Skipping initialization
-postgres-1 |
-chainlit-app-1 | 2025-12-25 18:06:10 - WARNING - chainlit - Translated markdown file for it-IT not found. Defaulting to chainlit.md.
-chainlit-app-1 | 2025-12-25 18:06:13 - INFO - chainlit - Missing custom logo. Falling back to default logo.
-chainlit-app-1 | 2025-12-25 18:06:21 - WARNING - chainlit - Translation file for it-IT not found. Using parent translation it.
-chainlit-app-1 | 2025-12-25 18:06:21 - WARNING - chainlit - Translation file for it-IT not found. Using parent translation it.
-qdrant-1 | [2025-12-25T16:43:53.617Z INFO actix_server::server] Actix runtime found; starting in Actix runtime
-qdrant-1 | [2025-12-25T16:56:42.005Z INFO actix_server::server] SIGTERM received; starting graceful shutdown
-qdrant-1 | [2025-12-25T16:56:42.006Z INFO actix_server::worker] Shutting down idle worker
-qdrant-1 | [2025-12-25T16:56:42.006Z INFO actix_server::worker] Shutting down idle worker
-qdrant-1 | [2025-12-25T16:56:42.007Z INFO actix_server::worker] Shutting down idle worker
-qdrant-1 | [2025-12-25T16:56:42.007Z INFO actix_server::accept] Accept thread stopped
-qdrant-1 | _ _
-qdrant-1 | __ _ __| |_ __ __ _ _ __ | |_
-qdrant-1 | / _` |/ _` | '__/ _` | '_ \| __|
-qdrant-1 | | (_| | (_| | | | (_| | | | | |_
-qdrant-1 | \__, |\__,_|_| \__,_|_| |_|\__|
-qdrant-1 | |_|
-qdrant-1 |
-qdrant-1 | Access web UI at https://ui.qdrant.tech/?v=v1.0.0
-qdrant-1 |
-qdrant-1 | [2025-12-25T16:56:52.790Z INFO storage::content_manager::consensus::persistent] Loading raft state from ./storage/raft_state
-qdrant-1 | [2025-12-25T16:56:52.796Z INFO qdrant] Distributed mode disabled
-qdrant-1 | [2025-12-25T16:56:52.796Z INFO qdrant] Telemetry reporting enabled, id: f821b8ea-9ee5-497e-a172-dfebf253f7b1
-qdrant-1 | [2025-12-25T16:56:52.797Z INFO qdrant::tonic] Qdrant gRPC listening on 6334
-qdrant-1 | [2025-12-25T16:56:52.798Z INFO actix_server::builder] Starting 3 workers
-qdrant-1 | [2025-12-25T16:56:52.798Z INFO actix_server::server] Actix runtime found; starting in Actix runtime
-qdrant-1 | [2025-12-25T18:05:25.183Z INFO actix_web::middleware::logger] 172.18.0.4 "GET /collections/documents HTTP/1.1" 404 110 "-" "python-client/1.16.2 python/3.10.19" 0.007704
-qdrant-1 | [2025-12-25T18:05:30.499Z INFO actix_web::middleware::logger] 172.18.0.4 "PUT /collections/documents HTTP/1.1" 200 71 "-" "python-client/1.16.2 python/3.10.19" 5.311157
-qdrant-1 | [2025-12-25T18:06:22.662Z INFO actix_web::middleware::logger] 172.18.0.4 "GET /collections/documents HTTP/1.1" 200 413 "-" "python-client/1.16.2 python/3.10.19" 0.005606
-postgres-1 | 2025-12-25 16:56:43.530 UTC [1] LOG: starting PostgreSQL 18.1 (Debian 18.1-1.pgdg13+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
-postgres-1 | 2025-12-25 16:56:43.532 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
-postgres-1 | 2025-12-25 16:56:43.532 UTC [1] LOG: listening on IPv6 address "::", port 5432
-postgres-1 | 2025-12-25 16:56:43.552 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
-postgres-1 | 2025-12-25 16:56:43.585 UTC [32] LOG: database system was shut down at 2025-12-25 16:56:42 UTC
-postgres-1 | 2025-12-25 16:56:43.616 UTC [1] LOG: database system is ready to accept connections
-postgres-1 | 2025-12-25 17:01:43.645 UTC [30] LOG: checkpoint starting: time
-postgres-1 | 2025-12-25 17:01:43.712 UTC [30] LOG: checkpoint complete: wrote 0 buffers (0.0%), wrote 3 SLRU buffers; 0 WAL file(s) added, 0 removed, 0 recycled; write=0.019 s, sync=0.009 s, total=0.068 s; sync files=2, longest=0.005 s, average=0.005 s; distance=0 kB, estimate=0 kB; lsn=0/1BEFC40, redo lsn=0/1BEFBE8
-chainlit-app-1 | 2025-12-25 18:06:21 - WARNING - chainlit - Translated markdown file for it-IT not found. Defaulting to chainlit.md.
-chainlit-app-1 | 2025-12-25 18:06:22 - INFO - httpx - HTTP Request: GET http://qdrant:6333 "HTTP/1.1 200 OK"
-chainlit-app-1 | 2025-12-25 18:06:22 - INFO - httpx - HTTP Request: GET http://qdrant:6333/collections/documents "HTTP/1.1 200 OK"
-chainlit-app-1 | 2025-12-25 18:06:28 - WARNING - chainlit - Translation file for it-IT not found. Using parent translation it.
-chainlit-app-1 | 2025-12-25 18:06:36 - INFO - httpx - HTTP Request: GET http://qdrant:6333 "HTTP/1.1 200 OK"
-chainlit-app-1 | 2025-12-25 18:06:37 - INFO - httpx - HTTP Request: POST http://192.168.1.243:11434/api/embed "HTTP/1.1 500 Internal Server Error"
-chainlit-app-1 | 2025-12-25 18:06:39 - INFO - httpx - HTTP Request: POST http://192.168.1.243:11434/api/chat "HTTP/1.1 200 OK"
-chainlit-app-1 | 2025-12-25 18:06:48 - WARNING - chainlit - Translation file for it-IT not found. Using parent translation it.
-chainlit-app-1 | 2025-12-25 18:07:02 - WARNING - chainlit - Translation file for it-IT not found. Using parent translation it.
-chainlit-app-1 | 2025-12-25 18:07:16 - INFO - httpx - HTTP Request: GET http://qdrant:6333 "HTTP/1.1 200 OK"
-chainlit-app-1 | 2025-12-25 18:07:22 - INFO - httpx - HTTP Request: POST http://192.168.1.243:11434/api/embed "HTTP/1.1 500 Internal Server Error"
-chainlit-app-1 | 2025-12-25 18:07:22 - INFO - httpx - HTTP Request: POST http://192.168.1.243:11434/api/chat "HTTP/1.1 200 OK"
-chainlit-app-1 | 2025-12-25 18:07:49 - WARNING - chainlit - Translation file for it-IT not found. Using parent translation it.
-chainlit-app-1 | 2025-12-25 18:07:54 - WARNING - chainlit - Translation file for it-IT not found. Using parent translation it.
-chainlit-app-1 | 2025-12-25 18:08:15 - INFO - httpx - HTTP Request: POST http://192.168.1.243:11434/api/chat "HTTP/1.1 200 OK"
-chainlit-app-1 | 2025-12-25 18:08:30 - WARNING - chainlit - Translation file for it-IT not found. Using parent translation it.
-chainlit-app-1 | 2025-12-25 18:08:57 - INFO - httpx - HTTP Request: GET http://qdrant:6333 "HTTP/1.1 200 OK"
-chainlit-app-1 | 2025-12-25 18:09:03 - INFO - httpx - HTTP Request: POST http://192.168.1.243:11434/api/embed "HTTP/1.1 500 Internal Server Error"
-chainlit-app-1 | 2025-12-25 18:09:03 - INFO - httpx - HTTP Request: POST http://192.168.1.243:11434/api/chat "HTTP/1.1 200 OK"
diff --git a/public/custom.css b/public/custom.css
index 68d56a43..c3495381 100755
--- a/public/custom.css
+++ b/public/custom.css
@@ -1,36 +1,12 @@
-/* dFm AI Station - Perplexity Clean Style */
-:root {
- --bg-color: #0B0F1A;
- --card-color: #161B2C;
- --accent-color: #6366F1;
+.user-badge {
+ background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
+ padding: 4px 12px;
+ border-radius: 12px;
+ font-size: 0.85em;
}
-body { background-color: var(--bg-color) !important; color: #F1F5F9 !important; }
-
-/* Header e Logo */
-header {
- background: rgba(11, 15, 26, 0.8) !important;
- backdrop-filter: blur(8px);
- border-bottom: 1px solid #23293F !important;
-}
-
-/* Sidebar */
-.MuiDrawer-paper {
- background-color: var(--bg-color) !important;
- border-right: 1px solid #23293F !important;
-}
-
-/* Messaggi */
-div[class*="user"] {
- background: #1E253A !important;
- border-radius: 12px !important;
- border-left: 4px solid var(--accent-color) !important;
-}
-
-/* Input Area */
-form {
- background: var(--card-color) !important;
- border-radius: 20px !important;
- border: 1px solid #334155 !important;
- box-shadow: 0 4px 20px rgba(0,0,0,0.5) !important;
+/* Evidenzia codice */
+.message pre {
+ background: #2d2d2d;
+ border-left: 4px solid #0066CC;
}
diff --git a/requirements.txt b/requirements.txt
index 0f574f8c..ee0742f3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -26,4 +26,7 @@ aiofiles>=23.0.0
sniffio
aiohttp
boto3>=1.28.0
-azure-storage-file-datalake>=12.14.0
\ No newline at end of file
+azure-storage-file-datalake>=12.14.0
+docling
+pillow
+requests
diff --git a/note command.txt b/script/note_command.txt
similarity index 100%
rename from note command.txt
rename to script/note_command.txt
diff --git a/script/test_vision.py b/script/test_vision.py
new file mode 100644
index 00000000..7c94ff74
--- /dev/null
+++ b/script/test_vision.py
@@ -0,0 +1,23 @@
+import httpx
+import base64
+import sys
+
+# Configurazione
+OLLAMA_URL = "http://192.168.1.243:11434"
+MODEL = "minicpm-v"
+
+print(f"👁️ Test Visione su {OLLAMA_URL} con modello {MODEL}...")
+
+# 1. Controlla se il modello è caricato
+try:
+ r = httpx.get(f"{OLLAMA_URL}/api/tags")
+ models = [m['name'] for m in r.json()['models']]
+ if MODEL not in str(models):
+ print(f"❌ Errore: Il modello {MODEL} non è stato trovato su Ollama!")
+ sys.exit(1)
+ print(f"✅ Modello {MODEL} trovato.")
+except Exception as e:
+ print(f"❌ Errore connessione Ollama: {e}")
+ sys.exit(1)
+
+print("🚀 Tutto pronto per l'implementazione!")