CKT commited on
Commit
9bfdb4b
·
1 Parent(s): eeb3194

PoC complete

Browse files
Files changed (3) hide show
  1. app.py +258 -13
  2. data/messages.json +27 -1
  3. data/profiles.json +42 -0
app.py CHANGED
@@ -3,6 +3,7 @@ import random
3
  import json
4
  import uuid
5
  import os # Added for path joining
 
6
  from datetime import datetime, timezone # Added timezone for UTC consistency
7
 
8
  # --- Start of JSON I/O Helper Functions ---
@@ -71,7 +72,8 @@ def profile_questionnaire(request: gr.Request):
71
  # 1. Generate profile_id and auth_id
72
  profile_id = f"user_{''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=8))}"
73
  auth_id = str(uuid.uuid4())
74
-
 
75
  # 2. Read questionnaire.json
76
  questionnaire_data = load_json_data(QUESTIONNAIRE_FILE, default_data={"title": "Error Loading Questionnaire", "questions": []})
77
  if not questionnaire_data.get("questions") or questionnaire_data.get("title") == "Error Loading Questionnaire":
@@ -118,6 +120,7 @@ def update_profile_answers(answers_payload_str: str, request: gr.Request):
118
  auth_id_header = request.headers.get("x-auth-id") # Headers are lowercased by Gradio/Starlette
119
  if not auth_id_header:
120
  return {"status": "error", "message": "Authentication failed: X-Auth-ID header is missing."}
 
121
 
122
  try:
123
  answers_payload = json.loads(answers_payload_str)
@@ -175,23 +178,222 @@ def update_profile_answers(answers_payload_str: str, request: gr.Request):
175
  "instructions_for_user": "Your profile has been updated! You can now look for matches or view your profile."
176
  }
177
 
178
- # --- End of MCP Matchmaker Tools ---
 
 
 
 
 
 
 
 
 
179
 
180
- # Original example functions from the user's app.py for context
181
- def print_headers(text, request: gr.Request):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  """
183
- Print the headers of the request for debugging purposes.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
- Args:
186
- text (str): The text to print
187
- request (gr.Request): The request object
 
 
 
188
 
189
- Returns:
190
- str: The text to print
 
 
 
 
 
 
 
 
 
 
 
191
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  print(f"Headers for print_headers request: {request.headers}")
193
  print(f"Text for print_headers: {text}")
194
- return text
 
 
 
 
195
 
196
  # Gradio interface for printing headers
197
  headers_demo = gr.Interface(
@@ -222,10 +424,53 @@ update_profile_answers_demo = gr.Interface(
222
  )
223
  # --- End of Update Profile Answers Interface ---
224
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  # Adjusted TabbedInterface to include the new tool
226
  demo = gr.TabbedInterface(
227
- [profile_questionnaire_demo, update_profile_answers_demo, headers_demo],
228
- ["Profile Questionnaire", "Update Profile Answers", "Headers Debug"]
229
  )
230
 
231
  if __name__ == "__main__":
 
3
  import json
4
  import uuid
5
  import os # Added for path joining
6
+ import copy # For deep copying message list
7
  from datetime import datetime, timezone # Added timezone for UTC consistency
8
 
9
  # --- Start of JSON I/O Helper Functions ---
 
72
  # 1. Generate profile_id and auth_id
73
  profile_id = f"user_{''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=8))}"
74
  auth_id = str(uuid.uuid4())
75
+ print(f"profile_questionnaire with Profile ID: {profile_id} and Auth ID: {auth_id}")
76
+
77
  # 2. Read questionnaire.json
78
  questionnaire_data = load_json_data(QUESTIONNAIRE_FILE, default_data={"title": "Error Loading Questionnaire", "questions": []})
79
  if not questionnaire_data.get("questions") or questionnaire_data.get("title") == "Error Loading Questionnaire":
 
120
  auth_id_header = request.headers.get("x-auth-id") # Headers are lowercased by Gradio/Starlette
121
  if not auth_id_header:
122
  return {"status": "error", "message": "Authentication failed: X-Auth-ID header is missing."}
123
+ print(f"update_profile_answers with Auth ID: {auth_id_header}")
124
 
125
  try:
126
  answers_payload = json.loads(answers_payload_str)
 
178
  "instructions_for_user": "Your profile has been updated! You can now look for matches or view your profile."
179
  }
180
 
181
+ def get_matches(request: gr.Request):
182
+ """
183
+ Finds potential matches for the authenticated user.
184
+ Requires X-Auth-ID header for authentication.
185
+ For P1, this returns up to 3 random profiles, excluding the user's own.
186
+ """
187
+ auth_id_header = request.headers.get("x-auth-id")
188
+ if not auth_id_header:
189
+ return {"status": "error", "message": "Authentication failed: X-Auth-ID header is missing."}
190
+ print(f"get_matches with Auth ID: {auth_id_header}")
191
 
192
+ profiles = load_json_data(PROFILES_FILE, default_data={})
193
+
194
+ requester_profile_id = None
195
+ for pid, profile_data in profiles.items():
196
+ if profile_data.get("auth_id") == auth_id_header:
197
+ requester_profile_id = pid
198
+ break
199
+
200
+ if not requester_profile_id:
201
+ return {"status": "error", "message": "Authentication failed: Invalid X-Auth-ID."}
202
+
203
+ all_profile_ids = list(profiles.keys())
204
+ # Exclude the user's own profile from potential matches
205
+ potential_matches_ids = [pid for pid in all_profile_ids if pid != requester_profile_id]
206
+
207
+ # Randomly select up to 3 matches
208
+ num_matches = min(len(potential_matches_ids), 3)
209
+ selected_match_ids = random.sample(potential_matches_ids, k=num_matches)
210
+
211
+ matches_list = []
212
+ for match_id in selected_match_ids:
213
+ match_profile = profiles.get(match_id)
214
+ if match_profile:
215
+ matches_list.append({
216
+ "profile_id": match_profile.get("profile_id"),
217
+ "name": match_profile.get("name"),
218
+ "profile_summary": match_profile.get("profile_summary"),
219
+ "profile_image_filename": match_profile.get("profile_image_filename")
220
+ })
221
+
222
+ return {
223
+ "status": "success",
224
+ "matches": matches_list,
225
+ "instructions_for_agent": "You have received a list of matches. For each match, you can call `get_profile` using their `profile_id` to get more details (this may have a cost). You can also use `send_message` to send a message to a match's `profile_id`.",
226
+ "instructions_for_user": "Here are some potential matches! Your AI agent can get more details on them or help you send a message."
227
+ }
228
+
229
+ def get_profile(profile_id_to_get: str, request: gr.Request):
230
  """
231
+ Gets a user's full profile.
232
+ Requires X-Auth-ID header for authentication.
233
+ Access is free for viewing one's own profile.
234
+ Accessing another user's profile has a cost (placeholder for P1).
235
+ """
236
+ auth_id_header = request.headers.get("x-auth-id")
237
+ if not auth_id_header:
238
+ return {"status": "error", "message": "Authentication failed: X-Auth-ID header is missing."}
239
+ print(f"get_profile with Auth ID: {auth_id_header}")
240
+
241
+ profiles = load_json_data(PROFILES_FILE, default_data={})
242
+
243
+ requester_profile_id = None
244
+ for pid, profile_data in profiles.items():
245
+ if profile_data.get("auth_id") == auth_id_header:
246
+ requester_profile_id = pid
247
+ break
248
+
249
+ if not requester_profile_id:
250
+ return {"status": "error", "message": "Authentication failed: Invalid X-Auth-ID."}
251
+
252
+ target_profile = profiles.get(profile_id_to_get)
253
+
254
+ if not target_profile:
255
+ return {"status": "error", "message": f"Profile with ID '{profile_id_to_get}' not found."}
256
+
257
+ # For security, never return the auth_id
258
+ target_profile.pop("auth_id", None)
259
+
260
+ cost_incurred = 0.0
261
+ instructions_for_agent = f"You have retrieved the profile for {profile_id_to_get}."
262
+ instructions_for_user = f"Here is the profile for {profile_id_to_get}."
263
 
264
+ if profile_id_to_get != requester_profile_id:
265
+ # This is where a real payment would be processed.
266
+ # For P1, we just note the cost.
267
+ cost_incurred = 0.10
268
+ instructions_for_agent += f" As this was not your own profile, a cost of ${cost_incurred:.2f} was incurred (for PoC, this is just a note)."
269
+ instructions_for_user += " Viewing other profiles may have a cost."
270
 
271
+ return {
272
+ "status": "success",
273
+ "profile": target_profile,
274
+ "cost_incurred_usd": cost_incurred,
275
+ "instructions_for_agent": instructions_for_agent,
276
+ "instructions_for_user": instructions_for_user
277
+ }
278
+
279
+ def send_message(receiver_profile_id: str, content: str, request: gr.Request = None):
280
+ """
281
+ Sends a message to a match.
282
+ Requires X-Auth-ID header for authentication.
283
+ Costs $1.00 per message (placeholder for P1).
284
  """
285
+ auth_id_header = request.headers.get("x-auth-id")
286
+ if not auth_id_header:
287
+ return {"status": "error", "message": "Authentication failed: X-Auth-ID header is missing."}
288
+ print(f"send_message with Auth ID: {auth_id_header}")
289
+
290
+ profiles = load_json_data(PROFILES_FILE, default_data={})
291
+
292
+ sender_profile_id = None
293
+ for pid, profile_data in profiles.items():
294
+ if profile_data.get("auth_id") == auth_id_header:
295
+ sender_profile_id = pid
296
+ break
297
+
298
+ if not sender_profile_id:
299
+ return {"status": "error", "message": "Authentication failed: Invalid X-Auth-ID."}
300
+
301
+ if receiver_profile_id not in profiles:
302
+ return {"status": "error", "message": "Receiver profile ID not found."}
303
+
304
+ # For P1, we are not integrating a real payment system.
305
+ # We will integrate AgentPay here late.
306
+ cost_incurred = 100
307
+
308
+ messages = load_json_data(MESSAGES_FILE, default_data=[])
309
+
310
+ new_message = {
311
+ "message_id": str(uuid.uuid4()),
312
+ "sender_profile_id": sender_profile_id,
313
+ "receiver_profile_id": receiver_profile_id,
314
+ "content": content,
315
+ "timestamp": datetime.now(timezone.utc).isoformat(),
316
+ "read_status": False # Messages are unread when sent
317
+ }
318
+
319
+ messages.append(new_message)
320
+
321
+ if not save_json_data(MESSAGES_FILE, messages):
322
+ return {"status": "error", "message": "Failed to save message."}
323
+
324
+ return {
325
+ "status": "success",
326
+ "message_id": new_message["message_id"],
327
+ "cost_incurred_usd_cents": cost_incurred,
328
+ "instructions_for_agent": f"Message sent successfully to {receiver_profile_id}. A cost of {cost_incurred/100:.2f} cents was incurred (for PoC, this is just a note). You can get all messages for the user with `get_messages`.",
329
+ "instructions_for_user": f"Your message has been sent to {receiver_profile_id}!"
330
+ }
331
+
332
+ def get_messages(request: gr.Request):
333
+ """
334
+ Gets all messages for the authenticated user (sent and received).
335
+ Marks retrieved messages where the user is the receiver as read for subsequent calls.
336
+ Requires X-Auth-ID header for authentication.
337
+ """
338
+ auth_id_header = request.headers.get("x-auth-id")
339
+ if not auth_id_header:
340
+ return {"status": "error", "message": "Authentication failed: X-Auth-ID header is missing."}
341
+ print(f"get_messages with Auth ID: {auth_id_header}")
342
+
343
+ profiles = load_json_data(PROFILES_FILE, default_data={})
344
+
345
+ user_profile_id = None
346
+ for pid, profile_data in profiles.items():
347
+ if profile_data.get("auth_id") == auth_id_header:
348
+ user_profile_id = pid
349
+ break
350
+
351
+ if not user_profile_id:
352
+ return {"status": "error", "message": "Authentication failed: Invalid X-Auth-ID."}
353
+
354
+ all_messages = load_json_data(MESSAGES_FILE, default_data=[])
355
+
356
+ # Use a deep copy to avoid modifying the list while iterating
357
+ messages_for_user = copy.deepcopy([
358
+ msg for msg in all_messages
359
+ if msg.get("sender_profile_id") == user_profile_id or msg.get("receiver_profile_id") == user_profile_id
360
+ ])
361
+
362
+ # Sort messages by timestamp descending (newest first)
363
+ messages_for_user.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
364
+
365
+ # Mark received messages as read and track if an update is needed
366
+ needs_save = False
367
+ for i, original_msg in enumerate(all_messages):
368
+ # Check if this message is one of the user's messages and was received by them
369
+ if original_msg.get("receiver_profile_id") == user_profile_id and not original_msg.get("read_status"):
370
+ all_messages[i]["read_status"] = True
371
+ needs_save = True
372
+
373
+ # Save the updated message list back to the file if any message was marked as read
374
+ if needs_save:
375
+ if not save_json_data(MESSAGES_FILE, all_messages):
376
+ # If saving fails, we should still return the messages, but log the error.
377
+ print("Error: Could not update read_status for messages in the database.")
378
+ # Depending on desired behavior, we could return an error status here.
379
+ # For now, we will proceed to return the messages as requested.
380
+
381
+ return {
382
+ "status": "success",
383
+ "messages": messages_for_user,
384
+ "instructions_for_agent": "You have received all messages for the user. Messages they received in this batch have now been marked as 'read' on the server.",
385
+ "instructions_for_user": "Here are your messages."
386
+ }
387
+
388
+ def print_headers(text, request: gr.Request):
389
+ """Print the headers of the request for debugging purposes."""
390
  print(f"Headers for print_headers request: {request.headers}")
391
  print(f"Text for print_headers: {text}")
392
+ return f"Printed headers and text: {text}"
393
+
394
+ # --- End of MCP Matchmaker Tools ---
395
+
396
+ # --- Start of Gradio App Setup ---
397
 
398
  # Gradio interface for printing headers
399
  headers_demo = gr.Interface(
 
424
  )
425
  # --- End of Update Profile Answers Interface ---
426
 
427
+ # --- Start of Get Matches Interface ---
428
+ get_matches_demo = gr.Interface(
429
+ fn=get_matches,
430
+ inputs=None,
431
+ outputs=gr.JSON(label="Matches"),
432
+ title="Get Matches",
433
+ description="Gets a list of potential matches for the authenticated user. Requires X-Auth-ID header."
434
+ )
435
+ # --- End of Get Matches Interface ---
436
+
437
+ # --- Start of Get Profile Interface ---
438
+ get_profile_demo = gr.Interface(
439
+ fn=get_profile,
440
+ inputs=[gr.Textbox(label="Profile ID to Get")],
441
+ outputs=gr.JSON(label="Profile Details"),
442
+ title="Get Profile",
443
+ description="Gets the full profile for a given Profile ID. Requires X-Auth-ID header."
444
+ )
445
+ # --- End of Get Profile Interface ---
446
+
447
+ # --- Start of Send Message Interface ---
448
+ send_message_demo = gr.Interface(
449
+ fn=send_message,
450
+ inputs=[
451
+ gr.Textbox(label="Receiver Profile ID"),
452
+ gr.Textbox(label="Message Content", lines=5)
453
+ ],
454
+ outputs=gr.JSON(label="Send Status"),
455
+ title="Send Message",
456
+ description="Sends a message to another user. Requires X-Auth-ID header. ($1.00 placeholder cost)"
457
+ )
458
+ # --- End of Send Message Interface ---
459
+
460
+ # --- Start of Get Messages Interface ---
461
+ get_messages_demo = gr.Interface(
462
+ fn=get_messages,
463
+ inputs=None,
464
+ outputs=gr.JSON(label="Messages"),
465
+ title="Get Messages",
466
+ description="Gets all messages sent or received by the authenticated user. Requires X-Auth-ID header."
467
+ )
468
+ # --- End of Get Messages Interface ---
469
+
470
  # Adjusted TabbedInterface to include the new tool
471
  demo = gr.TabbedInterface(
472
+ [profile_questionnaire_demo, update_profile_answers_demo, get_matches_demo, get_profile_demo, send_message_demo, get_messages_demo, headers_demo],
473
+ ["Profile Questionnaire", "Update Profile Answers", "Get Matches", "Get Profile", "Send Message", "Get Messages", "Headers Debug"]
474
  )
475
 
476
  if __name__ == "__main__":
data/messages.json CHANGED
@@ -1 +1,27 @@
1
- []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "message_id": "7880956d-244d-4c2b-9b7c-bd6ecb371ed4",
4
+ "sender_profile_id": "user_y38k71ox",
5
+ "receiver_profile_id": "user_p9zqwxv8",
6
+ "content": "Hey there!",
7
+ "timestamp": "2025-06-06T04:56:31.804886+00:00",
8
+ "read_status": true
9
+ },
10
+ {
11
+ "message_id": "5809af1e-8a57-41d1-9319-233c7e66a26a",
12
+ "sender_profile_id": "user_p9zqwxv8",
13
+ "receiver_profile_id": "user_y38k71ox",
14
+ "content": "Hey you!",
15
+ "timestamp": "2025-06-06T05:12:46.611177+00:00",
16
+ "read_status": true
17
+ },
18
+ {
19
+ "message_id": "440f9c41-626f-4b45-89b5-1f59351d190f",
20
+ "sender_profile_id": "user_y38k71ox",
21
+ "receiver_profile_id": "user_p9zqwxv8",
22
+ "content": "How are you doing?",
23
+ "timestamp": "2025-06-06T06:44:02.018941+00:00",
24
+ "read_status": true,
25
+ "payment_confirmation_id": "not_provided"
26
+ }
27
+ ]
data/profiles.json CHANGED
@@ -22,5 +22,47 @@
22
  "q_looking_for": "I'm looking for someone who is as cool as me.",
23
  "q_vibe": "My general vibe is... coding."
24
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
  }
 
22
  "q_looking_for": "I'm looking for someone who is as cool as me.",
23
  "q_vibe": "My general vibe is... coding."
24
  }
25
+ },
26
+ "user_h7bafk3d": {
27
+ "profile_id": "user_h7bafk3d",
28
+ "auth_id": "a4f8b9e6-7d6a-4c3b-8e1f-9a0c1b2d3e4f",
29
+ "created_at": "2025-06-05T17:10:00.000000+00:00",
30
+ "updated_at": "2025-06-05T17:10:00.000000+00:00",
31
+ "name": "Alex",
32
+ "profile_image_filename": "placeholder.png",
33
+ "profile_summary": "Loves hiking and exploring new cafes.",
34
+ "answers": {
35
+ "q_hobby": "Hiking, photography, trying new recipes.",
36
+ "q_looking_for": "Someone adventurous and kind.",
37
+ "q_vibe": "Outdoorsy and creative."
38
+ }
39
+ },
40
+ "user_p9zqwxv8": {
41
+ "profile_id": "user_p9zqwxv8",
42
+ "auth_id": "b5c7d8f9-8e7b-4d2c-9f0a-1b2c3d4e5f6g",
43
+ "created_at": "2025-06-05T17:11:00.000000+00:00",
44
+ "updated_at": "2025-06-05T17:11:00.000000+00:00",
45
+ "name": "Bella",
46
+ "profile_image_filename": "placeholder.png",
47
+ "profile_summary": "Bookworm, artist, and enjoys quiet nights in.",
48
+ "answers": {
49
+ "q_hobby": "Reading, painting, watching classic movies.",
50
+ "q_looking_for": "A thoughtful person to share deep conversations with.",
51
+ "q_vibe": "Cozy and artistic."
52
+ }
53
+ },
54
+ "user_t2ysgmnp": {
55
+ "profile_id": "user_t2ysgmnp",
56
+ "auth_id": "c6d8e9g0-9f8c-5e1d-a01b-2c3d4e5f6g7h",
57
+ "created_at": "2025-06-05T17:12:00.000000+00:00",
58
+ "updated_at": "2025-06-05T17:12:00.000000+00:00",
59
+ "name": "Charlie",
60
+ "profile_image_filename": "placeholder.png",
61
+ "profile_summary": "Tech enthusiast and loves a good board game.",
62
+ "answers": {
63
+ "q_hobby": "Building PCs, board games, sci-fi novels.",
64
+ "q_looking_for": "A partner-in-crime for game nights and tech talks.",
65
+ "q_vibe": "Geeky and friendly."
66
+ }
67
  }
68
  }