From ae894194217db69aa1ea9347f6e5bb9bf870c9c8 Mon Sep 17 00:00:00 2001 From: Beatrix Date: Tue, 28 Jan 2025 22:48:25 +0800 Subject: [PATCH 1/6] feat: add support for tags in ChatMessageContent This PR adds support for rendering tags in the ChatMessageContent component. The content is displayed in a collapsible details element, allowing users to view the AI's internal thought process. The MarkdownFromCody component is also updated to allow the element. --- .../ChatMessageContent/ChatMessageContent.tsx | 31 ++++++++++++++++++- .../webviews/components/MarkdownFromCody.tsx | 1 + 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx index ff5600a4ebdc..51ba6bc08d72 100644 --- a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx +++ b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx @@ -39,6 +39,22 @@ interface ChatMessageContentProps { className?: string } +const extractThinkContent = (content: string): { displayContent: string; thinkContent: string } => { + const thinkRegex = /([\s\S]*?)<\/think>/g + const thinkMatches = [...content.matchAll(thinkRegex)] + + // Collect all think content + const thinkContent = thinkMatches + .map(match => match[1].trim()) + .filter(Boolean) + .join('\n\n') + + // Remove think tags from display content + const displayContent = content.replace(thinkRegex, '') + + return { displayContent, thinkContent } +} + /** * A component presenting the content of a chat message. */ @@ -204,10 +220,23 @@ export const ChatMessageContent: React.FunctionComponent extractThinkContent(displayMarkdown), + [displayMarkdown] + ) + return (
+ {thinkContent && ( +
+ Thinking... + + {thinkContent} + +
+ )} - {displayMarkdown} + {displayContent}
) diff --git a/vscode/webviews/components/MarkdownFromCody.tsx b/vscode/webviews/components/MarkdownFromCody.tsx index dc952a29961f..f43fcd744445 100644 --- a/vscode/webviews/components/MarkdownFromCody.tsx +++ b/vscode/webviews/components/MarkdownFromCody.tsx @@ -53,6 +53,7 @@ const ALLOWED_ELEMENTS = [ 'h5', 'h6', 'br', + 'think', ] function defaultUrlProcessor(url: string): string { From be2347cd32524c466d1b1793d78ea794c71d4d1d Mon Sep 17 00:00:00 2001 From: arafatkatze Date: Fri, 31 Jan 2025 08:39:58 +0000 Subject: [PATCH 2/6] Adding a fix for think rendering --- .../ChatMessageContent/ChatMessageContent.tsx | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx index 51ba6bc08d72..19ec9721f8ba 100644 --- a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx +++ b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx @@ -39,20 +39,43 @@ interface ChatMessageContentProps { className?: string } -const extractThinkContent = (content: string): { displayContent: string; thinkContent: string } => { +interface StreamingContent { + displayContent: string + thinkContent: string + hasThinkTag: boolean +} + +const extractThinkContent = (content: string): StreamingContent => { const thinkRegex = /([\s\S]*?)<\/think>/g const thinkMatches = [...content.matchAll(thinkRegex)] - - // Collect all think content - const thinkContent = thinkMatches + + // Check if content has an unclosed think tag + const hasOpenThinkTag = content.includes('') && + (content.lastIndexOf('') > content.lastIndexOf('')) + + // Collect all think content, including partial content from unclosed tag + let thinkContent = thinkMatches .map(match => match[1].trim()) .filter(Boolean) .join('\n\n') + + if (hasOpenThinkTag) { + const lastThinkContent = content.slice(content.lastIndexOf('') + 7) + thinkContent = thinkContent ? `${thinkContent}\n\n${lastThinkContent}` : lastThinkContent + } - // Remove think tags from display content - const displayContent = content.replace(thinkRegex, '') + // Remove complete think tags from display content + let displayContent = content.replace(thinkRegex, '') + // Remove any unclosed think tag and its content + if (hasOpenThinkTag) { + displayContent = displayContent.slice(0, displayContent.lastIndexOf('')) + } - return { displayContent, thinkContent } + return { + displayContent, + thinkContent, + hasThinkTag: thinkMatches.length > 0 || hasOpenThinkTag + } } /** @@ -220,15 +243,15 @@ export const ChatMessageContent: React.FunctionComponent extractThinkContent(displayMarkdown), [displayMarkdown] ) return (
- {thinkContent && ( -
+ {hasThinkTag && ( +
Thinking... {thinkContent} From da034b970018e16424dfddee416cd45a40eaddb5 Mon Sep 17 00:00:00 2001 From: arafatkatze Date: Fri, 31 Jan 2025 08:42:35 +0000 Subject: [PATCH 3/6] Aesthetic --- .../ChatMessageContent/ChatMessageContent.tsx | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx index 19ec9721f8ba..e98d677a4a20 100644 --- a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx +++ b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx @@ -251,11 +251,50 @@ export const ChatMessageContent: React.FunctionComponent {hasThinkTag && ( -
- Thinking... - - {thinkContent} - +
+ + + + + + Thinking... + + +
+ + {thinkContent} + +
)} From abcae2bd8d781e55c43e7e967aaaf5d3820e5c49 Mon Sep 17 00:00:00 2001 From: arafatkatze Date: Fri, 31 Jan 2025 08:54:55 +0000 Subject: [PATCH 4/6] Aesthetic --- .../ChatMessageContent/ChatMessageContent.tsx | 90 +++++++++++++------ 1 file changed, 64 insertions(+), 26 deletions(-) diff --git a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx index e98d677a4a20..6155c9a0d37b 100644 --- a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx +++ b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx @@ -43,6 +43,7 @@ interface StreamingContent { displayContent: string thinkContent: string hasThinkTag: boolean + isThinking: boolean } const extractThinkContent = (content: string): StreamingContent => { @@ -74,7 +75,8 @@ const extractThinkContent = (content: string): StreamingContent => { return { displayContent, thinkContent, - hasThinkTag: thinkMatches.length > 0 || hasOpenThinkTag + hasThinkTag: thinkMatches.length > 0 || hasOpenThinkTag, + isThinking: hasOpenThinkTag } } @@ -243,7 +245,7 @@ export const ChatMessageContent: React.FunctionComponent extractThinkContent(displayMarkdown), [displayMarkdown] ) @@ -255,36 +257,72 @@ export const ChatMessageContent: React.FunctionComponent - - - - - Thinking... - +
+
+ + + +
+ + {isThinking ? "Thinking..." : "Thought Process"} + +
+
+
+ + + +
+
-
+
Date: Mon, 3 Feb 2025 18:06:13 +0400 Subject: [PATCH 5/6] rename --- .../ChatMessageContent.module.css | 137 ++++++++++++++++++ .../ChatMessageContent/ChatMessageContent.tsx | 109 +++++--------- 2 files changed, 177 insertions(+), 69 deletions(-) diff --git a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.module.css b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.module.css index e6497b2fb4f2..f2311e2eee32 100644 --- a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.module.css +++ b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.module.css @@ -217,3 +217,140 @@ body[data-vscode-theme-kind='vscode-light'] .content pre > code { color: var(--vscode-descriptionForeground); margin-left: auto; } + +/* Think Container Styles */ +.thinkContainer { + margin-bottom: 1rem; + border: 1px solid; + border-color: rgba(75, 85, 99, 0.15); + border-radius: 0.5rem; + overflow: hidden; + background-color: rgba(249, 250, 251, 0.5); + backdrop-filter: blur(4px); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +:global([data-vscode-theme-kind="vscode-dark"]) .thinkContainer { + border-color: rgba(107, 114, 128, 0.2); + background-color: rgba(17, 24, 39, 0.5); +} + +.thinkSummary { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.75rem 1rem; + background-color: rgba(243, 244, 246, 0.7); + cursor: pointer; + user-select: none; + transition: all 0.2s; +} + +.thinkSummary:hover { + background-color: rgba(229, 231, 235, 0.7); +} + +:global([data-vscode-theme-kind="vscode-dark"]) .thinkSummary { + background-color: rgba(31, 41, 55, 0.7); +} + +:global([data-vscode-theme-kind="vscode-dark"]) .thinkSummary:hover { + background-color: rgba(55, 65, 81, 0.7); +} + +.thinkTitleContainer { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.thinkIconContainer { + display: flex; + align-items: center; + justify-content: center; + width: 1.25rem; + height: 1.25rem; +} + +.thinkIcon { + width: 1rem; + height: 1rem; + color: rgba(59, 130, 246, 0.8); +} + +:global([data-vscode-theme-kind="vscode-dark"]) .thinkIcon { + color: rgba(96, 165, 250, 0.8); +} + +.thinking { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +.thinkTitle { + font-size: 0.875rem; + font-weight: 500; + color: rgb(55, 65, 81); +} + +:global([data-vscode-theme-kind="vscode-dark"]) .thinkTitle { + color: rgb(229, 231, 235); +} + +.thinkToggleContainer { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.thinkToggleButton { + display: flex; + align-items: center; + justify-content: center; + width: 1.5rem; + height: 1.5rem; + border-radius: 0.375rem; + background-color: rgba(229, 231, 235, 0.5); + color: rgb(107, 114, 128); + transition: background-color 0.2s; +} + +:global([data-vscode-theme-kind="vscode-dark"]) .thinkToggleButton { + background-color: rgba(55, 65, 81, 0.5); + color: rgb(156, 163, 175); +} + +.thinkToggleIcon { + width: 1rem; + height: 1rem; + transition: transform 0.2s; +} + +details[open] .thinkToggleIcon { + transform: rotate(180deg); +} + +.thinkContent { + padding: 0.75rem 1rem; + border-top: 1px solid rgba(229, 231, 235, 0.3); + background-color: rgba(249, 250, 251, 0.3); +} + +:global([data-vscode-theme-kind="vscode-dark"]) .thinkContent { + border-top-color: rgba(55, 65, 81, 0.3); + background-color: rgba(17, 24, 39, 0.3); +} + +.thinkMarkdown { + font-size: 0.875rem; + color: rgb(75, 85, 99); + line-height: 1.625; +} + +:global([data-vscode-theme-kind="vscode-dark"]) .thinkMarkdown { + color: rgb(209, 213, 219); +} diff --git a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx index 6155c9a0d37b..194dfa208184 100644 --- a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx +++ b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx @@ -49,17 +49,17 @@ interface StreamingContent { const extractThinkContent = (content: string): StreamingContent => { const thinkRegex = /([\s\S]*?)<\/think>/g const thinkMatches = [...content.matchAll(thinkRegex)] - + // Check if content has an unclosed think tag - const hasOpenThinkTag = content.includes('') && - (content.lastIndexOf('') > content.lastIndexOf('')) - + const hasOpenThinkTag = + content.includes('') && content.lastIndexOf('') > content.lastIndexOf('') + // Collect all think content, including partial content from unclosed tag let thinkContent = thinkMatches .map(match => match[1].trim()) .filter(Boolean) .join('\n\n') - + if (hasOpenThinkTag) { const lastThinkContent = content.slice(content.lastIndexOf('') + 7) thinkContent = thinkContent ? `${thinkContent}\n\n${lastThinkContent}` : lastThinkContent @@ -72,11 +72,11 @@ const extractThinkContent = (content: string): StreamingContent => { displayContent = displayContent.slice(0, displayContent.lastIndexOf('')) } - return { - displayContent, + return { + displayContent, thinkContent, hasThinkTag: thinkMatches.length > 0 || hasOpenThinkTag, - isThinking: hasOpenThinkTag + isThinking: hasOpenThinkTag, } } @@ -253,83 +253,54 @@ export const ChatMessageContent: React.FunctionComponent {hasThinkTag && ( -
- -
-
- + +
+
+ - Thinking indicator +
- - {isThinking ? "Thinking..." : "Thought Process"} + + {isThinking ? 'Thinking...' : 'Thought Process'}
-
-
- +
+ - Toggle thought process +
-
- +
+ {thinkContent}
From a04c8ae54dd2e8375fe83f4d185ce91e0db8a14f Mon Sep 17 00:00:00 2001 From: Ara Date: Tue, 4 Feb 2025 12:34:24 +0100 Subject: [PATCH 6/6] Update vscode/webviews/chat/ChatMessageContent/ChatMessageContent.module.css Co-authored-by: Beatrix <68532117+abeatrix@users.noreply.github.com> --- .../chat/ChatMessageContent/ChatMessageContent.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.module.css b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.module.css index f2311e2eee32..43a2e18082e9 100644 --- a/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.module.css +++ b/vscode/webviews/chat/ChatMessageContent/ChatMessageContent.module.css @@ -239,7 +239,7 @@ body[data-vscode-theme-kind='vscode-light'] .content pre > code { display: flex; align-items: center; justify-content: space-between; - padding: 0.75rem 1rem; + padding: 0.15rem 0.5rem; background-color: rgba(243, 244, 246, 0.7); cursor: pointer; user-select: none;