Another attempt to add toek usage info

This commit is contained in:
eduardruzga 2024-12-16 11:01:41 +02:00
parent 2e05270bab
commit 225b553876
6 changed files with 54 additions and 24 deletions

View File

@ -1 +1 @@
{ "commit": "fcb61ba49990d1d0cc13d05a0958007b6e9c1c38" } { "commit": "2e05270bab264d7ee83d7a1dd5408c969f648a68" }

View File

@ -1,14 +1,24 @@
import { memo } from 'react'; import { memo } from 'react';
import { Markdown } from './Markdown'; import { Markdown } from './Markdown';
import { USAGE_REGEX } from '~/utils/constants';
interface AssistantMessageProps { interface AssistantMessageProps {
content: string; content: string;
} }
export const AssistantMessage = memo(({ content }: AssistantMessageProps) => { export const AssistantMessage = memo(({ content }: AssistantMessageProps) => {
const match = content.match(USAGE_REGEX);
const usage = match ? JSON.parse(match[1]) : null;
const cleanContent = content.replace(USAGE_REGEX, '').trim();
return ( return (
<div className="overflow-hidden w-full"> <div className="overflow-hidden w-full">
<Markdown html>{content}</Markdown> {usage && (
<div className="text-sm text-bolt-elements-textSecondary mb-2">
Tokens: {usage.totalTokens} (prompt: {usage.promptTokens}, completion: {usage.completionTokens})
</div>
)}
<Markdown html>{cleanContent}</Markdown>
</div> </div>
); );
}); });

View File

@ -12,42 +12,36 @@ interface UserMessageProps {
export function UserMessage({ content }: UserMessageProps) { export function UserMessage({ content }: UserMessageProps) {
if (Array.isArray(content)) { if (Array.isArray(content)) {
const textItem = content.find((item) => item.type === 'text'); const textItem = content.find((item) => item.type === 'text');
const textContent = sanitizeUserMessage(textItem?.text || ''); const textContent = stripMetadata(textItem?.text || '');
const images = content.filter((item) => item.type === 'image' && item.image); const images = content.filter((item) => item.type === 'image' && item.image);
return ( return (
<div className="overflow-hidden pt-[4px]"> <div className="overflow-hidden pt-[4px]">
<div className="flex items-start gap-4"> <div className="flex flex-col gap-4">
<div className="flex-1"> {textContent && <Markdown html>{textContent}</Markdown>}
<Markdown limitedMarkdown>{textContent}</Markdown>
</div>
{images.length > 0 && (
<div className="flex-shrink-0 w-[160px]">
{images.map((item, index) => ( {images.map((item, index) => (
<div key={index} className="relative">
<img <img
key={index}
src={item.image} src={item.image}
alt={`Uploaded image ${index + 1}`} alt={`Image ${index + 1}`}
className="w-full h-[160px] rounded-lg object-cover border border-bolt-elements-borderColor" className="max-w-full h-auto rounded-lg"
style={{ maxHeight: '512px', objectFit: 'contain' }}
/> />
</div>
))} ))}
</div> </div>
)}
</div>
</div> </div>
); );
} }
const textContent = sanitizeUserMessage(content); const textContent = stripMetadata(content);
return ( return (
<div className="overflow-hidden pt-[4px]"> <div className="overflow-hidden pt-[4px]">
<Markdown limitedMarkdown>{textContent}</Markdown> <Markdown html>{textContent}</Markdown>
</div> </div>
); );
} }
function sanitizeUserMessage(content: string) { function stripMetadata(content: string) {
return content.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, ''); return content.replace(MODEL_REGEX, '').replace(PROVIDER_REGEX, '');
} }

View File

@ -1,5 +1,5 @@
export default class SwitchableStream extends TransformStream { export default class SwitchableStream extends TransformStream {
private _controller: TransformStreamDefaultController | null = null; _controller: TransformStreamDefaultController | null = null;
private _currentReader: ReadableStreamDefaultReader | null = null; private _currentReader: ReadableStreamDefaultReader | null = null;
private _switches = 0; private _switches = 0;

View File

@ -41,12 +41,36 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
const stream = new SwitchableStream(); const stream = new SwitchableStream();
const cumulativeUsage = {
completionTokens: 0,
promptTokens: 0,
totalTokens: 0,
};
try { try {
const options: StreamingOptions = { const options: StreamingOptions = {
toolChoice: 'none', toolChoice: 'none',
onFinish: async ({ text: content, finishReason, usage }) => { onFinish: async ({ text: content, finishReason, usage }) => {
console.log('usage', usage); console.log('usage', usage);
if (usage && stream._controller) {
cumulativeUsage.completionTokens += usage.completionTokens || 0;
cumulativeUsage.promptTokens += usage.promptTokens || 0;
cumulativeUsage.totalTokens += usage.totalTokens || 0;
// Send usage info in message metadata for assistant messages
const usageMetadata = `0:"[Usage: ${JSON.stringify({
completionTokens: cumulativeUsage.completionTokens,
promptTokens: cumulativeUsage.promptTokens,
totalTokens: cumulativeUsage.totalTokens,
})}\n]"`;
console.log(usageMetadata);
const encodedData = new TextEncoder().encode(usageMetadata);
stream._controller.enqueue(encodedData);
}
if (finishReason !== 'length') { if (finishReason !== 'length') {
return stream.close(); return stream.close();
} }
@ -83,6 +107,7 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
files, files,
providerSettings, providerSettings,
}); });
stream.switchSource(result.toDataStream()); stream.switchSource(result.toDataStream());
return new Response(stream.readable, { return new Response(stream.readable, {

View File

@ -9,6 +9,7 @@ export const WORK_DIR = `/home/${WORK_DIR_NAME}`;
export const MODIFICATIONS_TAG_NAME = 'bolt_file_modifications'; export const MODIFICATIONS_TAG_NAME = 'bolt_file_modifications';
export const MODEL_REGEX = /^\[Model: (.*?)\]\n\n/; export const MODEL_REGEX = /^\[Model: (.*?)\]\n\n/;
export const PROVIDER_REGEX = /\[Provider: (.*?)\]\n\n/; export const PROVIDER_REGEX = /\[Provider: (.*?)\]\n\n/;
export const USAGE_REGEX = /\[Usage: ({.*?})\]/; // Keep this regex for assistant messages
export const DEFAULT_MODEL = 'claude-3-5-sonnet-latest'; export const DEFAULT_MODEL = 'claude-3-5-sonnet-latest';
export const PROMPT_COOKIE_KEY = 'cachedPrompt'; export const PROMPT_COOKIE_KEY = 'cachedPrompt';