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
19 changes: 17 additions & 2 deletions src/backend/common/models/messages_kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from enum import Enum
from typing import Any, Dict, List, Literal, Optional

from semantic_kernel.kernel_pydantic import Field, KernelBaseModel

from semantic_kernel.kernel_pydantic import Field, KernelBaseModel
from dataclasses import dataclass

class DataType(str, Enum):
"""Enumeration of possible data types for documents in the database."""
Expand All @@ -16,7 +17,7 @@ class DataType(str, Enum):
team_config = "team_config"
user_current_team = "user_current_team"
m_plan = "m_plan"
m_plan_step = "m_plan_step"
m_plan_message = "m_plan_message"


class AgentType(str, Enum):
Expand Down Expand Up @@ -53,6 +54,7 @@ class PlanStatus(str, Enum):
in_progress = "in_progress"
completed = "completed"
failed = "failed"
canceled = "canceled"


class HumanFeedbackStatus(str, Enum):
Expand Down Expand Up @@ -93,6 +95,7 @@ class AgentMessage(BaseDataModel):
step_id: Optional[str] = None



class Session(BaseDataModel):
"""Represents a user session."""

Expand All @@ -119,6 +122,7 @@ class Plan(BaseDataModel):
user_id: str
initial_goal: str
overall_status: PlanStatus = PlanStatus.in_progress
approved: bool = False
source: str = AgentType.PLANNER.value
summary: Optional[str] = None
team_id: Optional[str] = None
Expand Down Expand Up @@ -250,3 +254,14 @@ class InputTask(KernelBaseModel):

class UserLanguage(KernelBaseModel):
language: str


class AgentMessageType(str, Enum):
HUMAN_AGENT = "Human_Agent",
AI_AGENT = "AI_Agent",


class AgentMessageData (BaseDataModel):
agent: str
agent_type: AgentMessageType = AgentMessageType.AI_AGENT
content: str
1 change: 0 additions & 1 deletion src/backend/v3/models/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ class ApprovalRequest(KernelBaseModel):
agent_name: str



class WebsocketMessageType(str, Enum):
"""Types of WebSocket messages."""
SYSTEM_MESSAGE = "system_message"
Expand Down
5 changes: 4 additions & 1 deletion src/backend/v3/models/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import uuid
from datetime import datetime, timezone
from enum import Enum
from typing import List, Optional
from typing import List, Literal, Optional

from pydantic import BaseModel, Field

from common.models.messages_kernel import DataType


class PlanStatus(str, Enum):
CREATED = "created"
Expand All @@ -22,6 +24,7 @@ class MStep(BaseModel):
class MPlan(BaseModel):
"""model of a plan"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
data_type: Literal[DataType.m_plan] = Field(DataType.m_plan, Literal=True)
user_id: str = ""
team_id: str = ""
plan_id: str = ""
Expand Down
9 changes: 4 additions & 5 deletions src/frontend/src/components/common/TeamSelected.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { TeamConfig } from "@/models";
import { Body1, Caption1 } from "@fluentui/react-components";

import styles from '../../styles/TeamSelector.module.css';
export interface TeamSelectedProps {
selectedTeam?: TeamConfig | null;
styles: { [key: string]: string };
}

const TeamSelected: React.FC<TeamSelectedProps> = ({ selectedTeam, styles }) => {
const TeamSelected: React.FC<TeamSelectedProps> = ({ selectedTeam }) => {
return (
<div className={styles.teamSelectorContent}>
<Caption1 className={styles.currentTeamLabel}>
Current Team
&nbsp;&nbsp;Current Team
</Caption1>
<Body1 className={styles.currentTeamName}>
{selectedTeam ? selectedTeam.name : 'No team selected'}
&nbsp;&nbsp;{selectedTeam ? selectedTeam.name : 'No team selected'}
</Body1>
</div>
);
Expand Down
82 changes: 15 additions & 67 deletions src/frontend/src/components/content/PlanChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ interface SimplifiedPlanChatProps extends PlanChatProps {
streamingMessageBuffer: string;
agentMessages: AgentMessageData[];
showProcessingPlanSpinner: boolean;
setShowProcessingPlanSpinner: (show: boolean) => void;
showApprovalButtons: boolean;
handleApprovePlan: () => Promise<void>;
handleRejectPlan: () => Promise<void>;
processingApproval: boolean;

}

const PlanChat: React.FC<SimplifiedPlanChatProps> = ({
Expand All @@ -61,79 +65,23 @@ const PlanChat: React.FC<SimplifiedPlanChatProps> = ({
streamingMessageBuffer,
agentMessages,
showProcessingPlanSpinner,
setShowProcessingPlanSpinner
showApprovalButtons,
handleApprovePlan,
handleRejectPlan,
processingApproval


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

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

const [processingApproval, setProcessingApproval] = useState(false);

const [showApprovalButtons, setShowApprovalButtons] = useState(true);




// Listen for m_plan streaming


// Handle plan approval
const handleApprovePlan = useCallback(async () => {
if (!planApprovalRequest) return;

setProcessingApproval(true);
let id = showToast("Submitting Approval", "progress");
try {
await apiService.approvePlan({
m_plan_id: planApprovalRequest.id,
plan_id: planData?.plan?.id,
approved: true,
feedback: 'Plan approved by user'
});

dismissToast(id);
onPlanApproval?.(true);
setShowProcessingPlanSpinner?.(true);
setShowApprovalButtons(false);

} catch (error) {
dismissToast(id);
showToast("Failed to submit approval", "error");
console.error('❌ Failed to approve plan:', error);
} finally {
setProcessingApproval(false);
}
}, [planApprovalRequest, planData, onPlanApproval, setShowProcessingPlanSpinner]);

// Handle plan rejection
const handleRejectPlan = useCallback(async () => {
if (!planApprovalRequest) return;

setProcessingApproval(true);
let id = showToast("Submitting cancellation", "progress");
try {
await apiService.approvePlan({
m_plan_id: planApprovalRequest.id,
plan_id: planData?.plan?.id,
approved: false,
feedback: 'Plan rejected by user'
});

dismissToast(id);
onPlanApproval?.(false);
navigate('/');

} catch (error) {
dismissToast(id);
showToast("Failed to submit cancellation", "error");
console.error('❌ Failed to reject plan:', error);
navigate('/');
} finally {
setProcessingApproval(false);
}
}, [planApprovalRequest, planData, onPlanApproval, navigate]);






if (!planData)
return (
Expand Down
24 changes: 18 additions & 6 deletions src/frontend/src/components/content/PlanPanelLeft.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import PanelUserCard from "../../coral/components/Panels/UserCard";
import { getUserInfoGlobal } from "@/api/config";
import TeamSelector from "../common/TeamSelector";
import { TeamConfig } from "../../models/Team";
import TeamSelected from "../common/TeamSelected";
import TeamService from "@/services/TeamService";

const PlanPanelLeft: React.FC<PlanPanelLefProps> = ({
reloadTasks,
Expand Down Expand Up @@ -172,13 +174,23 @@ const PlanPanelLeft: React.FC<PlanPanelLefProps> = ({
</PanelLeftToolbar>

{/* Team Selector right under the toolbar */}

<div style={{ marginTop: '8px', marginBottom: '8px' }}>
<TeamSelector
onTeamSelect={handleTeamSelect}
onTeamUpload={onTeamUpload}
selectedTeam={selectedTeam}
isHomePage={isHomePage}
/>
{isHomePage && (
<TeamSelector
onTeamSelect={handleTeamSelect}
onTeamUpload={onTeamUpload}
selectedTeam={selectedTeam}
isHomePage={isHomePage}
/>
)}

{!isHomePage && (
<TeamSelected
selectedTeam={TeamService.getStoredTeam()}
/>
)}

</div>
<div
className="tab tab-new-task"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,93 @@
import { AgentMessageData } from "@/models";
import { AgentMessageData, AgentMessageType, role } from "@/models";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import rehypePrism from "rehype-prism";
import { Body1, Button, Tag } from "@fluentui/react-components";
import { TaskService } from "@/services";
import { Copy } from "@/coral/imports/bundleicons";
import { DiamondRegular, HeartRegular } from "@fluentui/react-icons";


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

// Filter out messages with empty content
const validMessages = agentMessages.filter(msg => msg.raw_content?.trim());

const validMessages = agentMessages.filter(msg => msg.content?.trim());
const messages = validMessages;
if (!validMessages.length) return null;

return (
<div className="streaming-agent-messages">
{validMessages.map((message, index) => (
<div key={`${message.agent}-${message.timestamp}-${index}`} className="agent-message">
<div className="agent-name">
<strong>{message.agent}</strong>:
</div>
<div className="agent-content">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypePrism]}
>
{message.raw_content.trim()}
</ReactMarkdown>


<div className="message-wrapper">
{messages.map((msg, index) => {
const isHuman = msg.agent_type === AgentMessageType.HUMAN_AGENT;

return (
<div
key={index}
className={`message ${isHuman ? role.user : role.assistant}`}
>
{!isHuman && (
<div className="plan-chat-header">
<div className="plan-chat-speaker">
<Body1 className="speaker-name">
{TaskService.cleanTextToSpaces(msg.agent)}
</Body1>
<Tag
size="extra-small"
shape="rounded"
appearance="brand"
className="bot-tag"
>
BOT
</Tag>
</div>
</div>
)}

<Body1>
<div className="plan-chat-message-content">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypePrism]}
>
{TaskService.cleanHRAgent(msg.content) || ""}
</ReactMarkdown>

{!isHuman && (
<div className="assistant-footer">
<div className="assistant-actions">
<div>
<Button
onClick={() =>
msg.content &&
navigator.clipboard.writeText(msg.content)
}
title="Copy Response"
appearance="subtle"
style={{ height: 28, width: 28 }}
icon={<Copy />}
/>
</div>

<Tag
icon={<DiamondRegular />}
appearance="filled"
size="extra-small"
>
Sample data for demonstration purposes only.
</Tag>
</div>
</div>
)}
</div>
</Body1>
</div>
</div>
))}
);
})}
</div>

);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ const renderBufferMessage = (streamingMessageBuffer: string) => {

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

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

return (
<div style={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ const renderPlanExecutionMessage = () => {
backgroundColor: tokens.colorNeutralBackground2,
borderRadius: tokens.borderRadiusMedium,
border: `1px solid ${tokens.colorNeutralStroke1}`,
marginBottom: tokens.spacingVerticalXL
marginBottom: tokens.spacingVerticalXL,
marginTop: tokens.spacingVerticalXL
}}>
<Spinner size="small" />
<span style={{
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/models/agentMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ export interface AgentMessageData {
timestamp: number;
steps: any[]; // intentionally always empty
next_steps: []; // intentionally always empty
raw_content: string;
content: string;
raw_data: string;
}
Loading
Loading