This commit is contained in:
seb
2026-10-20 22:20:30 +02:00
parent c77297af72
commit 4f367ee943

View File

@@ -27,6 +27,7 @@ import CalculateIcon from '@mui/icons-material/Calculate';
import QrCodeScannerIcon from '@mui/icons-material/QrCodeScanner'; import QrCodeScannerIcon from '@mui/icons-material/QrCodeScanner';
import DirectionsIcon from '@mui/icons-material/Directions'; import DirectionsIcon from '@mui/icons-material/Directions';
import EditNoteIcon from '@mui/icons-material/EditNote'; import EditNoteIcon from '@mui/icons-material/EditNote';
import PaidRoundedIcon from '@mui/icons-material/PaidRounded';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import { enqueueSnackbar } from 'notistack'; import { enqueueSnackbar } from 'notistack';
import { deliveryEditorAutomat, setLastDeliveryPath } from '../../automats'; import { deliveryEditorAutomat, setLastDeliveryPath } from '../../automats';
@@ -73,7 +74,7 @@ function parseMessagePayload(raw) {
return { content: raw }; return { content: raw };
} }
function formatUsageText(usage, prefix = '') { function formatUsageText(usage) {
if (!usage) return ''; if (!usage) return '';
const input = Number(usage.input ?? usage.prompt_tokens ?? 0); const input = Number(usage.input ?? usage.prompt_tokens ?? 0);
const output = Number(usage.output ?? usage.completion_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 cost = typeof usage.cost === 'number' ? ` · $${usage.cost.toFixed(6)}` : '';
const fmt = (n) => n.toLocaleString('de-DE'); const fmt = (n) => n.toLocaleString('de-DE');
const cachedPart = cached > 0 ? ` / ${fmt(cached)} Cache` : ''; const cachedPart = cached > 0 ? ` / ${fmt(cached)} Cache` : '';
const pfx = prefix ? `${prefix} ` : ''; return `${fmt(input)} Prompt${cachedPart} · ${fmt(output)} Antwort${cost}`;
return `${pfx}${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 = []) { export function aggregateUsage(turns = []) {
@@ -685,11 +706,11 @@ function WorkIconView({ tool }) {
return iconElement; return iconElement;
} }
function UsageChipView({ usage, prefix = '', isSummary = false, title = '' }) { function UsageChipView({ usage, isSummary = false }) {
const text = formatUsageText(usage, prefix); const text = formatUsageText(usage);
if (!text) return null; if (!text) return null;
const chip = ( return (
<Box <Box
component="span" component="span"
sx={{ sx={{
@@ -711,16 +732,49 @@ function UsageChipView({ usage, prefix = '', isSummary = false, title = '' }) {
{text} {text}
</Box> </Box>
); );
if (title) {
return (
<Tooltip title={title} arrow placement="top">
{chip}
</Tooltip>
);
} }
return chip; function UsageIconView({ usage }) {
if (!usage) return null;
const tooltipText = formatUsageTooltip(usage);
if (!tooltipText) return null;
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 }) { 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 hasTools = Boolean(turn.tools && turn.tools.length > 0);
const hasContent = Boolean(turn.content); const hasContent = Boolean(turn.content);
const showHandshake = !hasReasonings && !hasTools && !hasContent; const hasUsage = Boolean(turn.usage);
const showStatusLine = showHandshake || hasReasonings || hasTools; const showHandshake = !hasReasonings && !hasTools && !hasContent && !hasUsage;
const showStatusLine = showHandshake || hasReasonings || hasTools || hasUsage;
return ( return (
<Box <Box
@@ -768,6 +823,10 @@ export function TurnRow({ turn, isLive = false }) {
{turn.tools?.map((t, i) => ( {turn.tools?.map((t, i) => (
<WorkIconView key={t.id || i} tool={t} /> <WorkIconView key={t.id || i} tool={t} />
))} ))}
{hasUsage ? (
<UsageIconView usage={turn.usage} />
) : null}
</Box> </Box>
) : null} ) : null}
@@ -794,13 +853,6 @@ export function TurnRow({ turn, isLive = false }) {
<HealingMarkdown text={turn.content} streaming={isLive && !turn.sealed} /> <HealingMarkdown text={turn.content} streaming={isLive && !turn.sealed} />
</Box> </Box>
) : null} ) : null}
{/* Pricing / usage is always on a new line */}
{turn.usage ? (
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<UsageChipView usage={turn.usage} />
</Box>
) : null}
</Box> </Box>
); );
} }
@@ -1263,18 +1315,11 @@ export default class ChatFlowList extends PureComponent {
{(() => { {(() => {
const activeSummary = aggregateUsage(activeTurns); const activeSummary = aggregateUsage(activeTurns);
if (!activeSummary) return null; 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 ( return (
<Box sx={{ display: 'flex', alignItems: 'center', px: 2, py: 0.4 }}> <Box sx={{ display: 'flex', alignItems: 'center', px: 2, py: 0.4 }}>
<UsageChipView <UsageChipView
usage={activeSummary} usage={activeSummary}
prefix={prefix}
isSummary={true} isSummary={true}
title={tooltipTitle}
/> />
</Box> </Box>
); );