Spaces:
Running
Create bridge_agent.py
Browse filesThe frustration is completely understandable—you shouldn't have to downgrade your entire system just because one library is fighting with another.
The "Headless" architecture we were using (where one ux_agent.py tries to be both an Admin Dashboard and an API for React) is the root cause of these conflicts.
The Pro Solution is to split the interface into two distinct agents. This is a standard Microservices pattern:
BridgeAgent (New): A dedicated, lightweight API agent strictly for your React Game. It has no visual interface, uses no themes, and purely handles data.
DashboardAgent (Old UX): Your Admin Dashboard. It runs separately and can use whatever fancy UI libraries it wants without breaking the React connection.
This allows us to Upgrade back to the latest Python and Gradio versions.
- src/bridge_agent.py +94 -0
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import json
|
| 3 |
+
import os
|
| 4 |
+
import shutil
|
| 5 |
+
import pandas as pd
|
| 6 |
+
import datetime
|
| 7 |
+
|
| 8 |
+
class BridgeAgent:
|
| 9 |
+
def __init__(self, input_agent, brain_agent, trust_agent):
|
| 10 |
+
print("🌉 Bridge Agent (React API) Online.")
|
| 11 |
+
self.input = input_agent
|
| 12 |
+
self.brain = brain_agent
|
| 13 |
+
self.trust = trust_agent
|
| 14 |
+
self.PENDING_FILE = "pending_reviews.csv"
|
| 15 |
+
|
| 16 |
+
# --- 1. MISSION GENERATOR ---
|
| 17 |
+
def api_generate_mission(self, context):
|
| 18 |
+
"""React calls this to get the mission text."""
|
| 19 |
+
mission_data = self.brain.generate_mission(context)
|
| 20 |
+
return json.dumps(mission_data)
|
| 21 |
+
|
| 22 |
+
# --- 2. DIALECT LIST ---
|
| 23 |
+
def api_get_dialects(self):
|
| 24 |
+
"""React calls this to populate the dropdown."""
|
| 25 |
+
try:
|
| 26 |
+
if not os.path.exists(self.brain.config.DATASET_DIR): return []
|
| 27 |
+
csv_files = [f for f in os.listdir(self.brain.config.DATASET_DIR) if f.endswith(".csv")]
|
| 28 |
+
return sorted([f.replace(".csv", "") for f in csv_files])
|
| 29 |
+
except: return []
|
| 30 |
+
|
| 31 |
+
# --- 3. SUBMISSION HANDLER ---
|
| 32 |
+
def api_submit_entry(self, orig, d_drop, d_new, clar, tone, context, user_key, audio_blob):
|
| 33 |
+
"""React calls this to submit a new recording."""
|
| 34 |
+
try:
|
| 35 |
+
# 1. Handle Audio
|
| 36 |
+
saved_path = "No Audio"
|
| 37 |
+
if audio_blob:
|
| 38 |
+
os.makedirs("saved_audio", exist_ok=True)
|
| 39 |
+
timestamp = int(pd.Timestamp.now().timestamp())
|
| 40 |
+
saved_path = f"saved_audio/mobile_{timestamp}.wav"
|
| 41 |
+
shutil.copy(audio_blob, saved_path)
|
| 42 |
+
|
| 43 |
+
# Transcribe if needed
|
| 44 |
+
if not orig or orig == "":
|
| 45 |
+
segments = self.input.transcribe(saved_path)
|
| 46 |
+
orig = " ".join([s["text"] for s in segments]).strip()
|
| 47 |
+
|
| 48 |
+
# 2. Determine Dialect Name
|
| 49 |
+
final_dialect = d_new.strip() if d_drop == "+ Add New Dialect" else d_drop
|
| 50 |
+
|
| 51 |
+
# 3. Save to Pending Queue
|
| 52 |
+
new_row = {
|
| 53 |
+
"User": user_key, "Utterance": orig, "Dialect": final_dialect,
|
| 54 |
+
"Clarification": clar, "Tone": tone, "Context": context,
|
| 55 |
+
"Audio": saved_path, "Timestamp": str(datetime.datetime.now())
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
df = pd.DataFrame([new_row])
|
| 59 |
+
if os.path.exists(self.PENDING_FILE):
|
| 60 |
+
df.to_csv(self.PENDING_FILE, mode='a', header=False, index=False)
|
| 61 |
+
else:
|
| 62 |
+
df.to_csv(self.PENDING_FILE, index=False)
|
| 63 |
+
|
| 64 |
+
return f"✅ Received: {orig[:20]}..."
|
| 65 |
+
except Exception as e:
|
| 66 |
+
print(f"Bridge Error: {e}")
|
| 67 |
+
return f"❌ Error: {str(e)}"
|
| 68 |
+
|
| 69 |
+
def launch(self):
|
| 70 |
+
"""
|
| 71 |
+
This launches a specialized, invisible Gradio app just for the API.
|
| 72 |
+
"""
|
| 73 |
+
with gr.Blocks() as api:
|
| 74 |
+
# Define the Inputs/Outputs invisible to the human eye, but visible to React
|
| 75 |
+
in_txt = gr.Textbox()
|
| 76 |
+
in_aud = gr.Audio(type="filepath")
|
| 77 |
+
out_json = gr.JSON()
|
| 78 |
+
out_txt = gr.Textbox()
|
| 79 |
+
|
| 80 |
+
# 1. Mission Bridge
|
| 81 |
+
btn_miss = gr.Button("Mission")
|
| 82 |
+
btn_miss.click(self.api_generate_mission, [in_txt], [out_json], api_name="generate_mission")
|
| 83 |
+
|
| 84 |
+
# 2. Dialect Bridge
|
| 85 |
+
btn_list = gr.Button("List")
|
| 86 |
+
btn_list.click(self.api_get_dialects, [], [out_json], api_name="get_dialects")
|
| 87 |
+
|
| 88 |
+
# 3. Submit Bridge (8 Arguments)
|
| 89 |
+
# Args: orig, d_drop, d_new, clar, tone, context, user_key, audio
|
| 90 |
+
args = [gr.Textbox() for _ in range(7)] + [in_aud]
|
| 91 |
+
btn_sub = gr.Button("Submit")
|
| 92 |
+
btn_sub.click(self.api_submit_entry, args, [out_txt], api_name="check_and_submit_logic")
|
| 93 |
+
|
| 94 |
+
return api
|