Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/backend/common/models/messages_kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ class Plan(BaseDataModel):
m_plan: Optional[Dict[str, Any]] = None
summary: Optional[str] = None
team_id: Optional[str] = None
streaming_message: Optional[str] = None
human_clarification_request: Optional[str] = None
human_clarification_response: Optional[str] = None

Expand Down
3 changes: 3 additions & 0 deletions src/backend/v3/api/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -1337,12 +1337,15 @@ async def get_plan_by_id(request: Request, plan_id: Optional[str] = Query(None)
team = await memory_store.get_team_by_id(team_id=plan.team_id)
agent_messages = await memory_store.get_agent_messages(plan_id=plan.plan_id)
mplan = plan.m_plan if plan.m_plan else None
streaming_message = plan.streaming_message if plan.streaming_message else ""
plan.streaming_message = "" # clear streaming message after retrieval
plan.m_plan = None # remove m_plan from plan object for response
return {
"plan": plan,
"team": team if team else None,
"messages": agent_messages,
"m_plan": mplan,
"streaming_message": streaming_message,
}
else:
track_event_if_configured(
Expand Down
1 change: 1 addition & 0 deletions src/backend/v3/common/services/plan_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ async def handle_agent_messages(
await memory_store.add_agent_message(agent_msg)
if agent_message.is_final:
plan = await memory_store.get_plan(agent_msg.plan_id)
plan.streaming_message = agent_message.streaming_message
plan.overall_status = PlanStatus.completed
await memory_store.update_plan(plan)
return True
Expand Down
1 change: 1 addition & 0 deletions src/backend/v3/models/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ class AgentMessageResponse:
agent_type: AgentMessageType
is_final: bool = False
raw_data: str = None
streaming_message: str = None


class WebsocketMessageType(str, Enum):
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/src/api/apiService.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ export class APIService {
plan: data.plan as Plan,
messages: data.messages as AgentMessageBE[],
m_plan: data.m_plan as MPlanBE | null,
team: data.team as TeamConfigurationBE | null
team: data.team as TeamConfigurationBE | null,
streaming_message: data.streaming_message as string | null
} as PlanFromAPI;
if (useCache) {
this._cache.set(cacheKey, results, 30000); // Cache for 30 seconds
Expand Down
25 changes: 10 additions & 15 deletions src/frontend/src/components/content/PlanChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ import renderPlanResponse from "./streaming/StreamingPlanResponse";
import { renderPlanExecutionMessage, renderThinkingState } from "./streaming/StreamingPlanState";
import ContentNotFound from "../NotFound/ContentNotFound";
import PlanChatBody from "./PlanChatBody";
import renderBufferMessage from "./streaming/StreamingBufferMessage";
import renderAgentMessages from "./streaming/StreamingAgentMessage";
import StreamingBufferMessage from "./streaming/StreamingBufferMessage";

interface SimplifiedPlanChatProps extends PlanChatProps {
onPlanReceived?: (planData: MPlanData) => void;
Expand All @@ -41,6 +41,7 @@ interface SimplifiedPlanChatProps extends PlanChatProps {
waitingForPlan: boolean;
messagesContainerRef: React.RefObject<HTMLDivElement>;
streamingMessageBuffer: string;
showBufferingText: boolean;
agentMessages: AgentMessageData[];
showProcessingPlanSpinner: boolean;
showApprovalButtons: boolean;
Expand All @@ -63,26 +64,16 @@ const PlanChat: React.FC<SimplifiedPlanChatProps> = ({
waitingForPlan,
messagesContainerRef,
streamingMessageBuffer,
showBufferingText,
agentMessages,
showProcessingPlanSpinner,
showApprovalButtons,
handleApprovePlan,
handleRejectPlan,
processingApproval


}) => {
const navigate = useNavigate();

const { showToast, dismissToast } = useInlineToaster();
// States







if (!planData)
return (
<ContentNotFound subtitle="The requested page could not be found." />
Expand Down Expand Up @@ -119,8 +110,12 @@ const PlanChat: React.FC<SimplifiedPlanChatProps> = ({

{showProcessingPlanSpinner && renderPlanExecutionMessage()}
{/* Streaming plan updates */}
{renderBufferMessage(streamingMessageBuffer)}

{showBufferingText && (
<StreamingBufferMessage
streamingMessageBuffer={streamingMessageBuffer}
isStreaming={true}
/>
)}
</div>

{/* Chat Input - only show if no plan is waiting for approval */}
Expand All @@ -132,7 +127,7 @@ const PlanChat: React.FC<SimplifiedPlanChatProps> = ({
OnChatSubmit={OnChatSubmit}
waitingForPlan={waitingForPlan}
loading={false} />

</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ interface StreamingBufferMessageProps {
isStreaming?: boolean;
}

const renderBufferMessage = (streamingMessageBuffer: string, isStreaming: boolean = false) => {
// Convert to a proper React component instead of a function
const StreamingBufferMessage: React.FC<StreamingBufferMessageProps> = ({
streamingMessageBuffer,
isStreaming = false
}) => {
const [isExpanded, setIsExpanded] = useState<boolean>(false);
const [shouldFade, setShouldFade] = useState<boolean>(false);
const contentRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -51,11 +55,11 @@ const renderBufferMessage = (streamingMessageBuffer: string, isStreaming: boolea
padding: '16px',
fontSize: '14px',
lineHeight: '1.5',
height: isExpanded ? 'auto' : '256px', // Auto height when expanded
height: isExpanded ? 'auto' : '256px',
display: 'flex',
flexDirection: 'column',
position: 'relative',
overflow: isExpanded ? 'visible' : 'hidden' // Allow overflow when expanded
overflow: isExpanded ? 'visible' : 'hidden'
}}>
{/* Header */}
<div style={{
Expand Down Expand Up @@ -185,7 +189,7 @@ const renderBufferMessage = (streamingMessageBuffer: string, isStreaming: boolea
</div>
)}

{/* Content area - expanded state (original behavior) */}
{/* Content area - expanded state */}
{isExpanded && (
<div style={{
padding: '12px',
Expand Down Expand Up @@ -221,4 +225,4 @@ const renderBufferMessage = (streamingMessageBuffer: string, isStreaming: boolea
);
};

export default renderBufferMessage;
export default StreamingBufferMessage;
2 changes: 2 additions & 0 deletions src/frontend/src/models/agentMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export interface AgentMessageResponse {
/** Raw data associated with the message */
raw_data: string;

streaming_message: string;

}

export interface FinalMessage {
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/src/models/plan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ export interface PlanFromAPI {
messages: AgentMessageBE[];
m_plan: MPlanBE | null;
team: TeamConfigurationBE | null;
streaming_message: string | null;
}
/**
* Interface for processed plan data
Expand All @@ -225,6 +226,7 @@ export interface ProcessedPlanData {
team: TeamConfig | null;
messages: AgentMessageData[];
mplan: MPlanData | null;
streaming_message: string | null;
}


Expand Down
66 changes: 54 additions & 12 deletions src/frontend/src/pages/PlanPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const PlanPage: React.FC = () => {
const [wsConnected, setWsConnected] = useState<boolean>(false);
const [streamingMessages, setStreamingMessages] = useState<StreamingPlanUpdate[]>([]);
const [streamingMessageBuffer, setStreamingMessageBuffer] = useState<string>("");

const [showBufferingText, setShowBufferingText] = useState<boolean>(false);
const [agentMessages, setAgentMessages] = useState<AgentMessageData[]>([]);

// Plan approval state - track when plan is approved
Expand All @@ -62,10 +62,10 @@ const PlanPage: React.FC = () => {



const processAgentMessage = useCallback((agentMessageData: AgentMessageData, planData: ProcessedPlanData, is_final: boolean = false) => {
const processAgentMessage = useCallback((agentMessageData: AgentMessageData, planData: ProcessedPlanData, is_final: boolean = false, streaming_message: string = '') => {

// Persist / forward to backend (fire-and-forget with logging)
const agentMessageResponse = PlanDataService.createAgentMessageResponse(agentMessageData, planData, is_final);
const agentMessageResponse = PlanDataService.createAgentMessageResponse(agentMessageData, planData, is_final, streaming_message);
console.log('📤 Persisting agent message:', agentMessageResponse);
void apiService.sendAgentMessage(agentMessageResponse)
.then(saved => {
Expand All @@ -82,9 +82,44 @@ const PlanPage: React.FC = () => {
}, []);

const resetPlanVariables = useCallback(() => {


}, []);
setInput("");
setPlanData(null);
setLoading(true);
setSubmittingChatDisableInput(true);
setErrorLoading(false);
setClarificationMessage(null);
setProcessingApproval(false);
setPlanApprovalRequest(null);
setReloadLeftList(true);
setWaitingForPlan(true);
setShowProcessingPlanSpinner(false);
setShowApprovalButtons(true);
setContinueWithWebsocketFlow(false);
setWsConnected(false);
setStreamingMessages([]);
setStreamingMessageBuffer("");
setShowBufferingText(false);
setAgentMessages([]);
}, [
setInput,
setPlanData,
setLoading,
setSubmittingChatDisableInput,
setErrorLoading,
setClarificationMessage,
setProcessingApproval,
setPlanApprovalRequest,
setReloadLeftList,
setWaitingForPlan,
setShowProcessingPlanSpinner,
setShowApprovalButtons,
setContinueWithWebsocketFlow,
setWsConnected,
setStreamingMessages,
setStreamingMessageBuffer,
setShowBufferingText,
setAgentMessages
]);

// Auto-scroll helper
const scrollToBottom = useCallback(() => {
Expand Down Expand Up @@ -146,6 +181,7 @@ const PlanPage: React.FC = () => {
//console.log('📋 Streaming Message', streamingMessage);
// if is final true clear buffer and add final message to agent messages
const line = PlanDataService.simplifyHumanClarification(streamingMessage.data.content);
setShowBufferingText(true);
setStreamingMessageBuffer(prev => prev + line);
//scrollToBottom();

Expand Down Expand Up @@ -175,7 +211,7 @@ const PlanPage: React.FC = () => {
console.log('✅ Parsed clarification message:', agentMessageData);
setClarificationMessage(clarificationMessage.data as ParsedUserClarification | null);
setAgentMessages(prev => [...prev, agentMessageData]);
setStreamingMessageBuffer("");
setShowBufferingText(false);
setShowProcessingPlanSpinner(false);
setSubmittingChatDisableInput(false);
scrollToBottom();
Expand Down Expand Up @@ -221,7 +257,8 @@ const PlanPage: React.FC = () => {
console.log('✅ Parsed final result message:', agentMessageData);
// we ignore the terminated message
if (finalMessage?.data?.status === PlanStatus.COMPLETED) {
setStreamingMessageBuffer("");

setShowBufferingText(true);
setShowProcessingPlanSpinner(false);
setAgentMessages(prev => [...prev, agentMessageData]);
scrollToBottom();
Expand All @@ -232,14 +269,14 @@ const PlanPage: React.FC = () => {
setPlanData({ ...planData });
}

processAgentMessage(agentMessageData, planData, is_final);
processAgentMessage(agentMessageData, planData, is_final, streamingMessageBuffer);
}


});

return () => unsubscribe();
}, [scrollToBottom, planData, processAgentMessage]);
}, [scrollToBottom, planData, processAgentMessage, streamingMessageBuffer]);

//WebsocketMessageType.AGENT_MESSAGE
useEffect(() => {
Expand Down Expand Up @@ -340,7 +377,7 @@ const PlanPage: React.FC = () => {
const loadPlanData = useCallback(
async (useCache = true): Promise<ProcessedPlanData | null> => {
if (!planId) return null;

resetPlanVariables();
setLoading(true);
try {

Expand All @@ -364,6 +401,10 @@ const PlanPage: React.FC = () => {
if (planResult?.mplan) {
setPlanApprovalRequest(planResult.mplan);
}
if (planResult?.streaming_message && planResult.streaming_message.trim() !== "") {
setStreamingMessageBuffer(planResult.streaming_message);
setShowBufferingText(true);
}
setPlanData(planResult);
return planResult;
} catch (err) {
Expand All @@ -373,7 +414,7 @@ const PlanPage: React.FC = () => {
setLoading(false);
}
},
[planId, navigate]
[planId, navigate, resetPlanVariables]
);


Expand Down Expand Up @@ -595,6 +636,7 @@ const PlanPage: React.FC = () => {
waitingForPlan={waitingForPlan}
messagesContainerRef={messagesContainerRef}
streamingMessageBuffer={streamingMessageBuffer}
showBufferingText={showBufferingText}
agentMessages={agentMessages}
showProcessingPlanSpinner={showProcessingPlanSpinner}
showApprovalButtons={showApprovalButtons}
Expand Down
11 changes: 7 additions & 4 deletions src/frontend/src/services/PlanDataService.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class PlanDataService {
try {
// Use optimized getPlanById method for better performance
const planBody = await apiService.getPlanById(planId, useCache);
console.log('Raw plan data fetched:', planBody);
return this.processPlanData(planBody);
} catch (error) {
console.log("Failed to fetch plan data:", error);
Expand Down Expand Up @@ -209,11 +210,13 @@ export class PlanDataService {
const team = this.convertTeamConfiguration(planFromAPI.team);
const mplan = this.convertMPlan(planFromAPI.m_plan);
const messages: AgentMessageData[] = this.convertAgentMessages(planFromAPI.messages || []);
const streaming_message = planFromAPI.streaming_message || null;
return {
plan,
team,
mplan,
messages
messages,
streaming_message
};
}

Expand All @@ -226,20 +229,20 @@ export class PlanDataService {
static createAgentMessageResponse(
agentMessage: AgentMessageData,
planData: ProcessedPlanData,
is_final: boolean = false
is_final: boolean = false,
streaming_message: string = ''
): AgentMessageResponse {
if (!planData || !planData.plan) {
console.log("Invalid plan data provided to createAgentMessageResponse");
}
return {

plan_id: planData.plan.plan_id,
agent: agentMessage.agent,
content: agentMessage.content,
agent_type: agentMessage.agent_type,
is_final: is_final,
raw_data: JSON.stringify(agentMessage.raw_data),

streaming_message: streaming_message
};
}

Expand Down
Loading