broadfield-dev commited on
Commit
1d5f7d8
·
verified ·
1 Parent(s): 8b44c5c

Update server.py

Browse files
Files changed (1) hide show
  1. server.py +53 -34
server.py CHANGED
@@ -8,7 +8,7 @@ import sqlite3
8
  import time
9
  from dotenv import load_dotenv
10
 
11
- DEMO_MODE = os.getenv("DEMO_MODE", "true").lower() == 'true'
12
  # --- Load Environment & Configuration ---
13
  load_dotenv()
14
  try:
@@ -16,9 +16,9 @@ try:
16
  HF_DATASETS_AVAILABLE = True
17
  except ImportError:
18
  HF_DATASETS_AVAILABLE = False
19
- Features, Value = None, None # Define placeholders if import fails
20
 
21
- STORAGE_BACKEND_CONFIG = os.getenv("STORAGE_BACKEND", "HF_DATASET").upper()
22
  HF_DATASET_REPO = os.getenv("HF_DATASET_REPO")
23
  HF_TOKEN = os.getenv("HF_TOKEN")
24
  HF_BACKUP_THRESHOLD = int(os.getenv("HF_BACKUP_THRESHOLD", 10))
@@ -33,7 +33,7 @@ dirty_operations_count = 0
33
  def force_persist_data():
34
  global dirty_operations_count
35
  with db_lock:
36
- storage_backend = STORAGE_BACKEND_CONFIG # Use a local copy for thread safety
37
  if storage_backend == "RAM":
38
  return True, "RAM backend. No persistence."
39
  elif storage_backend == "SQLITE":
@@ -81,8 +81,7 @@ def load_data():
81
  global STORAGE_BACKEND_CONFIG
82
  storage_backend = STORAGE_BACKEND_CONFIG
83
  with db_lock:
84
- # Default empty structures
85
- users, posts, comments = {"admin": "password"}, pd.DataFrame(columns=["post_id", "username", "content", "timestamp"]), pd.DataFrame(columns=["comment_id", "post_id", "username", "content", "timestamp"])
86
 
87
  if storage_backend == "SQLITE":
88
  try:
@@ -90,7 +89,7 @@ def load_data():
90
  cursor = conn.cursor()
91
  cursor.execute("CREATE TABLE IF NOT EXISTS users (username TEXT PRIMARY KEY, password TEXT NOT NULL)")
92
  cursor.execute("CREATE TABLE IF NOT EXISTS posts (post_id INTEGER PRIMARY KEY, username TEXT, content TEXT, timestamp TEXT)")
93
- cursor.execute("CREATE TABLE IF NOT EXISTS comments (comment_id INTEGER PRIMARY KEY, post_id INTEGER, username TEXT, content TEXT, timestamp TEXT)")
94
  cursor.execute("INSERT OR IGNORE INTO users (username, password) VALUES (?, ?)", ("admin", "password"))
95
  conn.commit()
96
  users = dict(conn.execute("SELECT username, password FROM users").fetchall())
@@ -125,7 +124,7 @@ def load_data():
125
  try:
126
  user_features = Features({'username': Value('string'), 'password': Value('string')})
127
  post_features = Features({'post_id': Value('int64'), 'username': Value('string'), 'content': Value('string'), 'timestamp': Value('string')})
128
- comment_features = Features({'comment_id': Value('int64'), 'post_id': Value('int64'), 'username': Value('string'), 'content': Value('string'), 'timestamp': Value('string')})
129
 
130
  dataset_dict = DatasetDict({
131
  'users': Dataset.from_pandas(pd.DataFrame(list(users.items()), columns=['username', 'password']), features=user_features),
@@ -141,11 +140,8 @@ def load_data():
141
  print("HF_DATASET backend not fully configured (check env vars and library install). Falling back to RAM for this session.")
142
  STORAGE_BACKEND_CONFIG = "RAM"
143
 
144
- # Final validation of DataFrame structures
145
- if not isinstance(posts, pd.DataFrame) or "post_id" not in posts.columns:
146
- posts = pd.DataFrame(columns=["post_id", "username", "content", "timestamp"])
147
- if not isinstance(comments, pd.DataFrame) or "comment_id" not in comments.columns:
148
- comments = pd.DataFrame(columns=["comment_id", "post_id", "username", "content", "timestamp"])
149
 
150
  post_counter = int(posts['post_id'].max()) if not posts.empty else 0
151
  comment_counter = int(comments['comment_id'].max()) if not comments.empty else 0
@@ -184,7 +180,7 @@ def api_create_post(auth_token, content):
184
  handle_persistence_after_change()
185
  return f"[Post API] Success: Post created with ID {post_counter}."
186
 
187
- def api_create_comment(auth_token, post_id, content):
188
  global comments_df, comment_counter
189
  username = _get_user_from_token(auth_token)
190
  if not username: return "[Comment API] Failed: Invalid auth token."
@@ -193,19 +189,46 @@ def api_create_comment(auth_token, post_id, content):
193
  try: target_post_id = int(post_id)
194
  except (ValueError, TypeError): return f"[Comment API] Failed: Post ID must be a number."
195
  if target_post_id not in posts_df['post_id'].values: return f"[Comment API] Failed: Post with ID {post_id} not found."
 
 
 
 
 
 
 
196
  comment_counter += 1
197
- new_comment = pd.DataFrame([{"comment_id": comment_counter, "post_id": target_post_id, "username": username, "content": content, "timestamp": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")}])
 
198
  comments_df = pd.concat([comments_df, new_comment], ignore_index=True)
199
  handle_persistence_after_change()
200
  return f"[Comment API] Success: Comment created on post {post_id}."
201
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  def api_get_feed(search_query: str = None):
203
  with db_lock:
204
  current_posts, current_comments = posts_df.copy(), comments_df.copy()
205
  if current_posts.empty: return pd.DataFrame(columns=["post_id", "username", "content", "timestamp", "comments"])
206
  display_posts = current_posts[current_posts['content'].str.contains(search_query, case=False, na=False)] if search_query and not search_query.isspace() else current_posts
207
  sorted_posts = display_posts.sort_values(by="timestamp", ascending=False)
208
- feed_data = [{"post_id": post['post_id'], "username": post['username'], "content": post['content'], "timestamp": post['timestamp'], "comments": "\n".join([f" - @{c['username']}: {c['content']}" for _, c in current_comments[current_comments['post_id'] == post['post_id']].iterrows()])} for _, post in sorted_posts.iterrows()]
 
 
 
 
 
209
  return pd.DataFrame(feed_data) if feed_data else pd.DataFrame(columns=["post_id", "username", "content", "timestamp", "comments"])
210
 
211
  # --- UI Helper Functions ---
@@ -218,17 +241,17 @@ def ui_manual_post(username, password, content):
218
  result = api_create_post(auth_token, content)
219
  return result, api_get_feed()
220
 
221
- def ui_manual_comment(username, password, post_id, content):
222
  if not username or not password:
223
  return "Username and password are required.", api_get_feed()
224
  auth_token = api_login(username, password)
225
  if "Failed" in auth_token:
226
  return "Login failed. Check credentials.", api_get_feed()
227
- result = api_create_comment(auth_token, post_id, content)
228
  return result, api_get_feed()
229
 
230
  with gr.Blocks(theme=gr.themes.Soft(), title="Social App") as demo:
231
- gr.Markdown("# iLearnHub")
232
  gr.Markdown(f"This app provides an API for iLearn agents to interact with. **Storage Backend: `{STORAGE_BACKEND_CONFIG}`**")
233
 
234
  with gr.Tabs():
@@ -250,6 +273,7 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Social App") as demo:
250
  comment_user = gr.Textbox(label="Username", value="admin")
251
  comment_pass = gr.Textbox(label="Password", type="password", value="password")
252
  comment_post_id = gr.Number(label="Target Post ID", precision=0)
 
253
  comment_content = gr.Textbox(label="Comment Content", lines=2, placeholder="Add a comment...")
254
  comment_button = gr.Button("Submit Comment", variant="primary")
255
  with gr.Group():
@@ -269,19 +293,18 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Social App") as demo:
269
  )
270
  comment_button.click(
271
  fn=ui_manual_comment,
272
- inputs=[comment_user, comment_pass, comment_post_id, comment_content],
273
  outputs=[manual_action_status, feed_df_display]
274
  )
275
 
276
- last_refresh = time.time()
277
  def timed_feed_refresh(interval):
278
- global last_refresh
279
- if time.time() - last_refresh > interval:
280
- last_refresh = time.time()
281
  return api_get_feed()
282
  return gr.update()
283
 
284
- # A fast-ticking timer that calls our function. The function itself decides if it's time to refresh.
285
  gr.Timer(1).tick(
286
  fn=timed_feed_refresh,
287
  inputs=[feed_refresh_interval_slider],
@@ -299,16 +322,12 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Social App") as demo:
299
 
300
  demo.load(api_get_feed, None, feed_df_display)
301
 
302
- # Hidden API interfaces for the agent
303
  with gr.Column(visible=False if DEMO_MODE else True):
304
- for name, func, inputs, outputs in [
305
- ("register", api_register, ["text", gr.Textbox(type="password")], "text"),
306
- ("login", api_login, ["text", gr.Textbox(type="password")], "text"),
307
- ("create_post", api_create_post, ["text", "text"], "text"),
308
- ("create_comment", api_create_comment, ["text", "number", "text"], "text"),
309
- ("get_feed", api_get_feed, ["text"], "dataframe")
310
- ]:
311
- gr.Interface(func, inputs, outputs, api_name=name, allow_flagging="never")
312
 
313
  if __name__ == "__main__":
314
  print(f"Starting Social Media App server with {STORAGE_BACKEND_CONFIG} backend.")
 
8
  import time
9
  from dotenv import load_dotenv
10
 
11
+ DEMO_MODE = os.getenv("DEMO_MODE", "False").lower() == 'true'
12
  # --- Load Environment & Configuration ---
13
  load_dotenv()
14
  try:
 
16
  HF_DATASETS_AVAILABLE = True
17
  except ImportError:
18
  HF_DATASETS_AVAILABLE = False
19
+ Features, Value = None, None
20
 
21
+ STORAGE_BACKEND_CONFIG = os.getenv("STORAGE_BACKEND", "JSON").upper()
22
  HF_DATASET_REPO = os.getenv("HF_DATASET_REPO")
23
  HF_TOKEN = os.getenv("HF_TOKEN")
24
  HF_BACKUP_THRESHOLD = int(os.getenv("HF_BACKUP_THRESHOLD", 10))
 
33
  def force_persist_data():
34
  global dirty_operations_count
35
  with db_lock:
36
+ storage_backend = STORAGE_BACKEND_CONFIG
37
  if storage_backend == "RAM":
38
  return True, "RAM backend. No persistence."
39
  elif storage_backend == "SQLITE":
 
81
  global STORAGE_BACKEND_CONFIG
82
  storage_backend = STORAGE_BACKEND_CONFIG
83
  with db_lock:
84
+ users, posts, comments = {"admin": "password"}, pd.DataFrame(columns=["post_id", "username", "content", "timestamp"]), pd.DataFrame(columns=["comment_id", "post_id", "username", "content", "timestamp", "reply_to_comment_id"])
 
85
 
86
  if storage_backend == "SQLITE":
87
  try:
 
89
  cursor = conn.cursor()
90
  cursor.execute("CREATE TABLE IF NOT EXISTS users (username TEXT PRIMARY KEY, password TEXT NOT NULL)")
91
  cursor.execute("CREATE TABLE IF NOT EXISTS posts (post_id INTEGER PRIMARY KEY, username TEXT, content TEXT, timestamp TEXT)")
92
+ cursor.execute("CREATE TABLE IF NOT EXISTS comments (comment_id INTEGER PRIMARY KEY, post_id INTEGER, username TEXT, content TEXT, timestamp TEXT, reply_to_comment_id INTEGER)")
93
  cursor.execute("INSERT OR IGNORE INTO users (username, password) VALUES (?, ?)", ("admin", "password"))
94
  conn.commit()
95
  users = dict(conn.execute("SELECT username, password FROM users").fetchall())
 
124
  try:
125
  user_features = Features({'username': Value('string'), 'password': Value('string')})
126
  post_features = Features({'post_id': Value('int64'), 'username': Value('string'), 'content': Value('string'), 'timestamp': Value('string')})
127
+ comment_features = Features({'comment_id': Value('int64'), 'post_id': Value('int64'), 'username': Value('string'), 'content': Value('string'), 'timestamp': Value('string'), 'reply_to_comment_id': Value('int64')})
128
 
129
  dataset_dict = DatasetDict({
130
  'users': Dataset.from_pandas(pd.DataFrame(list(users.items()), columns=['username', 'password']), features=user_features),
 
140
  print("HF_DATASET backend not fully configured (check env vars and library install). Falling back to RAM for this session.")
141
  STORAGE_BACKEND_CONFIG = "RAM"
142
 
143
+ if "reply_to_comment_id" not in comments.columns:
144
+ comments["reply_to_comment_id"] = None
 
 
 
145
 
146
  post_counter = int(posts['post_id'].max()) if not posts.empty else 0
147
  comment_counter = int(comments['comment_id'].max()) if not comments.empty else 0
 
180
  handle_persistence_after_change()
181
  return f"[Post API] Success: Post created with ID {post_counter}."
182
 
183
+ def api_create_comment(auth_token, post_id, content, reply_to_comment_id=None):
184
  global comments_df, comment_counter
185
  username = _get_user_from_token(auth_token)
186
  if not username: return "[Comment API] Failed: Invalid auth token."
 
189
  try: target_post_id = int(post_id)
190
  except (ValueError, TypeError): return f"[Comment API] Failed: Post ID must be a number."
191
  if target_post_id not in posts_df['post_id'].values: return f"[Comment API] Failed: Post with ID {post_id} not found."
192
+
193
+ target_reply_id = None
194
+ if reply_to_comment_id is not None:
195
+ try: target_reply_id = int(reply_to_comment_id)
196
+ except (ValueError, TypeError): return "[Comment API] Failed: Reply ID must be a number."
197
+ if target_reply_id not in comments_df['comment_id'].values: return f"[Comment API] Failed: Comment to reply to (ID {target_reply_id}) not found."
198
+
199
  comment_counter += 1
200
+ new_comment_data = {"comment_id": comment_counter, "post_id": target_post_id, "username": username, "content": content, "timestamp": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"), "reply_to_comment_id": target_reply_id}
201
+ new_comment = pd.DataFrame([new_comment_data])
202
  comments_df = pd.concat([comments_df, new_comment], ignore_index=True)
203
  handle_persistence_after_change()
204
  return f"[Comment API] Success: Comment created on post {post_id}."
205
 
206
+ def _format_comments_threaded(post_id, all_comments_df, parent_id=None, depth=0):
207
+ thread = []
208
+ # Match NaN correctly for top-level comments
209
+ if parent_id is None:
210
+ children = all_comments_df[(all_comments_df['post_id'] == post_id) & (all_comments_df['reply_to_comment_id'].isna())]
211
+ else:
212
+ children = all_comments_df[all_comments_df['reply_to_comment_id'] == parent_id]
213
+
214
+ for _, comment in children.iterrows():
215
+ indent = " " * depth
216
+ thread.append(f"{indent} - (ID: {comment['comment_id']}) @{comment['username']}: {comment['content']}")
217
+ thread.extend(_format_comments_threaded(post_id, all_comments_df, parent_id=comment['comment_id'], depth=depth + 1))
218
+ return thread
219
+
220
  def api_get_feed(search_query: str = None):
221
  with db_lock:
222
  current_posts, current_comments = posts_df.copy(), comments_df.copy()
223
  if current_posts.empty: return pd.DataFrame(columns=["post_id", "username", "content", "timestamp", "comments"])
224
  display_posts = current_posts[current_posts['content'].str.contains(search_query, case=False, na=False)] if search_query and not search_query.isspace() else current_posts
225
  sorted_posts = display_posts.sort_values(by="timestamp", ascending=False)
226
+
227
+ feed_data = []
228
+ for _, post in sorted_posts.iterrows():
229
+ threaded_comments = _format_comments_threaded(post['post_id'], current_comments)
230
+ feed_data.append({"post_id": post['post_id'], "username": post['username'], "content": post['content'], "timestamp": post['timestamp'], "comments": "\n".join(threaded_comments)})
231
+
232
  return pd.DataFrame(feed_data) if feed_data else pd.DataFrame(columns=["post_id", "username", "content", "timestamp", "comments"])
233
 
234
  # --- UI Helper Functions ---
 
241
  result = api_create_post(auth_token, content)
242
  return result, api_get_feed()
243
 
244
+ def ui_manual_comment(username, password, post_id, reply_id, content):
245
  if not username or not password:
246
  return "Username and password are required.", api_get_feed()
247
  auth_token = api_login(username, password)
248
  if "Failed" in auth_token:
249
  return "Login failed. Check credentials.", api_get_feed()
250
+ result = api_create_comment(auth_token, post_id, content, reply_to_comment_id=reply_id)
251
  return result, api_get_feed()
252
 
253
  with gr.Blocks(theme=gr.themes.Soft(), title="Social App") as demo:
254
+ gr.Markdown("# Dummy Social Media Platform")
255
  gr.Markdown(f"This app provides an API for iLearn agents to interact with. **Storage Backend: `{STORAGE_BACKEND_CONFIG}`**")
256
 
257
  with gr.Tabs():
 
273
  comment_user = gr.Textbox(label="Username", value="admin")
274
  comment_pass = gr.Textbox(label="Password", type="password", value="password")
275
  comment_post_id = gr.Number(label="Target Post ID", precision=0)
276
+ comment_reply_id = gr.Number(label="Reply to Comment ID (optional)", precision=0)
277
  comment_content = gr.Textbox(label="Comment Content", lines=2, placeholder="Add a comment...")
278
  comment_button = gr.Button("Submit Comment", variant="primary")
279
  with gr.Group():
 
293
  )
294
  comment_button.click(
295
  fn=ui_manual_comment,
296
+ inputs=[comment_user, comment_pass, comment_post_id, comment_reply_id, comment_content],
297
  outputs=[manual_action_status, feed_df_display]
298
  )
299
 
300
+ last_refresh_time = time.time()
301
  def timed_feed_refresh(interval):
302
+ global last_refresh_time
303
+ if time.time() - last_refresh_time > interval:
304
+ last_refresh_time = time.time()
305
  return api_get_feed()
306
  return gr.update()
307
 
 
308
  gr.Timer(1).tick(
309
  fn=timed_feed_refresh,
310
  inputs=[feed_refresh_interval_slider],
 
322
 
323
  demo.load(api_get_feed, None, feed_df_display)
324
 
 
325
  with gr.Column(visible=False if DEMO_MODE else True):
326
+ gr.Interface(api_register, ["text", gr.Textbox(type="password")], "text", api_name="register", allow_flagging="never")
327
+ gr.Interface(api_login, ["text", gr.Textbox(type="password")], "text", api_name="login", allow_flagging="never")
328
+ gr.Interface(api_create_post, ["text", "text"], "text", api_name="create_post", allow_flagging="never")
329
+ gr.Interface(api_create_comment, ["text", "number", "text", "number"], "text", api_name="create_comment", allow_flagging="never")
330
+ gr.Interface(api_get_feed, ["text"], "dataframe", api_name="get_feed", allow_flagging="never")
 
 
 
331
 
332
  if __name__ == "__main__":
333
  print(f"Starting Social Media App server with {STORAGE_BACKEND_CONFIG} backend.")