Skip to content

Commit

Permalink
feat: response speed (#42)
Browse files Browse the repository at this point in the history
* wip

* refactor: code cleanup
  • Loading branch information
mdjastrzebski authored Apr 2, 2024
1 parent 891dacf commit 5fdf95b
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 25 deletions.
4 changes: 2 additions & 2 deletions src/commands/chat/state/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export interface UserChatMessage {
export interface AiChatMessage {
type: 'ai';
text: string;
responseTime?: number;
usage?: ModelUsage;
responseTime: number;
usage: ModelUsage;
cost?: number;
data?: unknown;
}
Expand Down
47 changes: 30 additions & 17 deletions src/commands/chat/ui/StatusBar.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
import React, { useMemo } from 'react';
import { Box, Text } from 'ink';
import type { ModelUsage } from '../../../engine/inference.js';
import { formatCost, formatTokenCount } from '../../../format.js';
import { formatCost, formatSpeed, formatTokenCount } from '../../../format.js';
import { calculateUsageCost } from '../../../engine/session.js';
import { useChatState } from '../state/state.js';
import { useChatState, type ChatMessage } from '../state/state.js';

export function StatusBar() {
const verbose = useChatState((state) => state.verbose);
const items = useChatState((state) => state.chatMessages);
const provider = useChatState((state) => state.provider);
const providerConfig = useChatState((state) => state.providerConfig);

const totalUsage = useMemo(() => {
const usage: ModelUsage = { inputTokens: 0, outputTokens: 0, requests: 0 };
items.forEach((item) => {
if (item.type === 'ai') {
usage.inputTokens += item.usage?.inputTokens ?? 0;
usage.outputTokens += item.usage?.outputTokens ?? 0;
usage.requests += item.usage?.requests ?? 0;
}
});
return usage;
}, [items]);
const totalUsage = useMemo(() => calculateTotalUsage(items), [items]);
const totalTime = useMemo(() => calculateTotalResponseTime(items), [items]);

const modelPricing = provider.pricing[providerConfig.model];
const totalCost = calculateUsageCost(totalUsage, modelPricing) ?? 0;
Expand All @@ -30,15 +21,37 @@ export function StatusBar() {
<Box flexDirection="row" marginTop={1}>
<Text color={'gray'}>
LLM: {provider.label}/{providerConfig.model} - Total Cost:{' '}
{formatStats(totalCost, verbose ? totalUsage : undefined)}
{verbose ? formatVerboseStats(totalCost, totalUsage, totalTime) : formatCost(totalCost)}
</Text>
</Box>
);
}

const formatStats = (cost: number, usage?: ModelUsage) => {
function formatVerboseStats(cost: number, usage: ModelUsage, time: number) {
const usageOutput = usage
? ` (tokens: ${formatTokenCount(usage.inputTokens)} in + ${formatTokenCount(usage.outputTokens)} out, requests: ${usage.requests})`
? ` (tokens: ${formatTokenCount(usage.inputTokens)} in + ${formatTokenCount(usage.outputTokens)} out, requests: ${usage.requests}, speed: ${formatSpeed(usage.outputTokens, time)})`
: '';
return `${formatCost(cost)}${usageOutput}`;
};
}

function calculateTotalUsage(messages: ChatMessage[]) {
const usage: ModelUsage = { inputTokens: 0, outputTokens: 0, requests: 0 };
messages.forEach((message) => {
if (message.type === 'ai') {
usage.inputTokens += message.usage?.inputTokens ?? 0;
usage.outputTokens += message.usage?.outputTokens ?? 0;
usage.requests += message.usage?.requests ?? 0;
}
});
return usage;
}

function calculateTotalResponseTime(messages: ChatMessage[]) {
let total = 0;
messages.forEach((message) => {
if (message.type === 'ai') {
total += message.responseTime ?? 0;
}
});
return total;
}
8 changes: 6 additions & 2 deletions src/commands/chat/ui/list/AiChatMessageItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Text } from 'ink';
import { formatTime } from '../../../../format.js';
import { formatSpeed, formatTime } from '../../../../format.js';
import { colors } from '../../../../theme/colors.js';
import { useChatState, type AiChatMessage } from '../../state/state.js';
import { texts } from '../../texts.js';
Expand All @@ -19,7 +19,11 @@ export function AiChatMessageItem({ message }: AiChatMessageItemProps) {
<Text>{message.text}</Text>

{verbose && message.responseTime != null ? (
<Text color={colors.info}> ({formatTime(message.responseTime)})</Text>
<Text color={colors.info}>
{' '}
({formatTime(message.responseTime)},{' '}
{formatSpeed(message.usage?.outputTokens, message.responseTime)})
</Text>
) : null}
</Text>
{verbose ? <Text color={colors.debug}>{JSON.stringify(message.data, null, 2)}</Text> : null}
Expand Down
12 changes: 8 additions & 4 deletions src/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@ export function formatSessionCost(cost: SessionCost | undefined) {
return `costs: ${formatCost(cost.current)} (total: ${formatCost(cost.total)})`;
}

export function formatTime(timeInMs?: number) {
if (timeInMs == null) {
return '';
export function formatTime(timeInMs: number) {
return `${(timeInMs / 1000).toFixed(1)} s`;
}

export function formatSpeed(tokens: number, timeInMs: number) {
if (tokens == null || timeInMs == null || timeInMs === 0) {
return '? tok/s';
}

return `${(timeInMs / 1000).toFixed(1)} s`;
return `${((tokens * 1000) / timeInMs).toFixed(1)} tok/s`;
}

0 comments on commit 5fdf95b

Please sign in to comment.