Cleaned up some files
Browse files- database_maintenance/pruner_2.py +1 -0
- modelUpdating/bulkLabelByCentroid_2.py +0 -109
- modelUpdating/modelUpdater_1.py +0 -235
- modelUpdating/naturalLanguageTopics_3.py +0 -142
- pages/TopicWeb.py +0 -3
database_maintenance/pruner_2.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
from tqdm import tqdm
|
| 2 |
from supabase import create_client, Client
|
|
|
|
| 3 |
|
| 4 |
# --- Configuration ---
|
| 5 |
SECRETS_PATH = "secrets.json" # Adjust path if needed
|
|
|
|
| 1 |
from tqdm import tqdm
|
| 2 |
from supabase import create_client, Client
|
| 3 |
+
import os
|
| 4 |
|
| 5 |
# --- Configuration ---
|
| 6 |
SECRETS_PATH = "secrets.json" # Adjust path if needed
|
modelUpdating/bulkLabelByCentroid_2.py
CHANGED
|
@@ -1,112 +1,3 @@
|
|
| 1 |
-
# import chromadb
|
| 2 |
-
# from bertopic import BERTopic
|
| 3 |
-
# import numpy as np
|
| 4 |
-
# import time
|
| 5 |
-
# import os
|
| 6 |
-
# import supabase
|
| 7 |
-
|
| 8 |
-
# # --- CONFIGURATION ---
|
| 9 |
-
# CHROMA_PATH = "/home/wyomike/topicBuzz/my_mastodon_db"
|
| 10 |
-
# COLLECTION_NAME = "mastodon_posts"
|
| 11 |
-
# MODEL_PATH = "my_mastodon_model_reduced" # Using the clean/reduced model
|
| 12 |
-
# BATCH_SIZE = 2000 # Conservative batch size for read/write operations
|
| 13 |
-
|
| 14 |
-
# # --- CONFIGURATION ---
|
| 15 |
-
# SUPABASE_URL = os.environ.get("SUPABASE_URL") # Ensure these are in your .bashrc or .env
|
| 16 |
-
# SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
|
| 17 |
-
# LABELS_PATH = "topic_labels.json"
|
| 18 |
-
# MODEL_FILE = "my_online_mastodon_model.pkl"
|
| 19 |
-
# BUCKET = "topic_assets"
|
| 20 |
-
|
| 21 |
-
# EMBEDDING_MODEL_NAME = "all-MiniLM-L6-v2"
|
| 22 |
-
|
| 23 |
-
# def load_remote_model():
|
| 24 |
-
# try:
|
| 25 |
-
# print("☁️ Downloading model from Supabase Storage...")
|
| 26 |
-
# with open(MODEL_FILE, 'wb') as f:
|
| 27 |
-
# res = supabase.storage.from_(BUCKET).download(MODEL_FILE)
|
| 28 |
-
# f.write(res)
|
| 29 |
-
# return BERTopic.load(MODEL_FILE)
|
| 30 |
-
# except Exception as e:
|
| 31 |
-
# print(f"⚠️ Could not load remote model (first run?): {e}")
|
| 32 |
-
# return None
|
| 33 |
-
|
| 34 |
-
# # --- SETUP ---
|
| 35 |
-
# print(f"Connecting to ChromaDB at {CHROMA_PATH}...")
|
| 36 |
-
# client = chromadb.PersistentClient(path=CHROMA_PATH)
|
| 37 |
-
# collection = client.get_collection(COLLECTION_NAME)
|
| 38 |
-
|
| 39 |
-
# print(f"Loading BERTopic model from {MODEL_PATH}...")
|
| 40 |
-
# topic_model = BERTopic.load(MODEL_PATH)
|
| 41 |
-
|
| 42 |
-
# # Get total count to track progress
|
| 43 |
-
# total_docs = collection.count()
|
| 44 |
-
# print(f"Total documents to process: {total_docs}")
|
| 45 |
-
|
| 46 |
-
# # --- MAIN LOOP ---
|
| 47 |
-
# print("Starting bulk topic assignment...")
|
| 48 |
-
# start_time = time.time()
|
| 49 |
-
|
| 50 |
-
# for offset in range(0, total_docs, BATCH_SIZE):
|
| 51 |
-
# try:
|
| 52 |
-
# # 1. Fetch Batch (Include metadatas so we don't overwrite/lose existing ones!)
|
| 53 |
-
# batch_data = collection.get(
|
| 54 |
-
# limit=BATCH_SIZE,
|
| 55 |
-
# offset=offset,
|
| 56 |
-
# include=["documents", "embeddings", "metadatas"]
|
| 57 |
-
# )
|
| 58 |
-
|
| 59 |
-
# ids = batch_data['ids']
|
| 60 |
-
# docs = batch_data['documents']
|
| 61 |
-
# embeddings = batch_data['embeddings']
|
| 62 |
-
# metadatas = batch_data['metadatas']
|
| 63 |
-
|
| 64 |
-
# if not ids:
|
| 65 |
-
# break
|
| 66 |
-
|
| 67 |
-
# # 2. Predict Topics
|
| 68 |
-
# # We pass embeddings to speed this up massively (skips re-calculating them)
|
| 69 |
-
# # Note: We convert to numpy array as BERTopic expects it
|
| 70 |
-
# topics, _ = topic_model.transform(docs, np.array(embeddings))
|
| 71 |
-
|
| 72 |
-
# # 3. Prepare Metadata Updates
|
| 73 |
-
# updated_metadatas = []
|
| 74 |
-
|
| 75 |
-
# for i, topic_id in enumerate(topics):
|
| 76 |
-
# # Get existing metadata or create empty dict if None
|
| 77 |
-
# meta = metadatas[i] if metadatas[i] is not None else {}
|
| 78 |
-
|
| 79 |
-
# # Add the new cluster ID
|
| 80 |
-
# # We convert to int just to be safe for JSON serialization
|
| 81 |
-
# meta['cluster_id'] = int(topic_id)
|
| 82 |
-
|
| 83 |
-
# # OPTIONAL: Add the topic label string if you want it readable in DB
|
| 84 |
-
# meta['cluster_label'] = topic_model.get_topic_info(topic_id).Name.values[0]
|
| 85 |
-
|
| 86 |
-
# updated_metadatas.append(meta)
|
| 87 |
-
|
| 88 |
-
# # 4. Update ChromaDB
|
| 89 |
-
# # We ONLY update metadatas. Documents and embeddings remain untouched.
|
| 90 |
-
# collection.update(
|
| 91 |
-
# ids=ids,
|
| 92 |
-
# metadatas=updated_metadatas
|
| 93 |
-
# )
|
| 94 |
-
|
| 95 |
-
# # Progress Log
|
| 96 |
-
# processed_count = offset + len(ids)
|
| 97 |
-
# percent = (processed_count / total_docs) * 100
|
| 98 |
-
# elapsed = time.time() - start_time
|
| 99 |
-
# print(f"[{percent:.1f}%] Processed {processed_count}/{total_docs} docs... (Time: {elapsed:.0f}s)")
|
| 100 |
-
|
| 101 |
-
# except Exception as e:
|
| 102 |
-
# print(f"❌ Error processing batch starting at offset {offset}: {e}")
|
| 103 |
-
# # Optional: Decide if you want to 'continue' or 'break' here
|
| 104 |
-
# # continue
|
| 105 |
-
|
| 106 |
-
# print("✅ Done! All documents have been assigned a 'cluster_id'.")
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
import os
|
| 111 |
import time
|
| 112 |
import numpy as np
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import time
|
| 3 |
import numpy as np
|
modelUpdating/modelUpdater_1.py
CHANGED
|
@@ -1,238 +1,3 @@
|
|
| 1 |
-
# import os
|
| 2 |
-
# import json
|
| 3 |
-
# import shutil
|
| 4 |
-
# import numpy as np
|
| 5 |
-
# import datetime
|
| 6 |
-
# from supabase import create_client, Client
|
| 7 |
-
# from bertopic import BERTopic
|
| 8 |
-
# from sentence_transformers import SentenceTransformer
|
| 9 |
-
# from sklearn.cluster import MiniBatchKMeans
|
| 10 |
-
# from sklearn.decomposition import IncrementalPCA
|
| 11 |
-
# from bertopic.vectorizers import OnlineCountVectorizer
|
| 12 |
-
# import gzip
|
| 13 |
-
|
| 14 |
-
# # --- CONFIGURATION ---
|
| 15 |
-
# SUPABASE_URL = os.environ.get("SUPABASE_URL") # Ensure these are in your .bashrc or .env
|
| 16 |
-
# SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
|
| 17 |
-
|
| 18 |
-
# # MODEL_PATH = "my_online_mastodon_model.pkl"
|
| 19 |
-
# LABELS_PATH = "topic_labels.json"
|
| 20 |
-
# STATE_FILE = "training_state.json" # Tracks the last timestamp we processed
|
| 21 |
-
|
| 22 |
-
# MODEL_FILE = "my_online_mastodon_model.pkl"
|
| 23 |
-
# COMPRESSED_FILE = "my_online_mastodon_model.pkl.gz" # New filename for cloud
|
| 24 |
-
# BUCKET = "topic_assets"
|
| 25 |
-
|
| 26 |
-
# # IMPORTANT: This MUST match the model used to create your original embeddings!
|
| 27 |
-
# EMBEDDING_MODEL_NAME = "all-MiniLM-L6-v2"
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
# def load_remote_model():
|
| 31 |
-
# # Check if we already have the raw model
|
| 32 |
-
# if os.path.exists(MODEL_FILE):
|
| 33 |
-
# return BERTopic.load(MODEL_FILE)
|
| 34 |
-
|
| 35 |
-
# print(f"☁️ Downloading {COMPRESSED_FILE} from Supabase...")
|
| 36 |
-
# try:
|
| 37 |
-
# # 1. Download the compressed file
|
| 38 |
-
# with open(COMPRESSED_FILE, 'wb') as f:
|
| 39 |
-
# res = supabase.storage.from_(BUCKET).download(COMPRESSED_FILE)
|
| 40 |
-
# f.write(res)
|
| 41 |
-
|
| 42 |
-
# # 2. Decompress it
|
| 43 |
-
# print(" -> Decompressing...")
|
| 44 |
-
# with gzip.open(COMPRESSED_FILE, 'rb') as f_in:
|
| 45 |
-
# with open(MODEL_FILE, 'wb') as f_out:
|
| 46 |
-
# shutil.copyfileobj(f_in, f_out)
|
| 47 |
-
|
| 48 |
-
# # 3. Load the raw model
|
| 49 |
-
# return BERTopic.load(MODEL_FILE)
|
| 50 |
-
|
| 51 |
-
# except Exception as e:
|
| 52 |
-
# print(f"⚠️ Could not load remote model: {e}")
|
| 53 |
-
# return None
|
| 54 |
-
|
| 55 |
-
# def save_remote_model(model):
|
| 56 |
-
# print("💾 Saving and compressing model...")
|
| 57 |
-
# # 1. Save locally as raw pickle
|
| 58 |
-
# model.save(MODEL_FILE, serialization="pickle")
|
| 59 |
-
|
| 60 |
-
# # 2. Compress it (Raw 95MB -> Compressed ~30MB)
|
| 61 |
-
# with open(MODEL_FILE, 'rb') as f_in:
|
| 62 |
-
# with gzip.open(COMPRESSED_FILE, 'wb') as f_out:
|
| 63 |
-
# shutil.copyfileobj(f_in, f_out)
|
| 64 |
-
|
| 65 |
-
# # 3. Upload the COMPRESSED file
|
| 66 |
-
# print(f"☁️ Uploading {COMPRESSED_FILE} to Supabase...")
|
| 67 |
-
# with open(COMPRESSED_FILE, 'rb') as f:
|
| 68 |
-
# supabase.storage.from_(BUCKET).upload(
|
| 69 |
-
# path=COMPRESSED_FILE,
|
| 70 |
-
# file=f,
|
| 71 |
-
# file_options={"x-upsert": "true"}
|
| 72 |
-
# )
|
| 73 |
-
|
| 74 |
-
# # Clean up local files to save space
|
| 75 |
-
# if os.path.exists(MODEL_FILE): os.remove(MODEL_FILE)
|
| 76 |
-
# if os.path.exists(COMPRESSED_FILE): os.remove(COMPRESSED_FILE)
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
# # --- 1. Setup Connections ---
|
| 80 |
-
# if not SUPABASE_URL or not SUPABASE_KEY:
|
| 81 |
-
# raise ValueError("Missing Supabase credentials in environment variables.")
|
| 82 |
-
|
| 83 |
-
# supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
|
| 84 |
-
# embedding_model = SentenceTransformer(EMBEDDING_MODEL_NAME)
|
| 85 |
-
|
| 86 |
-
# # --- 2. Load State (Incremental Logic) ---
|
| 87 |
-
# last_processed_ts = 0
|
| 88 |
-
# if os.path.exists(STATE_FILE):
|
| 89 |
-
# with open(STATE_FILE, 'r') as f:
|
| 90 |
-
# state = json.load(f)
|
| 91 |
-
# last_processed_ts = state.get('last_timestamp', 0)
|
| 92 |
-
|
| 93 |
-
# print(f"📅 Looking for posts newer than timestamp: {last_processed_ts}")
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
# print("📥 Checking for previous state in cloud...")
|
| 97 |
-
# last_processed_ts = 0
|
| 98 |
-
|
| 99 |
-
# try:
|
| 100 |
-
# # Try to download state file from Supabase
|
| 101 |
-
# with open(STATE_FILE, 'wb') as f:
|
| 102 |
-
# res = supabase.storage.from_(BUCKET).download(STATE_FILE)
|
| 103 |
-
# f.write(res)
|
| 104 |
-
|
| 105 |
-
# # If successful, read it
|
| 106 |
-
# with open(STATE_FILE, 'r') as f:
|
| 107 |
-
# state = json.load(f)
|
| 108 |
-
# last_processed_ts = state.get('last_timestamp', 0)
|
| 109 |
-
# print(f" -> Resuming from timestamp: {last_processed_ts}")
|
| 110 |
-
|
| 111 |
-
# except Exception as e:
|
| 112 |
-
# print(f" -> No remote state found (or error: {e}). Starting from 0.")
|
| 113 |
-
# last_processed_ts = 0
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
# # --- 3. Load or Initialize BERTopic ---
|
| 117 |
-
# # if os.path.exists(MODEL_PATH):
|
| 118 |
-
# print(f"🔄 Loading existing model from supabase...")
|
| 119 |
-
# topic_model = load_remote_model()
|
| 120 |
-
# if topic_model is None:
|
| 121 |
-
# print("✨ Creating NEW model structure...")
|
| 122 |
-
# umap_model = IncrementalPCA(n_components=5)
|
| 123 |
-
# cluster_model = MiniBatchKMeans(n_clusters=200, random_state=42)
|
| 124 |
-
# vectorizer_model = OnlineCountVectorizer(stop_words="english", min_df=10)
|
| 125 |
-
|
| 126 |
-
# topic_model = BERTopic(
|
| 127 |
-
# embedding_model=None, # We pass embeddings manually
|
| 128 |
-
# umap_model=umap_model,
|
| 129 |
-
# hdbscan_model=cluster_model,
|
| 130 |
-
# vectorizer_model=vectorizer_model
|
| 131 |
-
# )
|
| 132 |
-
|
| 133 |
-
# # --- 4. Fetch Data from Supabase ---
|
| 134 |
-
# BATCH_SIZE = 1000
|
| 135 |
-
# total_processed = 0
|
| 136 |
-
# new_max_ts = last_processed_ts
|
| 137 |
-
|
| 138 |
-
# while True:
|
| 139 |
-
# # Fetch batch of new posts
|
| 140 |
-
# # We select 'content' and 'post_timestamp'. Adjust column names if yours differ.
|
| 141 |
-
# try:
|
| 142 |
-
# response = supabase.table("posts") \
|
| 143 |
-
# .select("content, post_timestamp") \
|
| 144 |
-
# .gt("post_timestamp", last_processed_ts) \
|
| 145 |
-
# .order("post_timestamp", desc=False) \
|
| 146 |
-
# .limit(BATCH_SIZE) \
|
| 147 |
-
# .execute()
|
| 148 |
-
|
| 149 |
-
# posts = response.data
|
| 150 |
-
# except Exception as e:
|
| 151 |
-
# print(f"❌ Supabase Error: {e}")
|
| 152 |
-
# break
|
| 153 |
-
|
| 154 |
-
# if not posts:
|
| 155 |
-
# print("✅ No more new data to process.")
|
| 156 |
-
# break
|
| 157 |
-
|
| 158 |
-
# # Extract text and update timestamp tracker
|
| 159 |
-
# batch_docs = [p['content'] for p in posts if p['content']]
|
| 160 |
-
# batch_timestamps = [p['post_timestamp'] for p in posts]
|
| 161 |
-
|
| 162 |
-
# if not batch_docs:
|
| 163 |
-
# continue
|
| 164 |
-
|
| 165 |
-
# # Update our cursor for the next batch
|
| 166 |
-
# current_batch_max = max(batch_timestamps)
|
| 167 |
-
# last_processed_ts = current_batch_max
|
| 168 |
-
# new_max_ts = max(new_max_ts, current_batch_max)
|
| 169 |
-
|
| 170 |
-
# print(f"🧠 Generating embeddings for {len(batch_docs)} posts...")
|
| 171 |
-
|
| 172 |
-
# # Generate Embeddings locally
|
| 173 |
-
# embeddings = embedding_model.encode(batch_docs, show_progress_bar=False)
|
| 174 |
-
|
| 175 |
-
# # Train the Model
|
| 176 |
-
# topic_model.partial_fit(batch_docs, np.array(embeddings).astype(np.float64))
|
| 177 |
-
|
| 178 |
-
# total_processed += len(batch_docs)
|
| 179 |
-
# print(f" -> Processed batch. Total new: {total_processed}")
|
| 180 |
-
|
| 181 |
-
# if total_processed > 0:
|
| 182 |
-
# print(f"💾 Saving updated assets to Supabase (Bucket: {BUCKET})...")
|
| 183 |
-
|
| 184 |
-
# # ---------------------------
|
| 185 |
-
# # A. Regenerate Labels Map
|
| 186 |
-
# # ---------------------------
|
| 187 |
-
# labels_map = {}
|
| 188 |
-
# info_df = topic_model.get_topic_info()
|
| 189 |
-
# for index, row in info_df.iterrows():
|
| 190 |
-
# topic_id = row['Topic']
|
| 191 |
-
# # Handle outlier topic -1 (JSON keys must be strings)
|
| 192 |
-
# if topic_id == -1:
|
| 193 |
-
# labels_map[f"Topic_{topic_id}"] = "Outliers"
|
| 194 |
-
# else:
|
| 195 |
-
# labels_map[f"Topic_{topic_id}"] = row['Name']
|
| 196 |
-
|
| 197 |
-
# # ---------------------------
|
| 198 |
-
# # B. Upload All Artifacts
|
| 199 |
-
# # ---------------------------
|
| 200 |
-
|
| 201 |
-
# # 1. Upload Model (using the helper function defined earlier)
|
| 202 |
-
# save_remote_model(topic_model)
|
| 203 |
-
|
| 204 |
-
# # 2. Upload Labels
|
| 205 |
-
# print(" -> Uploading labels...")
|
| 206 |
-
# with open(LABELS_PATH, "w") as f:
|
| 207 |
-
# json.dump(labels_map, f)
|
| 208 |
-
|
| 209 |
-
# with open(LABELS_PATH, "rb") as f:
|
| 210 |
-
# supabase.storage.from_(BUCKET).upload(
|
| 211 |
-
# LABELS_PATH,
|
| 212 |
-
# f,
|
| 213 |
-
# file_options={"x-upsert": "true"}
|
| 214 |
-
# )
|
| 215 |
-
|
| 216 |
-
# # 3. Upload State (Crucial for Hugging Face!)
|
| 217 |
-
# # We must save the new timestamp to the cloud, or the bot will forget it processed these posts.
|
| 218 |
-
# print(f" -> Updating state to timestamp {new_max_ts}...")
|
| 219 |
-
# state_data = {"last_timestamp": new_max_ts}
|
| 220 |
-
|
| 221 |
-
# with open(STATE_FILE, "w") as f:
|
| 222 |
-
# json.dump(state_data, f)
|
| 223 |
-
|
| 224 |
-
# with open(STATE_FILE, "rb") as f:
|
| 225 |
-
# supabase.storage.from_(BUCKET).upload(
|
| 226 |
-
# STATE_FILE,
|
| 227 |
-
# f,
|
| 228 |
-
# file_options={"x-upsert": "true"}
|
| 229 |
-
# )
|
| 230 |
-
|
| 231 |
-
# print(f"✅ Success! Trained on {total_processed} new posts and synced to cloud.")
|
| 232 |
-
|
| 233 |
-
# else:
|
| 234 |
-
# print("💤 No new data found. Model untouched.")
|
| 235 |
-
|
| 236 |
import os
|
| 237 |
import json
|
| 238 |
import shutil
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import json
|
| 3 |
import shutil
|
modelUpdating/naturalLanguageTopics_3.py
CHANGED
|
@@ -1,145 +1,3 @@
|
|
| 1 |
-
# import google.genai as genai
|
| 2 |
-
# from bertopic import BERTopic
|
| 3 |
-
# import json
|
| 4 |
-
# import os
|
| 5 |
-
# import time
|
| 6 |
-
# from tqdm import tqdm
|
| 7 |
-
|
| 8 |
-
# NSFW_KEYWORDS = {
|
| 9 |
-
# "nsfw", "porn", "nude", "naked", "sex", "18+", "xxx",
|
| 10 |
-
# "cum", "dick", "pussy", "hentai", "erotic", "onlyfans",
|
| 11 |
-
# "boobs", "tits", "cock", "slut", "whore", "bitch", "incest",
|
| 12 |
-
# "lewd", "horny", "sensual", "fetish", "bondage"
|
| 13 |
-
# }
|
| 14 |
-
|
| 15 |
-
# def get_safe_topic_ids(model, nsfw_set):
|
| 16 |
-
# """
|
| 17 |
-
# Returns a list of Topic IDs that do NOT contain any blocklisted words
|
| 18 |
-
# in their top 10 keywords.
|
| 19 |
-
# """
|
| 20 |
-
# safe_ids = []
|
| 21 |
-
# unsafe_ids = []
|
| 22 |
-
|
| 23 |
-
# # get_topics() returns a dictionary: {topic_id: [(word, score), ...]}
|
| 24 |
-
# all_topics = model.get_topics()
|
| 25 |
-
|
| 26 |
-
# print(f"Scanning {len(all_topics)} topics for NSFW content...")
|
| 27 |
-
|
| 28 |
-
# for topic_id, words_with_scores in all_topics.items():
|
| 29 |
-
# # Topic -1 is outliers. We usually keep it, but check its content too.
|
| 30 |
-
|
| 31 |
-
# # Extract just the words from the tuples
|
| 32 |
-
# topic_words = {word for word, score in words_with_scores}
|
| 33 |
-
|
| 34 |
-
# # Check for intersection
|
| 35 |
-
# # If the intersection is NOT empty, it contains a bad word
|
| 36 |
-
# intersection = topic_words.intersection(nsfw_set)
|
| 37 |
-
|
| 38 |
-
# if intersection:
|
| 39 |
-
# print(f" [BLOCKED] Topic {topic_id}")
|
| 40 |
-
# unsafe_ids.append(topic_id)
|
| 41 |
-
# else:
|
| 42 |
-
# safe_ids.append(topic_id)
|
| 43 |
-
|
| 44 |
-
# print(f"\nResult: {len(safe_ids)} Safe, {len(unsafe_ids)} Unsafe.")
|
| 45 |
-
# return safe_ids
|
| 46 |
-
|
| 47 |
-
# # --- CONFIGURATION ---
|
| 48 |
-
# MODEL_PATH = "my_mastodon_model_reduced"
|
| 49 |
-
# OUTPUT_JSON = "topic_labels.json"
|
| 50 |
-
# API_KEY = json.load(open("config.json"))["googleKey"] # Replace or set env var
|
| 51 |
-
|
| 52 |
-
# # --- SETUP ---
|
| 53 |
-
# client = genai.Client(api_key=API_KEY)
|
| 54 |
-
# # model = client.models.get() # Fast and cheap
|
| 55 |
-
|
| 56 |
-
# def generate_labels():
|
| 57 |
-
# if not os.path.exists(MODEL_PATH):
|
| 58 |
-
# print("Model not found!")
|
| 59 |
-
# return
|
| 60 |
-
|
| 61 |
-
# print("Loading BERTopic model...")
|
| 62 |
-
# topic_model = BERTopic.load(MODEL_PATH)
|
| 63 |
-
|
| 64 |
-
# # Get the master table of topic info
|
| 65 |
-
# info = topic_model.get_topic_info()
|
| 66 |
-
# print(f"Found {len(info)} topics.")
|
| 67 |
-
# safe_topics = get_safe_topic_ids(topic_model, nsfw_set=NSFW_KEYWORDS)
|
| 68 |
-
|
| 69 |
-
# # Dictionary to map "Topic_5" -> "Nature Photography"
|
| 70 |
-
# labels_map = {}
|
| 71 |
-
|
| 72 |
-
# print("Asking Gemini to label topics...")
|
| 73 |
-
|
| 74 |
-
# for index, row in tqdm(info.iterrows(), total=len(info)):
|
| 75 |
-
# if row['Topic'] not in safe_topics:
|
| 76 |
-
# labels_map[f"Topic_{row['Topic']}"] = "NSFW" # TODO - check if this is actually the bag of words
|
| 77 |
-
# continue
|
| 78 |
-
# topic_id = row['Topic']
|
| 79 |
-
|
| 80 |
-
# # Handle Outliers
|
| 81 |
-
# if topic_id == 0:
|
| 82 |
-
# labels_map[f"Topic_{topic_id}"] = "Outliers / Noise"
|
| 83 |
-
# continue
|
| 84 |
-
|
| 85 |
-
# # 1. Get Bag of Words
|
| 86 |
-
# # row['Representation'] is a list of keywords like ['cat', 'meow', 'feline']
|
| 87 |
-
# keywords = row['Representation']
|
| 88 |
-
|
| 89 |
-
# # 2. Get Representative Docs
|
| 90 |
-
# # row['Representative_Docs'] is a list of actual posts
|
| 91 |
-
# docs = row['Representative_Docs']
|
| 92 |
-
|
| 93 |
-
# # 3. Construct Prompt
|
| 94 |
-
# # We take top 10 keywords and top 3 docs to save tokens
|
| 95 |
-
# prompt = f"""
|
| 96 |
-
# I have a cluster of social media posts from Mastodon.
|
| 97 |
-
|
| 98 |
-
# Keywords: {', '.join(keywords[:10])}
|
| 99 |
-
|
| 100 |
-
# Representative Posts:
|
| 101 |
-
# {chr(10).join([f"- {d[:300]}..." for d in docs[:3]])}
|
| 102 |
-
|
| 103 |
-
# Based on this, provide a short, specific, natural language label (max 5 words) for this topic.
|
| 104 |
-
# Do not use quotes. Just the label.
|
| 105 |
-
# """
|
| 106 |
-
|
| 107 |
-
# try:
|
| 108 |
-
# # 4. Call Gemini
|
| 109 |
-
# response = client.models.generate_content(
|
| 110 |
-
# model='gemini-2.5-flash',
|
| 111 |
-
# contents=prompt
|
| 112 |
-
# )
|
| 113 |
-
# label = response.text.strip()
|
| 114 |
-
|
| 115 |
-
# # 5. Store result
|
| 116 |
-
# labels_map[f"Topic_{topic_id}"] = label
|
| 117 |
-
|
| 118 |
-
# # Sleep briefly to respect rate limits if needed
|
| 119 |
-
# time.sleep(1.0)
|
| 120 |
-
|
| 121 |
-
# except Exception as e:
|
| 122 |
-
# print(f"Error labeling Topic {topic_id}: {e}")
|
| 123 |
-
# labels_map[f"Topic_{topic_id}"] = f"Topic {topic_id}"
|
| 124 |
-
|
| 125 |
-
# # Save to JSON
|
| 126 |
-
# with open("topic_labels.json", "w") as f:
|
| 127 |
-
# json.dump(labels_map, f)
|
| 128 |
-
|
| 129 |
-
# with open("topic_labels.json", "rb") as f:
|
| 130 |
-
# supabase.storage.from_("topic_assets").upload(
|
| 131 |
-
# "topic_labels.json",
|
| 132 |
-
# f,
|
| 133 |
-
# file_options={"x-upsert": "true"}
|
| 134 |
-
# )
|
| 135 |
-
# print("☁️ Labels uploaded to cloud.")
|
| 136 |
-
|
| 137 |
-
# print(f"Success! Labels saved to {OUTPUT_JSON}")
|
| 138 |
-
|
| 139 |
-
# if __name__ == "__main__":
|
| 140 |
-
# generate_labels()
|
| 141 |
-
|
| 142 |
-
|
| 143 |
import os
|
| 144 |
import json
|
| 145 |
import time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import json
|
| 3 |
import time
|
pages/TopicWeb.py
CHANGED
|
@@ -1,6 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
import streamlit as st
|
| 5 |
import networkx as nx
|
| 6 |
import pickle
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import networkx as nx
|
| 3 |
import pickle
|