Fix AI mode: race condition, split-brain UI, and stuck local state
- Backend: set status to ai_mode in the route handler before submitting to AI runner, eliminating the race condition where frontend's immediate status poll read the old status - Frontend: replace all raw isAiModeActive prop usages with effectiveAiModeActive in DiscussionPanel (13 locations) so ReasoningPanel, status text, loading indicator, and manual/AI controls all reflect the correct state instantly on Start AI Mode click - Frontend: add useEffect to sync localAiModeActive back to null once the parent prop catches up, preventing permanent override after natural session end - These fixes also unblock the 3-second AI message polling which was never activating due to isAiModeActive staying false Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b4978989a5
commit
283b31e786
2 changed files with 36 additions and 16 deletions
|
|
@ -757,7 +757,20 @@ async def start_autonomous_conversation(focus_group_id):
|
|||
if not ai_runner.is_running:
|
||||
current_app.logger.error("AI Runner service is not running")
|
||||
return jsonify({"error": "AI Runner service is not available"}), 503
|
||||
|
||||
|
||||
# Set status to ai_mode NOW (before submitting to background runner) so that
|
||||
# the frontend's immediate status poll after this response reads the correct state.
|
||||
# The controller will also set it, making this idempotent.
|
||||
from datetime import datetime, timezone
|
||||
try:
|
||||
await FocusGroup.update(focus_group_id, {
|
||||
'status': 'ai_mode',
|
||||
'autonomous_started_at': datetime.now(timezone.utc)
|
||||
})
|
||||
current_app.logger.info("Set focus group status to ai_mode before AI runner submission")
|
||||
except Exception as e:
|
||||
current_app.logger.warning(f"Failed to pre-set ai_mode status: {e}")
|
||||
|
||||
# Submit the conversation to the AI Runner (non-blocking)
|
||||
current_app.logger.info("Submitting conversation to AI Runner...")
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -77,12 +77,19 @@ const DiscussionPanel = ({
|
|||
// Calculate reasoning panel visibility - only show when user explicitly expands it
|
||||
const reasoningPanelVisible = reasoningPanelExpanded;
|
||||
|
||||
// Sync localAiModeActive back to null once parent prop has caught up
|
||||
useEffect(() => {
|
||||
if (localAiModeActive !== null && localAiModeActive === isAiModeActive) {
|
||||
setLocalAiModeActive(null);
|
||||
}
|
||||
}, [isAiModeActive, localAiModeActive]);
|
||||
|
||||
// Fetch reasoning history when in AI mode
|
||||
useEffect(() => {
|
||||
if (isAiModeActive && focusGroupId) {
|
||||
if (effectiveAiModeActive && focusGroupId) {
|
||||
checkAutonomousStatus();
|
||||
}
|
||||
}, [isAiModeActive, focusGroupId]);
|
||||
}, [effectiveAiModeActive, focusGroupId]);
|
||||
|
||||
// Check autonomous conversation status
|
||||
const checkAutonomousStatus = async () => {
|
||||
|
|
@ -91,7 +98,7 @@ const DiscussionPanel = ({
|
|||
try {
|
||||
// Status is managed by parent component through isAiModeActive prop
|
||||
// Just fetch reasoning history if AI mode is active
|
||||
if (isAiModeActive) {
|
||||
if (effectiveAiModeActive) {
|
||||
fetchReasoningHistory();
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -123,21 +130,21 @@ const DiscussionPanel = ({
|
|||
// Polling for reasoning history and status when AI mode is active
|
||||
useEffect(() => {
|
||||
let interval: NodeJS.Timeout;
|
||||
|
||||
if (isAiModeActive && focusGroupId) {
|
||||
|
||||
if (effectiveAiModeActive && focusGroupId) {
|
||||
// Poll more frequently for reasoning updates and status sync (every 5 seconds)
|
||||
interval = setInterval(() => {
|
||||
fetchReasoningHistory();
|
||||
checkAutonomousStatus(); // Also check if status has changed
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
|
||||
return () => {
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
};
|
||||
}, [isAiModeActive, focusGroupId]);
|
||||
}, [effectiveAiModeActive, focusGroupId]);
|
||||
|
||||
// Initialize message count reference
|
||||
useEffect(() => {
|
||||
|
|
@ -1176,17 +1183,17 @@ const DiscussionPanel = ({
|
|||
/>
|
||||
)
|
||||
))}
|
||||
{(isTyping || isAiModeActive) && (
|
||||
{(isTyping || effectiveAiModeActive) && (
|
||||
<div className="flex items-center space-x-2 text-sm text-slate-500 animate-pulse">
|
||||
<div className="bg-primary/10 p-2 rounded-full">
|
||||
{isAiModeActive ? (
|
||||
{effectiveAiModeActive ? (
|
||||
<Bot className="h-4 w-4 text-primary animate-spin" />
|
||||
) : (
|
||||
<MessageCircle className="h-4 w-4 text-primary" />
|
||||
)}
|
||||
</div>
|
||||
<span>
|
||||
{isAiModeActive ? 'AI is generating next response...' : 'Generating AI response...'}
|
||||
{effectiveAiModeActive ? 'AI is generating next response...' : 'Generating AI response...'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -1217,7 +1224,7 @@ const DiscussionPanel = ({
|
|||
reasoningHistory={reasoningHistory}
|
||||
isVisible={reasoningPanelVisible}
|
||||
onToggle={() => setReasoningPanelExpanded(!reasoningPanelExpanded)}
|
||||
isAiMode={isAiModeActive}
|
||||
isAiMode={effectiveAiModeActive}
|
||||
/>
|
||||
|
||||
{/* Control panel - pinned to bottom */}
|
||||
|
|
@ -1292,7 +1299,7 @@ const DiscussionPanel = ({
|
|||
<div className="flex items-center space-x-2">
|
||||
<p className="text-sm text-slate-500">
|
||||
{isSpeaking ? 'Speaking...' :
|
||||
isAiModeActive ? 'AI mode active' :
|
||||
effectiveAiModeActive ? 'AI mode active' :
|
||||
'Manual moderation mode'}
|
||||
</p>
|
||||
|
||||
|
|
@ -1312,7 +1319,7 @@ const DiscussionPanel = ({
|
|||
{autonomousLoading ? (
|
||||
<>
|
||||
<Bot className="mr-1 h-3 w-3 animate-spin" />
|
||||
{isAiModeActive ? 'Stopping...' : 'Starting...'}
|
||||
{effectiveAiModeActive ? 'Stopping...' : 'Starting...'}
|
||||
</>
|
||||
) : effectiveAiModeActive ? (
|
||||
<>
|
||||
|
|
@ -1348,7 +1355,7 @@ const DiscussionPanel = ({
|
|||
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Show manual controls only when not in AI mode */}
|
||||
{!isAiModeActive && (
|
||||
{!effectiveAiModeActive && (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
|
|
@ -1376,7 +1383,7 @@ const DiscussionPanel = ({
|
|||
)}
|
||||
|
||||
{/* Show AI mode status and controls */}
|
||||
{isAiModeActive && (
|
||||
{effectiveAiModeActive && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1 text-sm text-slate-600">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue