import { useAuth } from "@lib/auth-context"
import { Avatar, AvatarFallback, AvatarImage } from "@ui/components/avatar"
import type { UIMessage } from "@ai-sdk/react"
import {
type ChatMemoryCard,
memoryResultsFromSearchToolOutput,
} from "@/lib/chat-search-memory-results"
import { isWebSearchToolName } from "@/lib/chat-web-search-tools"
import { MemorySearchResultCard } from "../message/memory-search-result-card"
interface ReasoningStep {
type: string
state?: string
message: string
}
export function ChainOfThought({ messages }: { messages: UIMessage[] }) {
const { user } = useAuth()
// Group messages into user-assistant pairs
const messagePairs: Array<{
userMessage: UIMessage
agentMessage?: UIMessage
}> = []
let lastUserPair: {
userMessage: UIMessage
agentMessage?: UIMessage
} | null = null
for (let i = 0; i < messages.length; i++) {
const message = messages[i]
if (!message) continue
if (message.role === "user") {
lastUserPair = { userMessage: message }
messagePairs.push(lastUserPair)
} else if (
message.role === "assistant" &&
lastUserPair &&
!lastUserPair.agentMessage
) {
lastUserPair.agentMessage = message
}
}
return (
{messagePairs.map((pair, pairIdx) => {
const userMessageText =
pair.userMessage.parts.find((part) => part.type === "text")?.text ??
""
const reasoningSteps: ReasoningStep[] = []
if (pair.agentMessage) {
pair.agentMessage.parts.forEach((part) => {
if (part.type === "tool-searchMemories") {
if (
part.state === "input-available" ||
part.state === "input-streaming"
) {
reasoningSteps.push({
type: part.type,
state: part.state,
message: "Searching memories...",
})
} else if (part.state === "output-available") {
reasoningSteps.push({
type: part.type,
state: part.state,
message: "Found relevant memories",
})
} else if (part.state === "output-error") {
reasoningSteps.push({
type: part.type,
state: part.state,
message: "Error searching memories",
})
}
return
}
const webSearchPart =
part.type === "dynamic-tool" &&
isWebSearchToolName(
(part as { toolName?: string }).toolName ?? "",
)
? (part as {
type: "dynamic-tool"
toolName: string
state: string
})
: part.type === "tool-web_search" ||
part.type === "tool-google_search"
? (part as { type: string; state: string })
: null
if (webSearchPart) {
if (
webSearchPart.state === "input-available" ||
webSearchPart.state === "input-streaming"
) {
reasoningSteps.push({
type: "web-search",
state: webSearchPart.state,
message: "Searching the web...",
})
} else if (webSearchPart.state === "output-available") {
reasoningSteps.push({
type: "web-search",
state: webSearchPart.state,
message: "Explored the web",
})
} else if (webSearchPart.state === "output-error") {
reasoningSteps.push({
type: "web-search",
state: webSearchPart.state,
message: "Web search failed",
})
}
}
})
const webSourceCount = pair.agentMessage.parts.filter(
(p) => p.type === "source-url",
).length
if (webSourceCount > 0) {
const hasToolWebDone = reasoningSteps.some(
(s) => s.type === "web-search" && s.state === "output-available",
)
if (!hasToolWebDone) {
reasoningSteps.push({
type: "web-sources",
state: "done",
message:
webSourceCount === 1
? "Found 1 web source"
: `Found ${webSourceCount} web sources`,
})
}
}
}
const memoryResults: ChatMemoryCard[] = []
if (pair.agentMessage) {
pair.agentMessage.parts.forEach((part) => {
if (
part.type === "tool-searchMemories" &&
part.state === "output-available"
) {
memoryResults.push(
...memoryResultsFromSearchToolOutput(part.output),
)
}
})
}
return (
{user && (
{user?.name?.charAt(0)}
)}
{userMessageText}
{(reasoningSteps.length > 0 || memoryResults.length > 0) && (
{reasoningSteps.length > 0 && (
{reasoningSteps.map((step, idx) => (
{step.message}
))}
)}
{memoryResults.length > 0 && (
{memoryResults.map((result, idx) => (
))}
)}
)}
)
})}
)
}