handle ActivitySnapshotEvent in web frontend and fix CopilotKit 1.54.0 styling

This commit is contained in:
Yiorgis Gozadinos 2026-03-13 12:39:55 +02:00
parent 1d108b42ec
commit 9e29a7e849
No known key found for this signature in database
4 changed files with 636 additions and 252 deletions

View file

@ -36,9 +36,38 @@ a {
--ring: #3b82f6;
}
/* Chat message area — use full width instead of max-w-3xl */
.chat-content .max-w-3xl {
max-width: 100%;
/* Chat message area use full width instead of CopilotKit's max-width constraints.
Scoped to scroll area; the input area inherits CopilotKit's natural sizing. */
.chat-scroll-area .max-w-3xl,
.chat-scroll-area [class*="cpk:max-w-3xl"],
.chat-scroll-area [class*="cpk:max-w-"] {
max-width: 100% !important;
}
/* In the empty state (no chat-scroll-area), widen only the message view container */
.copilotKitChat [class*="cpk:max-w-3xl"]:not(:has(.copilotKitInput)) {
max-width: 100% !important;
}
/* Empty state input — stretch to full width with horizontal margin */
.copilotKitChat [class*="cpk:max-w-3xl"]:has(.copilotKitInput) {
max-width: 100% !important;
padding: 0 1rem;
}
/* Input box — breathing room for send button and bottom spacing */
.copilotKitInput {
padding-right: 0.5rem !important;
margin-bottom: 0.5rem !important;
}
/* User message bubble — more generous padding and spacing */
.copilotKitUserMessage {
margin-top: 1.5rem !important;
}
.copilotKitUserMessage [class*="cpk:rounded"] {
padding: 12px 20px !important;
}
/* Render-prop layout: flex column so citations sit between messages and input */
@ -57,7 +86,7 @@ a {
.chat-input-area {
position: relative;
padding: 0 0.75rem 0.5rem;
padding: 0 1rem 1rem;
}
/* Override CopilotKit's absolute positioning on the input container
@ -69,6 +98,11 @@ a {
right: auto;
}
/* Make the input stretch to full width within its padded container */
.chat-input-area [class*="cpk:max-w-"] {
max-width: 100% !important;
}
/* User message bubble spacing and padding */
[data-message-id].items-end {
padding-top: 1rem;

View file

@ -230,6 +230,39 @@ function ToolCallIndicator({
);
}
// Render an activity message from a skill sub-agent tool call/result
function ActivityIndicator({
message,
isComplete,
}: {
// biome-ignore lint/suspicious/noExplicitAny: AG-UI activity message shape
message: any;
isComplete: boolean;
}) {
const content = message.content ?? {};
const toolName = content.tool_name ?? "tool";
let args: Record<string, unknown> = {};
if (content.args) {
try {
args =
typeof content.args === "string"
? JSON.parse(content.args)
: content.args;
} catch {
// ignore parse errors
}
}
return (
<ToolCallIndicator
toolName={toolName}
status={isComplete ? "complete" : "loading"}
args={args}
/>
);
}
// Context for sharing chat state with the message view
const ChatStateContext = createContext<RAGState | null>(null);
@ -261,6 +294,21 @@ function MessageViewWithCitations({
const ragState = useContext(ChatStateContext);
const citationsHistory = ragState ? deriveCitationsHistory(ragState) : [];
// Collect completed tool_call_ids from skill_tool_result activity messages
const completedToolCallIds = useMemo(() => {
const ids = new Set<string>();
for (const msg of messages) {
if (
msg.role === "activity" &&
msg.activityType === "skill_tool_result" &&
msg.content?.tool_call_id
) {
ids.add(msg.content.tool_call_id);
}
}
return ids;
}, [messages]);
const cursor = isRunning ? (
<div key="cursor" className="streaming-cursor">
<span className="dot" />
@ -273,17 +321,63 @@ function MessageViewWithCitations({
<CopilotChatMessageView messages={messages} isRunning={isRunning}>
{({ messageElements }) => {
if (!citationsHistory.length) {
// Check if there are activity messages to render
const hasActivity = messages.some(
(msg: { role: string }) => msg.role === "activity",
);
if (!hasActivity) {
return (
<>
{messageElements}
{cursor}
</>
);
}
// Interleave activity indicators with CopilotKit elements
const result: React.ReactNode[] = [];
let eIdx = 0;
for (const msg of messages) {
if (
msg.role === "activity" &&
msg.activityType === "skill_tool_call"
) {
const toolCallId = msg.content?.tool_call_id;
result.push(
<ActivityIndicator
key={msg.id}
message={msg}
isComplete={
toolCallId ? completedToolCallIds.has(toolCallId) : false
}
/>,
);
continue;
}
if (msg.role === "activity") continue;
if (msg.role !== "user" && msg.role !== "assistant") continue;
if (eIdx < messageElements.length) {
result.push(messageElements[eIdx]);
eIdx++;
}
}
while (eIdx < messageElements.length) {
result.push(messageElements[eIdx]);
eIdx++;
}
return (
<>
{messageElements}
{result}
{cursor}
</>
);
}
// CopilotChatMessageView renders one element per user/assistant/activity
// CopilotChatMessageView renders one element per user/assistant
// message (tool messages produce nothing). We correlate elements with
// messages to inject CitationBlocks after the right assistant responses.
// Activity messages (skill sub-agent tool calls) are rendered by us
// directly, not by CopilotKit.
//
// Both search and ask tools append to citations via qa_history,
// so after each assistant text response that followed tool calls,
@ -306,10 +400,28 @@ function MessageViewWithCitations({
seenToolCalls = true;
}
const isRendered =
msg.role === "user" ||
msg.role === "assistant" ||
msg.role === "activity";
// Activity messages are not rendered by CopilotKit —
// render them ourselves without consuming messageElements
if (msg.role === "activity") {
if (msg.activityType === "skill_tool_call") {
const toolCallId = msg.content?.tool_call_id;
const isComplete = toolCallId
? completedToolCallIds.has(toolCallId)
: false;
result.push(
<ActivityIndicator
key={msg.id}
message={msg}
isComplete={isComplete}
/>,
);
}
// Skip skill_tool_result — completion is shown
// on the corresponding skill_tool_call indicator
continue;
}
const isRendered = msg.role === "user" || msg.role === "assistant";
if (!isRendered) continue;
if (elemIdx < messageElements.length) {

View file

@ -11,9 +11,10 @@
"format": "biome check --write app components lib"
},
"dependencies": {
"@ag-ui/client": "^0.0.43",
"@copilotkit/react-core": "^1.51.4",
"@copilotkit/runtime": "^1.51.4",
"@ag-ui/client": "^0.0.47",
"@copilotkit/react-core": "^1.54.0",
"@copilotkit/runtime": "^1.54.0",
"zod": "^3.24.0",
"next": "^16.1.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"

File diff suppressed because it is too large Load diff