Path: blob/main/src/vs/workbench/contrib/chat/browser/chatContentParts/chatThinkingContentPart.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { $, clearNode } from '../../../../../base/browser/dom.js';6import { IChatThinkingPart } from '../../common/chatService.js';7import { IChatContentPartRenderContext, IChatContentPart } from './chatContentParts.js';8import { IChatRendererContent } from '../../common/chatViewModel.js';9import { ChatTreeItem } from '../chat.js';10import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';11import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';12import { MarkdownString } from '../../../../../base/common/htmlContent.js';13import { MarkdownRenderer, IMarkdownRenderResult } from '../../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js';14import { ChatCollapsibleContentPart } from './chatCollapsibleContentPart.js';15import { localize } from '../../../../../nls.js';1617function extractTextFromPart(content: IChatThinkingPart): string {18const raw = Array.isArray(content.value) ? content.value.join('') : (content.value || '');19return raw.replace(/<\|im_sep\|>\*{4,}/g, '').trim();20}2122function extractTitleFromThinkingContent(content: string): string | undefined {23const headerMatch = content.match(/^\*\*([^*]+)\*\*\s*\n\n/);24return headerMatch ? headerMatch[1].trim() : undefined;25}2627export class ChatThinkingContentPart extends ChatCollapsibleContentPart implements IChatContentPart {28public readonly codeblocks: undefined;29public readonly codeblocksPartId: undefined;3031private id: string | undefined;32private currentThinkingValue: string;33private currentTitle: string;34private defaultTitle = localize('chat.thinking.header', 'Thinking...');35private readonly renderer: MarkdownRenderer;36private textContainer!: HTMLElement;37private markdownResult: IMarkdownRenderResult | undefined;38private wrapper!: HTMLElement;3940constructor(41content: IChatThinkingPart,42context: IChatContentPartRenderContext,43@IInstantiationService instantiationService: IInstantiationService,44@IConfigurationService private readonly configurationService: IConfigurationService,45) {46const initialText = extractTextFromPart(content);47const extractedTitle = extractTitleFromThinkingContent(initialText)48?? localize('chat.thinking.header', 'Thinking...');4950super(extractedTitle, context);5152this.renderer = instantiationService.createInstance(MarkdownRenderer, {});53this.id = content.id;54this.currentThinkingValue = initialText;55this.currentTitle = extractedTitle;5657const mode = this.configurationService.getValue<string>('chat.agent.thinkingStyle') ?? 'none';58if (mode === 'expanded' || mode === 'collapsedPreview') {59this.setExpanded(true);60} else if (mode === 'collapsed') {61this.setExpanded(false);62}6364const node = this.domNode;65node.classList.add('chat-thinking-box');66node.tabIndex = 0;6768}6970private parseContent(content: string): string {71const noSep = content.replace(/<\|im_sep\|>\*{4,}/g, '').trim();72return noSep;73}7475protected override initContent(): HTMLElement {76this.wrapper = $('.chat-used-context-list.chat-thinking-collapsible');77this.textContainer = $('.chat-thinking-item.markdown-content');78this.wrapper.appendChild(this.textContainer);7980if (this.currentThinkingValue) {81this.renderMarkdown(this.currentThinkingValue);82}8384return this.wrapper;85}8687private renderMarkdown(content: string): void {88if (this.markdownResult) {89this.markdownResult.dispose();90this.markdownResult = undefined;91}9293const cleanedContent = this.parseContent(content);94if (!cleanedContent) {95return;96}9798clearNode(this.textContainer);99this.markdownResult = this._register(this.renderer.render(new MarkdownString(cleanedContent)));100this.textContainer.appendChild(this.markdownResult.element);101}102103public resetId(): void {104this.id = undefined;105}106107public collapseContent(): void {108this.setExpanded(false);109}110111public updateThinking(content: IChatThinkingPart): void {112const raw = extractTextFromPart(content);113const next = this.parseContent(raw);114if (next === this.currentThinkingValue) {115return;116}117this.currentThinkingValue = next;118this.renderMarkdown(next);119120// if title is present now (e.g., arrived mid-stream), update the header label121const maybeTitle = extractTitleFromThinkingContent(raw);122if (maybeTitle && maybeTitle !== this.currentTitle) {123this.setTitle(maybeTitle);124this.currentTitle = maybeTitle;125}126}127128public finalizeTitleIfDefault(): void {129if (this.currentTitle === this.defaultTitle) {130const done = localize('chat.pinned.thinking.header.done', 'Thought for a few seconds...');131this.setTitle(done);132this.currentTitle = done;133}134}135136public appendItem(content: HTMLElement): void {137this.wrapper.appendChild(content);138}139140// makes a new text container. when we update, we now update this container.141public setupThinkingContainer(content: IChatThinkingPart, context: IChatContentPartRenderContext) {142this.textContainer = $('.chat-thinking-item.markdown-content');143this.wrapper.appendChild(this.textContainer);144this.id = content?.id;145this.updateThinking(content);146}147148hasSameContent(other: IChatRendererContent, _followingContent: IChatRendererContent[], _element: ChatTreeItem): boolean {149150// only need this check if we are adding tools into thinking dropdown.151// if (other.kind === 'toolInvocation' || other.kind === 'toolInvocationSerialized') {152// return true;153// }154155if (other.kind !== 'thinking') {156return false;157}158159return other?.id !== this.id;160}161162override dispose(): void {163if (this.markdownResult) {164this.markdownResult.dispose();165this.markdownResult = undefined;166}167super.dispose();168}169}170171172