Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Showing the Think process for Deep Seek R1 in Cody UI #6896

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions vscode/webviews/chat/ChatMessageContent/ChatMessageContent.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@taiyab I don't actually know much about how CSS works and this code was mostly LLM generated so

How about this? @abeatrix tests this code on functionality and verifies if this does what it claims to do. We can merge this in for now but down the line when you do a much proper cleanup and merger for agentic/normal chat you can take care of this too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we want to do this in a stylesheet instead of using tailwind?

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.15rem 0.5rem;
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
background-color: rgba(229, 231, 235, 0.5);

Do we need a background color for it?

image

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);
}
102 changes: 101 additions & 1 deletion vscode/webviews/chat/ChatMessageContent/ChatMessageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,47 @@ interface ChatMessageContentProps {
className?: string
}

interface StreamingContent {
displayContent: string
thinkContent: string
hasThinkTag: boolean
isThinking: boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
isThinking: boolean

}

const extractThinkContent = (content: string): StreamingContent => {
const thinkRegex = /<think>([\s\S]*?)<\/think>/g
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const thinkRegex = /<think>([\s\S]*?)<\/think>/g
const thinkRegex = /^<think>([\s\S]*?)<\/think>/g

Do we want to make sure only the tags at the beginning are being considered? I know I wrote the original regex but haven't really tested it throughoutly 😆

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, do we want to consider "thinking" in additional to "think" for other provider? For example, Anthropic suggested using "" tags for CoT https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/chain-of-thought#how-to-prompt-for-thinking

const thinkMatches = [...content.matchAll(thinkRegex)]

// Check if content has an unclosed think tag
const hasOpenThinkTag =
content.includes('<think>') && content.lastIndexOf('<think>') > content.lastIndexOf('</think>')

// 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('<think>') + 7)
thinkContent = thinkContent ? `${thinkContent}\n\n${lastThinkContent}` : lastThinkContent
}

// 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('<think>'))
}

return {
displayContent,
thinkContent,
hasThinkTag: thinkMatches.length > 0 || hasOpenThinkTag,
isThinking: hasOpenThinkTag,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
isThinking: hasOpenThinkTag,

If hasThinkTag is false it already means isThinking is true? Alternatively we can rename hasThinkTag to isThinking if you think that's more clear, but I don't think we will need both.

}
}

/**
* A component presenting the content of a chat message.
*/
Expand Down Expand Up @@ -204,10 +245,69 @@ export const ChatMessageContent: React.FunctionComponent<ChatMessageContentProps
smartApplyStates,
])

const { displayContent, thinkContent, hasThinkTag, isThinking } = useMemo(
() => extractThinkContent(displayMarkdown),
[displayMarkdown]
)

return (
<div ref={rootRef} data-testid="chat-message-content">
{hasThinkTag && (
<details open className={styles.thinkContainer}>
<summary className={styles.thinkSummary}>
<div className={styles.thinkTitleContainer}>
<div className={styles.thinkIconContainer}>
<svg
className={clsx(styles.thinkIcon, isThinking && styles.thinking)}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
role="img"
aria-label="Thinking indicator"
>
<title>Thinking indicator</title>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707"
/>
</svg>
</div>
<span className={styles.thinkTitle}>
{isThinking ? 'Thinking...' : 'Thought Process'}
</span>
</div>
<div className={styles.thinkToggleContainer}>
<div className={styles.thinkToggleButton}>
<svg
className={styles.thinkToggleIcon}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
role="img"
aria-label="Toggle thought process"
>
<title>Toggle thought process</title>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M19 9l-7 7-7-7"
/>
</svg>
</div>
</div>
</summary>
<div className={styles.thinkContent}>
<MarkdownFromCody className={styles.thinkMarkdown}>
{thinkContent}
</MarkdownFromCody>
</div>
</details>
)}
<MarkdownFromCody className={clsx(styles.content, className)}>
{displayMarkdown}
{displayContent}
</MarkdownFromCody>
</div>
)
Expand Down
1 change: 1 addition & 0 deletions vscode/webviews/components/MarkdownFromCody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const ALLOWED_ELEMENTS = [
'h5',
'h6',
'br',
'think',
]

function defaultUrlProcessor(url: string): string {
Expand Down
Loading