Path: blob/main/extensions/copilot/src/extension/inlineChat/node/rendererVisualization.ts
13399 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 { PromptRenderer } from '@vscode/prompt-tsx';6import { IDebugValueEditorGlobals } from '../../../util/common/debugValueEditorGlobals';78export class RendererVisualizations {9public static getIfVisualizationTestIsRunning(): RendererVisualizations | undefined {10if (VisualizationTestRun.instance) {11return new RendererVisualizations();12}13return undefined;14}1516/**17* Exposes the rendering to the visualization extension.18* Also overrides the render method so that we can show the tree once rendering is done.19*/20public decorateAndRegister<T extends PromptRenderer<any, any>>(renderer: T, label: string): T {21let first = false;22renderer.render = async function (this: unknown, ...args: any[]) {23const result = await Object.getPrototypeOf(renderer).render.apply(this, ...args);24if (!first) {25first = true;26new RendererVisualization(renderer, label);27VisualizationTestRun.instance?.reload();28}2930return result;31} as any;32return renderer;33}34}3536/**37* Describes the visualization of a prompt renderer.38* Only used for debugging.39*/40class RendererVisualization {41constructor(42private readonly _renderer: PromptRenderer<any, any>,43label: string,44) {45VisualizationTestRun.instance?.addData(`Prompt ${label}`, () => this.getData());46}4748getData() {49class RenderedNode {50constructor(51public readonly label: string,52private readonly children: RenderedNode[],53private readonly range: [number, number] | undefined,54) {55if (!range) {56const childrenRanges = children.map(c => c.range).filter(r => !!r);57if (childrenRanges.length > 0) {58range = [Number.MAX_SAFE_INTEGER, 0];59for (const crange of childrenRanges) {60range[0] = Math.min(range[0], crange[0]);61range[1] = Math.max(range[1], crange[1]);62}63this.range = range;64}65}66}6768toObj(): unknown {69return {70label: this.label,71codicon: (this.label === 'Text' || this.label === 'LineBreak') ? 'text-size' : 'symbol-class',72range: this.range,73children: this.children.map(c => c.toObj()),74};75}76}7778const data = this._renderer;79let promptResult = '';8081function walk(item: /*PromptTreeElement |*/ any): RenderedNode {82if (item.kind === 0 /* Piece */) {83const messageClasses = [84'SystemMessage',85'UserMessage',86'AssistantMessage',87];88const ctorName = item['_obj'].constructor.name;8990if (messageClasses.some(c => ctorName.indexOf(c) !== -1)) {91promptResult += `\n======== ${ctorName} ========\n`;92}9394const children = (item['_children'] as any[]).map(c => walk(c)).filter(c => c.label !== 'LineBreak');9596return new RenderedNode(ctorName, children, undefined);9798} else if (item.kind === 1 /* Text */) {99const start = promptResult.length;100promptResult = promptResult + item.text;101return new RenderedNode('Text', [], [start, promptResult.length]);102} else if (item.kind === 2 /* LineBreak */) {103const start = promptResult.length;104promptResult = promptResult + '\n';105return new RenderedNode('LineBreak', [], [start, promptResult.length]);106}107throw new Error();108}109110const n = walk(data['_root']);111112const d = {113root: n.toObj(),114source: promptResult,115...{ $fileExtension: 'ast.w' },116};117118return d;119}120}121122export class VisualizationTestRun {123private static _instance: VisualizationTestRun | undefined = undefined;124public static get instance() { return this._instance; }125126public static startRun() {127this._instance = new VisualizationTestRun();128}129130private readonly g = globalThis as any as IDebugValueEditorGlobals;131132private _data: readonly any[] = [];133private _knownLabels: Set<string> = new Set();134135constructor() {136this.g.$$debugValueEditor_properties = [];137}138139public addData(label: string, getData: () => unknown, suffix?: string, property?: string): void {140const propertyName = 'debugValueProperty###' + label;141(globalThis as any)[propertyName] = () => {142const data = getData();143if (suffix) {144return { [suffix]: data };145}146return data;147};148149if (!this._knownLabels.has(propertyName)) {150this._knownLabels.add(propertyName);151const suffixStr = suffix ? `.${suffix}` : '';152this._data = [...this._data, { label, expression: `globalThis[${JSON.stringify(propertyName)}]()${suffixStr}${property ?? ''}` }];153this.g.$$debugValueEditor_properties = this._data;154} else {155this.g.$$debugValueEditor_refresh?.('{}');156}157}158159public reload(): void {160this.g.$$debugValueEditor_refresh?.('{}');161}162}163164165