legends810/testingnew
0
1import { memo } from 'react';2import { Markdown } from './Markdown';3import type { JSONValue } from 'ai';4import Popover from '~/components/ui/Popover';5import { workbenchStore } from '~/lib/stores/workbench';6import { WORK_DIR } from '~/utils/constants';7 8interface AssistantMessageProps {9 content: string;10 annotations?: JSONValue[];11}12 13function openArtifactInWorkbench(filePath: string) {14 filePath = normalizedFilePath(filePath);15 16 if (workbenchStore.currentView.get() !== 'code') {17 workbenchStore.currentView.set('code');18 }19 20 workbenchStore.setSelectedFile(`${WORK_DIR}/${filePath}`);21}22 23function normalizedFilePath(path: string) {24 let normalizedPath = path;25 26 if (normalizedPath.startsWith(WORK_DIR)) {27 normalizedPath = path.replace(WORK_DIR, '');28 }29 30 if (normalizedPath.startsWith('/')) {31 normalizedPath = normalizedPath.slice(1);32 }33 34 return normalizedPath;35}36 37export const AssistantMessage = memo(({ content, annotations }: AssistantMessageProps) => {38 const filteredAnnotations = (annotations?.filter(39 (annotation: JSONValue) => annotation && typeof annotation === 'object' && Object.keys(annotation).includes('type'),40 ) || []) as { type: string; value: any } & { [key: string]: any }[];41 42 let chatSummary: string | undefined = undefined;43 44 if (filteredAnnotations.find((annotation) => annotation.type === 'chatSummary')) {45 chatSummary = filteredAnnotations.find((annotation) => annotation.type === 'chatSummary')?.summary;46 }47 48 let codeContext: string[] | undefined = undefined;49 50 if (filteredAnnotations.find((annotation) => annotation.type === 'codeContext')) {51 codeContext = filteredAnnotations.find((annotation) => annotation.type === 'codeContext')?.files;52 }53 54 const usage: {55 completionTokens: number;56 promptTokens: number;57 totalTokens: number;58 isCacheHit?: boolean;59 isCacheMiss?: boolean;60 } = filteredAnnotations.find((annotation) => annotation.type === 'usage')?.value ?? undefined;61 62 const cacheHitMsg = usage?.isCacheHit ? ' [Cache Hit]' : '';63 const cacheMissMsg = usage?.isCacheMiss ? ' [Cache Miss]' : '';64 65 return (66 <div className="overflow-hidden w-full">67 <>68 <div className=" flex gap-2 items-center text-sm text-bolt-elements-textSecondary mb-2">69 {(codeContext || chatSummary) && (70 <Popover side="right" align="start" trigger={<div className="i-ph:info" />}>71 {chatSummary && (72 <div className="max-w-chat">73 <div className="summary max-h-96 flex flex-col">74 <h2 className="border border-bolt-elements-borderColor rounded-md p4">Summary</h2>75 <div style={{ zoom: 0.7 }} className="overflow-y-auto m4">76 <Markdown>{chatSummary}</Markdown>77 </div>78 </div>79 {codeContext && (80 <div className="code-context flex flex-col p4 border border-bolt-elements-borderColor rounded-md">81 <h2>Context</h2>82 <div className="flex gap-4 mt-4 bolt" style={{ zoom: 0.6 }}>83 {codeContext.map((x) => {84 const normalized = normalizedFilePath(x);85 return (86 <>87 <code88 className="bg-bolt-elements-artifacts-inlineCode-background text-bolt-elements-artifacts-inlineCode-text px-1.5 py-1 rounded-md text-bolt-elements-item-contentAccent hover:underline cursor-pointer"89 onClick={(e) => {90 e.preventDefault();91 e.stopPropagation();92 openArtifactInWorkbench(normalized);93 }}94 >95 {normalized}96 </code>97 </>98 );99 })}100 </div>101 </div>102 )}103 </div>104 )}105 <div className="context"></div>106 </Popover>107 )}108 {usage && (109 <div className="text-sm text-bolt-elements-textSecondary mb-2">110 Tokens: {usage.totalTokens} (prompt: {usage.promptTokens}, completion: {usage.completionTokens})111 <span className="text-sm text-green-500 ml-1">{cacheHitMsg}</span>112 <span className="text-sm text-red-500 ml-1">{cacheMissMsg}</span>113 </div>114 )}115 </div>116 </>117 <Markdown html>{content}</Markdown>118 </div>119 );120});121 