Madras1 commited on
Commit
dc37acf
·
verified ·
1 Parent(s): 3a10977

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +219 -0
app.py ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ==============================================================================
2
+ # داشبورد_3d_magic_streamlit.py (VERSÃO PARA HUGGING FACE SPACES)
3
+ # Arquivo: app.py
4
+ # ==============================================================================
5
+ import streamlit as st
6
+ import numpy as np
7
+ import pandas as pd
8
+ from sentence_transformers import SentenceTransformer
9
+ import umap
10
+ import plotly.express as px
11
+ import hdbscan
12
+ from sklearn.preprocessing import StandardScaler
13
+ from sklearn.metrics.pairwise import cosine_similarity
14
+ import torch
15
+ import gc
16
+
17
+ # ================================
18
+ # CONFIGURAÇÕES DA PÁGINA E DO MODELO
19
+ # ================================
20
+ st.set_page_config(
21
+ page_title="Dashboard 3D Mágico",
22
+ page_icon="🌌",
23
+ layout="wide",
24
+ initial_sidebar_state="expanded"
25
+ )
26
+
27
+ # Constantes do seu script original
28
+ DEFAULT_MODEL = 'all-MiniLM-L6-v2'
29
+ BATCH_SIZE = 256
30
+ UMAP_N_NEIGHBORS = 30
31
+ HDBSCAN_MIN_SIZE = 50
32
+
33
+ # ================================
34
+ # FUNÇÕES CACHEADAS (O CORAÇÃO DA PERFORMANCE)
35
+ # ================================
36
+
37
+ # @st.cache_resource: Carrega o modelo de embedding UMA ÚNICA VEZ e o mantém na memória.
38
+ # É o nosso "superpoder" para não ter que esperar o modelo carregar a cada interação.
39
+ @st.cache_resource
40
+ def load_model():
41
+ """Carrega o modelo SentenceTransformer e o coloca no dispositivo correto."""
42
+ device = "cuda" if torch.cuda.is_available() else "cpu"
43
+ print(f"Carregando modelo para o dispositivo: {device}")
44
+ model = SentenceTransformer(DEFAULT_MODEL, device=device)
45
+ return model
46
+
47
+ # @st.cache_data: Executa o processamento pesado e guarda o resultado.
48
+ # Se o usuário subir o mesmo arquivo com os mesmos parâmetros, o Streamlit usa o resultado
49
+ # guardado em vez de reprocessar tudo. Magia!
50
+ @st.cache_data
51
+ def process_data(texts, n_samples):
52
+ """
53
+ Função que encapsula todo o pipeline de processamento:
54
+ Embeddings -> UMAP -> HDBSCAN -> DataFrame
55
+ """
56
+ model = load_model() # Pega o modelo já carregado da função cacheada
57
+
58
+ st.info(f"Processando {len(texts):,} textos... Isso pode levar alguns minutos.")
59
+
60
+ # --- 2. EMBEDDINGS ---
61
+ progress_bar = st.progress(0, text="Gerando embeddings...")
62
+ embeddings = model.encode(texts, batch_size=BATCH_SIZE, show_progress_bar=False, convert_to_numpy=True)
63
+ progress_bar.progress(30, text="Reduzindo dimensionalidade com UMAP 3D...")
64
+
65
+ # --- 3. UMAP 3D ---
66
+ reducer = umap.UMAP(n_components=3, n_neighbors=UMAP_N_NEIGHBORS, min_dist=0.0, metric='cosine', random_state=42)
67
+ embedding_3d = reducer.fit_transform(embeddings)
68
+ embedding_3d = StandardScaler().fit_transform(embedding_3d)
69
+ progress_bar.progress(70, text="Clusterizando com HDBSCAN...")
70
+
71
+ # --- 4. HDBSCAN ---
72
+ clusterer = hdbscan.HDBSCAN(min_cluster_size=HDBSCAN_MIN_SIZE)
73
+ clusters = clusterer.fit_predict(embedding_3d)
74
+ n_clusters = len(set(clusters)) - (1 if -1 in clusters else 0)
75
+ st.success(f"Clusterização concluída! Encontrados {n_clusters} clusters.")
76
+ progress_bar.progress(90, text="Montando o DataFrame final...")
77
+
78
+ # --- 5 & 6. DATAFRAME ---
79
+ df = pd.DataFrame({
80
+ 'x': embedding_3d[:, 0], 'y': embedding_3d[:, 1], 'z': embedding_3d[:, 2],
81
+ 'text_hover': [t[:300] + "..." if len(t) > 300 else t for t in texts],
82
+ 'length': [len(t) for t in texts],
83
+ 'cluster': clusters.astype(str)
84
+ })
85
+ df['color'] = df['cluster'].astype(int)
86
+ # Ajuste de tamanho para melhor visualização
87
+ df['size'] = np.log1p(df['length']) * 1.5 + 1
88
+
89
+ progress_bar.progress(100, text="Processamento concluído!")
90
+ progress_bar.empty()
91
+
92
+ # Limpeza de memória
93
+ del reducer, clusterer, embedding_3d
94
+ gc.collect()
95
+
96
+ return df, embeddings, np.array(texts, dtype=object)
97
+
98
+ # ================================
99
+ # LAYOUT DA APLICAÇÃO (A INTERFACE)
100
+ # ================================
101
+
102
+ # Título e descrição
103
+ st.title("🌌 Dashboard 3D Mágico de Textos")
104
+ st.markdown("""
105
+ Bem-vindo, meu príncipe Gabriel Yogi!
106
+ Esta é a nossa oficina mágica. Faça o upload de um arquivo `.txt` e veja seus textos ganharem vida em um universo 3D.
107
+ Depois, use a busca semântica para encontrar constelações de ideias.
108
+ """)
109
+
110
+ # --- BARRA LATERAL PARA CONTROLES ---
111
+ with st.sidebar:
112
+ st.header("⚙️ Controles")
113
+ uploaded_file = st.file_uploader("1. Escolha seu arquivo .txt", type="txt")
114
+
115
+ # O slider só aparece se um arquivo for carregado
116
+ if uploaded_file:
117
+ max_samples_default = 10000
118
+ n_samples = st.slider(
119
+ "2. Selecione o número máximo de amostras",
120
+ min_value=500,
121
+ max_value=50000,
122
+ value=max_samples_default,
123
+ step=500,
124
+ help="Valores mais altos exigem mais tempo de processamento."
125
+ )
126
+
127
+ process_button = st.button("✨ Gerar Dashboard ✨", disabled=not uploaded_file, type="primary")
128
+
129
+ # --- ÁREA PRINCIPAL PARA VISUALIZAÇÃO ---
130
+
131
+ # Inicializa o 'session_state' para guardar nossos dados
132
+ if 'processed' not in st.session_state:
133
+ st.session_state.processed = False
134
+
135
+ if process_button:
136
+ if uploaded_file is not None:
137
+ with st.spinner('Lendo e validando o arquivo...'):
138
+ # Decodifica o arquivo e lê as linhas
139
+ lines = uploaded_file.getvalue().decode('utf-8').splitlines()
140
+ texts = []
141
+ for line in lines:
142
+ s = line.strip()
143
+ if s and len(s.split()) > 3:
144
+ texts.append(s)
145
+
146
+ # Limita ao número de amostras
147
+ texts = texts[:n_samples]
148
+
149
+ if len(texts) > 0:
150
+ # Chama a função de processamento pesado e guarda os resultados no 'session_state'
151
+ df, embeddings, full_texts = process_data(texts, n_samples)
152
+ st.session_state.df = df
153
+ st.session_state.embeddings = embeddings
154
+ st.session_state.full_texts = full_texts
155
+ st.session_state.processed = True
156
+ else:
157
+ st.error("Nenhum texto válido encontrado no arquivo! Verifique se ele não está vazio e se as linhas têm mais de 3 palavras.")
158
+ st.session_state.processed = False
159
+ else:
160
+ st.warning("Por favor, faça o upload de um arquivo primeiro.")
161
+
162
+ # Se os dados já foram processados, mostramos o dashboard
163
+ if st.session_state.processed:
164
+ st.header("🔎 Explore seu Universo de Textos")
165
+
166
+ query = st.text_input("Busca Semântica", placeholder="Digite algo para destacar no gráfico...")
167
+
168
+ df_display = st.session_state.df.copy()
169
+
170
+ if query:
171
+ model = load_model()
172
+ q_embedding = model.encode([query])
173
+ sims = cosine_similarity(q_embedding, st.session_state.embeddings)[0]
174
+
175
+ top_k = 50
176
+ idx = np.argsort(sims)[-top_k:][::-1]
177
+
178
+ # Cria uma coluna para destaque e ajusta o tamanho e a cor
179
+ df_display['highlight'] = '0' # Começa como string para ser categoria
180
+ df_display.loc[idx, 'highlight'] = '1'
181
+ df_display['display_size'] = np.where(df_display['highlight'] == '1', 10, df_display['size'] * 0.6)
182
+
183
+ fig = px.scatter_3d(
184
+ df_display, x='x', y='y', z='z',
185
+ color='highlight', size='display_size',
186
+ hover_data={'text_hover': True, 'cluster': True, 'length': True},
187
+ title=f"Busca por: '{query}'",
188
+ color_discrete_map={'0': 'grey', '1': 'yellow'},
189
+ labels={'highlight': 'Resultado'}
190
+ )
191
+ fig.update_traces(marker=dict(opacity=0.9))
192
+
193
+ # Mostra os top 5 resultados
194
+ st.subheader("Top 5 Resultados da Busca:")
195
+ for i, (r, s) in enumerate(zip(idx[:5], sims[idx[:5]])):
196
+ st.markdown(f"**{i+1}.** *Similaridade: {s:.3f}* \n> {st.session_state.full_texts[r][:350]}...")
197
+
198
+ else:
199
+ # Se não há busca, mostra o gráfico original por cluster
200
+ fig = px.scatter_3d(
201
+ df_display, x='x', y='y', z='z',
202
+ color='cluster', size='size',
203
+ hover_data={'text_hover': True, 'cluster': True, 'length': True},
204
+ title=f"Visualização 3D de {len(df_display):,} Textos",
205
+ color_continuous_scale='Turbo'
206
+ )
207
+ fig.update_traces(marker=dict(opacity=0.8))
208
+
209
+ # Configurações finais do layout do gráfico
210
+ fig.update_layout(
211
+ template='plotly_dark',
212
+ height=800,
213
+ margin=dict(l=0, r=0, b=0, t=40),
214
+ scene_camera=dict(eye=dict(x=1.5, y=1.5, z=1.5))
215
+ )
216
+ st.plotly_chart(fig, use_container_width=True)
217
+
218
+ else:
219
+ st.info("Aguardando um arquivo para começar a mágica... ✨")