toecm commited on
Commit
087a865
·
verified ·
1 Parent(s): 9baf14c

Update src/ux_agent.py

Browse files
Files changed (1) hide show
  1. src/ux_agent.py +127 -44
src/ux_agent.py CHANGED
@@ -61,8 +61,10 @@ class AgentUX:
61
  self.last_audio_path = None
62
  self.last_pending_count = 0
63
 
64
- # 🟢 NEW: In-memory mailbox for live device-to-device routing
65
- self.live_rooms = {}
 
 
66
 
67
  self.alert_sound = None
68
 
@@ -179,51 +181,122 @@ class AgentUX:
179
  print(f"Peer Translation Error: {e}")
180
  return text # Fallback to original text if API fails
181
 
182
- # 🟢 NEW: Evaluates deviation from Standard English and routes to the correct room
183
- def api_remote_eval_and_send(self, room_code, sender_id, text, dialect):
184
- if not text: return json.dumps({"status": "error", "msg": "Empty text"})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
 
186
- # The "Linguistic Bouncer" Prompt
187
- prompt = f"""
188
- Analyze this utterance from a speaker of '{dialect}': "{text}"
189
- Is this mutually intelligible Standard English?
190
- If YES, return EXACTLY this JSON: {{"needs_help": false, "translation": ""}}
191
- If NO (it uses heavy dialect, slang, or non-standard grammar), return EXACTLY this JSON: {{"needs_help": true, "translation": "The direct Standard English meaning here"}}
192
- """
 
 
 
 
 
193
 
194
  translation = ""
195
- try:
196
- res = self.brain.gemini_manager.client.models.generate_content(model='gemini-2.0-flash', contents=prompt)
197
- clean_json = res.text.replace("```json", "").replace("```", "").strip()
198
- data = json.loads(clean_json)
199
- if data.get("needs_help"):
200
- translation = data.get("translation", "")
201
- except Exception as e:
202
- print(f"Smart Eval Error: {e}")
 
203
 
204
- # Route to the mailbox
205
- if room_code not in self.live_rooms:
206
- self.live_rooms[room_code] = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
- msg = {
209
- "sender": sender_id,
210
- "original": text,
211
- "translation": translation,
212
- "dialect": dialect,
213
- "id": str(uuid.uuid4())[:8]
214
- }
215
  self.live_rooms[room_code].append(msg)
216
  return json.dumps({"status": "success"})
217
 
218
- # 🟢 NEW: Allows device B to pull messages sent by device A
219
  def api_remote_poll(self, room_code, last_index):
220
- if room_code not in self.live_rooms:
221
- return json.dumps([])
222
-
223
  idx = int(last_index)
224
- new_msgs = self.live_rooms[room_code][idx:]
225
- return json.dumps(new_msgs)
226
-
227
  # ==========================================
228
  # SOCIOLINGUISTIC PIPELINE
229
  # ==========================================
@@ -1196,20 +1269,30 @@ class AgentUX:
1196
  api_name="translate_peer"
1197
  )
1198
 
1199
- # 🟢 NEW: Dialect Relay (Device-to-Device) Endpoints
 
 
 
 
 
 
 
 
 
 
 
 
 
1200
  api_btn_relay_send = gr.Button()
1201
  api_relay_room = gr.Textbox()
1202
- api_relay_sender = gr.Textbox()
1203
  api_relay_text = gr.Textbox()
1204
- api_relay_dialect = gr.Textbox()
1205
  api_relay_out = gr.Textbox()
1206
- api_btn_relay_send.click(fn=self.api_remote_eval_and_send, inputs=[api_relay_room, api_relay_sender, api_relay_text, api_relay_dialect], outputs=[api_relay_out], api_name="relay_send")
1207
 
1208
  api_btn_relay_poll = gr.Button()
1209
- api_poll_room = gr.Textbox()
1210
  api_poll_idx = gr.Number()
1211
- api_poll_out = gr.Textbox()
1212
- api_btn_relay_poll.click(fn=self.api_remote_poll, inputs=[api_poll_room, api_poll_idx], outputs=[api_poll_out], api_name="relay_poll")
1213
 
1214
  # 7. Secure Feedback Endpoint
1215
  api_fb_btn = gr.Button()
 
61
  self.last_audio_path = None
62
  self.last_pending_count = 0
63
 
64
+ # 🟢 NEW: Matchmaking & Live Room Memory
65
+ self.waiting_pool = [] # List of operators looking for a match
66
+ self.active_matches = {} # Maps Operator ID -> Room Code
67
+ self.live_rooms = {} # Stores the actual chat logs per room
68
 
69
  self.alert_sound = None
70
 
 
181
  print(f"Peer Translation Error: {e}")
182
  return text # Fallback to original text if API fails
183
 
184
+ # ==========================================
185
+ # DIALECT RELAY (MATCHMAKING & CHAT)
186
+ # ==========================================
187
+ def api_join_queue(self, operator_id, dialect):
188
+ import uuid, json, time
189
+ # Clean up any old ghost matches
190
+ if operator_id in self.active_matches:
191
+ del self.active_matches[operator_id]
192
+
193
+ # 1. Check if someone else is waiting
194
+ partner = None
195
+ for p in self.waiting_pool:
196
+ if p['operator_id'] != operator_id:
197
+ partner = p
198
+ break
199
+
200
+ if partner:
201
+ # 2. Match found! Create a room.
202
+ self.waiting_pool.remove(partner)
203
+ room_id = f"FREQ-{uuid.uuid4().hex[:6].upper()}"
204
+
205
+ self.active_matches[operator_id] = {"room_id": room_id, "partner_dialect": partner['dialect']}
206
+ self.active_matches[partner['operator_id']] = {"room_id": room_id, "partner_dialect": dialect}
207
+ self.live_rooms[room_id] = []
208
+
209
+ return json.dumps({"status": "matched", "room_id": room_id, "partner_dialect": partner['dialect']})
210
+ else:
211
+ # 3. No one is waiting. Add self to pool.
212
+ self.waiting_pool = [p for p in self.waiting_pool if p['operator_id'] != operator_id] # Prevent duplicates
213
+ self.waiting_pool.append({"operator_id": operator_id, "dialect": dialect, "time": time.time()})
214
+ return json.dumps({"status": "waiting"})
215
+
216
+ def api_check_match(self, operator_id):
217
+ import json
218
+ if operator_id in self.active_matches:
219
+ match = self.active_matches[operator_id]
220
+ return json.dumps({"status": "matched", "room_id": match["room_id"], "partner_dialect": match["partner_dialect"]})
221
+ return json.dumps({"status": "waiting"})
222
 
223
+ def api_leave_queue(self, operator_id):
224
+ import json
225
+ self.waiting_pool = [p for p in self.waiting_pool if p['operator_id'] != operator_id]
226
+ if operator_id in self.active_matches:
227
+ del self.active_matches[operator_id]
228
+ return json.dumps({"status": "left"})
229
+
230
+ def api_remote_eval_and_send(self, room_code, sender_id, text, source_dialect, target_dialect):
231
+ import json, os, re, glob, uuid, threading
232
+ import pandas as pd
233
+
234
+ if not text: return json.dumps({"status": "error", "msg": "Empty text"})
235
 
236
  translation = ""
237
+ needs_help = False
238
+
239
+ # 1. Forward Lookup (Understand Source)
240
+ eval_result = self.brain.search_local_dataset(text)
241
+ if not eval_result: eval_result = self.brain.search_personas(text)
242
+ standard_meaning = eval_result.get("clarification", text) if eval_result else text
243
+
244
+ if standard_meaning.lower() != text.lower():
245
+ needs_help = True
246
 
247
+ # 2. Reverse Lookup (Translate to Target)
248
+ target_utterance = None
249
+ if target_dialect and target_dialect != "General English" and needs_help:
250
+ try:
251
+ if hasattr(self.brain, 'config') and os.path.exists(self.brain.config.DATASET_DIR):
252
+ target_file = os.path.join(self.brain.config.DATASET_DIR, f"{target_dialect}.csv")
253
+ if os.path.exists(target_file):
254
+ df = pd.read_csv(target_file)
255
+ if 'Clarification' in df.columns and 'Utterance' in df.columns:
256
+ pattern = re.compile(f"^{re.escape(standard_meaning)}$", re.IGNORECASE)
257
+ matches = df[df['Clarification'].astype(str).str.match(pattern, na=False)]
258
+ if not matches.empty: target_utterance = str(matches.iloc[0]['Utterance'])
259
+ except Exception as e: pass
260
+
261
+ # 3. Gemini Fallback
262
+ if not target_utterance:
263
+ prompt = f"""
264
+ Analyze this utterance from a speaker of '{source_dialect}': "{text}"
265
+ Is this mutually intelligible Standard English?
266
+ If YES, return EXACTLY: {{"needs_help": false, "translation": ""}}
267
+ If NO, translate it to '{target_dialect}' and return EXACTLY: {{"needs_help": true, "translation": "The translated text here"}}
268
+ """
269
+ try:
270
+ res = self.brain.gemini_manager.client.models.generate_content(model='gemini-2.0-flash', contents=prompt)
271
+ clean_json = res.text.replace("```json", "").replace("```", "").strip()
272
+ data = json.loads(clean_json)
273
+ if data.get("needs_help"): translation = data.get("translation", "")
274
+ except: translation = standard_meaning if needs_help else ""
275
+ else:
276
+ translation = target_utterance
277
+
278
+ # 🟢 THE DATA COLLECTION HOOK (Silent Background Saving)
279
+ clarification_to_save = translation if needs_help and translation else standard_meaning
280
+ try:
281
+ threading.Thread(
282
+ target=self.check_and_submit_logic,
283
+ args=(text, source_dialect, "", clarification_to_save, "Conversational", "Relay Peer-to-Peer Chat", "Automated Relay Extraction", "Game: Relay Pair", "AI Relay Bouncer", sender_id, None, False),
284
+ daemon=True
285
+ ).start()
286
+ except Exception as save_err:
287
+ print(f"Data Hook Error: {save_err}")
288
 
289
+ # 4. Route to Room
290
+ if room_code not in self.live_rooms: self.live_rooms[room_code] = []
291
+ msg = {"sender": sender_id, "original": text, "translation": translation, "dialect": source_dialect, "target_dialect": target_dialect, "id": str(uuid.uuid4())[:8]}
 
 
 
 
292
  self.live_rooms[room_code].append(msg)
293
  return json.dumps({"status": "success"})
294
 
 
295
  def api_remote_poll(self, room_code, last_index):
296
+ import json
297
+ if room_code not in self.live_rooms: return json.dumps([])
 
298
  idx = int(last_index)
299
+ return json.dumps(self.live_rooms[room_code][idx:])
 
 
300
  # ==========================================
301
  # SOCIOLINGUISTIC PIPELINE
302
  # ==========================================
 
1269
  api_name="translate_peer"
1270
  )
1271
 
1272
+ # 🟢 NEW: Dialect Relay Endpoints
1273
+ api_btn_join = gr.Button()
1274
+ api_q_op = gr.Textbox()
1275
+ api_q_dialect = gr.Textbox()
1276
+ api_q_out = gr.Textbox()
1277
+ api_btn_join.click(fn=self.api_join_queue, inputs=[api_q_op, api_q_dialect], outputs=[api_q_out], api_name="join_queue")
1278
+
1279
+ api_btn_check = gr.Button()
1280
+ api_c_out = gr.Textbox()
1281
+ api_btn_check.click(fn=self.api_check_match, inputs=[api_q_op], outputs=[api_c_out], api_name="check_match")
1282
+
1283
+ api_btn_leave = gr.Button()
1284
+ api_btn_leave.click(fn=self.api_leave_queue, inputs=[api_q_op], outputs=[api_c_out], api_name="leave_queue")
1285
+
1286
  api_btn_relay_send = gr.Button()
1287
  api_relay_room = gr.Textbox()
 
1288
  api_relay_text = gr.Textbox()
1289
+ api_relay_target = gr.Textbox()
1290
  api_relay_out = gr.Textbox()
1291
+ api_btn_relay_send.click(fn=self.api_remote_eval_and_send, inputs=[api_relay_room, api_q_op, api_relay_text, api_q_dialect, api_relay_target], outputs=[api_relay_out], api_name="relay_send")
1292
 
1293
  api_btn_relay_poll = gr.Button()
 
1294
  api_poll_idx = gr.Number()
1295
+ api_btn_relay_poll.click(fn=self.api_remote_poll, inputs=[api_relay_room, api_poll_idx], outputs=[api_c_out], api_name="relay_poll")
 
1296
 
1297
  # 7. Secure Feedback Endpoint
1298
  api_fb_btn = gr.Button()