Path: blob/main/extensions/copilot/test/simulation/workbench/components/request.tsx
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 { Badge, Text, Tooltip } from '@fluentui/react-components';6import * as mobxlite from 'mobx-react-lite';7import * as React from 'react';8import { coalesce } from '../../../../src/util/vs/base/common/arrays';9import { assertType } from '../../../../src/util/vs/base/common/types';10import { ISerialisedChatMessage, ISerialisedChatResponse, InterceptedRequest } from '../../shared/sharedTypes';11import { DiffEditor } from './diffEditor';12import { Editor } from './editor';13import { isToolCall } from '../utils/utils';14import { Raw } from '@vscode/prompt-tsx';1516type Props = {17readonly request: InterceptedRequest;18readonly title?: string;19readonly baselineRequest: InterceptedRequest | undefined;20readonly idx: number;21readonly expand: boolean;22};2324export const RequestView = mobxlite.observer(({ request, title, baselineRequest, idx, expand }: Props) => {2526const [expanded, setExpanded] = React.useState(expand);2728function renderResponse(response: ISerialisedChatResponse): string {29if (response.type === 'success') {30const parts = [response.value?.join('\n-----\n')];31parts.push(request.response.copilotFunctionCalls?.map(c => {32let argsStr = c.arguments;33try {34const parsedArgs = JSON.parse(c.arguments);35argsStr = JSON.stringify(parsedArgs, undefined, 2);36} catch (e) { }37return `🛠️ ${c.name} (${c.id}) ${argsStr}`;38}).join('\n'));39return coalesce(parts).join('\n');40} else if (response.type === 'length' && response.truncatedValue) {41return response.truncatedValue;42} else {43return '<NO REPLY>\n' + JSON.stringify(response);44}45}4647const currentRunRenderedMessages = renderRequestMessages(request.requestMessages);48const baselineRunRenderedMessages = baselineRequest ? renderRequestMessages(baselineRequest.requestMessages) : undefined;4950const requestChangeInfo = baselineRequest && (51<Tooltip content={'Below compares two requests - one from "Compare against" run and one from "Current run"'} relationship={'label'}>52<Text weight='bold'>{currentRunRenderedMessages === baselineRunRenderedMessages53? '(not changed)'54: '(changed)'}</Text>55</Tooltip>56);5758return (59<div className='request-container'>60<div className='title' onClick={() => setExpanded(!expanded)}>61{expanded ? '▼' : '▶'} {isToolCall(request) ? 'Tool Call' : 'Chat Request'} #{idx + 1} {title ? `- ${title}` : ``} - {getRequestStats(request)} {requestChangeInfo}62{request.model && <Badge title='Chat model used for request' color='informative' size='small'>{request.model}</Badge>}63</div>64{65!expanded66? null67: (68<div className='request-details' style={{ borderLeft: '1px solid #ccc', marginLeft: '7px', paddingLeft: '5px' }}>69<h3>Request to Model</h3>70{baselineRequest && <Text size={300}>Left editor - request from "Compare against" run, Right editor - request from "Current run"</Text>}71<div style={{ marginLeft: '-10px' }}>72{73baselineRequest74? (75assertType(baselineRunRenderedMessages, 'must be non-undefined as long as `baselineRequest` is defined'),76<DiffEditor original={baselineRunRenderedMessages} modified={currentRunRenderedMessages} languageId='markdown' />77)78: <Editor lineNumbers={false} languageId='markdown' contents={currentRunRenderedMessages} />79}80</div>81<h3>Response from Model</h3>82{baselineRequest && <Text size={300}>Left editor - response from "Compare against" run, Right editor - response from "Current run"</Text>}83<div style={{ marginLeft: '-10px' }}>84<div className='reply'>85{86baselineRequest87? <DiffEditor languageId='markdown' original={renderResponse(baselineRequest.response)} modified={renderResponse(request.response)} />88: ((request.response.type === 'success' && request.response.value !== undefined) || (request.response.type === 'length' && request.response.truncatedValue !== undefined)89? <Editor lineNumbers={false} languageId='markdown' contents={renderResponse(request.response)} />90: <div >{renderResponse(request.response)}</div>)91}92</div>93</div>94</div>95)96}97</div>98);99});100101function getRequestStats({ response }: InterceptedRequest) {102const result = [];103result.push(`(${response.type}`);104if (response.type === 'success') {105if (response.isCacheHit === true) {106result.push('cache hit');107if (response.cacheMetadata) {108result.push(`original fetch: ${response.cacheMetadata.requestTime.replace('T', ' ').substring(0, 19)}`);109result.push(`duration: ${response.cacheMetadata.requestDuration} ms`);110}111} else {112if (response.isCacheHit === false) {113result.push('cache miss');114}115result.push('server fetch');116if (response.cacheMetadata) {117result.push(`duration: ${response.cacheMetadata.requestDuration} ms`);118}119}120}121return result.join(', ') + ')';122}123124function renderRequestMessages(messages: string | ISerialisedChatMessage[]): string {125return Array.isArray(messages) ? renderChatMessages(messages) : messages;126}127128function renderChatMessages(messages: ISerialisedChatMessage[]): string {129const messageSeparator = '\n---------------------------------------------------------\n';130const roleSeparator = '\n------\n';131return (132messageSeparator +133messages134.map(m => {135const parts: string[] = [];136if (m.tool_call_id) {137parts.push(`🛠️ ${m.tool_call_id}`);138}139if (Array.isArray(m.content)) {140m.content.forEach(c => {141if (c.type === Raw.ChatCompletionContentPartKind.Text) {142parts.push(c.text);143} else {144parts.push(`${JSON.stringify(c)}`);145}146});147} else {148parts.push(m.content);149}150151if (m.tool_calls) {152parts.push(m.tool_calls.map(c => {153let argsStr = c.function.arguments;154try {155const parsedArgs = JSON.parse(c.function.arguments);156argsStr = JSON.stringify(parsedArgs, undefined, 2);157} catch (e) { }158return `🛠️ ${c.function.name} (${c.id}) ${argsStr}`;159}).join('\n'));160}161const content = coalesce(parts).join('\n');162return `${m.role.toUpperCase()}:${roleSeparator}${content}`;163})164.join(messageSeparator)165).trim();166}167168169