u
This commit is contained in:
@@ -27,6 +27,7 @@ import CalculateIcon from '@mui/icons-material/Calculate';
|
||||
import QrCodeScannerIcon from '@mui/icons-material/QrCodeScanner';
|
||||
import DirectionsIcon from '@mui/icons-material/Directions';
|
||||
import EditNoteIcon from '@mui/icons-material/EditNote';
|
||||
import PaidRoundedIcon from '@mui/icons-material/PaidRounded';
|
||||
import Button from '@mui/material/Button';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { deliveryEditorAutomat, setLastDeliveryPath } from '../../automats';
|
||||
@@ -73,7 +74,7 @@ function parseMessagePayload(raw) {
|
||||
return { content: raw };
|
||||
}
|
||||
|
||||
function formatUsageText(usage, prefix = '') {
|
||||
function formatUsageText(usage) {
|
||||
if (!usage) return '';
|
||||
const input = Number(usage.input ?? usage.prompt_tokens ?? 0);
|
||||
const output = Number(usage.output ?? usage.completion_tokens ?? 0);
|
||||
@@ -81,8 +82,28 @@ function formatUsageText(usage, prefix = '') {
|
||||
const cost = typeof usage.cost === 'number' ? ` · $${usage.cost.toFixed(6)}` : '';
|
||||
const fmt = (n) => n.toLocaleString('de-DE');
|
||||
const cachedPart = cached > 0 ? ` / ${fmt(cached)} Cache` : '';
|
||||
const pfx = prefix ? `${prefix} ` : '';
|
||||
return `${pfx}${fmt(input)} Prompt${cachedPart} · ${fmt(output)} Antwort${cost}`;
|
||||
return `${fmt(input)} Prompt${cachedPart} · ${fmt(output)} Antwort${cost}`;
|
||||
}
|
||||
|
||||
function formatUsageTooltip(usage) {
|
||||
if (!usage) return '';
|
||||
const input = Number(usage.input ?? usage.prompt_tokens ?? 0);
|
||||
const output = Number(usage.output ?? usage.completion_tokens ?? 0);
|
||||
const cached = Number(usage.cached ?? 0);
|
||||
const cost = typeof usage.cost === 'number' ? `$${usage.cost.toFixed(6)}` : null;
|
||||
const fmt = (n) => n.toLocaleString('de-DE');
|
||||
|
||||
const lines = [
|
||||
`Prompt: ${fmt(input)} Tokens`,
|
||||
];
|
||||
if (cached > 0) {
|
||||
lines.push(`Cache: ${fmt(cached)} Tokens`);
|
||||
}
|
||||
lines.push(`Antwort: ${fmt(output)} Tokens`);
|
||||
if (cost) {
|
||||
lines.push(`Kosten: ${cost}`);
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function aggregateUsage(turns = []) {
|
||||
@@ -685,11 +706,11 @@ function WorkIconView({ tool }) {
|
||||
return iconElement;
|
||||
}
|
||||
|
||||
function UsageChipView({ usage, prefix = '', isSummary = false, title = '' }) {
|
||||
const text = formatUsageText(usage, prefix);
|
||||
function UsageChipView({ usage, isSummary = false }) {
|
||||
const text = formatUsageText(usage);
|
||||
if (!text) return null;
|
||||
|
||||
const chip = (
|
||||
return (
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
@@ -711,16 +732,49 @@ function UsageChipView({ usage, prefix = '', isSummary = false, title = '' }) {
|
||||
{text}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (title) {
|
||||
return (
|
||||
<Tooltip title={title} arrow placement="top">
|
||||
{chip}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
function UsageIconView({ usage }) {
|
||||
if (!usage) return null;
|
||||
const tooltipText = formatUsageTooltip(usage);
|
||||
if (!tooltipText) return null;
|
||||
|
||||
return chip;
|
||||
return (
|
||||
<ClickTooltip
|
||||
title={
|
||||
<Box
|
||||
sx={{
|
||||
p: 0.5,
|
||||
maxWidth: 320,
|
||||
whiteSpace: 'pre-wrap',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '0.72rem',
|
||||
lineHeight: 1.4,
|
||||
}}
|
||||
>
|
||||
{tooltipText}
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: 24,
|
||||
height: 24,
|
||||
color: 'text.secondary',
|
||||
borderRadius: 1,
|
||||
'&:hover': {
|
||||
bgcolor: 'action.hover',
|
||||
color: 'text.primary',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PaidRoundedIcon sx={{ fontSize: 18, color: 'inherit' }} />
|
||||
</Box>
|
||||
</ClickTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export function TurnRow({ turn, isLive = false }) {
|
||||
@@ -732,8 +786,9 @@ export function TurnRow({ turn, isLive = false }) {
|
||||
);
|
||||
const hasTools = Boolean(turn.tools && turn.tools.length > 0);
|
||||
const hasContent = Boolean(turn.content);
|
||||
const showHandshake = !hasReasonings && !hasTools && !hasContent;
|
||||
const showStatusLine = showHandshake || hasReasonings || hasTools;
|
||||
const hasUsage = Boolean(turn.usage);
|
||||
const showHandshake = !hasReasonings && !hasTools && !hasContent && !hasUsage;
|
||||
const showStatusLine = showHandshake || hasReasonings || hasTools || hasUsage;
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -768,6 +823,10 @@ export function TurnRow({ turn, isLive = false }) {
|
||||
{turn.tools?.map((t, i) => (
|
||||
<WorkIconView key={t.id || i} tool={t} />
|
||||
))}
|
||||
|
||||
{hasUsage ? (
|
||||
<UsageIconView usage={turn.usage} />
|
||||
) : null}
|
||||
</Box>
|
||||
) : null}
|
||||
|
||||
@@ -794,13 +853,6 @@ export function TurnRow({ turn, isLive = false }) {
|
||||
<HealingMarkdown text={turn.content} streaming={isLive && !turn.sealed} />
|
||||
</Box>
|
||||
) : null}
|
||||
|
||||
{/* Pricing / usage is always on a new line */}
|
||||
{turn.usage ? (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<UsageChipView usage={turn.usage} />
|
||||
</Box>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1263,18 +1315,11 @@ export default class ChatFlowList extends PureComponent {
|
||||
{(() => {
|
||||
const activeSummary = aggregateUsage(activeTurns);
|
||||
if (!activeSummary) return null;
|
||||
const count = activeTurns.length;
|
||||
const prefix = count > 1 ? `Summe (${count} Turns):` : 'Summe:';
|
||||
const tooltipTitle = count > 1
|
||||
? `Zusammenfassung von ${count} LLM-Turns`
|
||||
: 'Zusammenfassung des LLM-Turns';
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', px: 2, py: 0.4 }}>
|
||||
<UsageChipView
|
||||
usage={activeSummary}
|
||||
prefix={prefix}
|
||||
isSummary={true}
|
||||
title={tooltipTitle}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user