Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,531 +1,373 @@
|
|
1 |
-
|
2 |
-
from chat_client import chat
|
3 |
-
from google_function import leggi_gmail
|
4 |
-
from google_function import scrivi_bozza_gmail
|
5 |
-
from google_function import leggi_calendario_google
|
6 |
-
from google_function import connetti_google
|
7 |
-
from google_function import crea_documento_google
|
8 |
-
import time
|
9 |
-
import os
|
10 |
-
from dotenv import load_dotenv
|
11 |
-
from sentence_transformers import SentenceTransformer
|
12 |
-
import requests
|
13 |
-
from langchain_community.vectorstores import Chroma
|
14 |
-
from langchain_community.embeddings import HuggingFaceEmbeddings
|
15 |
-
import json
|
16 |
-
from googlesearch import search
|
17 |
from bs4 import BeautifulSoup
|
18 |
-
import
|
19 |
-
import
|
20 |
-
|
21 |
-
|
22 |
-
import
|
23 |
-
|
24 |
-
import
|
25 |
-
from openai import OpenAI
|
26 |
-
|
27 |
-
load_dotenv()
|
28 |
-
EFFETTUA_LOGIN_GOOGLE = os.getenv('EFFETTUA_LOGIN_GOOGLE')=="1"
|
29 |
-
URL_APP_SCRIPT = os.getenv('URL_APP_SCRIPT')
|
30 |
-
URL_PROMPT = URL_APP_SCRIPT + '?IdFoglio=1cLw9q70BsPmxMBj9PIzgXtq6sm3X-GVBVnOB5wE8jr8'
|
31 |
-
URL_DOCUMENTI = URL_APP_SCRIPT + '?IdSecondoFoglio=1cLw9q70BsPmxMBj9PIzgXtq6sm3X-GVBVnOB5wE8jr8'
|
32 |
-
SYSTEM_PROMPT = ["Sei BonsiAI e mi aiuterai nelle mie richieste (Parla in ITALIANO)", "Esatto, sono BonsiAI. Di cosa hai bisogno?"]
|
33 |
-
CHAT_BOTS = {
|
34 |
-
"Mixtral 8x7B v0.1": {
|
35 |
-
"model": "mistralai/Mixtral-8x7B-Instruct-v0.1",
|
36 |
-
"description": "Un modello avanzato di chatbot con architettura 8x7B sviluppato da Mistral AI. Supporta fino a 30 pagine di input e costa zero",
|
37 |
-
"pagine_contesto": 30,
|
38 |
-
"richiede_api_key": False
|
39 |
-
},
|
40 |
-
"Mistral 7B v0.2": {
|
41 |
-
"model": "mistralai/Mistral-7B-Instruct-v0.2",
|
42 |
-
"description": "Una versione più leggera del modello Mistral, con architettura 7B, sviluppato da Mistral AI. Supporta fino a 8 pagine di input e costa zero",
|
43 |
-
"pagine_contesto": 8,
|
44 |
-
"richiede_api_key": False
|
45 |
-
},
|
46 |
-
"Gpt 3.5 Turbo": {
|
47 |
-
"model": "gpt-3.5-turbo",
|
48 |
-
"description": "Una versione ottimizzata e performante del modello GPT-3.5 di OpenAI. Supporta 16 Pagine di input e costa 2$ ogni 1000 Pagine",
|
49 |
-
"pagine_contesto": 16,
|
50 |
-
"richiede_api_key": True
|
51 |
-
},
|
52 |
-
"Gpt 4 Turbo": {
|
53 |
-
"model": "gpt-4-turbo",
|
54 |
-
"description": "Una versione avanzata e potenziata del famoso modello GPT-4 di OpenAI, ottimizzata per prestazioni superiori. Supporta fino a 120 Pagine di input e costa 30$ ogni 1000 Pagine",
|
55 |
-
"pagine_contesto": 120,
|
56 |
-
"richiede_api_key": True
|
57 |
-
},
|
58 |
-
"Gpt 4-o": {
|
59 |
-
"model": "gpt-4o",
|
60 |
-
"description": "L'ultimissima versione di GPT! Supporta fino a 120 Pagine di input e costa 20$ ogni 1000 Pagine (meno di GPT 4 Turbo)",
|
61 |
-
"pagine_contesto": 120,
|
62 |
-
"richiede_api_key": True
|
63 |
-
}
|
64 |
-
}
|
65 |
-
option_personalizzata = {'Personalizzata': {'systemRole': 'Tu sei BONSI AI, il mio assistente personale della scuola superiore del Bonsignori. Aiutami in base alle mie esigenze',
|
66 |
-
'systemStyle': 'Firmati sempre come BONSI AI. (scrivi in italiano)',
|
67 |
-
'instruction': '',
|
68 |
-
'tipo': '',
|
69 |
-
'RAG': False}
|
70 |
-
}
|
71 |
-
option_leggiemail = {'Leggi Gmail': {'systemRole': 'Tu sei BONSI AI, il mio assistente personale della scuola superiore del Bonsignori. Effettua l operazione richiesta sulla base delle seguenti email: ',
|
72 |
-
'systemStyle': 'Firmati sempre come BONSI AI. (scrivi in italiano)',
|
73 |
-
'instruction': '',
|
74 |
-
'tipo': 'EMAIL',
|
75 |
-
'RAG': False}
|
76 |
-
}
|
77 |
-
option_leggicalendar = {'Leggi Calendar': {'systemRole': 'Tu sei BONSI AI, il mio assistente personale della scuola superiore del Bonsignori. Effettua l operazione richiesta sulla base dei seguenti eventi di calendario: ',
|
78 |
-
'systemStyle': 'Firmati sempre come BONSI AI. (scrivi in italiano)',
|
79 |
-
'instruction': '',
|
80 |
-
'tipo': 'CALENDAR',
|
81 |
-
'RAG': False}
|
82 |
-
}
|
83 |
-
|
84 |
-
# ----------------------------------------------------------- Interfaccia --------------------------------------------------------------------
|
85 |
-
st.set_page_config(page_title="Bonsi A.I.", page_icon="🏫")
|
86 |
-
|
87 |
-
def init_state() :
|
88 |
-
if "messages" not in st.session_state:
|
89 |
-
st.session_state.messages = []
|
90 |
-
|
91 |
-
if "temp" not in st.session_state:
|
92 |
-
st.session_state.temp = 0.8
|
93 |
-
|
94 |
-
if "history" not in st.session_state:
|
95 |
-
st.session_state.history = [SYSTEM_PROMPT]
|
96 |
-
|
97 |
-
if "top_k" not in st.session_state:
|
98 |
-
st.session_state.top_k = 5
|
99 |
-
|
100 |
-
if "repetion_penalty" not in st.session_state :
|
101 |
-
st.session_state.repetion_penalty = 1
|
102 |
-
|
103 |
-
if "api_key" not in st.session_state :
|
104 |
-
st.session_state.api_key = ""
|
105 |
|
106 |
-
|
107 |
-
st.session_state.chat_bot = "Mixtral 8x7B v0.1"
|
108 |
|
109 |
-
|
110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
st.session_state.numero_generazioni = 1
|
129 |
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
135 |
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
if "urls" not in st.session_state:
|
149 |
-
st.session_state.urls = [""] * 5
|
150 |
-
|
151 |
-
if "creds" not in st.session_state:
|
152 |
-
st.session_state.creds = None
|
153 |
|
154 |
-
|
155 |
-
|
156 |
|
157 |
-
|
158 |
-
|
|
|
|
|
|
|
159 |
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
207 |
try:
|
208 |
-
|
209 |
-
|
|
|
|
|
|
|
210 |
except:
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
def open_new_tab(url):
|
215 |
-
placeholder = st.empty()
|
216 |
-
with placeholder:
|
217 |
-
placeholder.empty()
|
218 |
-
new_tab_js = f'''<script type="text/javascript">window.open("{url}", "_blank");</script>'''
|
219 |
-
st.components.v1.html(new_tab_js, height=1)
|
220 |
-
time.sleep(0.3)
|
221 |
-
placeholder.empty()
|
222 |
-
|
223 |
-
def esporta_testo(tipo, ultimo_messaggio):
|
224 |
-
testo = st.session_state.ultimo_messaggio if ultimo_messaggio else st.session_state.tutti_messaggi
|
225 |
-
if tipo == 'Bozza Email':
|
226 |
-
url = scrivi_bozza_gmail(testo)
|
227 |
-
open_new_tab(url)
|
228 |
-
if tipo == 'Google Documenti':
|
229 |
-
url = crea_documento_google(testo)
|
230 |
-
open_new_tab(url)
|
231 |
-
|
232 |
-
def sidebar():
|
233 |
-
def retrieval_settings() :
|
234 |
-
st.markdown("# Impostazioni Prompt")
|
235 |
-
st.session_state.selected_option_key = st.selectbox('Azione', list(st.session_state.options.keys()))
|
236 |
-
st.session_state.selected_option = st.session_state.options.get(st.session_state.selected_option_key, {})
|
237 |
|
238 |
-
if st.session_state.options.get(st.session_state.selected_option_key, {})["tipo"]=='DOCUMENTO':
|
239 |
-
st.session_state.selected_documento_key = st.selectbox('Documento', list(st.session_state.documenti.keys()))
|
240 |
-
st.session_state.selected_documento = st.session_state.documenti.get(st.session_state.selected_documento_key, {})
|
241 |
-
st.session_state.instruction = st.session_state.selected_documento.get('instruction', '')['Testo']
|
242 |
-
else:
|
243 |
-
st.session_state.instruction = st.session_state.selected_option.get('instruction', '')
|
244 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
if st.session_state.selected_option_key == 'Decreti':
|
261 |
-
st.session_state.top_k = st.slider(label="Documenti da ricercare", min_value=1, max_value=20, value=4, disabled=not st.session_state.rag_enabled)
|
262 |
-
st.session_state.decreti_escludere = st.multiselect(
|
263 |
-
'Decreti da escludere',
|
264 |
-
['23.10.2 destinazione risorse residue pnrr DGR 1051-2023_Destinazione risorse PNRR Duale.pdf', '23.10.25 accompagnatoria Circolare Inail assicurazione.pdf', '23.10.26 circolare Inail assicurazione.pdf', '23.10.3 FAQ in attesa di avviso_.pdf', '23.11.2 avviso 24_24 Decreto 17106-2023 Approvazione Avviso IeFP 2023-2024.pdf', '23.5.15 decreto linee inclusione x enti locali.pdf', '23.6.21 Circolare+esplicativa+DGR+312-2023.pdf', '23.7.3 1° Decreto R.L. 23_24 .pdf', '23.9 Regolamento_prevenzione_bullismo_e_cyberbullismo__Centro_Bonsignori.pdf', '23.9.1 FAQ inizio anno formativo.pdf', '23.9.15 DECRETO VERIFICHE AMMINISTR 15-09-23.pdf', '23.9.4 modifica decreto GRS.pdf', '23.9.8 Budget 23_24.pdf', '24.10.2022 DECRETO loghi N.15176.pdf', 'ALLEGATO C_Scheda Supporti al funzionamento.pdf', 'ALLEGATO_ B_ Linee Guida.pdf', 'ALLEGATO_A1_PEI_INFANZIA.pdf', 'ALLEGATO_A2_PEI_PRIMARIA.pdf', 'ALLEGATO_A3_PEI_SEC_1_GRADO.pdf', 'ALLEGATO_A4_PEI_SEC_2_GRADO.pdf', 'ALLEGATO_C_1_Tabella_Fabbisogni.pdf', 'Brand+Guidelines+FSE+.pdf', 'Decreto 20797 del 22-12-2023_Aggiornamento budget PNRR.pdf', 'Decreto 20874 del 29-12-2023 Avviso IeFP PNRR 2023-2024_file unico.pdf'],
|
265 |
-
[])
|
266 |
-
st.session_state.uploaded_files = st.file_uploader("Importa file", accept_multiple_files=True)
|
267 |
-
st.session_state.testo_documenti = ''
|
268 |
-
for uploaded_file in st.session_state.uploaded_files:
|
269 |
-
text_doc = read_text_from_file(uploaded_file)
|
270 |
-
st.session_state.testo_documenti += text_doc
|
271 |
-
print(st.session_state.testo_documenti)
|
272 |
-
st.markdown("---")
|
273 |
-
st.markdown("# Ricerca Online")
|
274 |
-
st.session_state.cerca_online = st.toggle("Attivata", value=False)
|
275 |
-
with st.popover("Siti Specifici", disabled=not st.session_state.cerca_online,use_container_width=True):
|
276 |
-
st.markdown("#### Inserisci Siti Web ")
|
277 |
-
for i in range(5):
|
278 |
-
st.session_state.urls[i] = st.text_input(f"URL Sito {i+1}", placeholder='Sito Web...', help='è possibile specificare anche il link di un video Youtube, in tal caso verrà restituita la trascrizione del video')
|
279 |
-
st.session_state.selected_tbs = st.selectbox("Periodo:", list(st.session_state.tbs_options.keys()), disabled=(not st.session_state.cerca_online) or (st.session_state.urls[0]!=""))
|
280 |
-
st.session_state.tbs_value = st.session_state.tbs_options[st.session_state.selected_tbs]
|
281 |
-
st.session_state.numero_siti = st.slider(label="Risultati", min_value = 1, max_value=20, value=3, disabled=(not st.session_state.cerca_online) or (st.session_state.urls[0]!=""))
|
282 |
-
#st.session_state.suddividi_ricerca = st.toggle("Attivata", value=False)
|
283 |
-
st.markdown("---")
|
284 |
-
|
285 |
-
def model_settings():
|
286 |
-
st.markdown("# Modello")
|
287 |
-
st.session_state.chat_bot = st.sidebar.selectbox('Tipo', list(CHAT_BOTS.keys()))
|
288 |
-
if CHAT_BOTS[st.session_state.chat_bot]["richiede_api_key"] == True:
|
289 |
-
st.session_state.api_key = st.text_input('Api Key', type = 'password', label_visibility='collapsed', placeholder='Inserisci la chiave API')
|
290 |
-
st.session_state.client = OpenAI(api_key=st.session_state.api_key)
|
291 |
-
print('xxxxxxx')
|
292 |
-
st.write(CHAT_BOTS[st.session_state.chat_bot]["description"])
|
293 |
-
st.session_state.split = st.slider(label="Pagine Suddivisione", min_value=1, max_value=CHAT_BOTS[st.session_state.chat_bot]["pagine_contesto"], value=CHAT_BOTS[st.session_state.chat_bot]["pagine_contesto"], help='Se il documento ha 100 pagine e suddivido per 20 pagine elaborerà la risposta 5 volte. Più alto è il numero e meno volte elaborerà ma la risposta sarà più imprecisa')
|
294 |
-
st.session_state.numero_generazioni = st.slider(label="Generazioni", min_value = 1, max_value=10, value=1)
|
295 |
-
st.session_state.enable_history = st.toggle("Storico Messaggi", value=True)
|
296 |
-
st.session_state.temp = st.slider(label="Creatività", min_value=0.0, max_value=1.0, step=0.1, value=0.9)
|
297 |
-
st.session_state.max_tokens = st.slider(label="Lunghezza Output", min_value = 2, max_value=4096, step= 32, value=1024)
|
298 |
-
st.markdown("---")
|
299 |
-
|
300 |
-
def export_settings():
|
301 |
-
st.markdown("# Esportazione")
|
302 |
-
st.session_state.export_type = st.selectbox('Tipologia', ('Non Esportare', 'Google Documenti', 'Bozza Email'), help='Seleziona la tipologia di esportazione del testo generato')
|
303 |
-
st.session_state.export_all = st.toggle("Considera tutte le chat", value=False)
|
304 |
-
if st.button("Esporta", type="primary", use_container_width=True):
|
305 |
-
esporta_testo(st.session_state.export_type, st.session_state.export_all)
|
306 |
-
st.markdown("---")
|
307 |
-
|
308 |
-
with st.sidebar:
|
309 |
-
retrieval_settings()
|
310 |
-
model_settings()
|
311 |
-
if EFFETTUA_LOGIN_GOOGLE:
|
312 |
-
export_settings()
|
313 |
-
st.markdown("""> **Creato da Matteo Bergamelli **""")
|
314 |
-
|
315 |
-
def header() :
|
316 |
-
st.title("Bonsi A.I.", anchor=False)
|
317 |
-
with st.expander("Cos'è BonsiAI?"):
|
318 |
-
st.info("""BonsiAI Chat è un ChatBot personalizzato basato su un database vettoriale, funziona secondo il principio della Generazione potenziata da Recupero (RAG).
|
319 |
-
La sua funzione principale ruota attorno alla gestione di un ampio repository di documenti BonsiAI e fornisce agli utenti risposte in linea con le loro domande.
|
320 |
-
Questo approccio garantisce una risposta più precisa sulla base della richiesta degli utenti.""")
|
321 |
-
|
322 |
-
def chat_box() :
|
323 |
-
for message in st.session_state.messages:
|
324 |
-
with st.chat_message(message["role"]):
|
325 |
-
st.markdown(message["content"])
|
326 |
-
|
327 |
-
def formattaPrompt(prompt, systemRole, systemStyle, instruction):
|
328 |
-
if st.session_state.cerca_online:
|
329 |
-
systemRole += '. Ti ho fornito una lista di materiali nelle instruction. Devi rispondere sulla base delle informazioni fonrnite!'
|
330 |
-
input_text = f'''
|
331 |
-
{{
|
332 |
-
"input": {{
|
333 |
-
"role": "system",
|
334 |
-
"content": "{systemRole}",
|
335 |
-
"style": "{systemStyle} "
|
336 |
-
}},
|
337 |
-
"messages": [
|
338 |
-
{{
|
339 |
-
"role": "instructions",
|
340 |
-
"content": "{instruction} ({systemStyle})"
|
341 |
-
}},
|
342 |
-
{{
|
343 |
-
"role": "user",
|
344 |
-
"content": "{prompt}"
|
345 |
-
}}
|
346 |
-
]
|
347 |
-
}}
|
348 |
-
'''
|
349 |
-
return input_text
|
350 |
-
|
351 |
-
def gen_augmented_prompt(prompt, top_k) :
|
352 |
-
links = ""
|
353 |
-
embedding = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
|
354 |
-
db = Chroma(persist_directory='./DB_Decreti', embedding_function=embedding)
|
355 |
-
docs = db.similarity_search(prompt, k=top_k)
|
356 |
-
links = []
|
357 |
-
context = ''
|
358 |
-
NomeCartellaOriginariaDB = 'Documenti_2\\'
|
359 |
-
for doc in docs:
|
360 |
-
testo = doc.page_content.replace('\n', ' ')
|
361 |
-
context += testo + '\n\n\n'
|
362 |
-
reference = doc.metadata["source"].replace(NomeCartellaOriginariaDB, '') + ' (Pag. ' + str(doc.metadata["page"]) + ')'
|
363 |
-
links.append((reference, testo))
|
364 |
-
return context, links
|
365 |
-
|
366 |
-
def get_search_results_int(url):
|
367 |
-
result = {'title': '', 'description': '', 'url': '', 'body': ''}
|
368 |
-
try:
|
369 |
-
if "www.youtube.com" in url:
|
370 |
-
video_id = url.split("=")[1]
|
371 |
-
title = 'Video Youtube'
|
372 |
-
description = ''
|
373 |
-
transcript = YouTubeTranscriptApi.get_transcript(video_id)
|
374 |
-
body_content = " ".join([segment["text"] for segment in transcript])
|
375 |
-
print(video_id)
|
376 |
-
print(body_content)
|
377 |
-
result = {'title': title, 'description': body_content, 'url': url, 'body': body_content}
|
378 |
-
else:
|
379 |
-
response = requests.get(url)
|
380 |
-
soup = BeautifulSoup(response.text, 'html.parser')
|
381 |
-
title = soup.title.string if soup.title else "N/A"
|
382 |
-
description = soup.find('meta', attrs={'name': 'description'})['content'] if soup.find('meta', attrs={'name': 'description'}) else "N/A"
|
383 |
-
body_content = soup.find('body').get_text() if soup.find('body') else "N/A"
|
384 |
-
result = {'title': title, 'description': description, 'url': url, 'body': body_content}
|
385 |
-
except Exception as e:
|
386 |
-
print(f"Error fetching data from {url}: {e}")
|
387 |
-
return result
|
388 |
-
|
389 |
-
def get_search_results(query, top_k):
|
390 |
-
results = []
|
391 |
-
if st.session_state.urls[0] != "":
|
392 |
-
for i in range(5):
|
393 |
-
url = st.session_state.urls[i]
|
394 |
-
if url != "":
|
395 |
-
results.append(get_search_results_int(url))
|
396 |
-
else:
|
397 |
-
for url in search(query, num=top_k, stop=top_k, tbs=st.session_state.tbs_value):
|
398 |
-
results.append(get_search_results_int(url))
|
399 |
-
return results
|
400 |
|
401 |
-
def
|
402 |
-
|
403 |
-
|
404 |
-
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
|
412 |
-
|
413 |
-
|
414 |
-
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
for chunk in chat_stream :
|
441 |
-
if CHAT_BOTS[st.session_state.chat_bot]["model"][:3] == 'gpt':
|
442 |
-
if chunk.choices[0].delta and chunk.choices[0].delta.content:
|
443 |
-
full_response += chunk.choices[0].delta.content
|
444 |
-
else:
|
445 |
-
if chunk.token.text!='</s>' :
|
446 |
-
full_response += chunk.token.text
|
447 |
-
placeholder.markdown(full_response + "▌")
|
448 |
-
placeholder.markdown(full_response)
|
449 |
-
return full_response
|
450 |
-
|
451 |
-
def show_source(links) :
|
452 |
-
with st.expander("Mostra fonti") :
|
453 |
-
for link in links:
|
454 |
-
reference, testo = link
|
455 |
-
st.info('##### ' + reference.replace('_', ' ') + '\n\n'+ testo)
|
456 |
-
|
457 |
-
def split_text(text, chunk_size):
|
458 |
-
testo_suddiviso = []
|
459 |
-
if text == '':
|
460 |
-
text = ' '
|
461 |
-
if chunk_size < 100:
|
462 |
-
chunk_size = 60000
|
463 |
-
for i in range(0, len(text), chunk_size):
|
464 |
-
testo_suddiviso.append(text[i:i+chunk_size])
|
465 |
-
return testo_suddiviso
|
466 |
-
|
467 |
-
init_state()
|
468 |
-
if not st.session_state.login_effettuato and EFFETTUA_LOGIN_GOOGLE:
|
469 |
-
connetti_google()
|
470 |
-
|
471 |
-
if st.session_state.login_effettuato or not EFFETTUA_LOGIN_GOOGLE:
|
472 |
-
st_javascript("localStorage.removeItem('token');")
|
473 |
-
init_state()
|
474 |
-
sidebar()
|
475 |
-
header()
|
476 |
-
chat_box()
|
477 |
|
478 |
-
|
479 |
-
|
480 |
-
|
481 |
-
|
482 |
-
|
483 |
-
|
484 |
-
|
485 |
-
|
486 |
-
|
487 |
-
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
|
492 |
-
|
493 |
-
|
494 |
-
|
495 |
-
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
|
511 |
-
|
512 |
-
|
513 |
-
|
514 |
-
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
519 |
-
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
st.session_state.tutti_messaggi += '\n\n' + full_response
|
524 |
-
if st.session_state.enable_history:
|
525 |
-
st.session_state.history.append([prompt_originale, full_response])
|
526 |
-
else:
|
527 |
-
st.session_state.history.append(['', ''])
|
528 |
-
st.success('Generazione Completata')
|
529 |
-
payload = {"domanda": prompt_originale, "risposta": risposta_completa}
|
530 |
-
json_payload = json.dumps(payload)
|
531 |
-
response = requests.post(URL_APP_SCRIPT, data=json_payload)
|
|
|
1 |
+
from io import BytesIO
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
from bs4 import BeautifulSoup
|
3 |
+
from collections import namedtuple
|
4 |
+
import requests
|
5 |
+
import re
|
6 |
+
import pandas as pd
|
7 |
+
import numpy as np
|
8 |
+
import time
|
9 |
+
import streamlit as st
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
|
11 |
+
prezzo_al_mq = 0
|
|
|
12 |
|
13 |
+
class Immobiliare:
|
14 |
+
|
15 |
+
def __init__(self, url, *,
|
16 |
+
verbose=True,
|
17 |
+
min_house_cost=10_000,
|
18 |
+
browse_all_pages=True,
|
19 |
+
area_not_found=0,
|
20 |
+
price_not_found=np.nan,
|
21 |
+
floor_not_found=0,
|
22 |
+
car_not_found=0,
|
23 |
+
energy_not_found="n/a",
|
24 |
+
invalid_price_per_area=0,
|
25 |
+
wait=60):
|
26 |
|
27 |
+
self.url = url
|
28 |
+
self.verbose = verbose
|
29 |
+
self.min_house_cost = min_house_cost
|
30 |
+
self.browse_all_pages = browse_all_pages
|
31 |
+
self.wait = wait / 1000
|
32 |
+
|
33 |
+
self.area_not_found = area_not_found
|
34 |
+
self.price_not_found = price_not_found
|
35 |
+
self.floor_not_found = floor_not_found
|
36 |
+
self.car_not_found = car_not_found
|
37 |
+
self.energy_not_found = energy_not_found
|
38 |
+
self.invalid_price_per_area = invalid_price_per_area
|
39 |
+
|
40 |
+
def _say(self, *args, **kwargs):
|
41 |
+
if self.verbose:
|
42 |
+
print(*args, **kwargs)
|
|
|
43 |
|
44 |
+
def get_all_urls(self):
|
45 |
+
pattern = re.compile(r"\d+\/$")
|
46 |
+
urls_ = []
|
47 |
+
|
48 |
+
# first page
|
49 |
+
self._say("Processing page 1")
|
50 |
+
page = self._get_page(self.url)
|
51 |
+
|
52 |
+
page.seek(0)
|
53 |
+
soup = BeautifulSoup(page, "html.parser")
|
54 |
+
|
55 |
+
for link in soup.find_all("a"):
|
56 |
+
time.sleep(self.wait)
|
57 |
+
l = link.get("href")
|
58 |
+
|
59 |
+
if l is None:
|
60 |
+
continue
|
61 |
+
|
62 |
+
if "https" in l and "annunci" in l:
|
63 |
+
if pattern.search(l):
|
64 |
+
urls_.append(l)
|
65 |
+
|
66 |
+
if self.browse_all_pages:
|
67 |
+
for i in range(2, 10_000):
|
68 |
+
self._say(f"Processing page {i}")
|
69 |
+
curr_url = self.url + f"&pag={i}"
|
70 |
+
|
71 |
+
t = self._get_text(curr_url).lower()
|
72 |
+
|
73 |
+
if "404 not found" in t or "non è presente" in t:
|
74 |
+
self.urls_ = urls_
|
75 |
+
break
|
76 |
+
|
77 |
+
else:
|
78 |
+
page = self._get_page(curr_url)
|
79 |
+
page.seek(0)
|
80 |
+
soup = BeautifulSoup(page, "html.parser")
|
81 |
+
|
82 |
+
for link in soup.find_all("a"):
|
83 |
+
l = link.get("href")
|
84 |
+
|
85 |
+
if l is None:
|
86 |
+
continue
|
87 |
+
|
88 |
+
if "https" in l and "annunci" in l:
|
89 |
+
if pattern.search(l):
|
90 |
+
urls_.append(l)
|
91 |
+
|
92 |
+
self.urls_ = urls_
|
93 |
+
self._say("All retrieved urls in attribute 'urls_'")
|
94 |
+
self._say(f"Found {len(urls_)} houses matching criteria.")
|
95 |
+
|
96 |
+
@staticmethod
|
97 |
+
def _get_page(url):
|
98 |
+
req = requests.get(url, allow_redirects=False)
|
99 |
+
page = BytesIO()
|
100 |
+
page.write(req.content)
|
101 |
+
return page
|
102 |
|
103 |
+
@staticmethod
|
104 |
+
def _get_text(sub_url):
|
105 |
+
req = requests.get(sub_url, allow_redirects=False)
|
106 |
+
page = BytesIO()
|
107 |
+
page.write(req.content)
|
108 |
+
page.seek(0)
|
109 |
+
soup = BeautifulSoup(page, "html.parser")
|
110 |
+
text = soup.get_text()
|
111 |
+
t = text.replace("\n", "")
|
112 |
+
for _ in range(50):
|
113 |
+
t = t.replace(" ", " ")
|
114 |
+
return t
|
|
|
|
|
|
|
|
|
|
|
115 |
|
116 |
+
def _get_data(self, sub_url):
|
117 |
+
t = self._get_text(sub_url).lower()
|
118 |
|
119 |
+
# costo appartamento
|
120 |
+
cost_patterns = (
|
121 |
+
r"€ (\d+\.\d+\.\d+)", #if that's more than 1M €
|
122 |
+
r"€ (\d+\.\d+)",
|
123 |
+
)
|
124 |
|
125 |
+
cost = None
|
126 |
+
locali = None
|
127 |
+
for pattern in cost_patterns:
|
128 |
+
cost_pattern = re.compile(pattern)
|
129 |
+
try:
|
130 |
+
cost = cost_pattern.search(t)
|
131 |
+
locali = str(cost.group(1).replace(".", ""))[-1]
|
132 |
+
cost = str(cost.group(1).replace(".", ""))[:-1]
|
133 |
+
#cost = cost.group(1).replace(".", "")
|
134 |
+
break
|
135 |
+
except AttributeError:
|
136 |
+
continue
|
137 |
+
|
138 |
+
if cost is None:
|
139 |
+
if "prezzo su richiesta" in t:
|
140 |
+
self._say(f"Price available upon request for {sub_url}")
|
141 |
+
cost = self.price_not_found
|
142 |
+
else:
|
143 |
+
self._say(f"Can't get price for {sub_url}")
|
144 |
+
cost = self.price_not_found
|
145 |
+
|
146 |
+
if cost is not None and cost is not self.price_not_found:
|
147 |
+
if int(cost) < self.min_house_cost:
|
148 |
+
if "prezzo su richiesta" in t:
|
149 |
+
self._say(f"Price available upon request for {sub_url}")
|
150 |
+
cost = self.price_not_found
|
151 |
+
else:
|
152 |
+
self._say(f"Too low house price: {int(cost)}? for {sub_url}")
|
153 |
+
cost = self.price_not_found
|
154 |
+
|
155 |
+
# piano
|
156 |
+
floor_patterns = (
|
157 |
+
r"piano (\d{1,2})",
|
158 |
+
r"(\d{1,2}) piano",
|
159 |
+
r"(\d{1,2}) piani",
|
160 |
+
)
|
161 |
+
|
162 |
+
floor = None
|
163 |
+
for pattern in floor_patterns:
|
164 |
+
floor_pattern = re.compile(pattern)
|
165 |
+
floor = floor_pattern.search(t)
|
166 |
+
if floor is not None:
|
167 |
+
floor = floor.group(1)
|
168 |
+
break
|
169 |
+
|
170 |
+
if "piano terra" in t:
|
171 |
+
floor = 1
|
172 |
+
|
173 |
+
ultimo = "ultimo" in t
|
174 |
+
|
175 |
+
# metri quadri
|
176 |
+
|
177 |
+
area_pattern = re.compile(r"(\d+) m²")
|
178 |
+
try:
|
179 |
+
area = area_pattern.search(t)
|
180 |
+
area = area.group(1)
|
181 |
+
except AttributeError:
|
182 |
+
area = self.area_not_found
|
183 |
+
if "asta" in t:
|
184 |
+
self._say(f"Auction house: no area info {sub_url}")
|
185 |
+
else:
|
186 |
+
self._say(f"Can't get area info from url {sub_url}")
|
187 |
+
|
188 |
+
# classe energetica
|
189 |
+
energy_patterns = (
|
190 |
+
r"energetica (\D{1,2}) ",
|
191 |
+
r"energetica(\S{1,2})",
|
192 |
+
)
|
193 |
+
|
194 |
+
def energy_acceptable(stringlike):
|
195 |
+
if not stringlike.startswith(("A", "B", "C", "D", "E", "F", "G")):
|
196 |
+
return False
|
197 |
+
else:
|
198 |
+
if len(stringlike) == 1:
|
199 |
+
return True
|
200 |
+
else:
|
201 |
+
if not stringlike.endswith(
|
202 |
+
("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+")
|
203 |
+
):
|
204 |
+
return False
|
205 |
+
else:
|
206 |
+
return True
|
207 |
+
|
208 |
+
energy = None
|
209 |
+
for i, pattern in enumerate(energy_patterns):
|
210 |
+
energy_pattern = re.compile(pattern)
|
211 |
+
energy = energy_pattern.search(t)
|
212 |
+
if energy is not None:
|
213 |
+
energy = energy.group(1).upper()
|
214 |
+
if energy_acceptable(energy):
|
215 |
+
break
|
216 |
+
|
217 |
+
if energy is None or not energy_acceptable(energy):
|
218 |
+
if "in attesa di certificazione" in t:
|
219 |
+
self._say(f"Energy efficiency still pending for {sub_url} ")
|
220 |
+
energy = self.energy_not_found
|
221 |
+
else:
|
222 |
+
self._say(f"Can't get energy efficiency from {sub_url}")
|
223 |
+
energy = self.energy_not_found
|
224 |
+
|
225 |
+
# posto auto
|
226 |
+
car_patterns = (
|
227 |
+
r"post\S auto (\d{1,2})",
|
228 |
+
)
|
229 |
+
|
230 |
+
car = None
|
231 |
+
for pattern in car_patterns:
|
232 |
+
car_pattern = re.compile(pattern)
|
233 |
+
car = car_pattern.search(t)
|
234 |
+
if car is not None:
|
235 |
+
car = car.group(1)
|
236 |
+
break
|
237 |
+
|
238 |
+
if car is None:
|
239 |
+
available_upon_request = re.compile(r"possibilit\S.{0,10}auto")
|
240 |
+
if available_upon_request.search(t) is not None:
|
241 |
+
self._say(f"Car spot/box available upon request for {sub_url}")
|
242 |
+
car = 0
|
243 |
+
else:
|
244 |
+
car = self.car_not_found
|
245 |
+
|
246 |
+
# €/m²
|
247 |
try:
|
248 |
+
price_per_area = round(int(cost) / int(area), 1)
|
249 |
+
differenza = prezzo_al_mq - price_per_area
|
250 |
+
vantaggio = (differenza / prezzo_al_mq) * 120
|
251 |
+
vantaggio = max(0, vantaggio)
|
252 |
+
vantaggio = int(vantaggio)
|
253 |
except:
|
254 |
+
price_per_area = self.energy_not_found
|
255 |
+
vantaggio = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
256 |
|
|
|
|
|
|
|
|
|
|
|
|
|
257 |
|
258 |
+
# packing the results
|
259 |
+
House = namedtuple(
|
260 |
+
"House", [
|
261 |
+
"Vantaggio",
|
262 |
+
"Prezzo_Mq",
|
263 |
+
"Prezzo",
|
264 |
+
"Superficie",
|
265 |
+
"Locali",
|
266 |
+
"Piano",
|
267 |
+
#"ultimo",
|
268 |
+
"Url"
|
269 |
+
#"energy",
|
270 |
+
#"posto_auto"
|
271 |
+
]
|
272 |
+
)
|
273 |
|
274 |
+
res = House(
|
275 |
+
vantaggio,
|
276 |
+
price_per_area,
|
277 |
+
cost,
|
278 |
+
area,
|
279 |
+
#ultimo,
|
280 |
+
locali,
|
281 |
+
floor,
|
282 |
+
sub_url
|
283 |
+
#energy,
|
284 |
+
#car
|
285 |
+
)
|
286 |
+
|
287 |
+
return res
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
288 |
|
289 |
+
def find_all_houses(self):
|
290 |
+
if not hasattr(self, "urls_"):
|
291 |
+
self.get_all_urls()
|
292 |
+
|
293 |
+
all_results = []
|
294 |
+
for url in self.urls_:
|
295 |
+
try:
|
296 |
+
all_results.append(self._get_data(url))
|
297 |
+
except:
|
298 |
+
print(f"offending_url='{url}'")
|
299 |
+
raise
|
300 |
+
|
301 |
+
self.df_ = pd.DataFrame(all_results)
|
302 |
+
self._say("Results stored in attribute 'df_'")
|
303 |
+
|
304 |
+
# Funzione di styling per evidenziare in rosso i valori inferiori alla variabile
|
305 |
+
def evidenzia_in_rosso(valore, soglia):
|
306 |
+
if valore < soglia:
|
307 |
+
return 'background-color: red; color: white'
|
308 |
+
return ''
|
309 |
+
|
310 |
+
st.set_page_config(layout="wide")
|
311 |
+
# Streamlit interface
|
312 |
+
|
313 |
+
st.title('🏠 Immobiliare A.I. ')
|
314 |
+
st.write("##### Il tuo assistente di intelligenza artificiale per la ricerca di occasioni immobiliari")
|
315 |
+
with st.expander("Informazioni"):
|
316 |
+
st.write("Immobiliare A.I. è la webapp che semplifica la ricerca di immobili, grazie a algoritmi avanzati che calcolano il vantaggio di ogni offerta. Trova le migliori occasioni sul mercato con analisi precise e personalizzate. Scopri l’immobile giusto per te con facilità e sicurezza!")
|
317 |
+
|
318 |
+
cerca_premuto = False
|
319 |
+
# Input field for 'comune'
|
320 |
+
with st.sidebar:
|
321 |
+
st.title("Filtri")
|
322 |
+
comune_input = st.text_input("Comune", 'lonato del garda')
|
323 |
+
prezzo_al_mq = st.number_input("Prezzo Medio al Mq", 2500)
|
324 |
+
prezzo_minimo = st.sidebar.slider("Prezzo Minimo", min_value=0, max_value=1000, value=200)
|
325 |
+
prezzo_massimo = st.sidebar.slider("Prezzo Massimo", min_value=0, max_value=1000, value=230)
|
326 |
+
|
327 |
+
locali = list(range(1, 21)) # Intervallo da 1 a 10
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
328 |
|
329 |
+
# Select slider unico per selezionare l'intervallo del numero di locali
|
330 |
+
locali_range = st.sidebar.select_slider(
|
331 |
+
"Locali",
|
332 |
+
options=locali,
|
333 |
+
value=(locali[2], locali[4]) # Valore iniziale, da 1 a 5 locali
|
334 |
+
)
|
335 |
+
|
336 |
+
# Dividi il range in minimo e massimo numero di locali
|
337 |
+
locali_minimo, locali_massimo = locali_range
|
338 |
+
prezzo_minimo = prezzo_minimo*1000
|
339 |
+
prezzo_massimo = prezzo_massimo*1000
|
340 |
+
cerca_premuto = st.button("Cerca", use_container_width=True, type='primary')
|
341 |
+
|
342 |
+
if cerca_premuto:
|
343 |
+
if comune_input:
|
344 |
+
comune = comune_input.replace(" ", "-")
|
345 |
+
|
346 |
+
|
347 |
+
url = f"https://www.immobiliare.it/vendita-case/{comune}/?prezzoMinimo={prezzo_minimo}&prezzoMassimo={prezzo_massimo}&localiMinimo={locali_minimo}&localiMassimo={locali_massimo}&random=123456"
|
348 |
+
#st.write(f"Seraching: {url}")
|
349 |
+
with st.spinner("Ricerca immobiliare in corso..."):
|
350 |
+
case = Immobiliare(url)
|
351 |
+
case.find_all_houses()
|
352 |
+
df = case.df_
|
353 |
+
df = df.sort_values(by="Prezzo_Mq", ascending=True)
|
354 |
+
|
355 |
+
st.dataframe(df, hide_index=True, use_container_width=True,
|
356 |
+
column_config ={
|
357 |
+
"Vantaggio": st.column_config.ProgressColumn(
|
358 |
+
"Vantaggio",
|
359 |
+
help="Vantaggio in %",
|
360 |
+
format='%f',
|
361 |
+
min_value=0,
|
362 |
+
max_value=100,
|
363 |
+
),
|
364 |
+
"Prezzo_Mq": " €/Mq",
|
365 |
+
"Prezzo": "Prezzo Totale",
|
366 |
+
"Superficie": "Superficie",
|
367 |
+
"Locali": "Locali",
|
368 |
+
"Piano": "Piano",
|
369 |
+
"Url": st.column_config.LinkColumn("App URL")
|
370 |
+
})
|
371 |
+
st.success("Elaborazione Completata")
|
372 |
+
else:
|
373 |
+
st.error("Per favore, inserisci il nome di un comune.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|