Skip to content

Commit 1abda34

Browse files
committed
fix (chat): tool code rendering (WIP)
1 parent 824bc7e commit 1abda34

1 file changed

Lines changed: 32 additions & 41 deletions

File tree

src/client/components/ChatBubble.js

Lines changed: 32 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
import React from "react"
2-
import { useState } from "react" // Importing useState hook from React for managing component state
2+
import { useState } from "react"
33
import {
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
2828
const 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
9797
const 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
133133
const 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
168169
const 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
/(<think>[\s\S]*?<\/think>|<tool_code name="[^"]+">[\s\S]*?<\/tool_code>|<tool_result tool_name="[^"]+">[\s\S]*?<\/tool_result>)/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

Comments
 (0)