perf(model-pricing): refine visual editor actions

- keep the global reset action in the top toolbar while moving visual-mode saves into the model editor footer.
- pin the actions header with the rest of the model table headers so horizontal scrolling keeps context visible.
- add action icons to make save and reset controls easier to scan.
This commit is contained in:
QuentinHsu
2026-06-05 01:04:47 +08:00
parent 6e5a359110
commit 5681c92b3f
4 changed files with 63 additions and 17 deletions
@@ -26,10 +26,11 @@ import {
} from 'react'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { AlertTriangle } from 'lucide-react'
import { AlertTriangle, Save } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { cn } from '@/lib/utils'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import {
Field,
FieldDescription,
@@ -86,6 +87,8 @@ type ModelPricingSheetProps = {
open: boolean
onOpenChange: (open: boolean) => void
editData?: ModelRatioData | null
onSave?: () => void | Promise<void>
isSaving?: boolean
}
type ModelPricingEditorPanelProps = Omit<
@@ -102,7 +105,10 @@ export type ModelPricingEditorPanelHandle = {
export const ModelPricingSheet = forwardRef<
ModelPricingEditorPanelHandle,
ModelPricingSheetProps
>(function ModelPricingSheet({ open, onOpenChange, editData }, ref) {
>(function ModelPricingSheet(
{ open, onOpenChange, editData, onSave, isSaving },
ref
) {
const { t } = useTranslation()
const title = editData ? t('Edit model pricing') : t('Add model pricing')
const description = editData?.name || t('New model')
@@ -120,6 +126,8 @@ export const ModelPricingSheet = forwardRef<
<ModelPricingEditorPanel
ref={ref}
editData={editData}
onSave={onSave}
isSaving={isSaving}
className='h-full rounded-none border-0'
/>
</SheetContent>
@@ -130,7 +138,10 @@ export const ModelPricingSheet = forwardRef<
export const ModelPricingEditorPanel = forwardRef<
ModelPricingEditorPanelHandle,
ModelPricingEditorPanelProps
>(function ModelPricingEditorPanel({ editData, className }, ref) {
>(function ModelPricingEditorPanel(
{ editData, className, onSave, isSaving },
ref
) {
const { t } = useTranslation()
const [pricingMode, setPricingMode] = useState<PricingMode>('per-token')
const [promptPrice, setPromptPrice] = useState('')
@@ -461,6 +472,7 @@ export const ModelPricingEditorPanel = forwardRef<
)
const activeName = watchedValues.name || editData?.name || t('New model')
const showActions = Boolean(onSave)
return (
<div
@@ -488,7 +500,7 @@ export const ModelPricingEditorPanel = forwardRef<
className='flex min-h-0 flex-1 flex-col'
autoComplete='off'
>
<div className='min-h-0 flex-1 overflow-y-auto p-4'>
<div className='min-h-0 flex-1 overflow-y-auto p-4 pb-6'>
<div className='grid items-start gap-4 xl:grid-cols-[minmax(0,1fr)_minmax(220px,260px)]'>
<FieldGroup>
{warnings.length > 0 && (
@@ -670,6 +682,23 @@ export const ModelPricingEditorPanel = forwardRef<
</aside>
</div>
</div>
{showActions && (
<div className='bg-background/95 supports-[backdrop-filter]:bg-background/80 shrink-0 border-t p-3 backdrop-blur'>
<div className='flex flex-col-reverse gap-2 sm:flex-row sm:justify-end'>
{onSave && (
<Button
type='button'
onClick={onSave}
disabled={isSaving}
className='w-full sm:w-auto'
>
<Save data-icon='inline-start' />
{isSaving ? t('Saving...') : t('Save model prices')}
</Button>
)}
</div>
</div>
)}
</form>
</Form>
</div>
@@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
*/
import { memo, useCallback, useRef, useState } from 'react'
import { type UseFormReturn } from 'react-hook-form'
import { Code2, Eye } from 'lucide-react'
import { Code2, Eye, RotateCcw, Save } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { Button } from '@/components/ui/button'
import {
@@ -110,16 +110,20 @@ export const ModelRatioForm = memo(function ModelRatioForm({
onClick={onReset}
disabled={isResetting}
>
<RotateCcw data-icon='inline-start' />
{t('Reset prices')}
</Button>
{editMode === 'json' && (
<Button
type='button'
size='sm'
onClick={handleSave}
disabled={isSaving}
>
<Save data-icon='inline-start' />
{isSaving ? t('Saving...') : t('Save model prices')}
</Button>
)}
<Button variant='outline' size='sm' onClick={toggleEditMode}>
{editMode === 'visual' ? (
<>
@@ -160,6 +164,8 @@ export const ModelRatioForm = memo(function ModelRatioForm({
audioCompletionRatio={form.watch('AudioCompletionRatio')}
billingMode={form.watch('BillingMode')}
billingExpr={form.watch('BillingExpr')}
onSave={handleSave}
isSaving={isSaving}
onChange={(field, value) => {
const fieldMap: Record<string, keyof ModelFormValues> = {
'billing_setting.billing_mode': 'BillingMode',
@@ -136,6 +136,7 @@ export function buildModelRatioColumns({
},
{
id: 'actions',
header: () => <div className='text-right'>{t('Actions')}</div>,
cell: ({ row }) => (
<div className='flex justify-end gap-2'>
<Button
@@ -90,6 +90,8 @@ type ModelRatioVisualEditorProps = {
billingMode: string
billingExpr: string
onChange: (field: string, value: string) => void
onSave: () => void | Promise<void>
isSaving: boolean
}
export type ModelRatioVisualEditorHandle = {
@@ -124,6 +126,8 @@ const ModelRatioVisualEditorComponent = forwardRef<
billingMode,
billingExpr,
onChange,
onSave,
isSaving,
},
ref
) {
@@ -672,7 +676,7 @@ const ModelRatioVisualEditorComponent = forwardRef<
) : (
<div className='min-h-0 flex-1 overflow-auto rounded-md border'>
<table className='w-full caption-bottom text-sm tabular-nums'>
<thead className='bg-background sticky top-0 z-10'>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id} className='border-b'>
{headerGroup.headers.map((header) => (
@@ -680,9 +684,9 @@ const ModelRatioVisualEditorComponent = forwardRef<
key={header.id}
colSpan={header.colSpan}
className={cn(
'text-foreground h-10 px-2 text-left align-middle text-sm font-medium whitespace-nowrap',
'bg-background text-foreground sticky top-0 z-10 h-10 px-2 text-left align-middle text-sm font-medium whitespace-nowrap',
header.column.id === 'actions' &&
'bg-background sticky right-0 z-20 w-24 min-w-24 shadow-[-10px_0_14px_-14px_hsl(var(--foreground))]'
'right-0 z-30 w-24 min-w-24 shadow-[-10px_0_14px_-14px_hsl(var(--foreground))]'
)}
>
{header.isPlaceholder
@@ -746,6 +750,8 @@ const ModelRatioVisualEditorComponent = forwardRef<
<ModelPricingEditorPanel
ref={editorPanelRef}
editData={editData}
onSave={onSave}
isSaving={isSaving}
className='h-full min-h-0'
/>
) : (
@@ -782,6 +788,8 @@ const ModelRatioVisualEditorComponent = forwardRef<
open={sheetOpen}
onOpenChange={setSheetOpen}
editData={editData}
onSave={onSave}
isSaving={isSaving}
/>
)}
</div>
@@ -803,7 +811,9 @@ export const ModelRatioVisualEditor = memo(
prevProps.audioCompletionRatio === nextProps.audioCompletionRatio &&
prevProps.billingMode === nextProps.billingMode &&
prevProps.billingExpr === nextProps.billingExpr &&
prevProps.onChange === nextProps.onChange
prevProps.onChange === nextProps.onChange &&
prevProps.onSave === nextProps.onSave &&
prevProps.isSaving === nextProps.isSaving
)
}
)