♻️ refactor(web): consolidate design-system primitives and responsive data views
This commit is contained in:
+4
-11
@@ -21,9 +21,9 @@ import { useState, useRef, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { Button } from '@/components/design-system/button'
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
|
||||
export interface AudioClip {
|
||||
@@ -88,12 +88,9 @@ function AudioClipCard({ clip }: { clip: AudioClip }) {
|
||||
<div className='mb-1 flex items-center gap-2'>
|
||||
<span className='truncate text-sm font-medium'>{title}</span>
|
||||
{duration != null && duration > 0 && (
|
||||
<StatusBadge
|
||||
label={formatDuration(duration)}
|
||||
variant='neutral'
|
||||
className='shrink-0'
|
||||
copyable={false}
|
||||
/>
|
||||
<StatusBadge variant='neutral' className='shrink-0'>
|
||||
{formatDuration(duration)}
|
||||
</StatusBadge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -108,8 +105,6 @@ function AudioClipCard({ clip }: { clip: AudioClip }) {
|
||||
</span>
|
||||
<Button
|
||||
variant='outline'
|
||||
size='sm'
|
||||
className='h-7 gap-1 text-xs'
|
||||
onClick={() => window.open(audioUrl, '_blank')}
|
||||
>
|
||||
<ExternalLink className='h-3 w-3' />
|
||||
@@ -117,8 +112,6 @@ function AudioClipCard({ clip }: { clip: AudioClip }) {
|
||||
</Button>
|
||||
<Button
|
||||
variant='outline'
|
||||
size='sm'
|
||||
className='h-7 gap-1 text-xs'
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(audioUrl)
|
||||
toast.success(t('Copied'))
|
||||
|
||||
+62
-92
@@ -33,9 +33,9 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { Button } from '@/components/design-system/button'
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { StatusBadge, type StatusBadgeProps } from '@/components/status-badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { StatusBadge, type StatusVariant } from '@/components/status-badge'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { DynamicPricingBreakdown } from '@/features/pricing/components/dynamic-pricing-breakdown'
|
||||
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
|
||||
@@ -74,14 +74,6 @@ const CHANNEL_FIELD_LABELS: Record<string, string> = {
|
||||
key: 'Key',
|
||||
}
|
||||
|
||||
function timingTextColorClass(
|
||||
variant: 'success' | 'warning' | 'danger'
|
||||
): string {
|
||||
if (variant === 'success') return 'text-success'
|
||||
if (variant === 'warning') return 'text-warning'
|
||||
return 'text-destructive'
|
||||
}
|
||||
|
||||
function DetailRow(props: {
|
||||
label: React.ReactNode
|
||||
value: React.ReactNode
|
||||
@@ -109,16 +101,16 @@ function DetailRow(props: {
|
||||
function DetailSection(props: {
|
||||
icon?: React.ReactNode
|
||||
label: string
|
||||
variant?: 'default' | 'danger'
|
||||
variant?: 'default' | 'destructive'
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const isDanger = props.variant === 'danger'
|
||||
const isDestructive = props.variant === 'destructive'
|
||||
return (
|
||||
<div className='min-w-0 space-y-1.5'>
|
||||
<Label
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 text-xs font-semibold',
|
||||
isDanger && 'text-red-500'
|
||||
isDestructive && 'text-destructive'
|
||||
)}
|
||||
>
|
||||
{props.icon}
|
||||
@@ -127,8 +119,8 @@ function DetailSection(props: {
|
||||
<div
|
||||
className={cn(
|
||||
'min-w-0 space-y-1 overflow-hidden rounded-md border p-2.5 max-sm:p-2',
|
||||
isDanger
|
||||
? 'border-red-200 bg-red-50 dark:border-red-900 dark:bg-red-950/20'
|
||||
isDestructive
|
||||
? 'border-destructive/25 bg-destructive/10'
|
||||
: 'bg-muted/30'
|
||||
)}
|
||||
>
|
||||
@@ -335,8 +327,8 @@ function BillingBreakdown(props: {
|
||||
|
||||
return (
|
||||
<DetailSection label={t('Billing Details')}>
|
||||
{rows.map((row, idx) => (
|
||||
<DetailRow key={idx} label={row.label} value={row.value} mono />
|
||||
{rows.map((row) => (
|
||||
<DetailRow key={row.label} label={row.label} value={row.value} mono />
|
||||
))}
|
||||
</DetailSection>
|
||||
)
|
||||
@@ -401,8 +393,8 @@ function TokenBreakdown(props: { log: UsageLog; other: LogOtherData }) {
|
||||
|
||||
return (
|
||||
<DetailSection label={t('Token Breakdown')}>
|
||||
{rows.map((row, idx) => (
|
||||
<DetailRow key={idx} label={row.label} value={row.value} mono />
|
||||
{rows.map((row) => (
|
||||
<DetailRow key={row.label} label={row.label} value={row.value} mono />
|
||||
))}
|
||||
</DetailSection>
|
||||
)
|
||||
@@ -421,6 +413,12 @@ export function DetailsDialog(props: DetailsDialogProps) {
|
||||
const details = props.log.content ?? ''
|
||||
const other = parseLogOther(props.log.other)
|
||||
const typeConfig = getLogTypeConfig(props.log.type)
|
||||
let reasoningEffortVariant: StatusVariant = 'success'
|
||||
if (other?.reasoning_effort === 'high') {
|
||||
reasoningEffortVariant = 'warning'
|
||||
} else if (other?.reasoning_effort === 'medium') {
|
||||
reasoningEffortVariant = 'info'
|
||||
}
|
||||
|
||||
const isViolation = isViolationFeeLog(other)
|
||||
const isRefund = props.log.type === 6
|
||||
@@ -551,12 +549,9 @@ export function DetailsDialog(props: DetailsDialogProps) {
|
||||
title={
|
||||
<>
|
||||
{t('Log Details')}
|
||||
<StatusBadge
|
||||
label={t(typeConfig.label)}
|
||||
variant={typeConfig.color as StatusBadgeProps['variant']}
|
||||
size='sm'
|
||||
copyable={false}
|
||||
/>
|
||||
<StatusBadge variant={typeConfig.variant} size='sm'>
|
||||
{t(typeConfig.label)}
|
||||
</StatusBadge>
|
||||
</>
|
||||
}
|
||||
description={t('View the complete details for this log entry')}
|
||||
@@ -643,32 +638,25 @@ export function DetailsDialog(props: DetailsDialogProps) {
|
||||
<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)}
|
||||
<span className='flex flex-wrap items-center gap-1'>
|
||||
<StatusBadge
|
||||
appearance='plain'
|
||||
variant={getResponseTimeColor(
|
||||
props.log.use_time,
|
||||
props.log.completion_tokens
|
||||
)}
|
||||
>
|
||||
{formatUseTime(props.log.use_time)}
|
||||
</StatusBadge>
|
||||
{props.log.is_stream &&
|
||||
other?.frt != null &&
|
||||
other.frt > 0 && (
|
||||
<span
|
||||
className={cn(
|
||||
'font-normal',
|
||||
timingTextColorClass(
|
||||
getFirstResponseTimeColor(other.frt / 1000)
|
||||
)
|
||||
)}
|
||||
<StatusBadge
|
||||
appearance='plain'
|
||||
variant={getFirstResponseTimeColor(other.frt / 1000)}
|
||||
>
|
||||
{' '}
|
||||
(FRT: {formatUseTime(other.frt / 1000)})
|
||||
</span>
|
||||
{`(FRT: ${formatUseTime(other.frt / 1000)})`}
|
||||
</StatusBadge>
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
@@ -682,14 +670,14 @@ export function DetailsDialog(props: DetailsDialogProps) {
|
||||
<div className='relative min-w-0'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
className='absolute top-0 right-0 h-5 w-5 p-0'
|
||||
size='icon-xs'
|
||||
className='absolute top-0 right-0'
|
||||
onClick={() => copyToClipboard(conversionLabel)}
|
||||
title={t('Copy to clipboard')}
|
||||
aria-label={t('Copy to clipboard')}
|
||||
>
|
||||
{copiedText === conversionLabel ? (
|
||||
<Check className='size-3 text-green-600' />
|
||||
<Check className='text-success size-3' />
|
||||
) : (
|
||||
<Copy className='size-3' />
|
||||
)}
|
||||
@@ -721,7 +709,7 @@ export function DetailsDialog(props: DetailsDialogProps) {
|
||||
<DetailSection
|
||||
icon={<AlertTriangle className='size-3.5' aria-hidden='true' />}
|
||||
label={t('Quota clamped')}
|
||||
variant='danger'
|
||||
variant='destructive'
|
||||
>
|
||||
<p className='mb-1 text-xs wrap-break-word'>
|
||||
{t('Quota saturation protection triggered')}
|
||||
@@ -756,7 +744,7 @@ export function DetailsDialog(props: DetailsDialogProps) {
|
||||
<DetailSection
|
||||
icon={<AlertTriangle className='size-3.5' aria-hidden='true' />}
|
||||
label={t('Reject Reason')}
|
||||
variant='danger'
|
||||
variant='destructive'
|
||||
>
|
||||
<p className='text-xs wrap-break-word'>{other.reject_reason}</p>
|
||||
</DetailSection>
|
||||
@@ -767,7 +755,7 @@ export function DetailsDialog(props: DetailsDialogProps) {
|
||||
<DetailSection
|
||||
icon={<AlertTriangle className='size-3.5' aria-hidden='true' />}
|
||||
label={t('Violation Fee')}
|
||||
variant='danger'
|
||||
variant='destructive'
|
||||
>
|
||||
{other.violation_fee_code && (
|
||||
<DetailRow
|
||||
@@ -808,9 +796,9 @@ export function DetailsDialog(props: DetailsDialogProps) {
|
||||
icon={<ShieldCheck className='size-3.5' aria-hidden='true' />}
|
||||
label={t('Top-up Audit Info')}
|
||||
>
|
||||
{topupAuditFields.map((field, idx) => (
|
||||
{topupAuditFields.map((field) => (
|
||||
<DetailRow
|
||||
key={idx}
|
||||
key={field.label}
|
||||
label={field.label}
|
||||
value={field.value}
|
||||
mono
|
||||
@@ -897,9 +885,9 @@ export function DetailsDialog(props: DetailsDialogProps) {
|
||||
{operationText != null && (
|
||||
<DetailRow label={t('Operation')} value={operationText} />
|
||||
)}
|
||||
{loginAuditFields.map((field, idx) => (
|
||||
{loginAuditFields.map((field) => (
|
||||
<DetailRow
|
||||
key={idx}
|
||||
key={field.label}
|
||||
label={field.label}
|
||||
value={field.value}
|
||||
mono
|
||||
@@ -950,18 +938,9 @@ export function DetailsDialog(props: DetailsDialogProps) {
|
||||
<DetailRow
|
||||
label={t('Reasoning Effort')}
|
||||
value={
|
||||
<StatusBadge
|
||||
label={other.reasoning_effort}
|
||||
variant={
|
||||
other.reasoning_effort === 'high'
|
||||
? 'orange'
|
||||
: other.reasoning_effort === 'medium'
|
||||
? 'yellow'
|
||||
: 'green'
|
||||
}
|
||||
size='sm'
|
||||
copyable={false}
|
||||
/>
|
||||
<StatusBadge variant={reasoningEffortVariant} size='sm'>
|
||||
{other.reasoning_effort}
|
||||
</StatusBadge>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
@@ -971,12 +950,9 @@ export function DetailsDialog(props: DetailsDialogProps) {
|
||||
<DetailRow
|
||||
label={t('System Prompt')}
|
||||
value={
|
||||
<StatusBadge
|
||||
label={t('Overwritten')}
|
||||
variant='orange'
|
||||
size='sm'
|
||||
copyable={false}
|
||||
/>
|
||||
<StatusBadge variant='warning' size='sm'>
|
||||
{t('Overwritten')}
|
||||
</StatusBadge>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
@@ -1055,12 +1031,9 @@ export function DetailsDialog(props: DetailsDialogProps) {
|
||||
<DetailRow
|
||||
label={t('Status')}
|
||||
value={
|
||||
<StatusBadge
|
||||
label={other.stream_status.status || t('Error')}
|
||||
variant='red'
|
||||
size='sm'
|
||||
copyable={false}
|
||||
/>
|
||||
<StatusBadge variant='destructive' size='sm'>
|
||||
{other.stream_status.status || t('Error')}
|
||||
</StatusBadge>
|
||||
}
|
||||
/>
|
||||
{other.stream_status.end_reason && (
|
||||
@@ -1144,20 +1117,17 @@ export function DetailsDialog(props: DetailsDialogProps) {
|
||||
icon={<Settings2 className='size-3.5' aria-hidden='true' />}
|
||||
label={`${t('Param Override')} (${other.po.length})`}
|
||||
>
|
||||
{other.po.filter(Boolean).map((line, idx) => {
|
||||
{other.po.filter(Boolean).map((line) => {
|
||||
const parsed = parseAuditLine(line)
|
||||
if (!parsed) return null
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
key={line}
|
||||
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}
|
||||
/>
|
||||
<StatusBadge variant='neutral' className='shrink-0'>
|
||||
{getParamOverrideActionLabel(parsed.action, t)}
|
||||
</StatusBadge>
|
||||
<span className='min-w-0 font-mono text-xs leading-relaxed break-all sm:wrap-break-word'>
|
||||
{parsed.content}
|
||||
</span>
|
||||
@@ -1174,14 +1144,14 @@ export function DetailsDialog(props: DetailsDialogProps) {
|
||||
<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'
|
||||
size='icon-xs'
|
||||
className='absolute top-1.5 right-1.5'
|
||||
onClick={() => copyToClipboard(details)}
|
||||
title={t('Copy to clipboard')}
|
||||
aria-label={t('Copy to clipboard')}
|
||||
>
|
||||
{copiedText === details ? (
|
||||
<Check className='size-3 text-green-600' />
|
||||
<Check className='text-success size-3' />
|
||||
) : (
|
||||
<Copy className='size-3' />
|
||||
)}
|
||||
|
||||
+3
-3
@@ -19,8 +19,8 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
import { Copy, Check } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { Button } from '@/components/design-system/button'
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
|
||||
@@ -58,8 +58,8 @@ export function FailReasonDialog({
|
||||
<div className='bg-muted/50 relative rounded-md border border-red-200 p-3'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
className='absolute top-2 right-2 h-8 w-8 p-0'
|
||||
size='icon'
|
||||
className='absolute top-2 right-2'
|
||||
onClick={() => copyToClipboard(failReason)}
|
||||
title={t('Copy to clipboard')}
|
||||
>
|
||||
|
||||
@@ -19,8 +19,8 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
import { Copy, Check } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { Button } from '@/components/design-system/button'
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
|
||||
@@ -59,8 +59,8 @@ export function PromptDialog({
|
||||
<div className='bg-muted/50 relative rounded-md border p-3'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
className='absolute top-2 right-2 h-8 w-8 p-0'
|
||||
size='icon'
|
||||
className='absolute top-2 right-2'
|
||||
onClick={() => copyToClipboard(prompt)}
|
||||
title={t('Copy to clipboard')}
|
||||
>
|
||||
@@ -85,8 +85,8 @@ export function PromptDialog({
|
||||
<div className='bg-muted/50 relative rounded-md border p-3'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
className='absolute top-2 right-2 h-8 w-8 p-0'
|
||||
size='icon'
|
||||
className='absolute top-2 right-2'
|
||||
onClick={() => copyToClipboard(promptEn)}
|
||||
title={t('Copy to clipboard')}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user