Skip to content

Commit 4edf41c

Browse files
authored
Merge pull request microsoft#476 from microsoft/macae-v3-fr-dev-92
Macae v3 fr dev 92
2 parents 264b107 + 1f52075 commit 4edf41c

File tree

13 files changed

+210
-155
lines changed

13 files changed

+210
-155
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: 15 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ interface SimplifiedPlanChatProps extends PlanChatProps {
4343
streamingMessageBuffer: string;
4444
agentMessages: AgentMessageData[];
4545
showProcessingPlanSpinner: boolean;
46-
setShowProcessingPlanSpinner: (show: boolean) => void;
46+
showApprovalButtons: boolean;
47+
handleApprovePlan: () => Promise<void>;
48+
handleRejectPlan: () => Promise<void>;
49+
processingApproval: boolean;
50+
4751
}
4852

4953
const PlanChat: React.FC<SimplifiedPlanChatProps> = ({
@@ -61,79 +65,23 @@ const PlanChat: React.FC<SimplifiedPlanChatProps> = ({
6165
streamingMessageBuffer,
6266
agentMessages,
6367
showProcessingPlanSpinner,
64-
setShowProcessingPlanSpinner
68+
showApprovalButtons,
69+
handleApprovePlan,
70+
handleRejectPlan,
71+
processingApproval
72+
6573

6674
}) => {
6775
const navigate = useNavigate();
6876

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

72-
const [processingApproval, setProcessingApproval] = useState(false);
73-
74-
const [showApprovalButtons, setShowApprovalButtons] = useState(true);
75-
76-
77-
78-
79-
// Listen for m_plan streaming
80-
81-
82-
// Handle plan approval
83-
const handleApprovePlan = useCallback(async () => {
84-
if (!planApprovalRequest) return;
85-
86-
setProcessingApproval(true);
87-
let id = showToast("Submitting Approval", "progress");
88-
try {
89-
await apiService.approvePlan({
90-
m_plan_id: planApprovalRequest.id,
91-
plan_id: planData?.plan?.id,
92-
approved: true,
93-
feedback: 'Plan approved by user'
94-
});
95-
96-
dismissToast(id);
97-
onPlanApproval?.(true);
98-
setShowProcessingPlanSpinner?.(true);
99-
setShowApprovalButtons(false);
100-
101-
} catch (error) {
102-
dismissToast(id);
103-
showToast("Failed to submit approval", "error");
104-
console.error('❌ Failed to approve plan:', error);
105-
} finally {
106-
setProcessingApproval(false);
107-
}
108-
}, [planApprovalRequest, planData, onPlanApproval, setShowProcessingPlanSpinner]);
109-
110-
// Handle plan rejection
111-
const handleRejectPlan = useCallback(async () => {
112-
if (!planApprovalRequest) return;
113-
114-
setProcessingApproval(true);
115-
let id = showToast("Submitting cancellation", "progress");
116-
try {
117-
await apiService.approvePlan({
118-
m_plan_id: planApprovalRequest.id,
119-
plan_id: planData?.plan?.id,
120-
approved: false,
121-
feedback: 'Plan rejected by user'
122-
});
123-
124-
dismissToast(id);
125-
onPlanApproval?.(false);
126-
navigate('/');
127-
128-
} catch (error) {
129-
dismissToast(id);
130-
showToast("Failed to submit cancellation", "error");
131-
console.error('❌ Failed to reject plan:', error);
132-
navigate('/');
133-
} finally {
134-
setProcessingApproval(false);
135-
}
136-
}, [planApprovalRequest, planData, onPlanApproval, navigate]);
80+
81+
82+
83+
84+
13785

13886
if (!planData)
13987
return (

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={{

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ const renderPlanExecutionMessage = () => {
6060
backgroundColor: tokens.colorNeutralBackground2,
6161
borderRadius: tokens.borderRadiusMedium,
6262
border: `1px solid ${tokens.colorNeutralStroke1}`,
63-
marginBottom: tokens.spacingVerticalXL
63+
marginBottom: tokens.spacingVerticalXL,
64+
marginTop: tokens.spacingVerticalXL
6465
}}>
6566
<Spinner size="small" />
6667
<span style={{

src/frontend/src/models/agentMessage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ export interface AgentMessageData {
2626
timestamp: number;
2727
steps: any[]; // intentionally always empty
2828
next_steps: []; // intentionally always empty
29-
raw_content: string;
29+
content: string;
3030
raw_data: string;
3131
}

0 commit comments

Comments
 (0)