11import React from "react"
2- import { useState } from "react" // Importing useState hook from React for managing component state
2+ import { useState } from "react"
33import {
4- IconClipboard , // Icon for clipboard (copy) action
5- IconCheck , // Icon for checkmark (confirmation) action
6- IconBrain , // Icon for brain (memory) feature
7- IconSettings , // Icon for settings (agents) feature
8- IconGlobe , // Icon for globe (internet) feature
9- IconLink , // Icon for external link
10- IconMail , // Icon for mail
4+ IconClipboard ,
5+ IconCheck ,
6+ IconBrain ,
7+ IconSettings ,
8+ IconGlobe ,
9+ IconLink ,
10+ IconMail ,
1111 IconCode ,
1212 IconChevronDown ,
1313 IconChevronUp ,
1414 IconTerminal2
15- } from "@tabler/icons-react" // Importing icons from tabler-icons-react library
16- import { Tooltip } from "react-tooltip" // Importing Tooltip component for displaying tooltips
17- import ReactMarkdown from "react-markdown" // Importing ReactMarkdown component for rendering Markdown content
18- import remarkGfm from "remark-gfm" // Importing remarkGfm plugin for ReactMarkdown to support GitHub Flavored Markdown
19- import IconGoogleDocs from "./icons/IconGoogleDocs" // Importing custom icon component for Google Docs
20- import IconGoogleSheets from "./icons/IconGoogleSheets" // Importing custom icon component for Google Sheets
21- import IconGoogleCalendar from "./icons/IconGoogleCalendar" // Importing custom icon component for Google Calendar
22- import IconGoogleSlides from "./icons/IconGoogleSlides" // Importing custom icon component for Google Slides
23- import IconGoogleDrive from "./icons/IconGoogleDrive" // Importing custom icon component for Google Drive
24- import IconGoogleMail from "./icons/IconGoogleMail" // Importing custom icon component for Google Mail
25- import toast from "react-hot-toast" // Importing toast for displaying toast notifications
15+ } from "@tabler/icons-react"
16+ import { Tooltip } from "react-tooltip"
17+ import ReactMarkdown from "react-markdown"
18+ import remarkGfm from "remark-gfm"
19+ import IconGoogleDocs from "./icons/IconGoogleDocs"
20+ import IconGoogleSheets from "./icons/IconGoogleSheets"
21+ import IconGoogleCalendar from "./icons/IconGoogleCalendar"
22+ import IconGoogleSlides from "./icons/IconGoogleSlides"
23+ import IconGoogleDrive from "./icons/IconGoogleDrive"
24+ import IconGoogleMail from "./icons/IconGoogleMail"
25+ import toast from "react-hot-toast"
2626
27- // LinkButton component remains the same...
27+ // LinkButton component to handle different types of links with custom icons
2828const LinkButton = ( { href, children } ) => {
2929 const toolMapping = {
3030 "drive.google.com" : {
@@ -88,12 +88,12 @@ const LinkButton = ({ href, children }) => {
8888 } }
8989 >
9090 { icon }
91- < span > { name } </ span > { " " }
91+ < span > { name } </ span >
9292 </ span >
9393 )
9494}
9595
96- // ToolCodeBlock component remains the same...
96+ // ToolCodeBlock component to display tool calls in a collapsible format
9797const ToolCodeBlock = ( { name, code, isExpanded, onToggle } ) => {
9898 let formattedCode = code
9999 try {
@@ -129,7 +129,7 @@ const ToolCodeBlock = ({ name, code, isExpanded, onToggle }) => {
129129 )
130130}
131131
132- // ToolResultBlock component remains the same...
132+ // ToolResultBlock component to display tool results in a collapsible format
133133const ToolResultBlock = ( { name, result, isExpanded, onToggle } ) => {
134134 let formattedResult = result
135135 try {
@@ -165,16 +165,19 @@ const ToolResultBlock = ({ name, result, isExpanded, onToggle }) => {
165165 )
166166}
167167
168+ // Main ChatBubble component
168169const ChatBubble = ( {
169170 message,
170171 isUser,
171172 memoryUsed,
172173 agentsUsed,
173- internetUsed
174+ internetUsed,
175+ isStreamDone // Prop to track if the stream is complete
174176} ) => {
175177 const [ copied , setCopied ] = useState ( false )
176178 const [ expandedStates , setExpandedStates ] = useState ( { } )
177179
180+ // Function to copy message content to clipboard
178181 const handleCopyToClipboard = ( ) => {
179182 let textToCopy = message
180183 try {
@@ -183,7 +186,6 @@ const ChatBubble = ({
183186 } catch ( e ) {
184187 // Not a JSON string, copy as is
185188 }
186-
187189 navigator . clipboard
188190 . writeText ( textToCopy )
189191 . then ( ( ) => {
@@ -193,10 +195,12 @@ const ChatBubble = ({
193195 . catch ( ( err ) => toast . error ( `Failed to copy text: ${ err } ` ) )
194196 }
195197
198+ // Function to toggle expansion of collapsible sections
196199 const toggleExpansion = ( id ) => {
197200 setExpandedStates ( ( prev ) => ( { ...prev , [ id ] : ! prev [ id ] } ) )
198201 }
199202
203+ // Function to render message content, processing special tags and text
200204 const renderMessageContent = ( ) => {
201205 if ( isUser || typeof message !== "string" || ! message ) {
202206 return (
@@ -218,20 +222,13 @@ const ChatBubble = ({
218222 / ( < t h i n k > [ \s \S ] * ?< \/ t h i n k > | < t o o l _ c o d e n a m e = " [ ^ " ] + " > [ \s \S ] * ?< \/ t o o l _ c o d e > | < t o o l _ r e s u l t t o o l _ n a m e = " [ ^ " ] + " > [ \s \S ] * ?< \/ t o o l _ r e s u l t > ) / g
219223 let lastIndex = 0
220224
221- // --- START OF THE ROBUST CONTEXTUAL FIX ---
222-
223225 for ( const match of message . matchAll ( regex ) ) {
224- // 1. Process the text *before* the current valid tag
225226 if ( match . index > lastIndex ) {
226227 const textContent = message . substring ( lastIndex , match . index )
227228 const lastPart =
228229 contentParts . length > 0
229230 ? contentParts [ contentParts . length - 1 ]
230231 : null
231-
232- // This is the key: Only add text if it's at the start of the message
233- // or if it follows another text block. This filters out any text
234- // sandwiched between special tags (e.g., </tool_code>...JUNK...<tool_result>).
235232 if ( ! lastPart || lastPart . type === "text" ) {
236233 if ( textContent . trim ( ) ) {
237234 contentParts . push ( {
@@ -242,7 +239,6 @@ const ChatBubble = ({
242239 }
243240 }
244241
245- // 2. Process the valid, complete tag itself
246242 const tag = match [ 0 ]
247243 let subMatch
248244
@@ -273,21 +269,17 @@ const ChatBubble = ({
273269 }
274270 }
275271
276- // 3. Update our position in the message string
277272 lastIndex = match . index + tag . length
278273 }
279274
280- // 4. Process any remaining text after the last valid tag.
281- // This is often the final answer from the assistant.
282- if ( lastIndex < message . length ) {
275+ // Only add remaining text if the stream is done
276+ if ( isStreamDone && lastIndex < message . length ) {
283277 const remainingText = message . substring ( lastIndex )
284278 if ( remainingText . trim ( ) ) {
285279 contentParts . push ( { type : "text" , content : remainingText } )
286280 }
287281 }
288282
289- // --- END OF THE ROBUST CONTEXTUAL FIX ---
290-
291283 return contentParts . map ( ( part , index ) => {
292284 const partId = `${ part . type } _${ index } `
293285 if ( part . type === "think" && part . content ) {
@@ -370,7 +362,6 @@ const ChatBubble = ({
370362 style = { { wordBreak : "break-word" } }
371363 >
372364 { renderMessageContent ( ) }
373-
374365 { ! isUser && (
375366 < div className = "flex justify-start items-center space-x-4 mt-6" >
376367 < Tooltip id = "chat-bubble-tooltip" />
@@ -421,4 +412,4 @@ const ChatBubble = ({
421412 )
422413}
423414
424- export default ChatBubble
415+ export default ChatBubble
0 commit comments