Path: blob/main/src/vs/workbench/contrib/chat/browser/chatDebug/chatDebugModelTurnContentRenderer.ts
13406 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 * as DOM from '../../../../../base/browser/dom.js';6import { DisposableStore } from '../../../../../base/common/lifecycle.js';7import { localize } from '../../../../../nls.js';8import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js';9import { ILanguageService } from '../../../../../editor/common/languages/language.js';10import { IChatDebugEventModelTurnContent } from '../../common/chatDebugService.js';11import { renderSection, tokenizeContent } from './chatDebugToolCallContentRenderer.js';12import { safeIntl } from '../../../../../base/common/date.js';1314const $ = DOM.$;15const numberFormatter = safeIntl.NumberFormat();1617/**18* Render a resolved model turn content with structured display of19* request metadata, token usage, and timing.20* When JSON is detected in section content, renders it with syntax highlighting.21*/22export async function renderModelTurnContent(content: IChatDebugEventModelTurnContent, languageService: ILanguageService, clipboardService?: IClipboardService, scrollable?: { scanDomNode(): void }): Promise<{ element: HTMLElement; disposables: DisposableStore }> {23const disposables = new DisposableStore();24const container = $('div.chat-debug-message-content');25container.tabIndex = 0;2627// Header: Model Turn28DOM.append(container, $('div.chat-debug-message-content-title', undefined, localize('chatDebug.modelTurn.title', "Model Turn")));2930// Status summary line31const statusParts: string[] = [];32if (content.requestName) {33statusParts.push(content.requestName);34}35if (content.model) {36statusParts.push(content.model);37}38if (content.status && content.status !== 'unknown') {39statusParts.push(content.status);40}41if (content.durationInMillis !== undefined) {42statusParts.push(localize('chatDebug.modelTurn.duration', "{0}ms", numberFormatter.value.format(content.durationInMillis)));43}44if (statusParts.length > 0) {45DOM.append(container, $('div.chat-debug-message-content-summary', undefined, statusParts.join(' \u00b7 ')));46}4748// Token usage details49const detailsContainer = DOM.append(container, $('div.chat-debug-model-turn-details'));5051if (content.inputTokens !== undefined) {52DOM.append(detailsContainer, $('div', undefined, localize('chatDebug.modelTurn.inputTokens', "Input tokens: {0}", numberFormatter.value.format(content.inputTokens))));53}54if (content.outputTokens !== undefined) {55DOM.append(detailsContainer, $('div', undefined, localize('chatDebug.modelTurn.outputTokens', "Output tokens: {0}", numberFormatter.value.format(content.outputTokens))));56}57if (content.cachedTokens !== undefined) {58DOM.append(detailsContainer, $('div', undefined, localize('chatDebug.modelTurn.cachedTokens', "Cached tokens: {0}", numberFormatter.value.format(content.cachedTokens))));59}60if (content.totalTokens !== undefined) {61DOM.append(detailsContainer, $('div', undefined, localize('chatDebug.modelTurn.totalTokens', "Total tokens: {0}", numberFormatter.value.format(content.totalTokens))));62}63if (content.timeToFirstTokenInMillis !== undefined) {64DOM.append(detailsContainer, $('div', undefined, localize('chatDebug.modelTurn.ttft', "Time to first token: {0}ms", numberFormatter.value.format(content.timeToFirstTokenInMillis))));65}66if (content.maxInputTokens !== undefined) {67DOM.append(detailsContainer, $('div', undefined, localize('chatDebug.modelTurn.maxInputTokens', "Max input tokens: {0}", numberFormatter.value.format(content.maxInputTokens))));68}69if (content.maxOutputTokens !== undefined) {70DOM.append(detailsContainer, $('div', undefined, localize('chatDebug.modelTurn.maxOutputTokens', "Max output tokens: {0}", numberFormatter.value.format(content.maxOutputTokens))));71}72if (content.errorMessage) {73DOM.append(detailsContainer, $('div.chat-debug-model-turn-error', undefined, localize('chatDebug.modelTurn.error', "Error: {0}", content.errorMessage)));74}7576// Collapsible sections (e.g., system prompt, user prompt, tools, response)77if (content.sections && content.sections.length > 0) {78const sectionsContainer = DOM.append(container, $('div.chat-debug-message-sections'));79DOM.append(sectionsContainer, $('div.chat-debug-message-sections-label', undefined,80localize('chatDebug.modelTurn.sections', "Sections ({0})", content.sections.length)));8182for (const section of content.sections) {83const { plainText, tokenizedHtml } = await tokenizeContent(section.content, languageService);84renderSection(sectionsContainer, section.name, plainText, tokenizedHtml, disposables, false, clipboardService, scrollable);85}86}8788return { element: container, disposables };89}9091/**92* Convert a resolved model turn content to plain text for clipboard / editor output.93*/94export function modelTurnContentToPlainText(content: IChatDebugEventModelTurnContent): string {95const lines: string[] = [];96lines.push(localize('chatDebug.modelTurn.requestLabel', "Request: {0}", content.requestName));9798if (content.model) {99lines.push(localize('chatDebug.modelTurn.modelLabel', "Model: {0}", content.model));100}101if (content.status && content.status !== 'unknown') {102lines.push(localize('chatDebug.modelTurn.statusLabel', "Status: {0}", content.status));103}104if (content.durationInMillis !== undefined) {105lines.push(localize('chatDebug.modelTurn.durationLabel', "Duration: {0}ms", numberFormatter.value.format(content.durationInMillis)));106}107if (content.timeToFirstTokenInMillis !== undefined) {108lines.push(localize('chatDebug.modelTurn.ttftLabel', "Time to first token: {0}ms", numberFormatter.value.format(content.timeToFirstTokenInMillis)));109}110if (content.inputTokens !== undefined) {111lines.push(localize('chatDebug.modelTurn.inputTokensLabel', "Input tokens: {0}", numberFormatter.value.format(content.inputTokens)));112}113if (content.outputTokens !== undefined) {114lines.push(localize('chatDebug.modelTurn.outputTokensLabel', "Output tokens: {0}", numberFormatter.value.format(content.outputTokens)));115}116if (content.cachedTokens !== undefined) {117lines.push(localize('chatDebug.modelTurn.cachedTokensLabel', "Cached tokens: {0}", numberFormatter.value.format(content.cachedTokens)));118}119if (content.totalTokens !== undefined) {120lines.push(localize('chatDebug.modelTurn.totalTokensLabel', "Total tokens: {0}", numberFormatter.value.format(content.totalTokens)));121}122if (content.maxInputTokens !== undefined) {123lines.push(localize('chatDebug.modelTurn.maxInputTokensLabel', "Max input tokens: {0}", numberFormatter.value.format(content.maxInputTokens)));124}125if (content.maxOutputTokens !== undefined) {126lines.push(localize('chatDebug.modelTurn.maxOutputTokensLabel', "Max output tokens: {0}", numberFormatter.value.format(content.maxOutputTokens)));127}128if (content.errorMessage) {129lines.push(localize('chatDebug.modelTurn.errorLabel', "Error: {0}", content.errorMessage));130}131132if (content.sections && content.sections.length > 0) {133lines.push('');134for (const section of content.sections) {135lines.push(`--- ${section.name} ---`);136lines.push(section.content);137lines.push('');138}139}140141return lines.join('\n');142}143144145