Skip to content

Commit 29b8630

Browse files
committed
Merge branch 'macae-v3-dev-v2-vip' of https://github.com/microsoft/Multi-Agent-Custom-Automation-Engine-Solution-Accelerator into macae-v3-dev-marktayl
2 parents 47fba1d + bf34b9a commit 29b8630

File tree

13 files changed

+251
-150
lines changed

13 files changed

+251
-150
lines changed

src/backend/common/models/messages_kernel.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
from enum import Enum
44
from typing import Any, Dict, List, Literal, Optional
55

6-
from semantic_kernel.kernel_pydantic import Field, KernelBaseModel
76

7+
from semantic_kernel.kernel_pydantic import Field, KernelBaseModel
8+
from dataclasses import dataclass
89

910
class DataType(str, Enum):
1011
"""Enumeration of possible data types for documents in the database."""
@@ -16,7 +17,7 @@ class DataType(str, Enum):
1617
team_config = "team_config"
1718
user_current_team = "user_current_team"
1819
m_plan = "m_plan"
19-
m_plan_step = "m_plan_step"
20+
m_plan_message = "m_plan_message"
2021

2122

2223
class AgentType(str, Enum):
@@ -53,6 +54,7 @@ class PlanStatus(str, Enum):
5354
in_progress = "in_progress"
5455
completed = "completed"
5556
failed = "failed"
57+
canceled = "canceled"
5658

5759

5860
class HumanFeedbackStatus(str, Enum):
@@ -93,6 +95,7 @@ class AgentMessage(BaseDataModel):
9395
step_id: Optional[str] = None
9496

9597

98+
9699
class Session(BaseDataModel):
97100
"""Represents a user session."""
98101

@@ -119,6 +122,7 @@ class Plan(BaseDataModel):
119122
user_id: str
120123
initial_goal: str
121124
overall_status: PlanStatus = PlanStatus.in_progress
125+
approved: bool = False
122126
source: str = AgentType.PLANNER.value
123127
summary: Optional[str] = None
124128
team_id: Optional[str] = None
@@ -250,3 +254,14 @@ class InputTask(KernelBaseModel):
250254

251255
class UserLanguage(KernelBaseModel):
252256
language: str
257+
258+
259+
class AgentMessageType(str, Enum):
260+
HUMAN_AGENT = "Human_Agent",
261+
AI_AGENT = "AI_Agent",
262+
263+
264+
class AgentMessageData (BaseDataModel):
265+
agent: str
266+
agent_type: AgentMessageType = AgentMessageType.AI_AGENT
267+
content: str

src/backend/v3/models/messages.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ class ApprovalRequest(KernelBaseModel):
123123
agent_name: str
124124

125125

126-
127126
class WebsocketMessageType(str, Enum):
128127
"""Types of WebSocket messages."""
129128
SYSTEM_MESSAGE = "system_message"

src/backend/v3/models/models.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import uuid
22
from datetime import datetime, timezone
33
from enum import Enum
4-
from typing import List, Optional
4+
from typing import List, Literal, Optional
55

66
from pydantic import BaseModel, Field
77

8+
from common.models.messages_kernel import DataType
9+
810

911
class PlanStatus(str, Enum):
1012
CREATED = "created"
@@ -22,6 +24,7 @@ class MStep(BaseModel):
2224
class MPlan(BaseModel):
2325
"""model of a plan"""
2426
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
27+
data_type: Literal[DataType.m_plan] = Field(DataType.m_plan, Literal=True)
2528
user_id: str = ""
2629
team_id: str = ""
2730
plan_id: str = ""

src/frontend/src/components/common/TeamSelected.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import { TeamConfig } from "@/models";
22
import { Body1, Caption1 } from "@fluentui/react-components";
3-
3+
import styles from '../../styles/TeamSelector.module.css';
44
export interface TeamSelectedProps {
55
selectedTeam?: TeamConfig | null;
6-
styles: { [key: string]: string };
76
}
87

9-
const TeamSelected: React.FC<TeamSelectedProps> = ({ selectedTeam, styles }) => {
8+
const TeamSelected: React.FC<TeamSelectedProps> = ({ selectedTeam }) => {
109
return (
1110
<div className={styles.teamSelectorContent}>
1211
<Caption1 className={styles.currentTeamLabel}>
13-
Current Team
12+
&nbsp;&nbsp;Current Team
1413
</Caption1>
1514
<Body1 className={styles.currentTeamName}>
16-
{selectedTeam ? selectedTeam.name : 'No team selected'}
15+
&nbsp;&nbsp;{selectedTeam ? selectedTeam.name : 'No team selected'}
1716
</Body1>
1817
</div>
1918
);

src/frontend/src/components/content/PlanChat.tsx

Lines changed: 16 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { AgentMessageData, WebsocketMessageType } from "@/models";
2828
import getUserPlan from "./streaming/StreamingUserPlan";
2929
import renderUserPlanMessage from "./streaming/StreamingUserPlanMessage";
3030
import renderPlanResponse from "./streaming/StreamingPlanResponse";
31-
import renderThinkingState from "./streaming/StreamingPlanState";
31+
import { renderPlanExecutionMessage, renderThinkingState } from "./streaming/StreamingPlanState";
3232
import ContentNotFound from "../NotFound/ContentNotFound";
3333
import PlanChatBody from "./PlanChatBody";
3434
import renderBufferMessage from "./streaming/StreamingBufferMessage";
@@ -42,6 +42,12 @@ interface SimplifiedPlanChatProps extends PlanChatProps {
4242
messagesContainerRef: React.RefObject<HTMLDivElement>;
4343
streamingMessageBuffer: string;
4444
agentMessages: AgentMessageData[];
45+
showProcessingPlanSpinner: boolean;
46+
showApprovalButtons: boolean;
47+
handleApprovePlan: () => Promise<void>;
48+
handleRejectPlan: () => Promise<void>;
49+
processingApproval: boolean;
50+
4551
}
4652

4753
const PlanChat: React.FC<SimplifiedPlanChatProps> = ({
@@ -57,78 +63,25 @@ const PlanChat: React.FC<SimplifiedPlanChatProps> = ({
5763
waitingForPlan,
5864
messagesContainerRef,
5965
streamingMessageBuffer,
60-
agentMessages
66+
agentMessages,
67+
showProcessingPlanSpinner,
68+
showApprovalButtons,
69+
handleApprovePlan,
70+
handleRejectPlan,
71+
processingApproval
72+
6173

6274
}) => {
6375
const navigate = useNavigate();
6476

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

68-
const [processingApproval, setProcessingApproval] = useState(false);
69-
70-
const [showApprovalButtons, setShowApprovalButtons] = useState(true);
71-
72-
73-
74-
75-
// Listen for m_plan streaming
7680

7781

78-
// Handle plan approval
79-
const handleApprovePlan = useCallback(async () => {
80-
if (!planApprovalRequest) return;
8182

82-
setProcessingApproval(true);
83-
let id = showToast("Submitting Approval", "progress");
84-
try {
85-
await apiService.approvePlan({
86-
m_plan_id: planApprovalRequest.id,
87-
plan_id: planData?.plan?.id,
88-
approved: true,
89-
feedback: 'Plan approved by user'
90-
});
9183

92-
dismissToast(id);
93-
onPlanApproval?.(true);
94-
setShowApprovalButtons(false);
9584

96-
} catch (error) {
97-
dismissToast(id);
98-
showToast("Failed to submit approval", "error");
99-
console.error('❌ Failed to approve plan:', error);
100-
} finally {
101-
setProcessingApproval(false);
102-
}
103-
}, [planApprovalRequest, planData, onPlanApproval]);
104-
105-
// Handle plan rejection
106-
const handleRejectPlan = useCallback(async () => {
107-
if (!planApprovalRequest) return;
108-
109-
setProcessingApproval(true);
110-
let id = showToast("Submitting cancellation", "progress");
111-
try {
112-
await apiService.approvePlan({
113-
m_plan_id: planApprovalRequest.id,
114-
plan_id: planData?.plan?.id,
115-
approved: false,
116-
feedback: 'Plan rejected by user'
117-
});
118-
119-
dismissToast(id);
120-
onPlanApproval?.(false);
121-
navigate('/');
122-
123-
} catch (error) {
124-
dismissToast(id);
125-
showToast("Failed to submit cancellation", "error");
126-
console.error('❌ Failed to reject plan:', error);
127-
navigate('/');
128-
} finally {
129-
setProcessingApproval(false);
130-
}
131-
}, [planApprovalRequest, planData, onPlanApproval, navigate]);
13285

13386
if (!planData)
13487
return (
@@ -164,9 +117,10 @@ const PlanChat: React.FC<SimplifiedPlanChatProps> = ({
164117
{renderPlanResponse(planApprovalRequest, handleApprovePlan, handleRejectPlan, processingApproval, showApprovalButtons)}
165118
{renderAgentMessages(agentMessages)}
166119

167-
120+
{showProcessingPlanSpinner && renderPlanExecutionMessage()}
168121
{/* Streaming plan updates */}
169122
{renderBufferMessage(streamingMessageBuffer)}
123+
170124
</div>
171125

172126
{/* Chat Input - only show if no plan is waiting for approval */}

src/frontend/src/components/content/PlanPanelLeft.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import PanelUserCard from "../../coral/components/Panels/UserCard";
3030
import { getUserInfoGlobal } from "@/api/config";
3131
import TeamSelector from "../common/TeamSelector";
3232
import { TeamConfig } from "../../models/Team";
33+
import TeamSelected from "../common/TeamSelected";
34+
import TeamService from "@/services/TeamService";
3335

3436
const PlanPanelLeft: React.FC<PlanPanelLefProps> = ({
3537
reloadTasks,
@@ -172,13 +174,23 @@ const PlanPanelLeft: React.FC<PlanPanelLefProps> = ({
172174
</PanelLeftToolbar>
173175

174176
{/* Team Selector right under the toolbar */}
177+
175178
<div style={{ marginTop: '8px', marginBottom: '8px' }}>
176-
<TeamSelector
177-
onTeamSelect={handleTeamSelect}
178-
onTeamUpload={onTeamUpload}
179-
selectedTeam={selectedTeam}
180-
isHomePage={isHomePage}
181-
/>
179+
{isHomePage && (
180+
<TeamSelector
181+
onTeamSelect={handleTeamSelect}
182+
onTeamUpload={onTeamUpload}
183+
selectedTeam={selectedTeam}
184+
isHomePage={isHomePage}
185+
/>
186+
)}
187+
188+
{!isHomePage && (
189+
<TeamSelected
190+
selectedTeam={TeamService.getStoredTeam()}
191+
/>
192+
)}
193+
182194
</div>
183195
<div
184196
className="tab tab-new-task"

src/frontend/src/components/content/streaming/StreamingAgentMessage.tsx

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,93 @@
1-
import { AgentMessageData } from "@/models";
1+
import { AgentMessageData, AgentMessageType, role } from "@/models";
22
import ReactMarkdown from "react-markdown";
33
import remarkGfm from "remark-gfm";
44
import rehypePrism from "rehype-prism";
5+
import { Body1, Button, Tag } from "@fluentui/react-components";
6+
import { TaskService } from "@/services";
7+
import { Copy } from "@/coral/imports/bundleicons";
8+
import { DiamondRegular, HeartRegular } from "@fluentui/react-icons";
59

610

711
const StreamingAgentMessage = (agentMessages: AgentMessageData[]) => {
812
if (!agentMessages?.length) return null;
913

1014
// Filter out messages with empty content
11-
const validMessages = agentMessages.filter(msg => msg.raw_content?.trim());
12-
15+
const validMessages = agentMessages.filter(msg => msg.content?.trim());
16+
const messages = validMessages;
1317
if (!validMessages.length) return null;
1418

1519
return (
16-
<div className="streaming-agent-messages">
17-
{validMessages.map((message, index) => (
18-
<div key={`${message.agent}-${message.timestamp}-${index}`} className="agent-message">
19-
<div className="agent-name">
20-
<strong>{message.agent}</strong>:
21-
</div>
22-
<div className="agent-content">
23-
<ReactMarkdown
24-
remarkPlugins={[remarkGfm]}
25-
rehypePlugins={[rehypePrism]}
26-
>
27-
{message.raw_content.trim()}
28-
</ReactMarkdown>
20+
21+
22+
<div className="message-wrapper">
23+
{messages.map((msg, index) => {
24+
const isHuman = msg.agent_type === AgentMessageType.HUMAN_AGENT;
25+
26+
return (
27+
<div
28+
key={index}
29+
className={`message ${isHuman ? role.user : role.assistant}`}
30+
>
31+
{!isHuman && (
32+
<div className="plan-chat-header">
33+
<div className="plan-chat-speaker">
34+
<Body1 className="speaker-name">
35+
{TaskService.cleanTextToSpaces(msg.agent)}
36+
</Body1>
37+
<Tag
38+
size="extra-small"
39+
shape="rounded"
40+
appearance="brand"
41+
className="bot-tag"
42+
>
43+
BOT
44+
</Tag>
45+
</div>
46+
</div>
47+
)}
48+
49+
<Body1>
50+
<div className="plan-chat-message-content">
51+
<ReactMarkdown
52+
remarkPlugins={[remarkGfm]}
53+
rehypePlugins={[rehypePrism]}
54+
>
55+
{TaskService.cleanHRAgent(msg.content) || ""}
56+
</ReactMarkdown>
57+
58+
{!isHuman && (
59+
<div className="assistant-footer">
60+
<div className="assistant-actions">
61+
<div>
62+
<Button
63+
onClick={() =>
64+
msg.content &&
65+
navigator.clipboard.writeText(msg.content)
66+
}
67+
title="Copy Response"
68+
appearance="subtle"
69+
style={{ height: 28, width: 28 }}
70+
icon={<Copy />}
71+
/>
72+
</div>
73+
74+
<Tag
75+
icon={<DiamondRegular />}
76+
appearance="filled"
77+
size="extra-small"
78+
>
79+
Sample data for demonstration purposes only.
80+
</Tag>
81+
</div>
82+
</div>
83+
)}
84+
</div>
85+
</Body1>
2986
</div>
30-
</div>
31-
))}
87+
);
88+
})}
3289
</div>
90+
3391
);
3492
};
3593

src/frontend/src/components/content/streaming/StreamingBufferMessage.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ const renderBufferMessage = (streamingMessageBuffer: string) => {
1212

1313
if (!streamingMessageBuffer || streamingMessageBuffer.trim() === "") return null;
1414

15-
const previewText = streamingMessageBuffer.length > 500
16-
? streamingMessageBuffer.substring(0, 500) + "..."
17-
: streamingMessageBuffer;
15+
const start = Math.max(0, streamingMessageBuffer.length - 500);
16+
const previewText = start === 0
17+
? streamingMessageBuffer
18+
: "..." + streamingMessageBuffer.substring(start);
1819

1920
return (
2021
<div style={{

0 commit comments

Comments
 (0)