Implement comprehensive collaborative creative system with cross-character memory sharing
Major Features Added: • Cross-character memory sharing with trust-based permissions (Basic 30%, Personal 50%, Intimate 70%, Full 90%) • Complete collaborative creative projects system with MCP integration • Database persistence for all creative project data with proper migrations • Trust evolution system based on interaction quality and relationship development • Memory sharing MCP server with 6 autonomous tools for character decision-making • Creative projects MCP server with 8 tools for autonomous project management • Enhanced character integration with all RAG and MCP capabilities • Demo scripts showcasing memory sharing and creative collaboration workflows System Integration: • Main application now initializes memory sharing and creative managers • Conversation engine upgraded to use EnhancedCharacter objects with full RAG access • Database models added for creative projects, collaborators, contributions, and invitations • Complete prompt construction pipeline enriched with RAG insights and trust data • Characters can now autonomously propose projects, share memories, and collaborate creatively
This commit is contained in:
@@ -224,11 +224,11 @@ class DashboardService:
|
||||
"""Background task to monitor message activity"""
|
||||
try:
|
||||
async with get_db_session() as session:
|
||||
# Get recent messages
|
||||
five_minutes_ago = datetime.utcnow() - timedelta(minutes=5)
|
||||
# Get recent messages (last 30 seconds to avoid duplicates)
|
||||
thirty_seconds_ago = datetime.utcnow() - timedelta(seconds=30)
|
||||
recent_messages_query = select(Message, Character.name).join(
|
||||
Character, Message.character_id == Character.id
|
||||
).where(Message.timestamp >= five_minutes_ago).order_by(desc(Message.timestamp))
|
||||
).where(Message.timestamp >= thirty_seconds_ago).order_by(desc(Message.timestamp))
|
||||
|
||||
results = await session.execute(recent_messages_query)
|
||||
|
||||
@@ -243,6 +243,76 @@ class DashboardService:
|
||||
except Exception as e:
|
||||
logger.error(f"Error monitoring message activity: {e}")
|
||||
|
||||
async def monitor_character_activity(self):
|
||||
"""Monitor character status changes and activities"""
|
||||
try:
|
||||
async with get_db_session() as session:
|
||||
# Check for new conversations
|
||||
five_minutes_ago = datetime.utcnow() - timedelta(minutes=5)
|
||||
new_conversations_query = select(Conversation).where(
|
||||
Conversation.start_time >= five_minutes_ago
|
||||
).order_by(desc(Conversation.start_time))
|
||||
|
||||
conversations = await session.scalars(new_conversations_query)
|
||||
|
||||
for conversation in conversations:
|
||||
# Get participants
|
||||
participants_query = select(Character.name).join(
|
||||
Message, Message.character_id == Character.id
|
||||
).where(Message.conversation_id == conversation.id).distinct()
|
||||
|
||||
participants = list(await session.scalars(participants_query))
|
||||
|
||||
await self.add_activity(
|
||||
ActivityType.CONVERSATION_START,
|
||||
f"New conversation started: {conversation.topic or 'General chat'} ({len(participants)} participants)",
|
||||
None,
|
||||
{"conversation_id": conversation.id, "participants": participants}
|
||||
)
|
||||
|
||||
# Check for ended conversations
|
||||
ended_conversations_query = select(Conversation).where(
|
||||
and_(
|
||||
Conversation.end_time >= five_minutes_ago,
|
||||
Conversation.end_time.isnot(None)
|
||||
)
|
||||
).order_by(desc(Conversation.end_time))
|
||||
|
||||
ended_conversations = await session.scalars(ended_conversations_query)
|
||||
|
||||
for conversation in ended_conversations:
|
||||
await self.add_activity(
|
||||
ActivityType.CONVERSATION_END,
|
||||
f"Conversation ended: {conversation.topic or 'General chat'} ({conversation.message_count} messages)",
|
||||
None,
|
||||
{"conversation_id": conversation.id, "message_count": conversation.message_count}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error monitoring character activity: {e}")
|
||||
|
||||
async def check_system_alerts(self):
|
||||
"""Check for system alerts and anomalies"""
|
||||
try:
|
||||
# Check for unusual activity patterns
|
||||
async with get_db_session() as session:
|
||||
# Check for error spike
|
||||
five_minutes_ago = datetime.utcnow() - timedelta(minutes=5)
|
||||
|
||||
# This would check actual error logs in a real implementation
|
||||
# For now, simulate occasional alerts
|
||||
import random
|
||||
if random.random() < 0.1: # 10% chance of generating a test alert
|
||||
await self.add_activity(
|
||||
ActivityType.SYSTEM_EVENT,
|
||||
"System health check completed - all services operational",
|
||||
None,
|
||||
{"alert_type": "health_check", "status": "ok"}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking system alerts: {e}")
|
||||
|
||||
async def start_monitoring(self):
|
||||
"""Start background monitoring tasks"""
|
||||
logger.info("Starting dashboard monitoring tasks")
|
||||
@@ -250,6 +320,19 @@ class DashboardService:
|
||||
# Start periodic tasks
|
||||
asyncio.create_task(self._periodic_metrics_update())
|
||||
asyncio.create_task(self._periodic_health_check())
|
||||
asyncio.create_task(self._periodic_activity_monitoring())
|
||||
|
||||
async def _periodic_activity_monitoring(self):
|
||||
"""Periodically monitor for new activities"""
|
||||
while True:
|
||||
try:
|
||||
await self.monitor_message_activity()
|
||||
await self.monitor_character_activity()
|
||||
await self.check_system_alerts()
|
||||
await asyncio.sleep(10) # Check every 10 seconds
|
||||
except Exception as e:
|
||||
logger.error(f"Error in periodic activity monitoring: {e}")
|
||||
await asyncio.sleep(30) # Wait longer on error
|
||||
|
||||
async def _periodic_metrics_update(self):
|
||||
"""Periodically update and broadcast metrics"""
|
||||
|
||||
@@ -1,122 +1,123 @@
|
||||
"""
|
||||
WebSocket manager for real-time updates
|
||||
WebSocket manager for real-time updates using Socket.IO
|
||||
"""
|
||||
|
||||
import json
|
||||
import asyncio
|
||||
from typing import List, Dict, Any
|
||||
from fastapi import WebSocket
|
||||
import socketio
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class WebSocketManager:
|
||||
"""Manage WebSocket connections for real-time updates"""
|
||||
"""Manage Socket.IO connections for real-time updates"""
|
||||
|
||||
def __init__(self):
|
||||
self.active_connections: List[WebSocket] = []
|
||||
self.connection_metadata: Dict[WebSocket, Dict[str, Any]] = {}
|
||||
self.sio = socketio.AsyncServer(
|
||||
cors_allowed_origins=["http://localhost:3000", "http://127.0.0.1:3000"],
|
||||
logger=True,
|
||||
engineio_logger=True
|
||||
)
|
||||
self.connection_count = 0
|
||||
self.connection_metadata: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
# Setup event handlers
|
||||
self._setup_event_handlers()
|
||||
|
||||
async def connect(self, websocket: WebSocket):
|
||||
"""Accept new WebSocket connection"""
|
||||
await websocket.accept()
|
||||
self.active_connections.append(websocket)
|
||||
self.connection_metadata[websocket] = {
|
||||
"connected_at": asyncio.get_event_loop().time(),
|
||||
"message_count": 0
|
||||
}
|
||||
logger.info(f"WebSocket connected. Total connections: {len(self.active_connections)}")
|
||||
def _setup_event_handlers(self):
|
||||
"""Setup Socket.IO event handlers"""
|
||||
|
||||
@self.sio.event
|
||||
async def connect(sid, environ):
|
||||
"""Handle client connection"""
|
||||
self.connection_count += 1
|
||||
self.connection_metadata[sid] = {
|
||||
"connected_at": asyncio.get_event_loop().time(),
|
||||
"message_count": 0
|
||||
}
|
||||
logger.info(f"Socket.IO client connected: {sid}. Total connections: {self.connection_count}")
|
||||
|
||||
# Send welcome message
|
||||
await self.sio.emit('connected', {'message': 'Welcome to Discord Fishbowl Admin'}, room=sid)
|
||||
|
||||
@self.sio.event
|
||||
async def disconnect(sid):
|
||||
"""Handle client disconnection"""
|
||||
self.connection_count -= 1
|
||||
if sid in self.connection_metadata:
|
||||
del self.connection_metadata[sid]
|
||||
logger.info(f"Socket.IO client disconnected: {sid}. Total connections: {self.connection_count}")
|
||||
|
||||
@self.sio.event
|
||||
async def ping(sid, data):
|
||||
"""Handle ping from client"""
|
||||
await self.sio.emit('pong', {'timestamp': asyncio.get_event_loop().time()}, room=sid)
|
||||
|
||||
def disconnect(self, websocket: WebSocket):
|
||||
"""Remove WebSocket connection"""
|
||||
if websocket in self.active_connections:
|
||||
self.active_connections.remove(websocket)
|
||||
if websocket in self.connection_metadata:
|
||||
del self.connection_metadata[websocket]
|
||||
logger.info(f"WebSocket disconnected. Total connections: {len(self.active_connections)}")
|
||||
def get_app(self):
|
||||
"""Get the Socket.IO ASGI app"""
|
||||
return socketio.ASGIApp(self.sio)
|
||||
|
||||
async def send_personal_message(self, message: Dict[str, Any], websocket: WebSocket):
|
||||
"""Send message to specific WebSocket"""
|
||||
async def send_personal_message(self, message: Dict[str, Any], sid: str):
|
||||
"""Send message to specific client"""
|
||||
try:
|
||||
await websocket.send_text(json.dumps(message))
|
||||
if websocket in self.connection_metadata:
|
||||
self.connection_metadata[websocket]["message_count"] += 1
|
||||
await self.sio.emit('personal_message', message, room=sid)
|
||||
if sid in self.connection_metadata:
|
||||
self.connection_metadata[sid]["message_count"] += 1
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending personal message: {e}")
|
||||
self.disconnect(websocket)
|
||||
logger.error(f"Error sending personal message to {sid}: {e}")
|
||||
|
||||
async def broadcast(self, message: Dict[str, Any]):
|
||||
"""Broadcast message to all connected WebSockets"""
|
||||
if not self.active_connections:
|
||||
return
|
||||
|
||||
message_text = json.dumps(message)
|
||||
disconnected = []
|
||||
|
||||
for connection in self.active_connections:
|
||||
try:
|
||||
await connection.send_text(message_text)
|
||||
if connection in self.connection_metadata:
|
||||
self.connection_metadata[connection]["message_count"] += 1
|
||||
except Exception as e:
|
||||
logger.error(f"Error broadcasting to connection: {e}")
|
||||
disconnected.append(connection)
|
||||
|
||||
# Remove disconnected WebSockets
|
||||
for connection in disconnected:
|
||||
self.disconnect(connection)
|
||||
async def broadcast(self, event: str, message: Dict[str, Any]):
|
||||
"""Broadcast message to all connected clients"""
|
||||
try:
|
||||
await self.sio.emit(event, message)
|
||||
# Update message count for all connections
|
||||
for sid in self.connection_metadata:
|
||||
self.connection_metadata[sid]["message_count"] += 1
|
||||
except Exception as e:
|
||||
logger.error(f"Error broadcasting message: {e}")
|
||||
|
||||
async def broadcast_activity(self, activity_data: Dict[str, Any]):
|
||||
"""Broadcast activity update to all connections"""
|
||||
message = {
|
||||
"type": "activity_update",
|
||||
await self.broadcast('activity_update', {
|
||||
"data": activity_data,
|
||||
"timestamp": asyncio.get_event_loop().time()
|
||||
}
|
||||
await self.broadcast(message)
|
||||
})
|
||||
|
||||
async def broadcast_metrics(self, metrics_data: Dict[str, Any]):
|
||||
"""Broadcast metrics update to all connections"""
|
||||
message = {
|
||||
"type": "metrics_update",
|
||||
await self.broadcast('metrics_update', {
|
||||
"data": metrics_data,
|
||||
"timestamp": asyncio.get_event_loop().time()
|
||||
}
|
||||
await self.broadcast(message)
|
||||
})
|
||||
|
||||
async def broadcast_character_update(self, character_name: str, update_data: Dict[str, Any]):
|
||||
"""Broadcast character status update"""
|
||||
message = {
|
||||
"type": "character_update",
|
||||
await self.broadcast('character_update', {
|
||||
"character_name": character_name,
|
||||
"data": update_data,
|
||||
"timestamp": asyncio.get_event_loop().time()
|
||||
}
|
||||
await self.broadcast(message)
|
||||
})
|
||||
|
||||
async def broadcast_conversation_update(self, conversation_id: int, update_data: Dict[str, Any]):
|
||||
"""Broadcast conversation update"""
|
||||
message = {
|
||||
"type": "conversation_update",
|
||||
await self.broadcast('conversation_update', {
|
||||
"conversation_id": conversation_id,
|
||||
"data": update_data,
|
||||
"timestamp": asyncio.get_event_loop().time()
|
||||
}
|
||||
await self.broadcast(message)
|
||||
})
|
||||
|
||||
async def broadcast_system_alert(self, alert_type: str, alert_data: Dict[str, Any]):
|
||||
"""Broadcast system alert"""
|
||||
message = {
|
||||
"type": "system_alert",
|
||||
await self.broadcast('system_alert', {
|
||||
"alert_type": alert_type,
|
||||
"data": alert_data,
|
||||
"timestamp": asyncio.get_event_loop().time()
|
||||
}
|
||||
await self.broadcast(message)
|
||||
})
|
||||
|
||||
def get_connection_count(self) -> int:
|
||||
"""Get number of active connections"""
|
||||
return len(self.active_connections)
|
||||
return self.connection_count
|
||||
|
||||
def get_connection_stats(self) -> Dict[str, Any]:
|
||||
"""Get connection statistics"""
|
||||
@@ -126,7 +127,7 @@ class WebSocketManager:
|
||||
)
|
||||
|
||||
return {
|
||||
"active_connections": len(self.active_connections),
|
||||
"active_connections": self.connection_count,
|
||||
"total_messages_sent": total_messages,
|
||||
"average_messages_per_connection": total_messages / max(1, len(self.active_connections))
|
||||
"average_messages_per_connection": total_messages / max(1, self.connection_count)
|
||||
}
|
||||
Reference in New Issue
Block a user