Spaces:
Sleeping
Sleeping
File size: 5,689 Bytes
bf8b26e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
import { memo, Fragment } from 'react';
import { Markdown } from './Markdown';
import type { JSONValue } from 'ai';
import Popover from '~/components/ui/Popover';
import { workbenchStore } from '~/lib/stores/workbench';
import { WORK_DIR } from '~/utils/constants';
import WithTooltip from '~/components/ui/Tooltip';
interface AssistantMessageProps {
content: string;
annotations?: JSONValue[];
messageId?: string;
onRewind?: (messageId: string) => void;
onFork?: (messageId: string) => void;
}
function openArtifactInWorkbench(filePath: string) {
filePath = normalizedFilePath(filePath);
if (workbenchStore.currentView.get() !== 'code') {
workbenchStore.currentView.set('code');
}
workbenchStore.setSelectedFile(`${WORK_DIR}/${filePath}`);
}
function normalizedFilePath(path: string) {
let normalizedPath = path;
if (normalizedPath.startsWith(WORK_DIR)) {
normalizedPath = path.replace(WORK_DIR, '');
}
if (normalizedPath.startsWith('/')) {
normalizedPath = normalizedPath.slice(1);
}
return normalizedPath;
}
export const AssistantMessage = memo(({ content, annotations, messageId, onRewind, onFork }: AssistantMessageProps) => {
const filteredAnnotations = (annotations?.filter(
(annotation: JSONValue) => annotation && typeof annotation === 'object' && Object.keys(annotation).includes('type'),
) || []) as { type: string; value: any } & { [key: string]: any }[];
let chatSummary: string | undefined = undefined;
if (filteredAnnotations.find((annotation) => annotation.type === 'chatSummary')) {
chatSummary = filteredAnnotations.find((annotation) => annotation.type === 'chatSummary')?.summary;
}
let codeContext: string[] | undefined = undefined;
if (filteredAnnotations.find((annotation) => annotation.type === 'codeContext')) {
codeContext = filteredAnnotations.find((annotation) => annotation.type === 'codeContext')?.files;
}
const usage: {
completionTokens: number;
promptTokens: number;
totalTokens: number;
} = filteredAnnotations.find((annotation) => annotation.type === 'usage')?.value;
return (
<div className="overflow-hidden w-full">
<>
<div className=" flex gap-2 items-center text-sm text-bolt-elements-textSecondary mb-2">
{(codeContext || chatSummary) && (
<Popover side="right" align="start" trigger={<div className="i-ph:info" />}>
{chatSummary && (
<div className="max-w-chat">
<div className="summary max-h-96 flex flex-col">
<h2 className="border border-bolt-elements-borderColor rounded-md p4">Summary</h2>
<div style={{ zoom: 0.7 }} className="overflow-y-auto m4">
<Markdown>{chatSummary}</Markdown>
</div>
</div>
{codeContext && (
<div className="code-context flex flex-col p4 border border-bolt-elements-borderColor rounded-md">
<h2>Context</h2>
<div className="flex gap-4 mt-4 bolt" style={{ zoom: 0.6 }}>
{codeContext.map((x) => {
const normalized = normalizedFilePath(x);
return (
<Fragment key={normalized}>
<code
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"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
openArtifactInWorkbench(normalized);
}}
>
{normalized}
</code>
</Fragment>
);
})}
</div>
</div>
)}
</div>
)}
<div className="context"></div>
</Popover>
)}
<div className="flex w-full items-center justify-between">
{usage && (
<div>
Tokens: {usage.totalTokens} (prompt: {usage.promptTokens}, completion: {usage.completionTokens})
</div>
)}
{(onRewind || onFork) && messageId && (
<div className="flex gap-2 flex-col lg:flex-row ml-auto">
{onRewind && (
<WithTooltip tooltip="Revert to this message">
<button
onClick={() => onRewind(messageId)}
key="i-ph:arrow-u-up-left"
className="i-ph:arrow-u-up-left text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors"
/>
</WithTooltip>
)}
{onFork && (
<WithTooltip tooltip="Fork chat from this message">
<button
onClick={() => onFork(messageId)}
key="i-ph:git-fork"
className="i-ph:git-fork text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors"
/>
</WithTooltip>
)}
</div>
)}
</div>
</div>
</>
<Markdown html>{content}</Markdown>
</div>
);
});
|