Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { $, clearNode } from '../../../../../base/browser/dom.js';
7
import { IChatThinkingPart } from '../../common/chatService.js';
8
import { IChatContentPartRenderContext, IChatContentPart } from './chatContentParts.js';
9
import { IChatRendererContent } from '../../common/chatViewModel.js';
10
import { ChatTreeItem } from '../chat.js';
11
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
12
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
13
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
14
import { MarkdownRenderer, IMarkdownRenderResult } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js';
15
import { ChatCollapsibleContentPart } from './chatCollapsibleContentPart.js';
16
import { localize } from '../../../../../nls.js';
17
18
function extractTextFromPart(content: IChatThinkingPart): string {
19
const raw = Array.isArray(content.value) ? content.value.join('') : (content.value || '');
20
return raw.replace(/<\|im_sep\|>\*{4,}/g, '').trim();
21
}
22
23
function extractTitleFromThinkingContent(content: string): string | undefined {
24
const headerMatch = content.match(/^\*\*([^*]+)\*\*\s*\n\n/);
25
return headerMatch ? headerMatch[1].trim() : undefined;
26
}
27
28
export class ChatThinkingContentPart extends ChatCollapsibleContentPart implements IChatContentPart {
29
public readonly codeblocks: undefined;
30
public readonly codeblocksPartId: undefined;
31
32
private id: string | undefined;
33
private currentThinkingValue: string;
34
private currentTitle: string;
35
private defaultTitle = localize('chat.thinking.header', 'Thinking...');
36
private readonly renderer: MarkdownRenderer;
37
private textContainer!: HTMLElement;
38
private markdownResult: IMarkdownRenderResult | undefined;
39
private wrapper!: HTMLElement;
40
41
constructor(
42
content: IChatThinkingPart,
43
context: IChatContentPartRenderContext,
44
@IInstantiationService instantiationService: IInstantiationService,
45
@IConfigurationService private readonly configurationService: IConfigurationService,
46
) {
47
const initialText = extractTextFromPart(content);
48
const extractedTitle = extractTitleFromThinkingContent(initialText)
49
?? localize('chat.thinking.header', 'Thinking...');
50
51
super(extractedTitle, context);
52
53
this.renderer = instantiationService.createInstance(MarkdownRenderer, {});
54
this.id = content.id;
55
this.currentThinkingValue = initialText;
56
this.currentTitle = extractedTitle;
57
58
const mode = this.configurationService.getValue<string>('chat.agent.thinkingStyle') ?? 'none';
59
if (mode === 'expanded' || mode === 'collapsedPreview') {
60
this.setExpanded(true);
61
} else if (mode === 'collapsed') {
62
this.setExpanded(false);
63
}
64
65
const node = this.domNode;
66
node.classList.add('chat-thinking-box');
67
node.tabIndex = 0;
68
69
}
70
71
private parseContent(content: string): string {
72
const noSep = content.replace(/<\|im_sep\|>\*{4,}/g, '').trim();
73
return noSep;
74
}
75
76
protected override initContent(): HTMLElement {
77
this.wrapper = $('.chat-used-context-list.chat-thinking-collapsible');
78
this.textContainer = $('.chat-thinking-item.markdown-content');
79
this.wrapper.appendChild(this.textContainer);
80
81
if (this.currentThinkingValue) {
82
this.renderMarkdown(this.currentThinkingValue);
83
}
84
85
return this.wrapper;
86
}
87
88
private renderMarkdown(content: string): void {
89
if (this.markdownResult) {
90
this.markdownResult.dispose();
91
this.markdownResult = undefined;
92
}
93
94
const cleanedContent = this.parseContent(content);
95
if (!cleanedContent) {
96
return;
97
}
98
99
clearNode(this.textContainer);
100
this.markdownResult = this._register(this.renderer.render(new MarkdownString(cleanedContent)));
101
this.textContainer.appendChild(this.markdownResult.element);
102
}
103
104
public resetId(): void {
105
this.id = undefined;
106
}
107
108
public collapseContent(): void {
109
this.setExpanded(false);
110
}
111
112
public updateThinking(content: IChatThinkingPart): void {
113
const raw = extractTextFromPart(content);
114
const next = this.parseContent(raw);
115
if (next === this.currentThinkingValue) {
116
return;
117
}
118
this.currentThinkingValue = next;
119
this.renderMarkdown(next);
120
121
// if title is present now (e.g., arrived mid-stream), update the header label
122
const maybeTitle = extractTitleFromThinkingContent(raw);
123
if (maybeTitle && maybeTitle !== this.currentTitle) {
124
this.setTitle(maybeTitle);
125
this.currentTitle = maybeTitle;
126
}
127
}
128
129
public finalizeTitleIfDefault(): void {
130
if (this.currentTitle === this.defaultTitle) {
131
const done = localize('chat.pinned.thinking.header.done', 'Thought for a few seconds...');
132
this.setTitle(done);
133
this.currentTitle = done;
134
}
135
}
136
137
public appendItem(content: HTMLElement): void {
138
this.wrapper.appendChild(content);
139
}
140
141
// makes a new text container. when we update, we now update this container.
142
public setupThinkingContainer(content: IChatThinkingPart, context: IChatContentPartRenderContext) {
143
this.textContainer = $('.chat-thinking-item.markdown-content');
144
this.wrapper.appendChild(this.textContainer);
145
this.id = content?.id;
146
this.updateThinking(content);
147
}
148
149
hasSameContent(other: IChatRendererContent, _followingContent: IChatRendererContent[], _element: ChatTreeItem): boolean {
150
151
// only need this check if we are adding tools into thinking dropdown.
152
// if (other.kind === 'toolInvocation' || other.kind === 'toolInvocationSerialized') {
153
// return true;
154
// }
155
156
if (other.kind !== 'thinking') {
157
return false;
158
}
159
160
return other?.id !== this.id;
161
}
162
163
override dispose(): void {
164
if (this.markdownResult) {
165
this.markdownResult.dispose();
166
this.markdownResult = undefined;
167
}
168
super.dispose();
169
}
170
}
171
172