-
{title}
+
+
+
OPS CONSOLE
+
{title}
{description && (
-
{description}
+
{description}
)}
- {action &&
{action}
}
+ {action &&
{action}
}
)
}
diff --git a/frontend/src/components/Pagination.tsx b/frontend/src/components/Pagination.tsx
index 97786e0d..17477afc 100644
--- a/frontend/src/components/Pagination.tsx
+++ b/frontend/src/components/Pagination.tsx
@@ -1,91 +1,91 @@
-interface PaginationProps {
- page: number
- pages: number
- total: number
- limit: number
- onChange: (page: number) => void
-}
-
-function buildPageNumbers(page: number, pages: number): (number | 'ellipsis')[] {
- if (pages <= 7) {
- return Array.from({ length: pages }, (_, i) => i + 1)
- }
-
- const result: (number | 'ellipsis')[] = []
-
- // Always show first page
- result.push(1)
-
- if (page <= 4) {
- // Near the start: show 1 2 3 4 5 ... N
- for (let i = 2; i <= Math.min(5, pages - 1); i++) result.push(i)
- result.push('ellipsis')
- } else if (page >= pages - 3) {
- // Near the end: show 1 ... N-4 N-3 N-2 N-1 N
- result.push('ellipsis')
- for (let i = Math.max(2, pages - 4); i <= pages - 1; i++) result.push(i)
- } else {
- // Middle: show 1 ... p-1 p p+1 ... N
- result.push('ellipsis')
- for (let i = page - 1; i <= page + 1; i++) result.push(i)
- result.push('ellipsis')
- }
-
- // Always show last page
- result.push(pages)
-
- return result
-}
-
-export default function Pagination({ page, pages, total, onChange }: PaginationProps) {
- const pageNumbers = buildPageNumbers(page, pages)
-
- return (
-
-
共 {total} 条
-
-
- {/* Prev */}
-
-
- {/* Page numbers */}
- {pageNumbers.map((p, i) =>
- p === 'ellipsis' ? (
-
- …
-
- ) : (
-
- )
- )}
-
- {/* Next */}
-
-
-
- )
-}
+interface PaginationProps {
+ page: number
+ pages: number
+ total: number
+ limit: number
+ onChange: (page: number) => void
+}
+
+function buildPageNumbers(page: number, pages: number): (number | 'ellipsis')[] {
+ if (pages <= 7) {
+ return Array.from({ length: pages }, (_, i) => i + 1)
+ }
+
+ const result: (number | 'ellipsis')[] = []
+
+ // Always show first page
+ result.push(1)
+
+ if (page <= 4) {
+ // Near the start: show 1 2 3 4 5 ... N
+ for (let i = 2; i <= Math.min(5, pages - 1); i++) result.push(i)
+ result.push('ellipsis')
+ } else if (page >= pages - 3) {
+ // Near the end: show 1 ... N-4 N-3 N-2 N-1 N
+ result.push('ellipsis')
+ for (let i = Math.max(2, pages - 4); i <= pages - 1; i++) result.push(i)
+ } else {
+ // Middle: show 1 ... p-1 p p+1 ... N
+ result.push('ellipsis')
+ for (let i = page - 1; i <= page + 1; i++) result.push(i)
+ result.push('ellipsis')
+ }
+
+ // Always show last page
+ result.push(pages)
+
+ return result
+}
+
+export default function Pagination({ page, pages, total, onChange }: PaginationProps) {
+ const pageNumbers = buildPageNumbers(page, pages)
+
+ return (
+
+
共 {total} 条
+
+
+ {/* Prev */}
+
+
+ {/* Page numbers */}
+ {pageNumbers.map((p, i) =>
+ p === 'ellipsis' ? (
+
+ …
+
+ ) : (
+
+ )
+ )}
+
+ {/* Next */}
+
+
+
+ )
+}
diff --git a/frontend/src/components/SkeletonLoader.tsx b/frontend/src/components/SkeletonLoader.tsx
index 6d74e973..90b60a9b 100644
--- a/frontend/src/components/SkeletonLoader.tsx
+++ b/frontend/src/components/SkeletonLoader.tsx
@@ -1,63 +1,63 @@
-import { Skeleton } from '@/components/ui/skeleton'
-
-interface TableSkeletonProps {
- rows?: number
-}
-
-export function TableSkeleton({ rows = 5 }: TableSkeletonProps) {
- return (
-
- {/* Header row */}
-
-
-
-
-
-
-
-
- {/* Data rows */}
- {Array.from({ length: rows }).map((_, i) => (
-
-
-
-
-
-
-
-
- ))}
-
- )
-}
-
-interface CardSkeletonProps {
- cards?: number
-}
-
-export function CardSkeleton({ cards = 4 }: CardSkeletonProps) {
- return (
-
- {Array.from({ length: cards }).map((_, i) => (
-
- ))}
-
- )
-}
+import { Skeleton } from '@/components/ui/skeleton'
+
+interface TableSkeletonProps {
+ rows?: number
+}
+
+export function TableSkeleton({ rows = 5 }: TableSkeletonProps) {
+ return (
+
+ {/* Header row */}
+
+
+
+
+
+
+
+
+ {/* Data rows */}
+ {Array.from({ length: rows }).map((_, i) => (
+
+
+
+
+
+
+
+
+ ))}
+
+ )
+}
+
+interface CardSkeletonProps {
+ cards?: number
+}
+
+export function CardSkeleton({ cards = 4 }: CardSkeletonProps) {
+ return (
+
+ {Array.from({ length: cards }).map((_, i) => (
+
+ ))}
+
+ )
+}
diff --git a/frontend/src/components/StatusBadge.tsx b/frontend/src/components/StatusBadge.tsx
index e74922f2..4638bfea 100644
--- a/frontend/src/components/StatusBadge.tsx
+++ b/frontend/src/components/StatusBadge.tsx
@@ -1,18 +1,20 @@
import { clsx } from 'clsx'
const STATUS_STYLES: Record
= {
- pending: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300',
- running: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300',
- ai_processing: 'bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300',
- completed: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300',
- failed: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300',
- cancelled: 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300',
- online: 'bg-green-100 text-green-800',
- offline: 'bg-gray-100 text-gray-700',
- sent: 'bg-green-100 text-green-800',
- raw: 'bg-gray-100 text-gray-700',
- normalized: 'bg-blue-100 text-blue-800',
- ai_processed: 'bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300',
+ pending: 'border-amber-400/40 bg-amber-400/10 text-amber-200',
+ running: 'border-zinc-200/50 bg-zinc-100/10 text-zinc-100',
+ ai_processing: 'border-primary-500/50 bg-primary-500/10 text-primary-100',
+ completed: 'border-emerald-400/40 bg-emerald-400/10 text-emerald-200',
+ failed: 'border-primary-500/60 bg-primary-500/15 text-primary-100',
+ cancelled: 'border-zinc-500/40 bg-zinc-500/10 text-zinc-300',
+ online: 'border-emerald-400/40 bg-emerald-400/10 text-emerald-200',
+ offline: 'border-zinc-500/40 bg-zinc-500/10 text-zinc-300',
+ sent: 'border-emerald-400/40 bg-emerald-400/10 text-emerald-200',
+ acked: 'border-emerald-400/40 bg-emerald-400/10 text-emerald-200',
+ not_required: 'border-zinc-500/40 bg-zinc-500/10 text-zinc-300',
+ raw: 'border-zinc-500/40 bg-zinc-500/10 text-zinc-300',
+ normalized: 'border-zinc-200/50 bg-zinc-100/10 text-zinc-100',
+ ai_processed: 'border-primary-500/50 bg-primary-500/10 text-primary-100',
}
const STATUS_LABELS: Record = {
@@ -26,6 +28,8 @@ const STATUS_LABELS: Record = {
normalized: '已归一化',
ai_processed: '已处理',
sent: '已发送',
+ acked: '已回执',
+ not_required: '无需回执',
online: '在线',
offline: '离线',
}
@@ -36,12 +40,12 @@ interface Props {
}
export default function StatusBadge({ status, className }: Props) {
- const style = STATUS_STYLES[status] ?? 'bg-gray-100 text-gray-700'
+ const style = STATUS_STYLES[status] ?? 'border-zinc-500/40 bg-zinc-500/10 text-zinc-300'
const label = STATUS_LABELS[status] ?? status
return (
(null)
- const hideTimer = useRef>()
-
- const open = (e: React.MouseEvent) => {
- clearTimeout(hideTimer.current)
- setRect(e.currentTarget.getBoundingClientRect())
- }
-
- const close = () => {
- hideTimer.current = setTimeout(() => setRect(null), 120)
- }
-
- const keepOpen = () => clearTimeout(hideTimer.current)
-
- // Compute popover position: flip above if too close to bottom of viewport
- const popoverStyle = (): React.CSSProperties => {
- if (!rect) return {}
- const spaceBelow = window.innerHeight - rect.bottom
- const popoverH = 200
- const top = spaceBelow > popoverH + 12 ? rect.bottom + 6 : rect.top - popoverH - 6
- return {
- position: 'fixed',
- top,
- left: rect.left,
- width: Math.max(rect.width, 320),
- maxWidth: Math.min(480, window.innerWidth - rect.left - 12),
- zIndex: 9999,
- }
- }
-
- return (
- <>
-
- {text}
-
-
- {rect &&
- createPortal(
-
- {text}
-
,
- document.body,
- )}
- >
- )
-}
+import { useRef, useState } from 'react'
+import { createPortal } from 'react-dom'
+
+interface Props {
+ text: string
+ lines?: number
+ className?: string
+}
+
+export default function TruncatedText({ text, lines = 2, className = '' }: Props) {
+ const [rect, setRect] = useState(null)
+ const hideTimer = useRef>()
+
+ const open = (e: React.MouseEvent) => {
+ clearTimeout(hideTimer.current)
+ setRect(e.currentTarget.getBoundingClientRect())
+ }
+
+ const close = () => {
+ hideTimer.current = setTimeout(() => setRect(null), 120)
+ }
+
+ const keepOpen = () => clearTimeout(hideTimer.current)
+
+ // Compute popover position: flip above if too close to bottom of viewport
+ const popoverStyle = (): React.CSSProperties => {
+ if (!rect) return {}
+ const spaceBelow = window.innerHeight - rect.bottom
+ const popoverH = 200
+ const top = spaceBelow > popoverH + 12 ? rect.bottom + 6 : rect.top - popoverH - 6
+ return {
+ position: 'fixed',
+ top,
+ left: rect.left,
+ width: Math.max(rect.width, 320),
+ maxWidth: Math.min(480, window.innerWidth - rect.left - 12),
+ zIndex: 9999,
+ }
+ }
+
+ return (
+ <>
+
+ {text}
+
+
+ {rect &&
+ createPortal(
+
+ {text}
+
,
+ document.body,
+ )}
+ >
+ )
+}
diff --git a/frontend/src/components/opencli/MetricTile.tsx b/frontend/src/components/opencli/MetricTile.tsx
new file mode 100644
index 00000000..34ec6f51
--- /dev/null
+++ b/frontend/src/components/opencli/MetricTile.tsx
@@ -0,0 +1,72 @@
+import type { LucideIcon } from 'lucide-react'
+import { cn } from '@/lib/utils'
+
+type MetricTone = 'neutral' | 'accent' | 'success' | 'warning' | 'danger'
+
+const TONE_STYLES: Record = {
+ neutral: {
+ rail: 'bg-zinc-300',
+ icon: 'border-zinc-400/30 bg-zinc-400/10 text-zinc-200',
+ value: 'text-zinc-50',
+ },
+ accent: {
+ rail: 'bg-primary-500',
+ icon: 'border-primary-500/40 bg-primary-500/10 text-primary-100',
+ value: 'text-zinc-50',
+ },
+ success: {
+ rail: 'bg-emerald-400',
+ icon: 'border-emerald-400/35 bg-emerald-400/10 text-emerald-200',
+ value: 'text-emerald-100',
+ },
+ warning: {
+ rail: 'bg-amber-400',
+ icon: 'border-amber-400/35 bg-amber-400/10 text-amber-200',
+ value: 'text-amber-100',
+ },
+ danger: {
+ rail: 'bg-primary-500',
+ icon: 'border-primary-500/50 bg-primary-500/15 text-primary-100',
+ value: 'text-primary-100',
+ },
+}
+
+interface MetricTileProps {
+ label: string
+ value: React.ReactNode
+ sub?: React.ReactNode
+ icon?: LucideIcon
+ tone?: MetricTone
+ className?: string
+}
+
+export function MetricTile({
+ label,
+ value,
+ sub,
+ icon: Icon,
+ tone = 'neutral',
+ className,
+}: MetricTileProps) {
+ const style = TONE_STYLES[tone]
+
+ return (
+
+
+
+
+
{label}
+
+ {value}
+
+
+ {Icon && (
+
+
+
+ )}
+
+ {sub &&
{sub}
}
+
+ )
+}
diff --git a/frontend/src/components/opencli/PanelHeader.tsx b/frontend/src/components/opencli/PanelHeader.tsx
new file mode 100644
index 00000000..7907569d
--- /dev/null
+++ b/frontend/src/components/opencli/PanelHeader.tsx
@@ -0,0 +1,28 @@
+import { cn } from '@/lib/utils'
+
+interface PanelHeaderProps {
+ label: string
+ title: React.ReactNode
+ description?: React.ReactNode
+ actions?: React.ReactNode
+ className?: string
+}
+
+export function PanelHeader({
+ label,
+ title,
+ description,
+ actions,
+ className,
+}: PanelHeaderProps) {
+ return (
+
+
+
{label}
+
{title}
+ {description &&
{description}
}
+
+ {actions &&
{actions}
}
+
+ )
+}
diff --git a/frontend/src/components/opencli/PlaybackControls.tsx b/frontend/src/components/opencli/PlaybackControls.tsx
new file mode 100644
index 00000000..d9f47f2d
--- /dev/null
+++ b/frontend/src/components/opencli/PlaybackControls.tsx
@@ -0,0 +1,47 @@
+import { Pause, Play, SkipBack, SkipForward } from 'lucide-react'
+import { Button } from '@/components/ui/button'
+import { cn } from '@/lib/utils'
+
+interface PlaybackControlsProps {
+ playing: boolean
+ disabled?: boolean
+ progressLabel: string
+ onToggle: () => void
+ onPrevious: () => void
+ onNext: () => void
+ onReset: () => void
+ className?: string
+}
+
+export function PlaybackControls({
+ playing,
+ disabled,
+ progressLabel,
+ onToggle,
+ onPrevious,
+ onNext,
+ onReset,
+ className,
+}: PlaybackControlsProps) {
+ return (
+
+
+
+
+
+
+ {progressLabel}
+
+
+ )
+}
diff --git a/frontend/src/components/opencli/index.ts b/frontend/src/components/opencli/index.ts
new file mode 100644
index 00000000..677e426a
--- /dev/null
+++ b/frontend/src/components/opencli/index.ts
@@ -0,0 +1,3 @@
+export { MetricTile } from './MetricTile'
+export { PanelHeader } from './PanelHeader'
+export { PlaybackControls } from './PlaybackControls'
diff --git a/frontend/src/components/ui/alert-dialog.tsx b/frontend/src/components/ui/alert-dialog.tsx
index 8722561c..c713ff0b 100644
--- a/frontend/src/components/ui/alert-dialog.tsx
+++ b/frontend/src/components/ui/alert-dialog.tsx
@@ -1,139 +1,139 @@
-import * as React from "react"
-import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
-
-import { cn } from "@/lib/utils"
-import { buttonVariants } from "@/components/ui/button"
-
-const AlertDialog = AlertDialogPrimitive.Root
-
-const AlertDialogTrigger = AlertDialogPrimitive.Trigger
-
-const AlertDialogPortal = AlertDialogPrimitive.Portal
-
-const AlertDialogOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
+import * as React from "react"
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+const AlertDialog = AlertDialogPrimitive.Root
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal
+
+const AlertDialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
-))
-AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
-
-const AlertDialogContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
+))
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
+
+const AlertDialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
-
-))
-AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
-
-const AlertDialogHeader = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
-)
-AlertDialogHeader.displayName = "AlertDialogHeader"
-
-const AlertDialogFooter = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
-)
-AlertDialogFooter.displayName = "AlertDialogFooter"
-
-const AlertDialogTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
-
-const AlertDialogDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-AlertDialogDescription.displayName =
- AlertDialogPrimitive.Description.displayName
-
-const AlertDialogAction = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
-
-const AlertDialogCancel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
-
-export {
- AlertDialog,
- AlertDialogPortal,
- AlertDialogOverlay,
- AlertDialogTrigger,
- AlertDialogContent,
- AlertDialogHeader,
- AlertDialogFooter,
- AlertDialogTitle,
- AlertDialogDescription,
- AlertDialogAction,
- AlertDialogCancel,
-}
+ {...props}
+ />
+
+))
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
+
+const AlertDialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+AlertDialogHeader.displayName = "AlertDialogHeader"
+
+const AlertDialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+AlertDialogFooter.displayName = "AlertDialogFooter"
+
+const AlertDialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
+
+const AlertDialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogDescription.displayName =
+ AlertDialogPrimitive.Description.displayName
+
+const AlertDialogAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
+
+const AlertDialogCancel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+}
diff --git a/frontend/src/components/ui/badge.tsx b/frontend/src/components/ui/badge.tsx
index f000e3ef..a66c5098 100644
--- a/frontend/src/components/ui/badge.tsx
+++ b/frontend/src/components/ui/badge.tsx
@@ -1,36 +1,36 @@
-import * as React from "react"
-import { cva, type VariantProps } from "class-variance-authority"
-
-import { cn } from "@/lib/utils"
-
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
const badgeVariants = cva(
- "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+ "inline-flex items-center rounded-[2px] border px-2 py-0.5 font-telemetry text-[10px] font-semibold uppercase tracking-[0.12em] transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500/50 focus:ring-offset-0",
{
variants: {
variant: {
default:
- "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
+ "border-primary-500/50 bg-primary-500/12 text-primary-100 hover:bg-primary-500/18",
secondary:
- "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ "border-white/12 bg-white/[0.045] text-zinc-300 hover:bg-white/[0.075]",
destructive:
- "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
- outline: "text-foreground",
+ "border-primary-500/70 bg-primary-500/18 text-primary-50 hover:bg-primary-500/24",
+ outline: "border-white/14 bg-transparent text-zinc-300",
},
},
- defaultVariants: {
- variant: "default",
- },
- }
-)
-
-export interface BadgeProps
- extends React.HTMLAttributes,
- VariantProps {}
-
-function Badge({ className, variant, ...props }: BadgeProps) {
- return (
-
- )
-}
-
-export { Badge, badgeVariants }
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+export interface BadgeProps
+ extends React.HTMLAttributes,
+ VariantProps {}
+
+function Badge({ className, variant, ...props }: BadgeProps) {
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx
index 36496a28..9c03f069 100644
--- a/frontend/src/components/ui/button.tsx
+++ b/frontend/src/components/ui/button.tsx
@@ -1,56 +1,57 @@
-import * as React from "react"
-import { Slot } from "@radix-ui/react-slot"
-import { cva, type VariantProps } from "class-variance-authority"
-
-import { cn } from "@/lib/utils"
-
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-[2px] border font-telemetry text-[11px] font-semibold uppercase tracking-[0.12em] ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/70 focus-visible:ring-offset-0 disabled:pointer-events-none disabled:opacity-45 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ default: "border-primary-500/70 bg-primary-500/16 text-white hover:border-primary-400 hover:bg-primary-500/24",
destructive:
- "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ "border-primary-500/80 bg-primary-500/22 text-primary-50 hover:border-primary-300 hover:bg-primary-500/30",
outline:
- "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+ "border-white/14 bg-black/25 text-zinc-200 hover:border-white/28 hover:bg-white/[0.075] hover:text-white",
secondary:
- "bg-secondary text-secondary-foreground hover:bg-secondary/80",
- ghost: "hover:bg-accent hover:text-accent-foreground",
- link: "text-primary underline-offset-4 hover:underline",
+ "border-white/10 bg-white/[0.045] text-zinc-200 hover:border-white/22 hover:bg-white/[0.08] hover:text-white",
+ ghost: "border-transparent bg-transparent text-zinc-400 hover:border-white/12 hover:bg-white/[0.055] hover:text-white",
+ link: "border-transparent bg-transparent px-0 text-primary-300 underline-offset-4 hover:text-primary-100 hover:underline",
},
size: {
- default: "h-10 px-4 py-2",
- sm: "h-9 rounded-md px-3",
- lg: "h-11 rounded-md px-8",
- icon: "h-10 w-10",
+ default: "h-9 px-3 py-2",
+ sm: "h-8 px-2.5",
+ lg: "h-10 px-4",
+ icon: "h-9 w-9",
+ xs: "h-7 px-2 text-[10px]",
},
},
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- }
-)
-
-export interface ButtonProps
- extends React.ButtonHTMLAttributes,
- VariantProps {
- asChild?: boolean
-}
-
-const Button = React.forwardRef(
- ({ className, variant, size, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button"
- return (
-
- )
- }
-)
-Button.displayName = "Button"
-
-export { Button, buttonVariants }
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+
+ )
+ }
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx
index c680b9d3..f5801aaf 100644
--- a/frontend/src/components/ui/dialog.tsx
+++ b/frontend/src/components/ui/dialog.tsx
@@ -1,120 +1,120 @@
-import * as React from "react"
-import * as DialogPrimitive from "@radix-ui/react-dialog"
-import { X } from "lucide-react"
-
-import { cn } from "@/lib/utils"
-
-const Dialog = DialogPrimitive.Root
-
-const DialogTrigger = DialogPrimitive.Trigger
-
-const DialogPortal = DialogPrimitive.Portal
-
-const DialogClose = DialogPrimitive.Close
-
-const DialogOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Dialog = DialogPrimitive.Root
+
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal = DialogPrimitive.Portal
+
+const DialogClose = DialogPrimitive.Close
+
+const DialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
-))
-DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
-
-const DialogContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
-
-
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
- {children}
-
-
- Close
-
-
-
-))
-DialogContent.displayName = DialogPrimitive.Content.displayName
-
-const DialogHeader = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
+ {children}
+
+
+ Close
+
+
+
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes
) => (
+
+)
+DialogHeader.displayName = "DialogHeader"
+
+const DialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogFooter.displayName = "DialogFooter"
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
-)
-DialogHeader.displayName = "DialogHeader"
-
-const DialogFooter = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
-)
-DialogFooter.displayName = "DialogFooter"
-
-const DialogTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-DialogTitle.displayName = DialogPrimitive.Title.displayName
-
-const DialogDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-))
-DialogDescription.displayName = DialogPrimitive.Description.displayName
-
-export {
- Dialog,
- DialogPortal,
- DialogOverlay,
- DialogClose,
- DialogTrigger,
- DialogContent,
- DialogHeader,
- DialogFooter,
- DialogTitle,
- DialogDescription,
-}
+ {...props}
+ />
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogClose,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+}
diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx
index 68551b92..7d38451c 100644
--- a/frontend/src/components/ui/input.tsx
+++ b/frontend/src/components/ui/input.tsx
@@ -1,22 +1,22 @@
-import * as React from "react"
-
-import { cn } from "@/lib/utils"
-
-const Input = React.forwardRef>(
- ({ className, type, ...props }, ref) => {
- return (
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Input = React.forwardRef>(
+ ({ className, type, ...props }, ref) => {
+ return (
- )
- }
-)
-Input.displayName = "Input"
-
-export { Input }
+ {...props}
+ />
+ )
+ }
+)
+Input.displayName = "Input"
+
+export { Input }
diff --git a/frontend/src/components/ui/select.tsx b/frontend/src/components/ui/select.tsx
index d826bdba..70cb43fb 100644
--- a/frontend/src/components/ui/select.tsx
+++ b/frontend/src/components/ui/select.tsx
@@ -1,158 +1,158 @@
-import * as React from "react"
-import * as SelectPrimitive from "@radix-ui/react-select"
-import { Check, ChevronDown, ChevronUp } from "lucide-react"
-
-import { cn } from "@/lib/utils"
-
-const Select = SelectPrimitive.Root
-
-const SelectGroup = SelectPrimitive.Group
-
-const SelectValue = SelectPrimitive.Value
-
-const SelectTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
+import * as React from "react"
+import * as SelectPrimitive from "@radix-ui/react-select"
+import { Check, ChevronDown, ChevronUp } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Select = SelectPrimitive.Root
+
+const SelectGroup = SelectPrimitive.Group
+
+const SelectValue = SelectPrimitive.Value
+
+const SelectTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
span]:line-clamp-1",
+ "flex h-9 w-full items-center justify-between rounded-[2px] border border-white/14 bg-black/35 px-3 py-2 text-sm text-zinc-100 ring-offset-background data-[placeholder]:text-zinc-600 focus:border-primary-500/70 focus:outline-none focus:ring-2 focus:ring-primary-500/30 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
- {...props}
- >
- {children}
-
-
-
-
-))
-SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
-
-const SelectScrollUpButton = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-))
-SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
-
-const SelectScrollDownButton = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-))
-SelectScrollDownButton.displayName =
- SelectPrimitive.ScrollDownButton.displayName
-
-const SelectContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, position = "popper", ...props }, ref) => (
-
-
+ {children}
+
+