Skip to content

Commit 9984cd9

Browse files
authored
make thinking tokens collapsible INLINE (microsoft#261600)
* make thinking tokens collapsible INLINE * some cleanup
1 parent fca79ef commit 9984cd9

4 files changed

Lines changed: 74 additions & 35 deletions

File tree

src/vs/workbench/contrib/chat/browser/chatContentParts/chatCollapsibleContentPart.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ export abstract class ChatCollapsibleContentPart extends Disposable implements I
2525

2626
protected readonly hasFollowingContent: boolean;
2727
protected _isExpanded = observableValue<boolean>(this, false);
28+
protected _collapseButton: ButtonWithIcon | undefined;
2829

2930
constructor(
30-
private readonly title: IMarkdownString | string,
31+
private title: IMarkdownString | string,
3132
protected readonly context: IChatContentPartRenderContext,
3233
) {
3334
super();
@@ -55,6 +56,7 @@ export abstract class ChatCollapsibleContentPart extends Disposable implements I
5556
buttonSecondaryHoverBackground: undefined,
5657
buttonSeparator: undefined
5758
}));
59+
this._collapseButton = collapseButton;
5860
this._domNode = $('.chat-used-context', undefined, buttonElement);
5961
collapseButton.label = referencesLabel;
6062

@@ -104,4 +106,12 @@ export abstract class ChatCollapsibleContentPart extends Disposable implements I
104106
protected setExpanded(value: boolean): void {
105107
this._isExpanded.set(value, undefined);
106108
}
109+
110+
protected setTitle(title: string): void {
111+
this.title = title;
112+
if (this._collapseButton) {
113+
this._collapseButton.label = title;
114+
this.updateAriaLabel(this._collapseButton.element, title, this.isExpanded());
115+
}
116+
}
107117
}

src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,15 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { $, clearNode } from '../../../../../base/browser/dom.js';
7-
import { Emitter } from '../../../../../base/common/event.js';
8-
import { Disposable } from '../../../../../base/common/lifecycle.js';
97
import { IChatThinkingPart } from '../../common/chatService.js';
10-
import { IChatContentPartRenderContext, IChatContentPart } from './chatContentParts.js';
8+
import { IChatContentPartRenderContext } from './chatContentParts.js';
119
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
1210
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
1311
import { MarkdownRenderer, IMarkdownRenderResult } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js';
12+
import { ChatCollapsibleContentPart } from './chatCollapsibleContentPart.js';
13+
import { localize } from '../../../../../nls.js';
1414

15-
export class ChatThinkingContentPart extends Disposable implements IChatContentPart {
16-
readonly domNode: HTMLElement;
17-
public readonly codeblocks: undefined;
18-
public readonly codeblocksPartId: undefined;
19-
20-
private readonly _onDidChangeHeight = this._register(new Emitter<void>());
21-
readonly onDidChangeHeight = this._onDidChangeHeight.event;
15+
export class ChatThinkingContentPart extends ChatCollapsibleContentPart {
2216

2317
private currentThinkingValue: string;
2418
private readonly renderer: MarkdownRenderer;
@@ -30,14 +24,15 @@ export class ChatThinkingContentPart extends Disposable implements IChatContentP
3024
_context: IChatContentPartRenderContext,
3125
@IInstantiationService instantiationService: IInstantiationService,
3226
) {
33-
super();
27+
super(localize('thinkingHeader', "Thinking..."), _context);
3428

3529
this.renderer = instantiationService.createInstance(MarkdownRenderer, {});
3630
this.currentThinkingValue = this.parseContent(content.value || '');
3731

38-
this.domNode = $('.chat-thinking-box');
32+
this.domNode.classList.add('chat-thinking-box');
3933
this.domNode.tabIndex = 0;
40-
this.renderContent();
34+
35+
this.setExpanded(false);
4136
}
4237

4338
private parseContent(content: string): string {
@@ -47,15 +42,6 @@ export class ChatThinkingContentPart extends Disposable implements IChatContentP
4742
.trim();
4843
}
4944

50-
private renderContent(): void {
51-
this.textContainer = $('.chat-thinking-text.markdown-content');
52-
this.domNode.appendChild(this.textContainer);
53-
54-
if (this.currentThinkingValue) {
55-
this.renderMarkdown(this.currentThinkingValue);
56-
}
57-
}
58-
5945
private renderMarkdown(content: string): void {
6046
if (this.markdownResult) {
6147
this.markdownResult.dispose();
@@ -72,11 +58,25 @@ export class ChatThinkingContentPart extends Disposable implements IChatContentP
7258
this.textContainer.appendChild(this.markdownResult.element);
7359
}
7460

75-
hasSameContent(other: any): boolean {
76-
if (other.kind !== 'thinking') {
77-
return false;
61+
protected override initContent(): HTMLElement {
62+
const container = $('.chat-used-context-list chat-thinking-content');
63+
this.textContainer = $('.chat-thinking-text.markdown-content');
64+
container.appendChild(this.textContainer);
65+
if (this.currentThinkingValue) {
66+
this.renderMarkdown(this.currentThinkingValue);
7867
}
79-
return true;
68+
return container;
69+
}
70+
71+
updateInProgressHeader(elapsedMs: number) {
72+
const seconds = Math.max(1, Math.floor(elapsedMs / 1000));
73+
this.setTitle(seconds === 1
74+
? localize('thinkingSingular', "Thought for 1 second")
75+
: localize('thinkingPlural', "Thought for {0} seconds", seconds));
76+
}
77+
78+
hasSameContent(other: any): boolean {
79+
return other.kind === 'thinking';
8080
}
8181

8282
override dispose(): void {

src/vs/workbench/contrib/chat/browser/chatListRenderer.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
181181
private readonly _diffEditorPool: DiffEditorPool;
182182
private readonly _treePool: TreePool;
183183
private readonly _contentReferencesListPool: CollapsibleListPool;
184+
private _thinkingRenderStartTime: number = 0;
185+
private _activeThinkingPart: ChatThinkingContentPart | undefined;
184186

185187
private _currentLayoutWidth: number = 0;
186188
private _isVisible = true;
@@ -276,6 +278,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
276278

277279
updateViewModel(viewModel: IChatViewModel | undefined): void {
278280
this.viewModel = viewModel;
281+
this._thinkingRenderStartTime = 0;
282+
this._activeThinkingPart = undefined;
279283
}
280284

281285
getCodeBlockInfoForEditor(uri: URI): IChatCodeBlockInfo | undefined {
@@ -702,6 +706,10 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
702706
private renderChatResponseBasic(element: IChatResponseViewModel, index: number, templateData: IChatListItemTemplate) {
703707
templateData.rowContainer.classList.toggle('chat-response-loading', (isResponseVM(element) && !element.isComplete));
704708

709+
if (element.isComplete) {
710+
this.finalizeActiveThinking();
711+
}
712+
705713
const content: IChatRendererContent[] = [];
706714
const isFiltered = !!element.errorDetails?.responseIsFiltered;
707715
if (!isFiltered) {
@@ -774,6 +782,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
774782
}
775783

776784
private renderChatRequest(element: IChatRequestViewModel, index: number, templateData: IChatListItemTemplate) {
785+
this.finalizeActiveThinking();
777786
templateData.rowContainer.classList.toggle('chat-response-loading', false);
778787
if (element.id === this.viewModel?.editing?.id) {
779788
this._onDidRerender.fire(templateData);
@@ -897,6 +906,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
897906

898907
if (element.isCanceled) {
899908
this.traceLayout('doNextProgressiveRender', `canceled, index=${index}`);
909+
this.finalizeActiveThinking();
900910
element.renderData = undefined;
901911
this.renderChatResponseBasic(element, index, templateData);
902912
return true;
@@ -916,6 +926,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
916926
} else if (element.isComplete) {
917927
// All content is rendered, and response is done, so do a normal render
918928
this.traceLayout('doNextProgressiveRender', `END progressive render, index=${index} and clearing renderData, response is complete`);
929+
this.finalizeActiveThinking();
919930
element.renderData = undefined;
920931
this.renderChatResponseBasic(element, index, templateData);
921932
return true;
@@ -1359,13 +1370,20 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
13591370
}
13601371

13611372
private renderThinking(context: IChatContentPartRenderContext, thinking: IChatThinkingPart, templateData: IChatListItemTemplate): IChatContentPart {
1373+
this.finalizeActiveThinking();
1374+
this._thinkingRenderStartTime = Date.now();
1375+
13621376
const thinkingPart = templateData.instantiationService.createInstance(
13631377
ChatThinkingContentPart,
13641378
thinking,
13651379
context
13661380
);
13671381

1368-
thinkingPart.domNode.classList.add('chat-thinking-part');
1382+
thinkingPart.addDisposable(thinkingPart.onDidChangeHeight(() => {
1383+
this.updateItemHeight(templateData);
1384+
}));
1385+
1386+
this._activeThinkingPart = thinkingPart;
13691387

13701388
return thinkingPart;
13711389
}
@@ -1391,6 +1409,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
13911409
}
13921410

13931411
private renderMarkdown(markdown: IChatMarkdownContent, templateData: IChatListItemTemplate, context: IChatContentPartRenderContext): IChatContentPart {
1412+
this.finalizeActiveThinking();
13941413
const element = context.element;
13951414
const fillInIncompleteTokens = isResponseVM(element) && (!element.isComplete || element.isCanceled || element.errorDetails?.responseIsFiltered || element.errorDetails?.responseIsIncomplete || !!element.renderData);
13961415
const codeBlockStartIndex = this.getCodeBlockStartIndex(context);
@@ -1481,6 +1500,15 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
14811500
private hoverHidden(requestHover: HTMLElement) {
14821501
requestHover.style.opacity = '0';
14831502
}
1503+
1504+
private finalizeActiveThinking() {
1505+
if (this._activeThinkingPart && this._thinkingRenderStartTime) {
1506+
const duration = Date.now() - this._thinkingRenderStartTime;
1507+
this._activeThinkingPart.updateInProgressHeader(duration);
1508+
}
1509+
this._activeThinkingPart = undefined;
1510+
this._thinkingRenderStartTime = 0;
1511+
}
14841512
}
14851513

14861514
export class ChatListDelegate implements IListVirtualDelegate<ChatTreeItem> {

src/vs/workbench/contrib/chat/browser/media/chat.css

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2546,17 +2546,18 @@ have to be updated for changes to the rules above, or to support more deeply nes
25462546
}
25472547

25482548

2549-
.interactive-session .interactive-response .chat-thinking-box {
2550-
border: 1px solid var(--vscode-panel-border);
2551-
border-radius: 4px;
2552-
padding: 0px 8px;
2553-
margin: 4px 0;
2554-
background-color: var(--vscode-editor-background);
2549+
.interactive-session .interactive-response .value .chat-thinking-box {
2550+
outline: none;
25552551

25562552
.chat-thinking-text {
2557-
font-size: inherit;
2553+
font-size: 12px;
2554+
padding: 3px 10px;
25582555
line-height: inherit;
25592556
display: flex;
25602557
align-items: flex-start;
25612558
}
2559+
2560+
.rendered-markdown > p {
2561+
margin: 0;
2562+
}
25622563
}

0 commit comments

Comments
 (0)