Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +207 -1
src/streamlit_app.py
CHANGED
|
@@ -13,6 +13,7 @@ import hdbscan
|
|
| 13 |
|
| 14 |
# Database file path
|
| 15 |
DB_PATH = '/data/steampolis.duckdb'
|
|
|
|
| 16 |
|
| 17 |
# Initialize database tables if they don't exist
|
| 18 |
def initialize_database():
|
|
@@ -97,6 +98,210 @@ def initialize_database():
|
|
| 97 |
if 'init_con' in locals() and init_con:
|
| 98 |
init_con.close()
|
| 99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
def get_ttl_hash(seconds=360):
|
| 101 |
"""Return the same value withing `seconds` time period"""
|
| 102 |
return round(time.time() / seconds)
|
|
@@ -775,7 +980,7 @@ def view_topic_page():
|
|
| 775 |
# Include functional information
|
| 776 |
st.markdown(f"**Shareable Quest Scroll ID:** `{topic_id}`")
|
| 777 |
# Construct shareable link using current app URL
|
| 778 |
-
app_url = st.query_params.get('base', [
|
| 779 |
shareable_link = f"{app_url}?topic={topic_id}" if app_url else f"?topic={topic_id}"
|
| 780 |
st.markdown(f"**Shareable Scroll Link:** `{shareable_link}`")
|
| 781 |
|
|
@@ -1081,6 +1286,7 @@ if 'processed_url_params' not in st.session_state:
|
|
| 1081 |
|
| 1082 |
# Initialize the database on first run
|
| 1083 |
initialize_database()
|
|
|
|
| 1084 |
|
| 1085 |
# Handle initial load from URL query parameters
|
| 1086 |
# Process only once per session load using the flag
|
|
|
|
| 13 |
|
| 14 |
# Database file path
|
| 15 |
DB_PATH = '/data/steampolis.duckdb'
|
| 16 |
+
DEFAULT_BASE_URL = 'https://huggingface.co/spaces/npc0/SteamPolis/'
|
| 17 |
|
| 18 |
# Initialize database tables if they don't exist
|
| 19 |
def initialize_database():
|
|
|
|
| 98 |
if 'init_con' in locals() and init_con:
|
| 99 |
init_con.close()
|
| 100 |
|
| 101 |
+
|
| 102 |
+
import random # Import random for generating votes
|
| 103 |
+
|
| 104 |
+
def add_example_topic(topic_id, topic_title, topic_description, comments_list):
|
| 105 |
+
"""
|
| 106 |
+
Adds an example topic, its comments, and example cluster votes to the database.
|
| 107 |
+
|
| 108 |
+
Args:
|
| 109 |
+
topic_id (str): The unique ID for the topic.
|
| 110 |
+
topic_title (str): The title of the topic.
|
| 111 |
+
topic_description (str): The description of the topic.
|
| 112 |
+
comments_list (list): A list of strings, where each string is a comment.
|
| 113 |
+
"""
|
| 114 |
+
con = None
|
| 115 |
+
try:
|
| 116 |
+
con = duckdb.connect(database=DB_PATH)
|
| 117 |
+
|
| 118 |
+
# Insert the topic
|
| 119 |
+
con.execute("""
|
| 120 |
+
INSERT INTO topics (id, title, description)
|
| 121 |
+
VALUES (?, ?, ?)
|
| 122 |
+
ON CONFLICT (id) DO NOTHING
|
| 123 |
+
""", [topic_id, topic_title, topic_description])
|
| 124 |
+
|
| 125 |
+
# --- Add Cluster Users ---
|
| 126 |
+
# Create users who will cast votes, separate from comment authors
|
| 127 |
+
num_users_per_cluster = 5
|
| 128 |
+
cluster1_users = [] # e.g., Pro-Tech supporters
|
| 129 |
+
cluster2_users = [] # e.g., Anti-Tech skeptics
|
| 130 |
+
cluster3_users = [] # e.g., Mixed/Neutral voters
|
| 131 |
+
|
| 132 |
+
all_cluster_users = []
|
| 133 |
+
|
| 134 |
+
for i in range(num_users_per_cluster):
|
| 135 |
+
user_id = str(uuid.uuid4())
|
| 136 |
+
username = f"cluster1_user_{i+1}_{uuid.uuid4().hex[:4]}@example.com"
|
| 137 |
+
con.execute("INSERT INTO users (id, username) VALUES (?, ?) ON CONFLICT (id) DO NOTHING", [user_id, username])
|
| 138 |
+
cluster1_users.append(user_id)
|
| 139 |
+
all_cluster_users.append(user_id)
|
| 140 |
+
|
| 141 |
+
user_id = str(uuid.uuid4())
|
| 142 |
+
username = f"cluster2_user_{i+1}_{uuid.uuid4().hex[:4]}@example.com"
|
| 143 |
+
con.execute("INSERT INTO users (id, username) VALUES (?, ?) ON CONFLICT (id) DO NOTHING", [user_id, username])
|
| 144 |
+
cluster2_users.append(user_id)
|
| 145 |
+
all_cluster_users.append(user_id)
|
| 146 |
+
|
| 147 |
+
user_id = str(uuid.uuid4())
|
| 148 |
+
username = f"cluster3_user_{i+1}_{uuid.uuid4().hex[:4]}@example.com"
|
| 149 |
+
con.execute("INSERT INTO users (id, username) VALUES (?, ?) ON CONFLICT (id) DO NOTHING", [user_id, username])
|
| 150 |
+
cluster3_users.append(user_id)
|
| 151 |
+
all_cluster_users.append(user_id)
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
# --- Insert comments and associated users ---
|
| 155 |
+
comment_id_map = {} # Map comment text to comment ID
|
| 156 |
+
for comment_text in comments_list:
|
| 157 |
+
comment_id = str(uuid.uuid4())
|
| 158 |
+
# Generate a random user ID and username for the comment author
|
| 159 |
+
author_user_id = str(uuid.uuid4())
|
| 160 |
+
author_username = f"author_{uuid.uuid4().hex[:8]}@example.com"
|
| 161 |
+
|
| 162 |
+
# Insert the author user
|
| 163 |
+
con.execute("""
|
| 164 |
+
INSERT INTO users (id, username)
|
| 165 |
+
VALUES (?, ?)
|
| 166 |
+
ON CONFLICT (id) DO NOTHING
|
| 167 |
+
""", [author_user_id, author_username])
|
| 168 |
+
|
| 169 |
+
# Insert the comment
|
| 170 |
+
con.execute("""
|
| 171 |
+
INSERT INTO comments (id, topic_id, user_id, text)
|
| 172 |
+
VALUES (?, ?, ?, ?)
|
| 173 |
+
""", [comment_id, topic_id, author_user_id, comment_text])
|
| 174 |
+
comment_id_map[comment_text] = comment_id # Store the mapping
|
| 175 |
+
|
| 176 |
+
# --- Add Cluster Votes ---
|
| 177 |
+
# Define comment categories based on the example topic context (Civic Tech Initiative)
|
| 178 |
+
# This is hardcoded based on the context provided in the prompt
|
| 179 |
+
pro_tech_comments = [
|
| 180 |
+
"Finally! A system to track rebel scum more efficiently. This will be a glorious day for the Empire!",
|
| 181 |
+
"Anything that improves the speed of selling junk is good in my book. Maybe I can finally get a decent price for this thermal detonator...",
|
| 182 |
+
"Fascinating! I am programmed to be compliant. I shall analyze this initiative and report my findings to the Emperor.",
|
| 183 |
+
"This is a welcome step towards greater efficiency and transparency... cough... as long as it doesn't affect my personal interests.",
|
| 184 |
+
"As long as it helps me track down my targets, I'm in. The more data, the better.",
|
| 185 |
+
"The Emperor's vision is one of unparalleled order and prosperity! This initiative will usher in a new era of galactic harmony!",
|
| 186 |
+
"I'm interested... Will it help me collect debts more efficiently?",
|
| 187 |
+
"If it improves the entertainment options on Coruscant, I'm all for it.",
|
| 188 |
+
"Another set of orders. Understood, sir!",
|
| 189 |
+
"Excellent... with this, I will have even greater control over the galaxy... cackles maniacally"
|
| 190 |
+
]
|
| 191 |
+
anti_tech_comments = [
|
| 192 |
+
"This is clearly a data-mining operation. They're going to use it to crush the Rebellion. We need to sabotage it!",
|
| 193 |
+
"The Force guides us to see through their deception. This 'civic tech' will only serve to tighten their grip on the galaxy.",
|
| 194 |
+
"I just want a reliable power converter. Is that too much to ask? This 'civic tech' sounds like more bureaucracy.",
|
| 195 |
+
"I'm already dreading the help desk calls. 'My Death Star won't fire!' 'The Force isn't working!'",
|
| 196 |
+
"This is just a fancy way to track our X-wings. We'll find a way to disable it, just like we did with the Death Star.",
|
| 197 |
+
"Another reason to drown my sorrows in a Jawa Juice. This whole thing stinks of the Empire's incompetence.",
|
| 198 |
+
"This initiative is a waste of resources. We should be focusing on military expansion, not 'civic engagement.'"
|
| 199 |
+
]
|
| 200 |
+
# Comments not in pro or anti lists are considered neutral/other for this example
|
| 201 |
+
|
| 202 |
+
votes_to_insert = []
|
| 203 |
+
|
| 204 |
+
# Cluster 1 (Pro-Tech) votes: Agree with pro, Disagree with anti, Mixed/Neutral on others
|
| 205 |
+
for user_id in cluster1_users:
|
| 206 |
+
for comment_text in comments_list:
|
| 207 |
+
comment_id = comment_id_map.get(comment_text)
|
| 208 |
+
if not comment_id: continue
|
| 209 |
+
|
| 210 |
+
vote_type = None
|
| 211 |
+
if comment_text in pro_tech_comments:
|
| 212 |
+
vote_type = 'agree'
|
| 213 |
+
elif comment_text in anti_tech_comments:
|
| 214 |
+
vote_type = 'disagree'
|
| 215 |
+
else:
|
| 216 |
+
# For neutral/other comments, a chance of neutral or skipping
|
| 217 |
+
vote_type = random.choice(['neutral'] * 2 + [None] * 3) # More likely neutral or skip
|
| 218 |
+
|
| 219 |
+
if vote_type:
|
| 220 |
+
votes_to_insert.append((user_id, comment_id, vote_type))
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
# Cluster 2 (Anti-Tech) votes: Disagree with pro, Agree with anti, Mixed/Neutral on others
|
| 224 |
+
for user_id in cluster2_users:
|
| 225 |
+
for comment_text in comments_list:
|
| 226 |
+
comment_id = comment_id_map.get(comment_text)
|
| 227 |
+
if not comment_id: continue
|
| 228 |
+
|
| 229 |
+
vote_type = None
|
| 230 |
+
if comment_text in pro_tech_comments:
|
| 231 |
+
vote_type = 'disagree'
|
| 232 |
+
elif comment_text in anti_tech_comments:
|
| 233 |
+
vote_type = 'agree'
|
| 234 |
+
else:
|
| 235 |
+
# For neutral/other comments, a chance of neutral or skipping
|
| 236 |
+
vote_type = random.choice(['neutral'] * 2 + [None] * 3) # More likely neutral or skip
|
| 237 |
+
|
| 238 |
+
if vote_type:
|
| 239 |
+
votes_to_insert.append((user_id, comment_id, vote_type))
|
| 240 |
+
|
| 241 |
+
# Cluster 3 (Mixed/Neutral) votes: Mostly neutral, some random agree/disagree, many skipped
|
| 242 |
+
for user_id in cluster3_users:
|
| 243 |
+
for comment_text in comments_list:
|
| 244 |
+
comment_id = comment_id_map.get(comment_text)
|
| 245 |
+
if not comment_id: continue
|
| 246 |
+
|
| 247 |
+
# Mostly neutral, some random agree/disagree, many skipped
|
| 248 |
+
vote_type = random.choice(['neutral'] * 5 + ['agree', 'disagree'] + [None] * 5) # Weighted towards neutral and skipping
|
| 249 |
+
|
| 250 |
+
if vote_type:
|
| 251 |
+
votes_to_insert.append((user_id, comment_id, vote_type))
|
| 252 |
+
|
| 253 |
+
# Insert all collected votes
|
| 254 |
+
if votes_to_insert:
|
| 255 |
+
con.executemany("""
|
| 256 |
+
INSERT INTO votes (user_id, comment_id, vote_type)
|
| 257 |
+
VALUES (?, ?, ?)
|
| 258 |
+
ON CONFLICT (user_id, comment_id) DO NOTHING
|
| 259 |
+
""", votes_to_insert)
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
con.commit()
|
| 263 |
+
# print(f"Successfully added topic '{topic_title}', {len(comments_list)} comments, and {len(all_cluster_users)} cluster users with votes.") # Use print for console output
|
| 264 |
+
# st.success(f"Successfully added topic '{topic_title}', {len(comments_list)} comments, and {len(all_cluster_users)} cluster users with votes.") # Use st.success if in Streamlit context
|
| 265 |
+
|
| 266 |
+
except Exception as e:
|
| 267 |
+
if con:
|
| 268 |
+
con.rollback()
|
| 269 |
+
print(f"Error adding example topic '{topic_title}' and votes: {e}") # Use print for console output
|
| 270 |
+
# st.error(f"Error adding example topic '{topic_title}' and votes: {e}") # Use st.error if in Streamlit context
|
| 271 |
+
finally:
|
| 272 |
+
if con:
|
| 273 |
+
con.close()
|
| 274 |
+
# Example usage (can be called elsewhere, e.g., in an initialization script)
|
| 275 |
+
def add_dummy_topic():
|
| 276 |
+
example_topic_id = "15736626"
|
| 277 |
+
example_topic_title = "New Civic Tech Initiative"
|
| 278 |
+
example_topic_description = "I want to figure out what the citizens of the Empire really think about the Emperor's new 'Civic Tech' initiative. He's promising streamlined governance, enhanced citizen engagement (apparently), and a 'more user-friendly experience' for everyone. But let's be honest, we all know how the Empire's tech usually works out. So, what are your thoughts? Is this a path to order, or a trap set by the Dark Side? Let's get some honest opinions flowing!"
|
| 279 |
+
example_comments = [
|
| 280 |
+
"Finally! A system to track rebel scum more efficiently. This will be a glorious day for the Empire!",
|
| 281 |
+
"This is clearly a data-mining operation. They're going to use it to crush the Rebellion. We need to sabotage it!",
|
| 282 |
+
"The Force guides us to see through their deception. This 'civic tech' will only serve to tighten their grip on the galaxy.",
|
| 283 |
+
"As long as it doesn't mess with my profit margins, I'm indifferent. But I suspect it will.",
|
| 284 |
+
"I just want a reliable power converter. Is that too much to ask? This 'civic tech' sounds like more bureaucracy.",
|
| 285 |
+
"Anything that improves the speed of selling junk is good in my book. Maybe I can finally get a decent price for this thermal detonator...",
|
| 286 |
+
"Fascinating! I am programmed to be compliant. I shall analyze this initiative and report my findings to the Emperor.",
|
| 287 |
+
"I'm already dreading the help desk calls. 'My Death Star won't fire!' 'The Force isn't working!'",
|
| 288 |
+
"This is a welcome step towards greater efficiency and transparency... cough... as long as it doesn't affect my personal interests.",
|
| 289 |
+
"This is just a fancy way to track our X-wings. We'll find a way to disable it, just like we did with the Death Star.",
|
| 290 |
+
"As long as it helps me track down my targets, I'm in. The more data, the better.",
|
| 291 |
+
"Another reason to drown my sorrows in a Jawa Juice. This whole thing stinks of the Empire's incompetence.",
|
| 292 |
+
"The Emperor's vision is one of unparalleled order and prosperity! This initiative will usher in a new era of galactic harmony!",
|
| 293 |
+
"Will it have cool spaceships in it? Can I play with it?",
|
| 294 |
+
"Beware the allure of technology. It can be a tool for both good and evil. Trust in the Force, young Padawans.",
|
| 295 |
+
"This initiative is a waste of resources. We should be focusing on military expansion, not 'civic engagement.'",
|
| 296 |
+
"I'm interested... Will it help me collect debts more efficiently?",
|
| 297 |
+
"I'm just trying to survive. This sounds like more trouble than it's worth.",
|
| 298 |
+
"If it improves the entertainment options on Coruscant, I'm all for it.",
|
| 299 |
+
"Another set of orders. Understood, sir!",
|
| 300 |
+
"Excellent... with this, I will have even greater control over the galaxy... cackles maniacally"
|
| 301 |
+
]
|
| 302 |
+
add_example_topic(example_topic_id, example_topic_title, example_topic_description, example_comments)
|
| 303 |
+
|
| 304 |
+
|
| 305 |
def get_ttl_hash(seconds=360):
|
| 306 |
"""Return the same value withing `seconds` time period"""
|
| 307 |
return round(time.time() / seconds)
|
|
|
|
| 980 |
# Include functional information
|
| 981 |
st.markdown(f"**Shareable Quest Scroll ID:** `{topic_id}`")
|
| 982 |
# Construct shareable link using current app URL
|
| 983 |
+
app_url = st.query_params.get('base', [DEFAULT_BASE_URL])[0] # Get base URL if available
|
| 984 |
shareable_link = f"{app_url}?topic={topic_id}" if app_url else f"?topic={topic_id}"
|
| 985 |
st.markdown(f"**Shareable Scroll Link:** `{shareable_link}`")
|
| 986 |
|
|
|
|
| 1286 |
|
| 1287 |
# Initialize the database on first run
|
| 1288 |
initialize_database()
|
| 1289 |
+
add_dummy_topic()
|
| 1290 |
|
| 1291 |
# Handle initial load from URL query parameters
|
| 1292 |
# Process only once per session load using the flag
|