feat: add TuningTab component for tuning task management and utility functions for memory operations

- Implemented TuningTab component to handle tuning objectives, intensity, sample size, and evaluation settings.
- Added UI elements for creating tuning tasks and displaying current configurations and recent tasks.
- Introduced utility functions for normalizing and formatting memory operation data, including feedback actions and delete operations.
This commit is contained in:
DrSmoothl
2026-05-01 20:14:37 +08:00
parent d9a509b6c2
commit 9e48cd2848
11 changed files with 3878 additions and 2546 deletions

View File

@@ -0,0 +1,54 @@
import { TabsList, TabsTrigger } from '@/components/ui/tabs'
import { cn } from '@/lib/utils'
export interface MemoryMiniTabItem<TValue extends string> {
value: TValue
label: string
description?: string
}
export interface MemoryMiniTabsProps<TValue extends string> {
items: ReadonlyArray<MemoryMiniTabItem<TValue>>
className?: string
/** 触发器额外样式 */
triggerClassName?: string
}
/**
* 长期记忆控制台统一的迷你标签页样式。
*
* - 复用 shadcn `Tabs` 原语,仅替换样式以保留无障碍能力(`role="tab"` 与文案不变)。
* - 胶囊形外观,激活态使用主色渐变,便于在密集表单上快速定位当前页签。
*/
export function MemoryMiniTabs<TValue extends string>({
items,
className,
triggerClassName,
}: MemoryMiniTabsProps<TValue>) {
return (
<TabsList
className={cn(
'h-auto w-full flex-wrap justify-start gap-1.5 rounded-full border border-border/60',
'bg-gradient-to-r from-muted/40 via-background to-muted/30 p-1.5 shadow-inner',
className,
)}
>
{items.map((item) => (
<TabsTrigger
key={item.value}
value={item.value}
title={item.description}
className={cn(
'rounded-full px-3.5 py-1.5 text-xs font-medium text-muted-foreground transition-colors',
'hover:bg-background/80 hover:text-foreground',
'data-[state=active]:bg-gradient-to-r data-[state=active]:from-primary data-[state=active]:to-primary/80',
'data-[state=active]:text-primary-foreground data-[state=active]:shadow-sm',
triggerClassName,
)}
>
{item.label}
</TabsTrigger>
))}
</TabsList>
)
}

View File

@@ -0,0 +1,130 @@
import { Loader2 } from 'lucide-react'
import { Badge } from '@/components/ui/badge'
import { Progress } from '@/components/ui/progress'
import { cn } from '@/lib/utils'
export interface MemoryProgressIndicatorProps {
/** 0-100 之间的进度百分比 */
value: number
/** 任务状态文本(如 “运行中”、“已完成”) */
statusLabel?: string
/** 当前步骤文本(如 “分块中”) */
stepLabel?: string
/** 状态对应的语义色(用于左侧圆环和徽标) */
tone?: 'default' | 'success' | 'warning' | 'destructive' | 'muted'
/** 是否显示加载动画(运行中/取消中场景) */
busy?: boolean
/** 紧凑模式:用于队列列表项 */
compact?: boolean
/** 额外说明(如 “已完成 36 / 120 分块”) */
detail?: string
className?: string
}
const TONE_RING_CLASS: Record<NonNullable<MemoryProgressIndicatorProps['tone']>, string> = {
default: 'text-primary',
success: 'text-emerald-500',
warning: 'text-amber-500',
destructive: 'text-rose-500',
muted: 'text-muted-foreground',
}
const TONE_BADGE_VARIANT: Record<
NonNullable<MemoryProgressIndicatorProps['tone']>,
'default' | 'secondary' | 'destructive' | 'outline'
> = {
default: 'default',
success: 'secondary',
warning: 'outline',
destructive: 'destructive',
muted: 'outline',
}
/**
* 长期记忆控制台统一的任务进度展示组件。
*
* 设计目标:
* - 让用户一眼看清「整体百分比 + 语义状态 + 当前步骤」。
* - 复用 shadcn `Progress` 与 `Badge`,避免引入额外样式来源。
* - 在紧凑模式下保留可读性,可放进队列卡片;非紧凑模式带圆环用于详情区。
*/
export function MemoryProgressIndicator({
value,
statusLabel,
stepLabel,
tone = 'default',
busy = false,
compact = false,
detail,
className,
}: MemoryProgressIndicatorProps) {
const safeValue = Number.isFinite(value) ? Math.max(0, Math.min(100, value)) : 0
const ringSize = compact ? 36 : 56
const ringStroke = compact ? 4 : 5
const radius = (ringSize - ringStroke) / 2
const circumference = 2 * Math.PI * radius
const dashOffset = circumference * (1 - safeValue / 100)
return (
<div className={cn('flex items-center gap-3', className)}>
<div
className={cn('relative shrink-0', TONE_RING_CLASS[tone])}
style={{ width: ringSize, height: ringSize }}
aria-hidden="true"
>
<svg width={ringSize} height={ringSize} className="-rotate-90">
<circle
cx={ringSize / 2}
cy={ringSize / 2}
r={radius}
strokeWidth={ringStroke}
className="stroke-muted/40"
fill="none"
/>
<circle
cx={ringSize / 2}
cy={ringSize / 2}
r={radius}
strokeWidth={ringStroke}
strokeLinecap="round"
stroke="currentColor"
fill="none"
strokeDasharray={circumference}
strokeDashoffset={dashOffset}
className="transition-[stroke-dashoffset] duration-500 ease-out"
/>
</svg>
<div className="absolute inset-0 flex items-center justify-center">
{busy ? (
<Loader2 className={cn('animate-spin', compact ? 'h-3.5 w-3.5' : 'h-4 w-4')} />
) : (
<span className={cn('font-medium tabular-nums', compact ? 'text-[10px]' : 'text-xs')}>
{Math.round(safeValue)}%
</span>
)}
</div>
</div>
<div className="min-w-0 flex-1 space-y-1">
<div className="flex flex-wrap items-center gap-2">
{statusLabel ? (
<Badge variant={TONE_BADGE_VARIANT[tone]} className="shrink-0">
{statusLabel}
</Badge>
) : null}
{stepLabel ? (
<span className="truncate text-xs text-muted-foreground">{stepLabel}</span>
) : null}
{!compact ? (
<span className="ml-auto text-xs tabular-nums text-muted-foreground">
{safeValue.toFixed(1)}%
</span>
) : null}
</div>
<Progress value={safeValue} className={cn(compact ? 'h-1' : 'h-1.5')} />
{detail ? <div className="truncate text-xs text-muted-foreground">{detail}</div> : null}
</div>
</div>
)
}