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 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) {
|
function UsageIconView({ usage }) {
|
||||||
return (
|
if (!usage) return null;
|
||||||
<Tooltip title={title} arrow placement="top">
|
const tooltipText = formatUsageTooltip(usage);
|
||||||
{chip}
|
if (!tooltipText) return null;
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 }) {
|
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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user