MatteoScript commited on
Commit
08c80af
·
verified ·
1 Parent(s): aab87ce

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +357 -515
app.py CHANGED
@@ -1,531 +1,373 @@
1
- import streamlit as st
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 PyPDF2
19
- import pytesseract
20
- from PIL import Image
21
- from youtube_transcript_api import YouTubeTranscriptApi
22
- import webbrowser
23
- from streamlit_javascript import st_javascript
24
- import datetime
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
- if "chat_bot" not in st.session_state :
107
- st.session_state.chat_bot = "Mixtral 8x7B v0.1"
108
 
109
- if 'loaded_data' not in st.session_state:
110
- st.session_state.loaded_data = False
 
 
 
 
 
 
 
 
 
 
 
111
 
112
- if "split" not in st.session_state:
113
- st.session_state.split = 30
114
-
115
- if "enable_history" not in st.session_state:
116
- st.session_state.enable_history = True
117
-
118
- if "audio_bytes" not in st.session_state:
119
- st.session_state.audio_bytes = False
120
-
121
- if "cerca_online" not in st.session_state:
122
- st.session_state.cerca_online = False
123
-
124
- if "numero_siti" not in st.session_state:
125
- st.session_state.numero_siti = 3
126
-
127
- if "numero_generazioni" not in st.session_state:
128
- st.session_state.numero_generazioni = 1
129
 
130
- if "numero_elementi" not in st.session_state:
131
- st.session_state.numero_elementi = 5
132
-
133
- if "data_inizio" not in st.session_state:
134
- st.session_state.data_inizio = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
- if "data_inizio" not in st.session_state:
137
- st.session_state.data_inizio = None
138
-
139
- if "testo_documenti" not in st.session_state:
140
- st.session_state.testo_documenti = ''
141
-
142
- if "uploaded_files" not in st.session_state:
143
- st.session_state.uploaded_files = None
144
-
145
- if "client" not in st.session_state:
146
- st.session_state.client = None
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
- if "login_effettuato" not in st.session_state:
155
- st.session_state.login_effettuato = False
156
 
157
- if "ultimo_messaggio" not in st.session_state:
158
- st.session_state.ultimo_messaggio = ""
 
 
 
159
 
160
- if "tutti_messaggi" not in st.session_state:
161
- st.session_state.tutti_messaggi = ""
162
-
163
- if "tbs_options" not in st.session_state:
164
- st.session_state.tbs_options = {
165
- "Sempre": "0",
166
- "Ultimo anno": "qdr:y",
167
- "Ultimo mese": "qdr:m",
168
- "Ultima settimana": "qdr:w",
169
- "Ultimo giorno": "qdr:d"
170
- }
171
-
172
- if not st.session_state.loaded_data and (st.session_state.login_effettuato == True or EFFETTUA_LOGIN_GOOGLE == False):
173
- place=st.empty()
174
- place=st.empty()
175
- with place:
176
- with st.status("Caricamento in corso...", expanded=True) as status:
177
- st.write("Inizializzazione Ambiente")
178
- time.sleep(1)
179
- st.write("Inizializzazione Prompt")
180
- URL_REDIRECT = os.getenv('URL_REDIRECT')
181
- options = {}
182
- if URL_REDIRECT != "http://localhost:8501/":
183
- options = requests.get(URL_PROMPT).json()
184
- st.write("Inizializzazione Documenti")
185
- documenti = {}
186
- if URL_REDIRECT != "http://localhost:8501/":
187
- documenti = requests.get(URL_DOCUMENTI).json()
188
- st.session_state.options = {**option_personalizzata, **options}
189
- if EFFETTUA_LOGIN_GOOGLE:
190
- st.session_state.options.update(option_leggiemail)
191
- st.session_state.options.update(option_leggicalendar)
192
- st.session_state.documenti = documenti
193
- st.session_state.loaded_data = True
194
- status.update(label="Caricamento Completato", state="complete", expanded=False)
195
- place.empty()
196
-
197
- def read_text_from_file(file):
198
- text = ""
199
- if file.name.endswith(".txt"):
200
- text = file.read().decode("utf-8")
201
- elif file.name.endswith(".pdf"):
202
- pdf_reader = PyPDF2.PdfReader(file)
203
- for page_num in range(len(pdf_reader.pages)):
204
- page = pdf_reader.pages[page_num]
205
- text += page.extract_text()
206
- else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  try:
208
- image = Image.open(file)
209
- text = pytesseract.image_to_string(image)
 
 
 
210
  except:
211
- st.write(f"Non è possibile leggere il testo dal file '{file.name}'.")
212
- return text
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
- st.session_state.systemRole = st.session_state.selected_option.get('systemRole', '')
248
- st.session_state.systemRole = st.text_area("Descrizione", st.session_state.systemRole, help='Ruolo del chatbot e descrizione dell\'azione che deve svolgere')
249
- st.session_state.systemStyle = st.session_state.selected_option.get('systemStyle', '')
250
- st.session_state.systemStyle = st.text_area("Stile", st.session_state.systemStyle, help='Descrizione dello stile utilizzato per generare il testo')
251
- if st.session_state.selected_option["tipo"]=='EMAIL':
252
- st.session_state.numero_elementi = st.slider(label="Numero Email", min_value=1, max_value=100, value=10)
253
- st.session_state.data_inizio = st.date_input("Email dal", value=datetime.date.today()-datetime.timedelta(days=7), format='DD/MM/YYYY')
254
- st.session_state.data_fine = st.date_input("Email al", value=datetime.date.today(), format='DD/MM/YYYY')
255
- if st.session_state.selected_option["tipo"]=='CALENDAR':
256
- st.session_state.numero_elementi = st.slider(label="Numero Eventi Calendario", min_value=1, max_value=100, value=10)
257
- st.session_state.data_inizio = st.date_input("Eventi dal", value=datetime.date.today(), format='DD/MM/YYYY')
258
- st.session_state.data_fine = st.date_input("Eventi al", value=datetime.date.today()+datetime.timedelta(days=7), format='DD/MM/YYYY')
259
- st.session_state.rag_enabled = st.session_state.selected_option.get('tipo', '')=='RAG'
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 gen_online_prompt(prompt, top_k) :
402
- links = []
403
- context = ''
404
- results = get_search_results(prompt, top_k)
405
- for i, result in enumerate(results, start=1):
406
- context += result['title'] + '\n' + result['description'] + '\n' + '\n\n' + result['body'].replace('\n','.') + '\n\n------------------------------------------------------------'
407
- links.append((str(i) + '. ' + result['title'], result['description'] + '\n\n' + result['url']))
408
- return context, links
409
-
410
- def generate_chat_stream(prompt):
411
- chat_stream = chat(prompt, st.session_state.history,chat_client=CHAT_BOTS[st.session_state.chat_bot]["model"],
412
- temperature=st.session_state.temp, max_new_tokens=st.session_state.max_tokens, client_openai = st.session_state.client)
413
- return chat_stream
414
-
415
- def inserisci_istruzioni(prompt_originale):
416
- links = []
417
- if st.session_state.cerca_online:
418
- with st.spinner("Ricerca Online...."):
419
- time.sleep(1)
420
- st.session_state.instruction, links = gen_online_prompt(prompt=prompt_originale, top_k=st.session_state.numero_siti)
421
- if st.session_state.rag_enabled :
422
- with st.spinner("Ricerca nei Decreti...."):
423
- time.sleep(1)
424
- st.session_state.instruction, links = gen_augmented_prompt(prompt=prompt_originale, top_k=st.session_state.top_k)
425
- if st.session_state.selected_option["tipo"]=='EMAIL':
426
- with st.spinner("Ricerca nelle Email...."):
427
- time.sleep(1)
428
- st.session_state.instruction, links = leggi_gmail(max_results=st.session_state.numero_elementi, data_inizio = st.session_state.data_inizio, data_fine = st.session_state.data_fine)
429
- if st.session_state.selected_option["tipo"]=='CALENDAR':
430
- with st.spinner("Ricerca nel Calendario...."):
431
- time.sleep(1)
432
- st.session_state.instruction, links = leggi_calendario_google(max_results=st.session_state.numero_elementi, data_inizio = st.session_state.data_inizio, data_fine = st.session_state.data_fine)
433
- with st.spinner("Generazione in corso...") :
434
- time.sleep(1)
435
- #st.session_state.instruction = instruction_originale + '\n----------------------------------------------\n' + st.session_state.instruction
436
- return links
437
-
438
- def stream_handler(chat_stream, placeholder) :
439
- full_response = ''
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
- if prompt := st.chat_input("Chatta con BonsiAI..."):
479
- prompt_originale = prompt
480
- links = inserisci_istruzioni(prompt_originale)
481
- st.session_state.instruction+= ' \n\n' + st.session_state.testo_documenti
482
- instruction_suddivise = split_text(st.session_state.instruction, st.session_state.split*2000)
483
- ruolo_originale = st.session_state.systemRole
484
- ruoli_divisi = ruolo_originale.split("&&")
485
- parte=1
486
- i=1
487
- risposta_completa = ''
488
- full_response = ''
489
- errore_generazione = False
490
- for ruolo_singolo in ruoli_divisi:
491
- for instruction_singola in instruction_suddivise:
492
- for numgen in range(1, st.session_state.numero_generazioni+1):
493
- if i==1:
494
- st.chat_message("user").markdown(prompt_originale + (': Parte ' + str(parte) if i > 1 else ''))
495
- i+=1
496
- prompt = formattaPrompt(prompt_originale, ruolo_singolo, st.session_state.systemStyle, instruction_singola)
497
- print('------------------------------------------------------------------------------------')
498
- print(prompt)
499
- st.session_state.messages.append({"role": "user", "content": prompt_originale})
500
- try:
501
- chat_stream = generate_chat_stream(prompt)
502
- with st.chat_message("assistant"):
503
- placeholder = st.empty()
504
- full_response = stream_handler(chat_stream, placeholder)
505
- if st.session_state.rag_enabled or st.session_state.cerca_online or st.session_state.selected_option["tipo"]=='EMAIL' or st.session_state.selected_option["tipo"]=='CALENDAR':
506
- show_source(links)
507
- if st.session_state.options.get(st.session_state.selected_option_key, {})["tipo"]=='DOCUMENTO':
508
- with st.expander("Mostra Documento") :
509
- st.info('##### ' + st.session_state.selected_documento_key + ' (Parte ' + str(parte) +')'+ '\n\n\n' + instruction_singola)
510
- parte+=1
511
- st.session_state.messages.append({"role": "assistant", "content": full_response})
512
- risposta_completa = risposta_completa + '\n' + full_response
513
- except Exception as e:
514
- print(str(e))
515
- errore_generazione = True
516
- if CHAT_BOTS[st.session_state.chat_bot]["model"][:3] == 'gpt':
517
- st.error('Inserisci una CHIAVE API valida e controlla il CREDITO residuo: https://platform.openai.com/api-keys')
518
- else:
519
- st.error('Modello AI attualmente non disponibile')
520
-
521
- if errore_generazione == False:
522
- st.session_state.ultimo_messaggio = full_response
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.")