import type { UseStream } from "@langchain/langgraph-sdk/react"; import { Conversation, ConversationContent, } from "@/components/ai-elements/conversation"; import { MessageResponse } from "@/components/ai-elements/message"; import { useI18n } from "@/core/i18n/hooks"; import { extractContentFromMessage, extractPresentFilesFromMessage, extractTextFromMessage, groupMessages, hasContent, hasPresentFiles, hasReasoning, } from "@/core/messages/utils"; import { useRehypeSplitWordsIntoSpans } from "@/core/rehype"; import type { Subtask } from "@/core/tasks"; import { useUpdateSubtask } from "@/core/tasks/context"; import type { AgentThreadState } from "@/core/threads"; import { cn } from "@/lib/utils"; import { ArtifactFileList } from "../artifacts/artifact-file-list"; import { StreamingIndicator } from "../streaming-indicator"; import { MessageGroup } from "./message-group"; import { MessageListItem } from "./message-list-item"; import { MessageListSkeleton } from "./skeleton"; import { SubtaskCard } from "./subtask-card"; export function MessageList({ className, threadId, thread, paddingBottom = 160, }: { className?: string; threadId: string; thread: UseStream; paddingBottom?: number; }) { const { t } = useI18n(); const rehypePlugins = useRehypeSplitWordsIntoSpans(thread.isLoading); const updateSubtask = useUpdateSubtask(); if (thread.isThreadLoading) { return ; } return ( {groupMessages(thread.messages, (group) => { if (group.type === "human" || group.type === "assistant") { return ( ); } else if (group.type === "assistant:clarification") { const message = group.messages[0]; if (message && hasContent(message)) { return ( {extractContentFromMessage(message)} ); } return null; } else if (group.type === "assistant:present-files") { const files: string[] = []; for (const message of group.messages) { if (hasPresentFiles(message)) { const presentFiles = extractPresentFilesFromMessage(message); files.push(...presentFiles); } } return (
{group.messages[0] && hasContent(group.messages[0]) && ( {extractContentFromMessage(group.messages[0])} )}
); } else if (group.type === "assistant:subagent") { const tasks = new Set(); for (const message of group.messages) { if (message.type === "ai") { for (const toolCall of message.tool_calls ?? []) { if (toolCall.name === "task") { const task: Subtask = { id: toolCall.id!, subagent_type: toolCall.args.subagent_type, description: toolCall.args.description, prompt: toolCall.args.prompt, status: "in_progress", }; updateSubtask(task); tasks.add(task); } } } else if (message.type === "tool") { const taskId = message.tool_call_id; if (taskId) { const result = extractTextFromMessage(message); if (result.startsWith("Task Succeeded. Result:")) { updateSubtask({ id: taskId, status: "completed", result: result .split("Task Succeeded. Result:")[1] ?.trim(), }); } else if (result.startsWith("Task failed.")) { updateSubtask({ id: taskId, status: "failed", error: result.split("Task failed.")[1]?.trim(), }); } else if (result.startsWith("Task timed out")) { updateSubtask({ id: taskId, status: "failed", error: result, }); } else { updateSubtask({ id: taskId, status: "in_progress", }); } } } } const results: React.ReactNode[] = []; for (const message of group.messages.filter( (message) => message.type === "ai", )) { if (hasReasoning(message)) { results.push( , ); } if (tasks.size > 1) { results.push(
{t.subtasks.executing(tasks.size)}
, ); } const taskIds = message.tool_calls?.map( (toolCall) => toolCall.id, ); for (const taskId of taskIds ?? []) { results.push( , ); } } return (
{results}
); } return ( ); })} {thread.isLoading && }
); }