diff --git a/frontend/src/features/models/components/models-action-dialog.tsx b/frontend/src/features/models/components/models-action-dialog.tsx index aef9d302a3..9236ef64d1 100644 --- a/frontend/src/features/models/components/models-action-dialog.tsx +++ b/frontend/src/features/models/components/models-action-dialog.tsx @@ -253,7 +253,6 @@ export function ModelsActionDialog() { icon: data.icon, group: data.group, modelCard: data.modelCard, - settings: data.settings, remark: data.remark, }; await updateModel.mutateAsync({ id: currentRow.id, input: updateData }); diff --git a/frontend/src/features/models/components/models-association-dialog.tsx b/frontend/src/features/models/components/models-association-dialog.tsx index 13a2bd2eb9..acca0da12a 100644 --- a/frontend/src/features/models/components/models-association-dialog.tsx +++ b/frontend/src/features/models/components/models-association-dialog.tsx @@ -5,28 +5,33 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { IconPlus, IconTrash, IconChevronDown, IconChevronUp, IconInfoCircle } from '@tabler/icons-react'; import { useQueryModels } from '@/gql/models'; import { useTranslation } from 'react-i18next'; +import { toast } from 'sonner'; import { extractNumberIDAsNumber } from '@/lib/utils'; import { useDebounce } from '@/hooks/use-debounce'; +import { Alert, AlertDescription } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; -import { Switch } from '@/components/ui/switch'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Switch } from '@/components/ui/switch'; import { TagsAutocompleteInput } from '@/components/ui/tags-autocomplete-input'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { AutoComplete } from '@/components/auto-complete'; import { AutoCompleteSelect } from '@/components/auto-complete-select'; -import { FilterBuilder, type FilterBuilderCondition, type FilterBuilderField, type FilterBuilderGroupListValue } from '@/components/filter-builder'; -import { Alert, AlertDescription } from '@/components/ui/alert'; +import { + FilterBuilder, + type FilterBuilderCondition, + type FilterBuilderField, + type FilterBuilderGroupListValue, +} from '@/components/filter-builder'; import { useAllChannelSummarys, useAllChannelTags } from '@/features/channels/data/channels'; import { useModelSettings, useUpdateModelSettings } from '@/features/system/data/system'; import { useModels } from '../context/models-context'; import { useQueryModelChannelConnections, ModelAssociationInput, ModelChannelConnection } from '../data/models'; import { useUpdateModel } from '../data/models'; import { ModelAssociation, normalizeModelRoutingPolicyValue } from '../data/schema'; -import { toast } from 'sonner'; import { ChannelModelsList } from './channel-models-list'; const MAX_ASSOCIATION_PRIORITY = 10; @@ -305,7 +310,12 @@ function validateWhenConditionNode( path: [...path, 'value'], }); } - if (condition.field === 'daily_time' && typeof condition.value === 'string' && dailyTimeRangePattern.test(condition.value) && !dailyTimeRangeHasDifferentEndpoints(condition.value)) { + if ( + condition.field === 'daily_time' && + typeof condition.value === 'string' && + dailyTimeRangePattern.test(condition.value) && + !dailyTimeRangeHasDifferentEndpoints(condition.value) + ) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Start and end time must be different', @@ -351,7 +361,9 @@ function validateWhenGroupList(value: FilterBuilderGroupListValue, ctx: z.Refine }); } - groups.slice(0, 1).forEach((group, index) => validateWhenConditionNode(group, ctx, [...path, 'groups', index], 1, MAX_WHEN_CONDITION_DEPTH)); + groups + .slice(0, 1) + .forEach((group, index) => validateWhenConditionNode(group, ctx, [...path, 'groups', index], 1, MAX_WHEN_CONDITION_DEPTH)); } const associationFormSchema = z.object({ @@ -362,7 +374,10 @@ const associationFormSchema = z.object({ .array( z.object({ type: z.enum(['channel_model', 'channel_regex', 'model', 'regex', 'channel_tags_model', 'channel_tags_regex']), - priority: z.number().min(0, 'Priority must be at least 0').max(MAX_ASSOCIATION_PRIORITY, `Priority cannot exceed ${MAX_ASSOCIATION_PRIORITY}`), + priority: z + .number() + .min(0, 'Priority must be at least 0') + .max(MAX_ASSOCIATION_PRIORITY, `Priority cannot exceed ${MAX_ASSOCIATION_PRIORITY}`), disabled: z.boolean().default(false), whenEnabled: z.boolean().default(false), whenCondition: z.custom().default(DEFAULT_WHEN_CONDITION), @@ -463,7 +478,9 @@ function isCompleteAssociationFormRow(assoc: AssociationFormRow, inheritedModelI return Boolean(associationModelID(assoc, inheritedModelID) || assoc.inheritModel); } if (assoc.type === 'channel_tags_model') { - return Boolean(assoc.channelTags && assoc.channelTags.length > 0 && (associationModelID(assoc, inheritedModelID) || assoc.inheritModel)); + return Boolean( + assoc.channelTags && assoc.channelTags.length > 0 && (associationModelID(assoc, inheritedModelID) || assoc.inheritModel) + ); } if (assoc.type === 'channel_tags_regex') { return Boolean(assoc.channelTags && assoc.channelTags.length > 0 && assoc.pattern); @@ -684,9 +701,7 @@ function buildDeveloperChannelPreview(associations: AssociationFormRow[], channe } if (assoc.type === 'channel_tags_model' && assoc.channelTags && assoc.channelTags.length > 0) { - channelOptions - .filter((channel) => assoc.channelTags?.some((tag) => channel.tags.includes(tag))) - .forEach(addChannel); + channelOptions.filter((channel) => assoc.channelTags?.some((tag) => channel.tags.includes(tag))).forEach(addChannel); } }); @@ -833,13 +848,9 @@ export function ModelsAssociationDialog() { if (isOpen) { const associations = isDeveloperMode ? developerAssociations : currentRow?.settings?.associations || []; form.reset({ - disableDeveloperSettingsInheritance: isDeveloperMode ? false : currentRow?.settings?.disableDeveloperSettingsInheritance ?? false, - loadBalancerStrategy: isDeveloperMode - ? 'default' - : normalizeModelRoutingPolicyValue(currentRow?.settings?.loadBalancerStrategy), - traceStickyMode: isDeveloperMode - ? 'default' - : normalizeModelRoutingPolicyValue(currentRow?.settings?.traceStickyMode), + disableDeveloperSettingsInheritance: isDeveloperMode ? false : (currentRow?.settings?.disableDeveloperSettingsInheritance ?? false), + loadBalancerStrategy: isDeveloperMode ? 'default' : normalizeModelRoutingPolicyValue(currentRow?.settings?.loadBalancerStrategy), + traceStickyMode: isDeveloperMode ? 'default' : normalizeModelRoutingPolicyValue(currentRow?.settings?.traceStickyMode), associations: associations .filter((assoc) => !isDeveloperMode || assoc.type === 'channel_model' || assoc.type === 'channel_tags_model') .map((assoc) => modelAssociationToFormRow(assoc, isDeveloperMode)), @@ -884,6 +895,8 @@ export function ModelsAssociationDialog() { associations, loadBalancerStrategy: data.loadBalancerStrategy, traceStickyMode: data.traceStickyMode, + visionDelegation: currentRow!.settings?.visionDelegation, + unsupportedImageFallback: currentRow!.settings?.unsupportedImageFallback, }, }, }); @@ -906,8 +919,7 @@ export function ModelsAssociationDialog() { // Get the priority of the last rule (highest priority) const currentAssociations = form.getValues('associations') || []; - const lastPriority = - currentAssociations.length > 0 ? Math.max(...currentAssociations.map((a) => a.priority ?? 0)) : 0; + const lastPriority = currentAssociations.length > 0 ? Math.max(...currentAssociations.map((a) => a.priority ?? 0)) : 0; append({ type: 'channel_model', @@ -935,7 +947,7 @@ export function ModelsAssociationDialog() { return ( - + {isDeveloperMode ? t('models.dialogs.developerAssociation.title') : t('models.dialogs.association.title')} @@ -1017,8 +1029,12 @@ export function ModelsAssociationDialog() { {t('models.fields.routingSystemDefault')} {t('system.retry.loadBalancerStrategy.options.adaptive')} {t('system.retry.loadBalancerStrategy.options.failover')} - {t('system.retry.loadBalancerStrategy.options.circuitBreaker')} - {t('system.retry.loadBalancerStrategy.options.roundRobin')} + + {t('system.retry.loadBalancerStrategy.options.circuitBreaker')} + + + {t('system.retry.loadBalancerStrategy.options.roundRobin')} + @@ -1060,7 +1076,9 @@ export function ModelsAssociationDialog() { {t('models.fields.routingSystemDefault')} - {t('system.retry.traceStickyMode.options.preferPreviousChannel')} + + {t('system.retry.traceStickyMode.options.preferPreviousChannel')} + {t('system.retry.traceStickyMode.options.disabled')} @@ -1074,13 +1092,19 @@ export function ModelsAssociationDialog() { )} - {fields.length === 0 &&

{t('models.dialogs.association.noRules')}

} + {fields.length === 0 && ( +

{t('models.dialogs.association.noRules')}

+ )} {fields.length > 0 && ( -
+
-
{t('models.dialogs.association.priority')}
-
{t('models.dialogs.association.type')}
+
+ {t('models.dialogs.association.priority')} +
+
+ {t('models.dialogs.association.type')} +
{t('models.dialogs.association.rule')}
@@ -1119,12 +1143,14 @@ export function ModelsAssociationDialog() {
- {/* Right Side - Preview */} -
+ {/* Right Side - Preview */} +

{t('models.dialogs.association.preview')}

- {isDeveloperMode ? t('models.dialogs.developerAssociation.previewDescription') : t('models.dialogs.association.previewDescription')} + {isDeveloperMode + ? t('models.dialogs.developerAssociation.previewDescription') + : t('models.dialogs.association.previewDescription')}

- + @@ -1164,9 +1190,7 @@ export function ModelsAssociationDialog() { ); } -function readPromptTokensCondition( - when: ModelAssociation['when'] -): { enabled: boolean; condition: FilterBuilderGroupListValue } { +function readPromptTokensCondition(when: ModelAssociation['when']): { enabled: boolean; condition: FilterBuilderGroupListValue } { if (!when) { return { enabled: false, condition: DEFAULT_WHEN_CONDITION }; } @@ -1316,7 +1340,16 @@ function AssociationTypeSelectContent({ isDeveloperMode }: { isDeveloperMode: bo ); } -function AssociationRow({ index, form, isDeveloperMode, channelOptions, allModelOptions, allTags, onRemove, portalContainer }: AssociationRowProps) { +function AssociationRow({ + index, + form, + isDeveloperMode, + channelOptions, + allModelOptions, + allTags, + onRemove, + portalContainer, +}: AssociationRowProps) { const { t } = useTranslation(); const type = form.watch(`associations.${index}.type`); @@ -1394,7 +1427,7 @@ function AssociationRow({ index, form, isDeveloperMode, channelOptions, allModel return (
-
+
{/* Enable/Disable Switch */}
field.onChange(Math.max(0, Math.min(MAX_ASSOCIATION_PRIORITY, Number(e.target.value) || 0)))} - className='h-10 sm:h-9 text-center [-moz-appearance:textfield] [&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:hidden [&::-webkit-inner-spin-button]:appearance-none' + className='h-10 text-center [-moz-appearance:textfield] sm:h-9 [&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:hidden [&::-webkit-inner-spin-button]:appearance-none' placeholder='0' /> @@ -1431,10 +1464,10 @@ function AssociationRow({ index, form, isDeveloperMode, channelOptions, allModel control={form.control} name={`associations.${index}.type`} render={({ field }) => ( - +