Commit 9b9b19e9 by yyhhyyyyyy Committed by GitHub

fix(web): prevent usage log details dialog from clipping bottom content (#5533)

parent 43c7e30a
...@@ -38,7 +38,6 @@ import { cn } from '@/lib/utils' ...@@ -38,7 +38,6 @@ import { cn } from '@/lib/utils'
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard' import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Dialog } from '@/components/dialog' import { Dialog } from '@/components/dialog'
import { StatusBadge, type StatusBadgeProps } from '@/components/status-badge' import { StatusBadge, type StatusBadgeProps } from '@/components/status-badge'
import { DynamicPricingBreakdown } from '@/features/pricing/components/dynamic-pricing-breakdown' import { DynamicPricingBreakdown } from '@/features/pricing/components/dynamic-pricing-breakdown'
...@@ -94,7 +93,7 @@ function DetailRow(props: { ...@@ -94,7 +93,7 @@ function DetailRow(props: {
</span> </span>
<span <span
className={cn( className={cn(
'max-w-full min-w-0 text-xs break-all sm:break-words', 'max-w-full min-w-0 text-xs break-all sm:wrap-break-word',
props.mono && 'font-mono', props.mono && 'font-mono',
props.muted && 'text-muted-foreground' props.muted && 'text-muted-foreground'
)} )}
...@@ -558,600 +557,592 @@ export function DetailsDialog(props: DetailsDialogProps) { ...@@ -558,600 +557,592 @@ export function DetailsDialog(props: DetailsDialogProps) {
headerClassName='max-sm:gap-1' headerClassName='max-sm:gap-1'
titleClassName='flex items-center gap-2 text-base' titleClassName='flex items-center gap-2 text-base'
descriptionClassName='sr-only' descriptionClassName='sr-only'
contentHeight='min(72vh, 720px)' contentHeight='min(72dvh, 720px)'
bodyClassName='space-y-4' bodyClassName='pr-2 sm:pr-4'
> >
<ScrollArea className='max-h-[70vh] min-w-0 overflow-hidden pr-2 max-sm:max-h-[calc(100dvh-7rem)] sm:pr-4'> <div className='w-full max-w-full min-w-0 space-y-2.5 overflow-x-hidden py-1 sm:space-y-3'>
<div className='w-full max-w-full min-w-0 space-y-2.5 overflow-hidden py-1 sm:space-y-3'> {/* Overview section - key identifiers */}
{/* Overview section - key identifiers */} <div className='min-w-0 space-y-1'>
<div className='min-w-0 space-y-1'> {props.log.request_id && (
{props.log.request_id && ( <DetailRow
label={t('Request ID')}
value={props.log.request_id}
mono
/>
)}
{props.log.upstream_request_id && (
<DetailRow
label={t('Upstream Request ID')}
value={props.log.upstream_request_id}
mono
/>
)}
{props.isAdmin && props.log.channel > 0 && (
<DetailRow
label={t('Channel')}
value={
<span>
{props.log.channel}
{props.log.channel_name && (
<span className='text-muted-foreground'>
{' '}
({props.log.channel_name})
</span>
)}
</span>
}
mono
/>
)}
{channelChain && props.isAdmin && (
<DetailRow label={t('Retry Chain')} value={channelChain} mono />
)}
{props.log.token_name && (
<DetailRow label={t('Token')} value={props.log.token_name} mono />
)}
{(props.log.group || other?.group) && (
<DetailRow
label={t('Group')}
value={props.log.group || other?.group || ''}
mono
/>
)}
{showAdminIp && (
<DetailRow
label={t('IP Address')}
value={
<span className='flex items-center gap-1'>
<Globe className='size-3 text-amber-500' aria-hidden='true' />
{props.log.ip}
</span>
}
mono
/>
)}
{showTiming && props.log.use_time > 0 && (
<DetailRow
label={t('Response Time')}
value={
<span
className={cn(
'font-medium',
timingTextColorClass(
getResponseTimeColor(
props.log.use_time,
props.log.completion_tokens
)
)
)}
>
{formatUseTime(props.log.use_time)}
{props.log.is_stream &&
other?.frt != null &&
other.frt > 0 && (
<span
className={cn(
'font-normal',
timingTextColorClass(
getFirstResponseTimeColor(other.frt / 1000)
)
)}
>
{' '}
(FRT: {formatUseTime(other.frt / 1000)})
</span>
)}
</span>
}
/>
)}
</div>
{/* Request conversion (admin only, not for refund) */}
{showConversion && (
<DetailSection label={t('Request Conversion')}>
<div className='relative min-w-0'>
<Button
variant='ghost'
size='sm'
className='absolute top-0 right-0 h-5 w-5 p-0'
onClick={() => copyToClipboard(conversionLabel)}
title={t('Copy to clipboard')}
aria-label={t('Copy to clipboard')}
>
{copiedText === conversionLabel ? (
<Check className='size-3 text-green-600' />
) : (
<Copy className='size-3' />
)}
</Button>
<div className='min-w-0 space-y-1 pr-6'>
{other?.request_path && (
<DetailRow
label={t('Path')}
value={other.request_path}
mono
/>
)}
<div className='flex min-w-0 items-center gap-1.5 text-xs'>
<Route
className='text-muted-foreground size-3'
aria-hidden='true'
/>
<span className='min-w-0 break-all sm:wrap-break-word'>
{conversionLabel}
</span>
</div>
</div>
</div>
</DetailSection>
)}
{/* Reject reason (admin only) */}
{props.isAdmin && other?.reject_reason && (
<DetailSection
icon={<AlertTriangle className='size-3.5' aria-hidden='true' />}
label={t('Reject Reason')}
variant='danger'
>
<p className='text-xs wrap-break-word'>{other.reject_reason}</p>
</DetailSection>
)}
{/* Violation fee info */}
{isViolation && other && (
<DetailSection
icon={<AlertTriangle className='size-3.5' aria-hidden='true' />}
label={t('Violation Fee')}
variant='danger'
>
{other.violation_fee_code && (
<DetailRow <DetailRow
label={t('Request ID')} label={t('Violation Code')}
value={props.log.request_id} value={other.violation_fee_code}
mono mono
/> />
)} )}
{props.log.upstream_request_id && ( {other.violation_fee_marker && (
<DetailRow <DetailRow
label={t('Upstream Request ID')} label={t('Violation Marker')}
value={props.log.upstream_request_id} value={other.violation_fee_marker}
mono
/> />
)} )}
<DetailRow
label={t('Fee Amount')}
value={formatLogQuota(other.fee_quota ?? props.log.quota)}
mono
/>
</DetailSection>
)}
{/* Refund details (type=6) */}
{isRefund && other && (other.task_id || other.reason) && (
<DetailSection label={t('Refund Details')}>
{other.task_id && (
<DetailRow label={t('Task ID')} value={other.task_id} mono />
)}
{other.reason && (
<DetailRow label={t('Reason')} value={other.reason} />
)}
</DetailSection>
)}
{props.isAdmin && props.log.channel > 0 && ( {/* Top-up audit info (type=1, admin only) */}
{showTopupAuditSection && (
<DetailSection
icon={<ShieldCheck className='size-3.5' aria-hidden='true' />}
label={t('Top-up Audit Info')}
>
{topupAuditFields.map((field, idx) => (
<DetailRow <DetailRow
label={t('Channel')} key={idx}
value={ label={field.label}
<span> value={field.value}
{props.log.channel}
{props.log.channel_name && (
<span className='text-muted-foreground'>
{' '}
({props.log.channel_name})
</span>
)}
</span>
}
mono mono
/> />
))}
{showLegacyTopupWarning && (
<div className='flex items-start gap-1.5 text-xs text-amber-600 dark:text-amber-400'>
<Info className='mt-0.5 size-3.5 shrink-0' aria-hidden='true' />
<span>
{t(
'This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.'
)}
</span>
</div>
)} )}
</DetailSection>
)}
{channelChain && props.isAdmin && ( {/* Manage operator (type=3, admin only) */}
<DetailRow label={t('Retry Chain')} value={channelChain} mono /> {manageOperator && (
)} <DetailRow
label={
<span className='flex items-center gap-1.5'>
<UserCog
className='text-muted-foreground size-3.5'
aria-hidden='true'
/>
{t('Operator Admin')}
</span>
}
value={manageOperator}
mono
/>
)}
{props.log.token_name && ( {/* Operation audit info (type=3, admin only) */}
<DetailRow label={t('Token')} value={props.log.token_name} mono /> {showManageAuditSection && (
<DetailSection
icon={<ShieldCheck className='size-3.5' aria-hidden='true' />}
label={t('Operation Audit Info')}
>
{operationText != null && (
<DetailRow label={t('Operation')} value={operationText} />
)} )}
{authMethodLabel !== '' && (
{(props.log.group || other?.group) && (
<DetailRow <DetailRow
label={t('Group')} label={t('Authentication Method')}
value={props.log.group || other?.group || ''} value={authMethodLabel}
mono
/> />
)} )}
{changedFieldsText !== '' && (
{showAdminIp && (
<DetailRow <DetailRow
label={t('IP Address')} label={t('Changed Fields')}
value={ value={changedFieldsText}
<span className='flex items-center gap-1'> />
<Globe )}
className='size-3 text-amber-500' {auditRoute?.method && auditRoute?.route && (
aria-hidden='true' <DetailRow
/> label={t('Request')}
{props.log.ip} value={`${auditRoute.method} ${auditRoute.route}`}
</span>
}
mono mono
/> />
)} )}
{auditRoute?.status != null && (
{showTiming && props.log.use_time > 0 && (
<DetailRow <DetailRow
label={t('Response Time')} label={t('Result')}
value={ value={
<span auditRoute.success
className={cn( ? `${t('Success')} (${auditRoute.status})`
'font-medium', : `${t('Failed')} (${auditRoute.status})`
timingTextColorClass(
getResponseTimeColor(
props.log.use_time,
props.log.completion_tokens
)
)
)}
>
{formatUseTime(props.log.use_time)}
{props.log.is_stream &&
other?.frt != null &&
other.frt > 0 && (
<span
className={cn(
'font-normal',
timingTextColorClass(
getFirstResponseTimeColor(other.frt / 1000)
)
)}
>
{' '}
(FRT: {formatUseTime(other.frt / 1000)})
</span>
)}
</span>
} }
mono
/> />
)} )}
</div> </DetailSection>
)}
{/* Request conversion (admin only, not for refund) */}
{showConversion && (
<DetailSection label={t('Request Conversion')}>
<div className='relative min-w-0'>
<Button
variant='ghost'
size='sm'
className='absolute top-0 right-0 h-5 w-5 p-0'
onClick={() => copyToClipboard(conversionLabel)}
title={t('Copy to clipboard')}
aria-label={t('Copy to clipboard')}
>
{copiedText === conversionLabel ? (
<Check className='size-3 text-green-600' />
) : (
<Copy className='size-3' />
)}
</Button>
<div className='min-w-0 space-y-1 pr-6'>
{other?.request_path && (
<DetailRow
label={t('Path')}
value={other.request_path}
mono
/>
)}
<div className='flex min-w-0 items-center gap-1.5 text-xs'>
<Route
className='text-muted-foreground size-3'
aria-hidden='true'
/>
<span className='min-w-0 break-all sm:break-words'>
{conversionLabel}
</span>
</div>
</div>
</div>
</DetailSection>
)}
{/* Reject reason (admin only) */} {/* Login audit info (type=7) */}
{props.isAdmin && other?.reject_reason && ( {isLogin && loginAuditFields.length > 0 && (
<DetailSection <DetailSection
icon={<AlertTriangle className='size-3.5' aria-hidden='true' />} icon={<LogIn className='size-3.5' aria-hidden='true' />}
label={t('Reject Reason')} label={t('Login Info')}
variant='danger' >
> {operationText != null && (
<p className='text-xs break-words'>{other.reject_reason}</p> <DetailRow label={t('Operation')} value={operationText} />
</DetailSection> )}
)} {loginAuditFields.map((field, idx) => (
<DetailRow
key={idx}
label={field.label}
value={field.value}
mono
/>
))}
</DetailSection>
)}
{/* Violation fee info */} {/* Audio/WebSocket token breakdown */}
{isViolation && other && ( {hasAudioTokens && other && (
<DetailSection <DetailSection
icon={<AlertTriangle className='size-3.5' aria-hidden='true' />} icon={<Headphones className='size-3.5' aria-hidden='true' />}
label={t('Violation Fee')} label={t('Audio Tokens')}
variant='danger' >
> {other.audio_input != null && other.audio_input > 0 && (
{other.violation_fee_code && (
<DetailRow
label={t('Violation Code')}
value={other.violation_fee_code}
mono
/>
)}
{other.violation_fee_marker && (
<DetailRow
label={t('Violation Marker')}
value={other.violation_fee_marker}
/>
)}
<DetailRow <DetailRow
label={t('Fee Amount')} label={t('Audio Input')}
value={formatLogQuota(other.fee_quota ?? props.log.quota)} value={formatTokens(other.audio_input)}
mono mono
/> />
</DetailSection> )}
)} {other.audio_output != null && other.audio_output > 0 && (
<DetailRow
label={t('Audio Output')}
value={formatTokens(other.audio_output)}
mono
/>
)}
{other.text_input != null && other.text_input > 0 && (
<DetailRow
label={t('Text Input')}
value={formatTokens(other.text_input)}
mono
/>
)}
{other.text_output != null && other.text_output > 0 && (
<DetailRow
label={t('Text Output')}
value={formatTokens(other.text_output)}
mono
/>
)}
</DetailSection>
)}
{/* Refund details (type=6) */} {/* Reasoning effort */}
{isRefund && other && (other.task_id || other.reason) && ( {other?.reasoning_effort && (
<DetailSection label={t('Refund Details')}> <DetailRow
{other.task_id && ( label={t('Reasoning Effort')}
<DetailRow label={t('Task ID')} value={other.task_id} mono /> value={
)} <StatusBadge
{other.reason && ( label={other.reasoning_effort}
<DetailRow label={t('Reason')} value={other.reason} /> variant={
)} other.reasoning_effort === 'high'
</DetailSection> ? 'orange'
)} : other.reasoning_effort === 'medium'
? 'yellow'
: 'green'
}
size='sm'
copyable={false}
/>
}
/>
)}
{/* Top-up audit info (type=1, admin only) */} {/* System prompt override */}
{showTopupAuditSection && ( {other?.is_system_prompt_overwritten && (
<DetailSection <DetailRow
icon={<ShieldCheck className='size-3.5' aria-hidden='true' />} label={t('System Prompt')}
label={t('Top-up Audit Info')} value={
> <StatusBadge
{topupAuditFields.map((field, idx) => ( label={t('Overwritten')}
<DetailRow variant='orange'
key={idx} size='sm'
label={field.label} copyable={false}
value={field.value} />
mono }
/> />
))} )}
{showLegacyTopupWarning && (
<div className='flex items-start gap-1.5 text-xs text-amber-600 dark:text-amber-400'>
<Info
className='mt-0.5 size-3.5 shrink-0'
aria-hidden='true'
/>
<span>
{t(
'This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.'
)}
</span>
</div>
)}
</DetailSection>
)}
{/* Manage operator (type=3, admin only) */} {/* Model mapping */}
{manageOperator && ( {other?.is_model_mapped && other?.upstream_model_name && (
<DetailSection label={t('Model Mapping')}>
<DetailRow <DetailRow
label={ label={t('Request Model')}
<span className='flex items-center gap-1.5'> value={props.log.model_name}
<UserCog
className='text-muted-foreground size-3.5'
aria-hidden='true'
/>
{t('Operator Admin')}
</span>
}
value={manageOperator}
mono mono
/> />
)} <DetailRow
label={t('Actual Model')}
{/* Operation audit info (type=3, admin only) */} value={other.upstream_model_name}
{showManageAuditSection && ( mono
<DetailSection />
icon={<ShieldCheck className='size-3.5' aria-hidden='true' />} </DetailSection>
label={t('Operation Audit Info')} )}
>
{operationText != null && (
<DetailRow label={t('Operation')} value={operationText} />
)}
{authMethodLabel !== '' && (
<DetailRow
label={t('Authentication Method')}
value={authMethodLabel}
/>
)}
{changedFieldsText !== '' && (
<DetailRow
label={t('Changed Fields')}
value={changedFieldsText}
/>
)}
{auditRoute?.method && auditRoute?.route && (
<DetailRow
label={t('Request')}
value={`${auditRoute.method} ${auditRoute.route}`}
mono
/>
)}
{auditRoute?.status != null && (
<DetailRow
label={t('Result')}
value={
auditRoute.success
? `${t('Success')} (${auditRoute.status})`
: `${t('Failed')} (${auditRoute.status})`
}
mono
/>
)}
</DetailSection>
)}
{/* Login audit info (type=7) */} {/* Token breakdown (for consume/error types with token data) */}
{isLogin && loginAuditFields.length > 0 && ( {isDisplayableType(props.log.type) && other && (
<DetailSection <TokenBreakdown log={props.log} other={other} />
icon={<LogIn className='size-3.5' aria-hidden='true' />} )}
label={t('Login Info')}
>
{operationText != null && (
<DetailRow label={t('Operation')} value={operationText} />
)}
{loginAuditFields.map((field, idx) => (
<DetailRow
key={idx}
label={field.label}
value={field.value}
mono
/>
))}
</DetailSection>
)}
{/* Audio/WebSocket token breakdown */} {/* Billing breakdown (consume type) */}
{hasAudioTokens && other && ( {isConsume && other && !isViolation && (
<DetailSection <BillingBreakdown
icon={<Headphones className='size-3.5' aria-hidden='true' />} log={props.log}
label={t('Audio Tokens')} other={other}
> isAdmin={props.isAdmin}
{other.audio_input != null && other.audio_input > 0 && ( />
<DetailRow )}
label={t('Audio Input')}
value={formatTokens(other.audio_input)}
mono
/>
)}
{other.audio_output != null && other.audio_output > 0 && (
<DetailRow
label={t('Audio Output')}
value={formatTokens(other.audio_output)}
mono
/>
)}
{other.text_input != null && other.text_input > 0 && (
<DetailRow
label={t('Text Input')}
value={formatTokens(other.text_input)}
mono
/>
)}
{other.text_output != null && other.text_output > 0 && (
<DetailRow
label={t('Text Output')}
value={formatTokens(other.text_output)}
mono
/>
)}
</DetailSection>
)}
{/* Reasoning effort */} {/* Tiered pricing breakdown (when billing_mode is tiered_expr) */}
{other?.reasoning_effort && ( {isTieredBilling && other?.expr_b64 && (
<DetailRow <div className='bg-muted/30 min-w-0 overflow-hidden rounded-md border px-3 max-sm:px-2'>
label={t('Reasoning Effort')} <DynamicPricingBreakdown
value={ billingExpr={decodeBillingExprB64(other.expr_b64)}
<StatusBadge matchedTierLabel={other.matched_tier}
label={other.reasoning_effort} hideCacheColumns={!hasAnyCacheTokens(other)}
variant={
other.reasoning_effort === 'high'
? 'orange'
: other.reasoning_effort === 'medium'
? 'yellow'
: 'green'
}
size='sm'
copyable={false}
/>
}
/> />
)} </div>
)}
{/* System prompt override */} {/* Admin billing mode indicator for non-consume */}
{other?.is_system_prompt_overwritten && ( {props.isAdmin &&
!isConsume &&
props.log.type !== 6 &&
other?.admin_info && (
<DetailRow <DetailRow
label={t('System Prompt')} label={t('Billing Source')}
value={ value={
<StatusBadge <span className='flex items-center gap-1'>
label={t('Overwritten')} {other.admin_info.local_count_tokens ? (
variant='orange' <Monitor className='size-3 text-blue-500' />
size='sm' ) : (
copyable={false} <Cloud className='size-3 text-emerald-500' />
/> )}
<span className='text-xs'>
{other.admin_info.local_count_tokens
? t('Local Billing')
: t('Upstream Response')}
</span>
</span>
} }
/> />
)} )}
{/* Model mapping */} {/* Stream status details (admin only) */}
{other?.is_model_mapped && other?.upstream_model_name && ( {props.isAdmin &&
<DetailSection label={t('Model Mapping')}> other?.stream_status &&
<DetailRow other.stream_status.status !== 'ok' && (
label={t('Request Model')} <DetailSection label={t('Stream Status')}>
value={props.log.model_name}
mono
/>
<DetailRow
label={t('Actual Model')}
value={other.upstream_model_name}
mono
/>
</DetailSection>
)}
{/* Token breakdown (for consume/error types with token data) */}
{isDisplayableType(props.log.type) && other && (
<TokenBreakdown log={props.log} other={other} />
)}
{/* Billing breakdown (consume type) */}
{isConsume && other && !isViolation && (
<BillingBreakdown
log={props.log}
other={other}
isAdmin={props.isAdmin}
/>
)}
{/* Tiered pricing breakdown (when billing_mode is tiered_expr) */}
{isTieredBilling && other?.expr_b64 && (
<div className='bg-muted/30 min-w-0 overflow-hidden rounded-md border px-3 max-sm:px-2'>
<DynamicPricingBreakdown
billingExpr={decodeBillingExprB64(other.expr_b64)}
matchedTierLabel={other.matched_tier}
hideCacheColumns={!hasAnyCacheTokens(other)}
/>
</div>
)}
{/* Admin billing mode indicator for non-consume */}
{props.isAdmin &&
!isConsume &&
props.log.type !== 6 &&
other?.admin_info && (
<DetailRow <DetailRow
label={t('Billing Source')} label={t('Status')}
value={ value={
<span className='flex items-center gap-1'> <StatusBadge
{other.admin_info.local_count_tokens ? ( label={other.stream_status.status || t('Error')}
<Monitor className='size-3 text-blue-500' /> variant='red'
) : ( size='sm'
<Cloud className='size-3 text-emerald-500' /> copyable={false}
)} />
<span className='text-xs'>
{other.admin_info.local_count_tokens
? t('Local Billing')
: t('Upstream Response')}
</span>
</span>
} }
/> />
)} {other.stream_status.end_reason && (
{/* Stream status details (admin only) */}
{props.isAdmin &&
other?.stream_status &&
other.stream_status.status !== 'ok' && (
<DetailSection label={t('Stream Status')}>
<DetailRow <DetailRow
label={t('Status')} label={t('End Reason')}
value={ value={other.stream_status.end_reason}
<StatusBadge
label={other.stream_status.status || t('Error')}
variant='red'
size='sm'
copyable={false}
/>
}
/>
{other.stream_status.end_reason && (
<DetailRow
label={t('End Reason')}
value={other.stream_status.end_reason}
/>
)}
{(other.stream_status.error_count ?? 0) > 0 && (
<DetailRow
label={t('Soft Errors')}
value={String(other.stream_status.error_count)}
/>
)}
{other.stream_status.end_error && (
<DetailRow
label={t('End Error')}
value={other.stream_status.end_error}
/>
)}
{Array.isArray(other.stream_status.errors) &&
other.stream_status.errors.length > 0 && (
<pre className='bg-background/60 mt-1 max-h-32 overflow-y-auto rounded border p-2 font-mono text-[11px] leading-relaxed break-words whitespace-pre-wrap'>
{other.stream_status.errors.join('\n')}
</pre>
)}
</DetailSection>
)}
{/* Subscription billing details */}
{isSubscription && other && (
<DetailSection label={t('Subscription Billing')}>
{other.subscription_plan_id && (
<DetailRow
label={t('Plan')}
value={`#${other.subscription_plan_id} ${other.subscription_plan_title || ''}`.trim()}
/> />
)} )}
{other.subscription_id && ( {(other.stream_status.error_count ?? 0) > 0 && (
<DetailRow <DetailRow
label={t('Instance')} label={t('Soft Errors')}
value={`#${other.subscription_id}`} value={String(other.stream_status.error_count)}
mono
/> />
)} )}
{other.subscription_pre_consumed != null && ( {other.stream_status.end_error && (
<DetailRow <DetailRow
label={t('Pre-consumed')} label={t('End Error')}
value={formatLogQuota(other.subscription_pre_consumed)} value={other.stream_status.end_error}
mono
/> />
)} )}
{other.subscription_post_delta != null && {Array.isArray(other.stream_status.errors) &&
other.subscription_post_delta !== 0 && ( other.stream_status.errors.length > 0 && (
<DetailRow <pre className='bg-background/60 mt-1 max-h-32 overflow-y-auto rounded border p-2 font-mono text-[11px] leading-relaxed wrap-break-word whitespace-pre-wrap'>
label={t('Post Delta')} {other.stream_status.errors.join('\n')}
value={formatLogQuota(other.subscription_post_delta)} </pre>
mono
/>
)} )}
{other.subscription_consumed != null && ( </DetailSection>
<DetailRow )}
label={t('Final Consumed')}
value={formatLogQuota(other.subscription_consumed)} {/* Subscription billing details */}
mono {isSubscription && other && (
/> <DetailSection label={t('Subscription Billing')}>
)} {other.subscription_plan_id && (
{other.subscription_remain != null && ( <DetailRow
label={t('Plan')}
value={`#${other.subscription_plan_id} ${other.subscription_plan_title || ''}`.trim()}
/>
)}
{other.subscription_id && (
<DetailRow
label={t('Instance')}
value={`#${other.subscription_id}`}
mono
/>
)}
{other.subscription_pre_consumed != null && (
<DetailRow
label={t('Pre-consumed')}
value={formatLogQuota(other.subscription_pre_consumed)}
mono
/>
)}
{other.subscription_post_delta != null &&
other.subscription_post_delta !== 0 && (
<DetailRow <DetailRow
label={t('Remaining')} label={t('Post Delta')}
value={`${formatLogQuota(other.subscription_remain)}${other.subscription_total != null ? ` / ${formatLogQuota(other.subscription_total)}` : ''}`} value={formatLogQuota(other.subscription_post_delta)}
mono mono
/> />
)} )}
</DetailSection> {other.subscription_consumed != null && (
)} <DetailRow
label={t('Final Consumed')}
{/* Param override */} value={formatLogQuota(other.subscription_consumed)}
{other?.po && Array.isArray(other.po) && other.po.length > 0 && ( mono
<DetailSection />
icon={<Settings2 className='size-3.5' aria-hidden='true' />} )}
label={`${t('Param Override')} (${other.po.length})`} {other.subscription_remain != null && (
> <DetailRow
{other.po.filter(Boolean).map((line, idx) => { label={t('Remaining')}
const parsed = parseAuditLine(line) value={`${formatLogQuota(other.subscription_remain)}${other.subscription_total != null ? ` / ${formatLogQuota(other.subscription_total)}` : ''}`}
if (!parsed) return null mono
return ( />
<div )}
key={idx} </DetailSection>
className='bg-background/60 flex min-w-0 flex-col gap-1.5 rounded border p-2 sm:flex-row sm:items-start sm:gap-2' )}
>
<StatusBadge
variant='neutral'
label={getParamOverrideActionLabel(parsed.action, t)}
className='shrink-0 font-medium'
copyable={false}
/>
<span className='min-w-0 font-mono text-[11px] leading-relaxed break-all sm:break-words'>
{parsed.content}
</span>
</div>
)
})}
</DetailSection>
)}
{/* Content */} {/* Param override */}
{details && ( {other?.po && Array.isArray(other.po) && other.po.length > 0 && (
<div className='space-y-1.5'> <DetailSection
<Label className='text-xs font-semibold'>{t('Content')}</Label> icon={<Settings2 className='size-3.5' aria-hidden='true' />}
<div className='bg-muted/30 relative min-w-0 overflow-hidden rounded-md border p-2.5'> label={`${t('Param Override')} (${other.po.length})`}
<Button >
variant='ghost' {other.po.filter(Boolean).map((line, idx) => {
size='sm' const parsed = parseAuditLine(line)
className='absolute top-1.5 right-1.5 h-5 w-5 p-0' if (!parsed) return null
onClick={() => copyToClipboard(details)} return (
title={t('Copy to clipboard')} <div
aria-label={t('Copy to clipboard')} key={idx}
className='bg-background/60 flex min-w-0 flex-col gap-1.5 rounded border p-2 sm:flex-row sm:items-start sm:gap-2'
> >
{copiedText === details ? ( <StatusBadge
<Check className='size-3 text-green-600' /> variant='neutral'
) : ( label={getParamOverrideActionLabel(parsed.action, t)}
<Copy className='size-3' /> className='shrink-0 font-medium'
)} copyable={false}
</Button> />
<p className='min-w-0 pr-6 text-xs leading-relaxed break-all whitespace-pre-wrap sm:break-words'> <span className='min-w-0 font-mono text-[11px] leading-relaxed break-all sm:wrap-break-word'>
{details} {parsed.content}
</p> </span>
</div> </div>
)
})}
</DetailSection>
)}
{/* Content */}
{details && (
<div className='space-y-1.5'>
<Label className='text-xs font-semibold'>{t('Content')}</Label>
<div className='bg-muted/30 relative min-w-0 overflow-hidden rounded-md border p-2.5'>
<Button
variant='ghost'
size='sm'
className='absolute top-1.5 right-1.5 h-5 w-5 p-0'
onClick={() => copyToClipboard(details)}
title={t('Copy to clipboard')}
aria-label={t('Copy to clipboard')}
>
{copiedText === details ? (
<Check className='size-3 text-green-600' />
) : (
<Copy className='size-3' />
)}
</Button>
<p className='min-w-0 pr-6 text-xs leading-relaxed break-all whitespace-pre-wrap sm:wrap-break-word'>
{details}
</p>
</div> </div>
)} </div>
</div> )}
</ScrollArea> </div>
</Dialog> </Dialog>
) )
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment