feat(23-03): add messageType dispatch, ChatMessageList propagation, and chatApi handoff methods
- ChatMessage: add messageType/conversationId/onHandoff props; dispatch to ChatSpecCard, ChatHandoffIndicator, ChatTaskCreatedBadge, ChatStatusUpdateBadge based on messageType - ChatMessageList: propagate messageType and conversationId to ChatMessage; add onHandoff prop - chatApi: add handoffSpec() and postStatusUpdate() methods
This commit is contained in:
parent
9205ee82e3
commit
71839e0032
3 changed files with 67 additions and 0 deletions
|
|
@ -148,4 +148,22 @@ export const chatApi = {
|
|||
credentials: "include",
|
||||
});
|
||||
},
|
||||
|
||||
handoffSpec(
|
||||
conversationId: string,
|
||||
spec: { what: string; why: string; constraints: string; success: string },
|
||||
targetRole: string = "pm",
|
||||
) {
|
||||
return api.post<{ handoffMessageId: string; issues: Array<{ id: string; identifier: string; title: string }> }>(
|
||||
`/conversations/${conversationId}/handoff`,
|
||||
{ spec, targetRole },
|
||||
);
|
||||
},
|
||||
|
||||
postStatusUpdate(
|
||||
conversationId: string,
|
||||
data: { agentName: string; taskId: string; taskTitle?: string; taskUrl?: string },
|
||||
) {
|
||||
return api.post<{ id: string }>(`/conversations/${conversationId}/status-update`, data);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ import { ChatMarkdownMessage } from "./ChatMarkdownMessage";
|
|||
import { ChatMessageIdentityBar } from "./ChatMessageIdentityBar";
|
||||
import { ChatStreamingCursor } from "./ChatStreamingCursor";
|
||||
import { ChatMessageActions } from "./ChatMessageActions";
|
||||
import { ChatSpecCard } from "./ChatSpecCard";
|
||||
import { ChatHandoffIndicator } from "./ChatHandoffIndicator";
|
||||
import { ChatTaskCreatedBadge } from "./ChatTaskCreatedBadge";
|
||||
import { ChatStatusUpdateBadge } from "./ChatStatusUpdateBadge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "../lib/utils";
|
||||
import type { AgentRole } from "@paperclipai/shared";
|
||||
|
|
@ -11,6 +15,8 @@ interface ChatMessageProps {
|
|||
id?: string;
|
||||
role: "user" | "assistant" | "system";
|
||||
content: string;
|
||||
messageType?: string | null;
|
||||
conversationId?: string;
|
||||
agentName?: string | null;
|
||||
agentIcon?: string | null;
|
||||
agentRole?: AgentRole | null;
|
||||
|
|
@ -19,12 +25,15 @@ interface ChatMessageProps {
|
|||
isAnyStreaming?: boolean;
|
||||
onEdit?: (messageId: string, newContent: string) => void;
|
||||
onRetry?: (messageId: string) => void;
|
||||
onHandoff?: (spec: { what: string; why: string; constraints: string; success: string }) => void;
|
||||
}
|
||||
|
||||
export function ChatMessage({
|
||||
id,
|
||||
role,
|
||||
content,
|
||||
messageType,
|
||||
conversationId,
|
||||
agentName,
|
||||
agentIcon,
|
||||
agentRole,
|
||||
|
|
@ -33,10 +42,45 @@ export function ChatMessage({
|
|||
isAnyStreaming,
|
||||
onEdit,
|
||||
onRetry,
|
||||
onHandoff,
|
||||
}: ChatMessageProps) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editValue, setEditValue] = useState(content);
|
||||
|
||||
// Dispatch to specialized system message components (Phase 23)
|
||||
if (role === "system" || messageType) {
|
||||
if (messageType === "spec_card") {
|
||||
return (
|
||||
<ChatSpecCard
|
||||
content={content}
|
||||
messageId={id}
|
||||
conversationId={conversationId}
|
||||
onHandoff={onHandoff}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (messageType === "handoff") {
|
||||
return <ChatHandoffIndicator content={content} />;
|
||||
}
|
||||
if (messageType === "task_created") {
|
||||
try {
|
||||
const data = JSON.parse(content) as { taskId?: string; taskTitle?: string; taskUrl?: string };
|
||||
return <ChatTaskCreatedBadge taskId={data.taskId} taskTitle={data.taskTitle} taskUrl={data.taskUrl} />;
|
||||
} catch {
|
||||
return <ChatTaskCreatedBadge />;
|
||||
}
|
||||
}
|
||||
if (messageType === "status_update") {
|
||||
try {
|
||||
const data = JSON.parse(content) as { agentName: string; taskId: string; taskTitle?: string; taskUrl?: string };
|
||||
return <ChatStatusUpdateBadge agentName={data.agentName} taskId={data.taskId} taskTitle={data.taskTitle} taskUrl={data.taskUrl} />;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Fall through to default system message rendering (plain markdown)
|
||||
}
|
||||
|
||||
if (role === "user") {
|
||||
if (isEditing) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ interface ChatMessageListProps {
|
|||
streamingAgentRole?: AgentRole | null;
|
||||
onEdit?: (messageId: string, newContent: string) => void;
|
||||
onRetry?: (messageId: string) => void;
|
||||
onHandoff?: (spec: { what: string; why: string; constraints: string; success: string }) => void;
|
||||
agentMap?: Map<string, { name: string; icon: string | null; role: AgentRole | null }>;
|
||||
}
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ export function ChatMessageList({
|
|||
streamingAgentRole,
|
||||
onEdit,
|
||||
onRetry,
|
||||
onHandoff,
|
||||
agentMap,
|
||||
}: ChatMessageListProps) {
|
||||
const { messages, isLoading } = useChatMessages(conversationId);
|
||||
|
|
@ -143,6 +145,8 @@ export function ChatMessageList({
|
|||
id={msg.id}
|
||||
role={msg.role as "user" | "assistant" | "system"}
|
||||
content={msg.content}
|
||||
messageType={msg.messageType}
|
||||
conversationId={conversationId}
|
||||
agentName={agent?.name ?? streamingAgentName}
|
||||
agentIcon={agent?.icon ?? streamingAgentIcon}
|
||||
agentRole={agent?.role ?? streamingAgentRole}
|
||||
|
|
@ -151,6 +155,7 @@ export function ChatMessageList({
|
|||
isAnyStreaming={isStreaming}
|
||||
onEdit={onEdit}
|
||||
onRetry={onRetry}
|
||||
onHandoff={onHandoff}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue