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
|