fix: improve maintenance and synchronize live conversations

This commit is contained in:
WangShen007 committed 2026-10-29 09:46:21 +08:00
1 parent bb541bd212
commit ef86a5d3af
297 files changed
+2983 -1274

No files matched your search

+44 -15
View File
@@ -14,10 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
import asyncio
import datetime
import json
import os
import re
from types import SimpleNamespace
from fastapi import APIRouter
@@ -50,7 +47,11 @@ from app.services.file_storage import resolve_public_url
from app.models.session_to_agent import ACTIVE_STATUSES, SessionStatus
from app.agents.transfer_agent import get_agent_availability_response
from app.repositories.agent import AgentRepository
from app.services.agent_rooms import is_org_inbox_room, may_join_org_inbox
from app.services.agent_rooms import (
CHAT_WATCH_PREFIX, ORG_CHAT_UPDATES_PREFIX, is_org_inbox_room,
may_join_chat_updates, may_join_org_inbox, may_watch_chat,
notify_conversation_changed,
)
from app.services.chat_notifications import notify_new_chat
from app.services.contact_capture import retain_unstored_email
from app.services.human_routing import (
@@ -637,6 +638,7 @@ async def handle_widget_chat(sid, data):
)
db.add(file_attachment)
db.commit()
await notify_conversation_changed(org_id, session_id)
chat_history = []
chat_history = await chat_repo.get_session_history(session_id)
jira_repo = JiraRepository(db)
@@ -691,6 +693,7 @@ async def handle_widget_chat(sid, data):
"shopify_output": response_content.shopify_output
}
})
await notify_conversation_changed(org_id, session_id)
response = response_content
elif active_session.workflow_id and active_session.user_id is not None:
# Workflow session but human agent has taken over - handle like regular human takeover
@@ -720,6 +723,7 @@ async def handle_widget_chat(sid, data):
)
db.add(file_attachment)
db.commit()
await notify_conversation_changed(org_id, session_id)
# Get session data to find assigned user
session_data = session_repo.get_session(session_id)
user_id = str(session_data.user_id) if session_data and session_data.user_id else None
@@ -779,6 +783,7 @@ async def handle_widget_chat(sid, data):
)
db.add(file_attachment)
db.commit()
await notify_conversation_changed(org_id, session_id)
# Get session data to find assigned user
session_data = session_repo.get_session(session_id)
user_id = str(session_data.user_id) if session_data and session_data.user_id else None
@@ -1291,6 +1296,7 @@ async def handle_agent_message(sid, data):
logger.info(f"Agent ended chat session {session_id}")
# Update session status to closed
session_repo.update_session_status(session_id, "CLOSED")
await notify_conversation_changed(session.get('organization_id'), session_id)
# Emit to widget clients
@@ -1396,6 +1402,22 @@ async def handle_join_room(sid, data):
await sio.enter_room(sid, session_id, namespace='/agent')
return
# These rooms carry invalidations only, never message content. A
# watcher must satisfy the same scope as the chat-detail endpoint.
if session_id.startswith(ORG_CHAT_UPDATES_PREFIX):
db = next(get_db())
if not may_join_chat_updates(db, session, session_id):
raise ValueError("Unauthorized to join chat updates room")
await sio.enter_room(sid, session_id, namespace='/agent')
return
if session_id.startswith(CHAT_WATCH_PREFIX):
db = next(get_db())
if not await may_watch_chat(db, session, session_id):
raise ValueError("Unauthorized to watch chat")
await sio.enter_room(sid, session_id, namespace='/agent')
return
# Verify this agent has permission to join this room
db = next(get_db())
session_repo = SessionToAgentRepository(db)
@@ -1451,7 +1473,9 @@ async def handle_leave_room(sid, data):
# Notify a conversation room of the leave — not the org inbox room,
# where the whole team would hear every chat switch
if not is_org_inbox_room(session_id):
if not (is_org_inbox_room(session_id) or session_id.startswith(
(ORG_CHAT_UPDATES_PREFIX, CHAT_WATCH_PREFIX)
)):
await sio.emit('room_event', {
'type': 'leave',
'user_id': str(session.get('user_id')),
@@ -1641,6 +1665,7 @@ async def handle_get_workflow_state(sid):
"initial_execution": True
}
})
await notify_conversation_changed(org_id, session_id)
# Emit intermediate message to client immediately
await sio.emit('chat_response', {
@@ -1686,6 +1711,7 @@ async def handle_get_workflow_state(sid):
"initial_message": True
}
})
await notify_conversation_changed(org_id, session_id)
# The widget does not render a workflow_state message, so
# send the text as a chat_response like the other handlers.
@@ -1934,6 +1960,7 @@ async def handle_proceed_workflow(sid, data):
"message_node": True
}
})
await notify_conversation_changed(org_id, session_id)
# Emit intermediate message to client immediately
await sio.emit('chat_response', {
@@ -1971,6 +1998,7 @@ async def handle_proceed_workflow(sid, data):
"request_rating": workflow_result.request_rating,
}
})
await notify_conversation_changed(org_id, session_id)
# Emit chat response
await sio.emit('chat_response', {
@@ -2109,6 +2137,7 @@ async def handle_form_submission(sid, data):
# Validate form data
form_data = data.get('form_data', {})
if not form_data:
logger.warning(f"Form submission rejected for sid {sid}: no form data")
raise ValueError("No form data provided")
db = next(get_db())
@@ -2117,6 +2146,7 @@ async def handle_form_submission(sid, data):
active_session = _own_session(session_repo, session)
if not active_session or not active_session.workflow_id:
logger.warning(f"Form submission rejected for sid {sid}: no active workflow session")
raise ValueError("No active workflow session found")
# Get the current form configuration for validation
@@ -2125,6 +2155,7 @@ async def handle_form_submission(sid, data):
workflow = workflow_repo.get_workflow_with_nodes_and_connections(active_session.workflow_id)
if not workflow:
logger.warning(f"Form submission rejected for sid {sid}: workflow not found")
raise ValueError("Workflow not found")
# Find the current form node to get field configurations
@@ -2144,7 +2175,7 @@ async def handle_form_submission(sid, data):
if validation_errors:
error_message = "; ".join(validation_errors)
logger.error(f"Form validation failed: {error_message}")
logger.error(f"Form validation failed for sid {sid}: {error_message}")
await sio.emit('error', {
'error': error_message,
'type': 'validation_error'
@@ -2184,14 +2215,13 @@ async def handle_form_submission(sid, data):
existing_customer = customer_repo.get_customer_by_email(submitted_email, org_id)
if not existing_customer:
# Update customer email
old_email = customer.email
customer.email = submitted_email
db.commit()
logger.info(f"Updated customer {customer_id} email from {old_email} to {submitted_email}")
logger.info(f"Updated email for customer {customer_id} in session {session_id}")
else:
logger.warning(f"Email {submitted_email} already exists for another customer, skipping update")
logger.warning(f"Email already belongs to customer {existing_customer.id}; skipping update for customer {customer_id}")
except Exception as e:
logger.error(f"Failed to update customer email: {str(e)}")
logger.error(f"Failed to update email for customer {customer_id}: {type(e).__name__}")
db.rollback()
# Store form submission in chat history
@@ -2201,8 +2231,6 @@ async def handle_form_submission(sid, data):
if workflow_result.intermediate_messages:
logger.info(f"Found {len(workflow_result.intermediate_messages)} intermediate messages from MESSAGE nodes")
for intermediate_message in workflow_result.intermediate_messages:
logger.debug(f"Emitting intermediate message: {intermediate_message}")
# Store intermediate message in chat history
chat_repo.create_message({
"message": intermediate_message,
@@ -2218,6 +2246,7 @@ async def handle_form_submission(sid, data):
"message_node": True
}
})
await notify_conversation_changed(org_id, session_id)
# Emit intermediate message to client immediately
await sio.emit('chat_response', {
@@ -2248,6 +2277,7 @@ async def handle_form_submission(sid, data):
"request_rating": workflow_result.request_rating,
}
})
await notify_conversation_changed(org_id, session_id)
# Emit chat response
await sio.emit('chat_response', {
@@ -2273,14 +2303,13 @@ async def handle_form_submission(sid, data):
}, to=sid, namespace='/widget')
except ValueError as e:
logger.error(f"Form submission error for sid {sid}: {str(e)}")
logger.error(f"Form submission error for sid {sid}: {type(e).__name__}")
await sio.emit('error', {
'error': str(e),
'type': 'form_error'
}, to=sid, namespace='/widget')
except Exception as e:
logger.error(f"Form submission error for sid {sid}: {str(e)}")
logger.error(traceback.format_exc())
logger.error(f"Form submission error for sid {sid}: {type(e).__name__}")
await sio.emit('error', {
'error': 'Failed to submit form',
'type': 'form_error'