Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -293,6 +293,244 @@ class OpenFloorAgentServer:
|
|
293 |
print(f"🚀 OpenFloor agent '{self.agent.manifest.identification.conversationalName}' started on port {self.port}")
|
294 |
return True
|
295 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
296 |
def get_session_id(request: gr.Request = None) -> str:
|
297 |
"""Generate or retrieve session ID"""
|
298 |
if request and hasattr(request, 'session_hash'):
|
|
|
293 |
print(f"🚀 OpenFloor agent '{self.agent.manifest.identification.conversationalName}' started on port {self.port}")
|
294 |
return True
|
295 |
|
296 |
+
|
297 |
+
class OpenFloorManager:
|
298 |
+
"""Central floor manager for coordinating all OpenFloor agents"""
|
299 |
+
|
300 |
+
def __init__(self, port: int = 7860):
|
301 |
+
self.port = port
|
302 |
+
self.agent_registry = {} # speakerUri -> agent info
|
303 |
+
self.active_conversations = {} # conversation_id -> conversation state
|
304 |
+
self.visual_callback = None
|
305 |
+
|
306 |
+
def register_agent(self, manifest: Manifest, agent_url: str):
|
307 |
+
"""Register an agent with the floor manager"""
|
308 |
+
speaker_uri = manifest.identification.speakerUri
|
309 |
+
self.agent_registry[speaker_uri] = {
|
310 |
+
'manifest': manifest,
|
311 |
+
'url': agent_url,
|
312 |
+
'status': 'available',
|
313 |
+
'last_seen': datetime.now()
|
314 |
+
}
|
315 |
+
print(f"🏛️ Floor Manager: Registered agent {manifest.identification.conversationalName}")
|
316 |
+
|
317 |
+
def discover_agents(self) -> List[Manifest]:
|
318 |
+
"""Return manifests of all registered agents"""
|
319 |
+
return [info['manifest'] for info in self.agent_registry.values()]
|
320 |
+
|
321 |
+
def create_conversation(self, initial_participants: List[str] = None) -> str:
|
322 |
+
"""Create a new conversation with optional initial participants"""
|
323 |
+
conversation_id = f"conv:{uuid.uuid4()}"
|
324 |
+
|
325 |
+
self.active_conversations[conversation_id] = {
|
326 |
+
'id': conversation_id,
|
327 |
+
'participants': initial_participants or [],
|
328 |
+
'messages': [],
|
329 |
+
'created_at': datetime.now(),
|
330 |
+
'status': 'active'
|
331 |
+
}
|
332 |
+
|
333 |
+
print(f"🏛️ Floor Manager: Created conversation {conversation_id}")
|
334 |
+
return conversation_id
|
335 |
+
|
336 |
+
def invite_agent_to_conversation(self, conversation_id: str, target_speaker_uri: str,
|
337 |
+
inviting_speaker_uri: str) -> bool:
|
338 |
+
"""Send InviteEvent to an agent"""
|
339 |
+
if conversation_id not in self.active_conversations:
|
340 |
+
return False
|
341 |
+
|
342 |
+
if target_speaker_uri not in self.agent_registry:
|
343 |
+
return False
|
344 |
+
|
345 |
+
conversation = self.active_conversations[conversation_id]
|
346 |
+
target_agent = self.agent_registry[target_speaker_uri]
|
347 |
+
|
348 |
+
# Create proper InviteEvent
|
349 |
+
invite_envelope = Envelope(
|
350 |
+
conversation=Conversation(id=conversation_id),
|
351 |
+
sender=Sender(speakerUri=inviting_speaker_uri),
|
352 |
+
events=[
|
353 |
+
InviteEvent(
|
354 |
+
to=To(speakerUri=target_speaker_uri),
|
355 |
+
parameters={
|
356 |
+
'conversation_id': conversation_id,
|
357 |
+
'invited_by': inviting_speaker_uri,
|
358 |
+
'invitation_message': f"You are invited to join the expert analysis discussion"
|
359 |
+
}
|
360 |
+
)
|
361 |
+
]
|
362 |
+
)
|
363 |
+
|
364 |
+
# Send invitation to target agent
|
365 |
+
response = self._send_to_agent(target_agent['url'], invite_envelope)
|
366 |
+
|
367 |
+
if response:
|
368 |
+
# Add to conversation participants
|
369 |
+
if target_speaker_uri not in conversation['participants']:
|
370 |
+
conversation['participants'].append(target_speaker_uri)
|
371 |
+
self._update_visual_state(conversation_id)
|
372 |
+
return True
|
373 |
+
|
374 |
+
return False
|
375 |
+
|
376 |
+
def route_message(self, envelope: Envelope) -> bool:
|
377 |
+
"""Route message to appropriate recipients"""
|
378 |
+
conversation_id = envelope.conversation.id
|
379 |
+
|
380 |
+
if conversation_id not in self.active_conversations:
|
381 |
+
return False
|
382 |
+
|
383 |
+
conversation = self.active_conversations[conversation_id]
|
384 |
+
|
385 |
+
# Process each event in the envelope
|
386 |
+
for event in envelope.events:
|
387 |
+
if hasattr(event, 'to') and event.to:
|
388 |
+
# Directed message - send to specific agent
|
389 |
+
target_uri = event.to.speakerUri
|
390 |
+
if target_uri in self.agent_registry:
|
391 |
+
target_agent = self.agent_registry[target_uri]
|
392 |
+
self._send_to_agent(target_agent['url'], envelope)
|
393 |
+
else:
|
394 |
+
# Broadcast to all conversation participants
|
395 |
+
for participant_uri in conversation['participants']:
|
396 |
+
if participant_uri != envelope.sender.speakerUri: # Don't echo back
|
397 |
+
if participant_uri in self.agent_registry:
|
398 |
+
participant_agent = self.agent_registry[participant_uri]
|
399 |
+
self._send_to_agent(participant_agent['url'], envelope)
|
400 |
+
|
401 |
+
# Store message in conversation history
|
402 |
+
conversation['messages'].append({
|
403 |
+
'envelope': envelope,
|
404 |
+
'timestamp': datetime.now()
|
405 |
+
})
|
406 |
+
|
407 |
+
self._update_visual_state(conversation_id)
|
408 |
+
return True
|
409 |
+
|
410 |
+
def _send_to_agent(self, agent_url: str, envelope: Envelope) -> bool:
|
411 |
+
"""Send envelope to specific agent"""
|
412 |
+
try:
|
413 |
+
response = requests.post(
|
414 |
+
f"{agent_url}/openfloor/conversation",
|
415 |
+
json=json.loads(envelope.to_json()),
|
416 |
+
headers={'Content-Type': 'application/json'},
|
417 |
+
timeout=30
|
418 |
+
)
|
419 |
+
return response.status_code == 200
|
420 |
+
except Exception as e:
|
421 |
+
print(f"🏛️ Floor Manager: Error sending to {agent_url}: {e}")
|
422 |
+
return False
|
423 |
+
|
424 |
+
def _update_visual_state(self, conversation_id: str):
|
425 |
+
"""Update visual interface based on conversation state"""
|
426 |
+
if self.visual_callback and conversation_id in self.active_conversations:
|
427 |
+
conversation = self.active_conversations[conversation_id]
|
428 |
+
|
429 |
+
# Convert to visual format
|
430 |
+
participants = []
|
431 |
+
messages = []
|
432 |
+
|
433 |
+
for participant_uri in conversation['participants']:
|
434 |
+
if participant_uri in self.agent_registry:
|
435 |
+
agent_info = self.agent_registry[participant_uri]
|
436 |
+
participants.append(agent_info['manifest'].identification.conversationalName)
|
437 |
+
|
438 |
+
for msg_info in conversation['messages']:
|
439 |
+
envelope = msg_info['envelope']
|
440 |
+
sender_uri = envelope.sender.speakerUri
|
441 |
+
|
442 |
+
if sender_uri in self.agent_registry:
|
443 |
+
sender_name = self.agent_registry[sender_uri]['manifest'].identification.conversationalName
|
444 |
+
|
445 |
+
# Extract message content
|
446 |
+
for event in envelope.events:
|
447 |
+
if hasattr(event, 'eventType') and event.eventType == 'utterance':
|
448 |
+
# Extract text from dialog event
|
449 |
+
dialog_event = event.parameters.get('dialogEvent')
|
450 |
+
if dialog_event:
|
451 |
+
features = dialog_event.get('features', {})
|
452 |
+
text_feature = features.get('text', {})
|
453 |
+
tokens = text_feature.get('tokens', [])
|
454 |
+
text = ' '.join([token.get('value', '') for token in tokens])
|
455 |
+
|
456 |
+
messages.append({
|
457 |
+
'speaker': sender_name,
|
458 |
+
'text': text,
|
459 |
+
'timestamp': msg_info['timestamp'].strftime('%H:%M:%S')
|
460 |
+
})
|
461 |
+
|
462 |
+
self.visual_callback({
|
463 |
+
'participants': participants,
|
464 |
+
'messages': messages,
|
465 |
+
'currentSpeaker': None,
|
466 |
+
'thinking': [],
|
467 |
+
'showBubbles': participants,
|
468 |
+
'avatarImages': avatar_images
|
469 |
+
})
|
470 |
+
|
471 |
+
def set_visual_callback(self, callback):
|
472 |
+
"""Set callback for visual updates"""
|
473 |
+
self.visual_callback = callback
|
474 |
+
|
475 |
+
def start_floor_manager_service(self):
|
476 |
+
"""Start the floor manager HTTP service"""
|
477 |
+
from flask import Flask, request, jsonify
|
478 |
+
|
479 |
+
app = Flask("openfloor-manager")
|
480 |
+
|
481 |
+
@app.route('/openfloor/discover', methods=['GET'])
|
482 |
+
def discover_agents():
|
483 |
+
"""Return list of available agent manifests"""
|
484 |
+
manifests = [json.loads(manifest.to_json()) for manifest in self.discover_agents()]
|
485 |
+
return jsonify(manifests)
|
486 |
+
|
487 |
+
@app.route('/openfloor/conversation', methods=['POST'])
|
488 |
+
def handle_conversation():
|
489 |
+
"""Handle incoming conversation messages"""
|
490 |
+
try:
|
491 |
+
envelope_data = request.get_json()
|
492 |
+
envelope = Envelope.from_json(json.dumps(envelope_data))
|
493 |
+
|
494 |
+
success = self.route_message(envelope)
|
495 |
+
|
496 |
+
if success:
|
497 |
+
return jsonify({'status': 'routed'})
|
498 |
+
else:
|
499 |
+
return jsonify({'error': 'Failed to route message'}), 400
|
500 |
+
|
501 |
+
except Exception as e:
|
502 |
+
return jsonify({'error': str(e)}), 500
|
503 |
+
|
504 |
+
@app.route('/openfloor/invite', methods=['POST'])
|
505 |
+
def invite_agent():
|
506 |
+
"""Handle agent invitation requests"""
|
507 |
+
try:
|
508 |
+
data = request.get_json()
|
509 |
+
conversation_id = data['conversation_id']
|
510 |
+
target_speaker_uri = data['target_speaker_uri']
|
511 |
+
inviting_speaker_uri = data['inviting_speaker_uri']
|
512 |
+
|
513 |
+
success = self.invite_agent_to_conversation(
|
514 |
+
conversation_id, target_speaker_uri, inviting_speaker_uri
|
515 |
+
)
|
516 |
+
|
517 |
+
return jsonify({'success': success})
|
518 |
+
|
519 |
+
except Exception as e:
|
520 |
+
return jsonify({'error': str(e)}), 500
|
521 |
+
|
522 |
+
# Start server in background thread
|
523 |
+
import threading
|
524 |
+
server_thread = threading.Thread(
|
525 |
+
target=lambda: app.run(host='localhost', port=self.port + 100, debug=False)
|
526 |
+
)
|
527 |
+
server_thread.daemon = True
|
528 |
+
server_thread.start()
|
529 |
+
|
530 |
+
print(f"🏛️ OpenFloor Manager started on port {self.port + 100}")
|
531 |
+
return True
|
532 |
+
|
533 |
+
|
534 |
def get_session_id(request: gr.Request = None) -> str:
|
535 |
"""Generate or retrieve session ID"""
|
536 |
if request and hasattr(request, 'session_hash'):
|