Skip to content
Open
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
134 changes: 54 additions & 80 deletions src/app/components/ChatInterface/ChatInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type { SubAgent, TodoItem, ToolCall } from "../../types/types";
import { useChat } from "../../hooks/useChat";
import styles from "./ChatInterface.module.scss";
import { Message } from "@langchain/langgraph-sdk";
import { extractStringFromMessageContent } from "../../utils/utils";
import { extractStringFromMessageContent, generateToolCallId } from "../../utils/utils";

interface ChatInterfaceProps {
threadId: string | null;
Expand Down Expand Up @@ -93,93 +93,67 @@ export const ChatInterface = React.memo<ChatInterfaceProps>(
const hasMessages = messages.length > 0;

const processedMessages = useMemo(() => {
/*
1. Loop through all messages
2. For each AI message, add the AI message, and any tool calls to the messageMap
3. For each tool message, find the corresponding tool call in the messageMap and update the status and output
*/
const messageMap = new Map<string, any>();
messages.forEach((message: Message) => {
if (message.type === "ai") {
const toolCallsInMessage: any[] = [];
if (
message.additional_kwargs?.tool_calls &&
Array.isArray(message.additional_kwargs.tool_calls)
) {
toolCallsInMessage.push(...message.additional_kwargs.tool_calls);
} else if (message.tool_calls && Array.isArray(message.tool_calls)) {
toolCallsInMessage.push(
...message.tool_calls.filter(
(toolCall: any) => toolCall.name !== "",
),
);
} else if (Array.isArray(message.content)) {
const toolUseBlocks = message.content.filter(
(block: any) => block.type === "tool_use",
);
toolCallsInMessage.push(...toolUseBlocks);
}
const toolCallsWithStatus = toolCallsInMessage.map(
(toolCall: any) => {
const name =
toolCall.function?.name ||
toolCall.name ||
toolCall.type ||
"unknown";
const args =
toolCall.function?.arguments ||
toolCall.args ||
toolCall.input ||
{};
return {
id: toolCall.id || `tool-${Math.random()}`,
name,
args,
status: "pending" as const,
} as ToolCall;
},
);
messageMap.set(message.id!, {
message,
toolCalls: toolCallsWithStatus,
});
} else if (message.type === "tool") {
const toolCallId = message.tool_call_id;
if (!toolCallId) {
return;
}
for (const [, data] of messageMap.entries()) {
const toolCallIndex = data.toolCalls.findIndex(
(tc: any) => tc.id === toolCallId,
);
if (toolCallIndex === -1) {
continue;
}
const messageMap = new Map<string, { message: Message; toolCalls: ToolCall[]; showAvatar: boolean }>();

messages.forEach((message, index) => {
if (message.type === "ai") {
const toolCallsInMessage: any[] = [];
if (message.additional_kwargs?.tool_calls && Array.isArray(message.additional_kwargs.tool_calls)) {
toolCallsInMessage.push(...message.additional_kwargs.tool_calls);
} else if (message.tool_calls && Array.isArray(message.tool_calls)) {
toolCallsInMessage.push(...message.tool_calls.filter((toolCall: any) => toolCall.name !== ""));
} else if (Array.isArray(message.content)) {
const toolUseBlocks = message.content.filter((block: any) => block.type === "tool_use");
toolCallsInMessage.push(...toolUseBlocks);
}

const toolCallsWithStatus = toolCallsInMessage.map((toolCall: any) => ({
id: generateToolCallId(toolCall),
name: toolCall.function?.name || toolCall.name || toolCall.type || "unknown",
args: toolCall.function?.arguments || toolCall.args || toolCall.input || {},
status: "pending" as const,
} as ToolCall));

messageMap.set(message.id!, {
message,
toolCalls: toolCallsWithStatus,
showAvatar: false, // Will be updated later
});
} else if (message.type === "tool") {
const toolCallId = message.tool_call_id;
if (!toolCallId) {
return;
}
for (const [, data] of messageMap.entries()) {
const toolCallIndex = data.toolCalls.findIndex((tc) => tc.id === toolCallId);
if (toolCallIndex !== -1) {
data.toolCalls[toolCallIndex] = {
...data.toolCalls[toolCallIndex],
status: "completed" as const,
// TODO: Make this nicer
result: extractStringFromMessageContent(message),
};
break;
}
} else if (message.type === "human") {
messageMap.set(message.id!, {
message,
toolCalls: [],
});
}
});
const processedArray = Array.from(messageMap.values());
return processedArray.map((data, index) => {
const prevMessage =
index > 0 ? processedArray[index - 1].message : null;
return {
...data,
showAvatar: data.message.type !== prevMessage?.type,
};
});
}, [messages]);
} else if (message.type === "human") {
messageMap.set(message.id!, {
message,
toolCalls: [],
showAvatar: false, // Will be updated later
});
}
});

const processedArray = Array.from(messageMap.values());

return processedArray.map((data, index) => {
const prevMessage = index > 0 ? processedArray[index - 1].message : null;
return {
...data,
showAvatar: data.message.type !== prevMessage?.type,
};
});
}, [messages]);

return (
<div className={styles.container}>
Expand Down
31 changes: 21 additions & 10 deletions src/app/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { Message } from "@langchain/langgraph-sdk";

export function extractStringFromMessageContent(message: Message): string {
return typeof message.content === "string"
? message.content
: Array.isArray(message.content)
? message.content
.filter((c: any) => c.type === "text" || typeof c === "string")
.map((c: any) => (typeof c === "string" ? c : c.text || ""))
.join("")
: "";
}
export const extractStringFromMessageContent = (message: Message): string => {
if (typeof message.content === "string") {
return message.content;
}
if (Array.isArray(message.content)) {
const textBlock = message.content.find((block) => block.type === "text");
if (textBlock && "text" in textBlock) {
return textBlock.text;
}
}
return JSON.stringify(message.content);
};

export const generateToolCallId = (toolCall: any): string => {
if (toolCall.id) {
return toolCall.id;
}
const name = toolCall.function?.name || toolCall.name || toolCall.type || "unknown";
const args = toolCall.function?.arguments || toolCall.args || toolCall.input || {};
return `tool-${name}-${JSON.stringify(args)}`;
};