Skip to content

Commit 0b4cc27

Browse files
committed
fix (chat): tool code rendering (WIP)
1 parent ffecc93 commit 0b4cc27

File tree

2 files changed

+39
-48
lines changed

2 files changed

+39
-48
lines changed

src/client/components/ChatBubble.js

Lines changed: 37 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -172,22 +172,23 @@ const ChatBubble = ({
172172
memoryUsed,
173173
agentsUsed,
174174
internetUsed,
175-
isStreamDone // Prop to track if the stream is complete
175+
isStreamDone
176176
}) => {
177177
const [copied, setCopied] = useState(false)
178178
const [expandedStates, setExpandedStates] = useState({})
179179

180180
// Function to copy message content to clipboard
181181
const handleCopyToClipboard = () => {
182-
let textToCopy = message
183-
try {
184-
const parsed = JSON.parse(message)
185-
textToCopy = JSON.stringify(parsed, null, 2)
186-
} catch (e) {
187-
// Not a JSON string, copy as is
188-
}
182+
// Filter out the tags and copy the plain text content.
183+
const plainText = message
184+
.replace(/<think>[\s\S]*?<\/think>/g, "")
185+
.replace(/<tool_code[^>]*>[\s\S]*?<\/tool_code>/g, "")
186+
.replace(/<tool_result[^>]*>[\s\S]*?<\/tool_result>/g, "")
187+
.replace(/<answer>([\s\S]*?)<\/answer>/g, "$1")
188+
.trim()
189+
189190
navigator.clipboard
190-
.writeText(textToCopy)
191+
.writeText(plainText)
191192
.then(() => {
192193
setCopied(true)
193194
setTimeout(() => setCopied(false), 2000)
@@ -219,20 +220,12 @@ const ChatBubble = ({
219220

220221
const contentParts = []
221222
const regex =
222-
/(<think>[\s\S]*?<\/think>|<tool_code name="[^"]+">[\s\S]*?<\/tool_code>|<tool_result tool_name="[^"]+">[\s\S]*?<\/tool_result>)/g
223+
/(<think>[\s\S]*?<\/think>|<tool_code name="[^"]+">[\s\S]*?<\/tool_code>|<tool_result tool_name="[^"]+">[\s\S]*?<\/tool_result>|<answer>[\s\S]*?<\/answer>)/g
223224
let lastIndex = 0
224225

225226
// Parse the message into parts
226227
for (const match of message.matchAll(regex)) {
227-
if (match.index > lastIndex) {
228-
const textContent = message.substring(lastIndex, match.index)
229-
if (textContent.trim()) {
230-
contentParts.push({
231-
type: "text",
232-
content: textContent
233-
})
234-
}
235-
}
228+
// By not capturing the text between matches, we ignore "junk tokens"
236229

237230
const tag = match[0]
238231
let subMatch
@@ -254,26 +247,29 @@ const ChatBubble = ({
254247
})
255248
} else if (
256249
(subMatch = tag.match(
257-
/<tool_result tool_name="([^"]+)">([\s\S]*?)<\/tool_result>/
250+
/<tool_result tool_name="([^"]+)">[\s\S]*?<\/tool_result>/
258251
))
259252
) {
260253
contentParts.push({
261254
type: "tool_result",
262255
name: subMatch[1],
263256
result: subMatch[2] ? subMatch[2].trim() : "{}"
264257
})
258+
} else if ((subMatch = tag.match(/<answer>([\s\S]*?)<\/answer>/))) {
259+
const answerContent = subMatch[1]
260+
// Don't trim, whitespace might be intentional
261+
if (answerContent) {
262+
contentParts.push({
263+
type: "answer",
264+
content: answerContent
265+
})
266+
}
265267
}
266268

267269
lastIndex = match.index + tag.length
268270
}
269271

270-
// Capture remaining text after the last tag
271-
if (lastIndex < message.length) {
272-
const remainingText = message.substring(lastIndex)
273-
if (remainingText.trim()) {
274-
contentParts.push({ type: "text", content: remainingText })
275-
}
276-
}
272+
// By not capturing text after the last tag, we ignore trailing junk.
277273

278274
// Render all parts
279275
return contentParts.map((part, index) => {
@@ -327,26 +323,20 @@ const ChatBubble = ({
327323
/>
328324
)
329325
}
330-
if (part.type === "text" && part.content.trim()) {
331-
// Render text parts only when the stream is complete
332-
if (isStreamDone) {
333-
return (
334-
<ReactMarkdown
335-
key={partId}
336-
className="prose prose-invert"
337-
remarkPlugins={[remarkGfm]}
338-
children={part.content}
339-
components={{
340-
a: ({ href, children }) => (
341-
<LinkButton
342-
href={href}
343-
children={children}
344-
/>
345-
)
346-
}}
347-
/>
348-
)
349-
}
326+
if (part.type === "answer" && part.content) {
327+
return (
328+
<ReactMarkdown
329+
key={partId}
330+
className="prose prose-invert"
331+
remarkPlugins={[remarkGfm]}
332+
children={part.content}
333+
components={{
334+
a: ({ href, children }) => (
335+
<LinkButton href={href} children={children} />
336+
)
337+
}}
338+
/>
339+
)
350340
}
351341
return null
352342
})

src/server/main/chat/utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ def worker():
102102
f"1. **ALWAYS Use Memory First:** Before answering ANY query, you MUST use the `supermemory-search` tool to check if you already know relevant information about the user or the topic. This is not optional. Personalize your response based on what you find.\n"
103103
f"2. **Continuously Learn:** If you learn a new, permanent fact about the user (their preferences, relationships, personal details, goals, etc.), you MUST use the `supermemory-addToSupermemory` tool to remember it for the future. For example, if the user says 'my wife's name is Jane', you must call the tool to save this fact.\n"
104104
f"3. **Delegate Complex Tasks:** For requests that require multiple steps, research, or actions over time (e.g., 'plan my trip', 'summarize this topic'), use the `create_task_from_description` tool. Do not try to perform complex tasks yourself.\n"
105-
f"4. **Use Your Journal:** For daily notes, simple reminders, or retrieving information from a specific day, use the journal tools (`search_journal`, `summarize_day`, `add_journal_entry`).\n\n"
105+
f"4. **Use Your Journal:** For daily notes, simple reminders, or retrieving information from a specific day, use the journal tools (`search_journal`, `summarize_day`, `add_journal_entry`).\n"
106+
f"5. **Final Answer Format:** When you have a complete, final answer for the user that is not a tool call, you MUST wrap it in `<answer>` tags. For example: `<answer>The weather in London is 15°C and cloudy.</answer>`. Any self-correction or thought process should be inside `<think>` tags, which can precede the final answer.\n\n"
106107
f"**User Context (for your reference):**\n"
107108
f"- **User's Name:** {username}\n"
108109
f"- **User's Location:** {location}\n"

0 commit comments

Comments
 (0)