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:
@@ -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>
|
||||
<Button
|
||||
type='button'
|
||||
size='sm'
|
||||
onClick={handleSave}
|
||||
disabled={isSaving}
|
||||
>
|
||||
{isSaving ? t('Saving...') : t('Save model 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
|
||||
|
||||
+14
-4
@@ -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
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user