diff --git a/src/components/BitteAiChat.tsx b/src/components/BitteAiChat.tsx index d767a3d..ee886b6 100644 --- a/src/components/BitteAiChat.tsx +++ b/src/components/BitteAiChat.tsx @@ -13,6 +13,7 @@ export const BitteAiChat = ({ historyApiUrl, agentId, options, + messageComponents }: BitteAiChatProps) => { const [loadedData, setLoadedData] = useState({ agentIdLoaded: "", @@ -61,6 +62,7 @@ export const BitteAiChat = ({ agentId={agentId ?? agentIdLoaded} messages={uiMessages} options={optionsProps} + messageComponents={messageComponents} /> ); diff --git a/src/components/chat/ChatContent.tsx b/src/components/chat/ChatContent.tsx index 1ec2f3d..7d4478c 100644 --- a/src/components/chat/ChatContent.tsx +++ b/src/components/chat/ChatContent.tsx @@ -32,6 +32,7 @@ export const ChatContent = ({ apiKey, options, messages: initialMessages, + messageComponents }: BitteAiChatProps) => { const chatId = useRef(options?.chatId || generateId()).current; const [isAtBottom, setIsAtBottom] = useState(true); @@ -239,6 +240,7 @@ export const ChatContent = ({ textColor={textColor!} agentImage={options?.agentImage} addToolResult={addToolResult} + components={messageComponents} /> ); })} diff --git a/src/components/chat/MessageGroup.tsx b/src/components/chat/MessageGroup.tsx index 0bae7f9..020e39b 100644 --- a/src/components/chat/MessageGroup.tsx +++ b/src/components/chat/MessageGroup.tsx @@ -1,73 +1,124 @@ -import { useState, useEffect } from "react"; +import { MessageSquare } from 'lucide-react' +import { useEffect, useState } from "react" +import { getAgentIdFromMessage } from '../../lib/chat' +import { DEFAULT_AGENT_ID } from '../../lib/constants' +import { BITTE_BLACK_IMG } from "../../lib/images" +import { cn } from "../../lib/utils" +import type { BitteToolResult, ChatCustomComponents, MessageGroupComponentProps, SmartActionAiMessage, ToolResultComponentProps } from "../../types/types" +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "../ui/accordion" +import { Card } from "../ui/card" +import { ImageWithFallback } from "../ui/ImageWithFallback" +import { SAMessage } from "./Message" -import { MessageSquare } from "lucide-react"; - -import { NearSafe } from "near-safe"; - -import { Wallet } from "@near-wallet-selector/core"; -import { Account } from "near-api-js"; -import { cn, safeStringify } from "../../lib/utils"; + // Function to remove ".vercel.app" from agentId + export const formatAgentId = (agentId: string) => { + return agentId.replace(".vercel.app", ""); + }; -import { getAgentIdFromMessage } from "../../lib/chat"; -import { BittePrimitiveName, DEFAULT_AGENT_ID } from "../../lib/constants"; -import { BITTE_BLACK_IMG } from "../../lib/images"; -import { isDataString } from "../../lib/regex"; -import { SmartActionAiMessage, BitteToolResult } from "../../types/types"; -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "../ui/accordion"; -import { Button } from "../ui/button"; -import { Card } from "../ui/card"; -import { ChartWrapper } from "../ui/charts/ChartWrapper"; -import { ImageWithFallback } from "../ui/ImageWithFallback"; -import { CodeBlock } from "./CodeBlock"; -import { ErrorBoundary } from "./ErrorBoundary"; -import { SAMessage } from "./Message"; -import { EvmTxCard } from "./transactions/EvmTxCard"; -import { ReviewTransaction } from "./transactions/ReviewTransaction"; -import { ReviewSignMessage } from "./transactions/ReviewSignMessage"; interface MessageGroupProps { - chatId: string | undefined; - groupKey: string; - messages: SmartActionAiMessage[]; - accountId: string; - creator?: string; - isLoading?: boolean; - agentImage?: string; - agentName?: string; - evmAdapter?: NearSafe; - account?: Account; - wallet?: Wallet; - messageBackgroundColor: string; - borderColor: string; - textColor: string; - addToolResult: (params: { - toolCallId: string; - result: BitteToolResult; - }) => void; + chatId?: string + groupKey: string + messages: SmartActionAiMessage[] + accountId: string + creator?: string + isLoading?: boolean + agentImage?: string + agentName?: string + messageBackgroundColor: string + borderColor: string + textColor: string + addToolResult: (params: { toolCallId: string; result: BitteToolResult }) => void + components?: ChatCustomComponents } +// Default components +const DefaultMessageContainer = ({ message, isUser, userName, children, style }: MessageGroupComponentProps) => ( + + + + +
+ {isUser ? ( + <> + +

{userName}

+ + ) : ( + <> + +

+ {formatAgentId(message.agentId ?? "Bitte Assistant")} +

+ + )} +
+
+ + {children} + +
+
+
+) + +const DefaultToolResult = ({ toolName, result, style }: ToolResultComponentProps) => ( +
+
+
Tool Call
+
+ {toolName} +
+
+
+ {/* Tool result rendering logic */} +
+
+
+) + export const MessageGroup = ({ groupKey, messages, accountId, creator, - isLoading, agentImage, messageBackgroundColor, borderColor, textColor, chatId, addToolResult, + components = {} }: MessageGroupProps) => { - // State to track agentId for each message - const [messagesWithAgentId, setMessagesWithAgentId] = useState< - SmartActionAiMessage[] - >([]); + const { + MessageContainer = DefaultMessageContainer, + ToolResult = DefaultToolResult + } = components + + const [messagesWithAgentId, setMessagesWithAgentId] = useState([]) // Function to update agentId for each message const updateAgentIdForMessages = ( @@ -102,237 +153,40 @@ export const MessageGroup = ({ return (
{messagesWithAgentId?.map((message, index) => { - const uniqueKey = `${groupKey}-${index}`; - - if (message.toolInvocations) { - for (const invocation of message.toolInvocations) { - const { toolName, toolCallId, state, args } = invocation; - const result = - invocation.state === "result" ? invocation.result : null; - - if (state !== "result") { - if (toolName === BittePrimitiveName.SIGN_MESSAGE) { - const { message, nonce, recipient, callbackUrl } = args; - - return ( - - addToolResult({ - toolCallId: toolCallId, - result, - }) - } - /> - ); - } - - continue; - } - - if ( - toolName === BittePrimitiveName.GENERATE_TRANSACTION || - toolName === BittePrimitiveName.TRANSFER_FT || - toolName === BittePrimitiveName.GENERATE_EVM_TX - ) { - const transactions = result?.data?.transactions || []; - const evmSignRequest = result?.data?.evmSignRequest; - - return ( - - {evmSignRequest ? ( -
- -
- ) : ( -
- -
- )} -
- ); - } - } - } + const isUser = message.role === "user" + const userName = creator || accountId return ( - - - - -
- {message.role === "user" ? ( - <> - -

- {creator || accountId} -

- - ) : ( - <> - -

- {formatAgentId(message?.agentId ?? "Bitte Assistant")} -

- - )} -
-
- - -
- {message.content && ( -
- -
- )} - - {message.toolInvocations?.map((toolInvocation, index) => { - const { toolName, toolCallId, state } = toolInvocation; - const result = - toolInvocation.state === "result" - ? toolInvocation.result - : null; - - return ( -
-
-
Tool Call
-
- {toolName} -
-
-
- {(() => { - if (state === "result") { - switch (toolName) { - case BittePrimitiveName.GENERATE_IMAGE: { - return ( - - ); - } - case BittePrimitiveName.CREATE_DROP: { - return ( - - ); - } - - case BittePrimitiveName.RENDER_CHART: { - const { - title, - description, - chartConfig, - chartData, - metricData, - metricLabels, - dataFormat, - chartType, - } = result.data; - - return ( - - ); - } - default: { - const safeData = safeStringify( - result?.data - ); - return isDataString(safeData) ? ( - - ) : ( -
{safeData}
- ); - } - } - } - })()} -
- -
-
- ); - })} -
- - - - - ); +
+ {message.content && ( +
+ +
+ )} + + {message.toolInvocations?.map((toolInvocation, index) => ( + + ))} +
+ + ) })}
- ); -}; + ) +} \ No newline at end of file diff --git a/src/types/types.ts b/src/types/types.ts index be4ad8f..0606200 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -230,6 +230,7 @@ export interface BitteAiChatProps { messages?: Message[]; wallet?: WalletOptions; options?: BitteAiChatOptions; + messageComponents?: ChatCustomComponents } @@ -332,4 +333,30 @@ export interface AccountCreationData { accountId: string; isCreated: boolean; txnHash?: string; // TODO - I believe this field is unused. +} + +export interface MessageGroupComponentProps { + message: SmartActionAiMessage + isUser: boolean + userName: string + children: React.ReactNode + style: { + backgroundColor: string + borderColor: string + textColor: string + } +} + +export interface ToolResultComponentProps { + toolName: string + result: any + style: { + borderColor: string + } +} + +export interface ChatCustomComponents { + MessageContainer?: React.ComponentType + ToolResult?: React.ComponentType + } \ No newline at end of file