diff --git a/apps/web/src/components/add-provider/index.vue b/apps/web/src/components/add-provider/index.vue index e8ddf90a..4105da60 100644 --- a/apps/web/src/components/add-provider/index.vue +++ b/apps/web/src/components/add-provider/index.vue @@ -91,6 +91,25 @@ + + + + {{ $t('provider.clientType') }} + + + + + + + - - - - - {{ $t('models.importClientType') }} - - - - - - {{ $t('models.importClientTypeHint') }} - - - @@ -163,13 +159,23 @@ import { useI18n } from 'vue-i18n' import FormDialogShell from '@/components/form-dialog-shell/index.vue' import { useDialogMutation } from '@/composables/useDialogMutation' import SearchableSelectPopover from '@/components/searchable-select-popover/index.vue' -import { CLIENT_TYPE_LIST } from '@/constants/client-types' +import { CLIENT_TYPE_LIST, CLIENT_TYPE_META } from '@/constants/client-types' import { toast } from 'vue-sonner' +import { computed } from 'vue' const open = defineModel('open') const { t } = useI18n() const { run } = useDialogMutation() +const clientTypeOptions = computed(() => + CLIENT_TYPE_LIST.map((ct) => ({ + value: ct.value, + label: ct.label, + description: ct.hint, + keywords: [ct.label, ct.hint, CLIENT_TYPE_META[ct.value]?.value ?? ct.value], + })), +) + const queryCache = useQueryCache() const { mutateAsync: createProviderMutation, isLoading } = useMutation({ mutation: async (data: Record) => { @@ -182,7 +188,6 @@ const { mutateAsync: createProviderMutation, isLoading } = useMutation({ try { const { data: importResult } = await postProvidersByIdImportModels({ path: { id: result.id }, - body: { client_type: data.client_type as string }, throwOnError: true, }) if (importResult) { @@ -206,8 +211,8 @@ const providerSchema = toTypedSchema(z.object({ api_key: z.string().min(1), base_url: z.string().min(1), name: z.string().min(1), + client_type: z.string().min(1), auto_import: z.boolean().optional(), - client_type: z.string().optional(), })) const form = useForm({ diff --git a/apps/web/src/components/create-model/index.vue b/apps/web/src/components/create-model/index.vue index 3e93cdff..917b53b7 100644 --- a/apps/web/src/components/create-model/index.vue +++ b/apps/web/src/components/create-model/index.vue @@ -48,40 +48,6 @@ - - - - {{ $t('models.clientType') }} - - - - - - {{ displayLabel || $t('models.clientTypePlaceholder') }} - - - - - - - - + - {{ $t('models.inputModalities') }} + {{ $t('models.compatibilities') }} toggleModality(mod, val)" + :model-value="selectedCompat.includes(opt.value)" + @update:model-value="(val: boolean) => toggleCompat(opt.value, val)" /> - {{ $t(`models.modality.${mod}`) }} + {{ $t(`models.compatibility.${opt.value}`) }} - - + - {{ $t('models.supportsReasoning') }} - supportsReasoning = !!val" - /> - + + + {{ $t('models.contextWindow') }} + ({{ $t('common.optional') }}) + + + + + + @@ -194,7 +168,6 @@ import { FormItem, Checkbox, Label, - Switch, } from '@memoh/ui' import { useForm } from 'vee-validate' import { inject, computed, watch, nextTick, type Ref, ref } from 'vue' @@ -204,24 +177,20 @@ import { useMutation, useQueryCache } from '@pinia/colada' import { postModels, putModelsById, putModelsModelByModelId } from '@memoh/sdk' import type { ModelsGetResponse, ModelsAddRequest, ModelsUpdateRequest } from '@memoh/sdk' import { useI18n } from 'vue-i18n' -import { CLIENT_TYPE_LIST, CLIENT_TYPE_META } from '@/constants/client-types' +import { COMPATIBILITY_OPTIONS } from '@/constants/compatibilities' import FormDialogShell from '@/components/form-dialog-shell/index.vue' -import SearchableSelectPopover from '@/components/searchable-select-popover/index.vue' -import type { SearchableSelectOption } from '@/components/searchable-select-popover/index.vue' import { useDialogMutation } from '@/composables/useDialogMutation' -const availableInputModalities = ['text', 'image', 'audio', 'video', 'file'] as const -const selectedModalities = ref(['text']) -const supportsReasoning = ref(false) +const selectedCompat = ref([]) const { t } = useI18n() const { run } = useDialogMutation() const formSchema = toTypedSchema(z.object({ type: z.string().min(1), - client_type: z.string().optional(), model_id: z.string().min(1), name: z.string().optional(), dimensions: z.coerce.number().min(1).optional(), + context_window: z.coerce.number().min(1).optional(), })) const form = useForm({ @@ -233,37 +202,22 @@ const form = useForm({ const selectedType = computed(() => form.values.type || 'chat') -const clientTypeModel = computed({ - get: () => form.values.client_type || '', - set: (value: string) => form.setFieldValue('client_type', value), -}) - -const clientTypeOptions = computed(() => - CLIENT_TYPE_LIST.map((ct) => ({ - value: ct.value, - label: ct.label, - description: ct.hint, - keywords: [ct.label, ct.hint, CLIENT_TYPE_META[ct.value]?.value ?? ct.value], - })), -) - const open = inject>('openModel', ref(false)) const title = inject>('openModelTitle', ref('title')) const editInfo = inject>('openModelState', ref(null)) const canSubmit = computed(() => { if (title.value === 'edit') return true - const { type, model_id, client_type } = form.values + const { type, model_id } = form.values if (!type || !model_id) return false - if (type === 'chat' && !client_type) return false return true }) -function toggleModality(mod: string, checked: boolean) { +function toggleCompat(cap: string, checked: boolean) { if (checked) { - selectedModalities.value = [...selectedModalities.value, mod] + selectedCompat.value = [...selectedCompat.value, cap] } else { - selectedModalities.value = selectedModalities.value.filter(m => m !== mod) + selectedCompat.value = selectedCompat.value.filter(c => c !== cap) } } @@ -318,44 +272,39 @@ const { mutateAsync: updateModelByLegacyModelID, isLoading: updateLegacyLoading const isLoading = computed(() => createLoading.value || updateLoading.value || updateLegacyLoading.value) async function addModel() { - - const isEdit = title.value === 'edit' && !!editInfo?.value + const isEdit = title.value === 'edit' && !!editInfo?.value const fallback = editInfo?.value const type = form.values.type || (isEdit ? fallback!.type : 'chat') - const client_type = type === 'chat' - ? (form.values.client_type || (isEdit ? fallback!.client_type : '')) - : undefined const model_id = form.values.model_id || (isEdit ? fallback!.model_id : '') const name = form.values.name ?? (isEdit ? fallback!.name : '') - const dimensions = form.values.dimensions ?? (isEdit ? fallback!.dimensions : undefined) if (!type || !model_id) return - if (type === 'chat' && !client_type) return + + const config: Record = {} + + if (type === 'embedding') { + const dim = form.values.dimensions ?? (isEdit ? fallback!.config?.dimensions : undefined) + if (dim) config.dimensions = dim + } + + if (type === 'chat') { + config.compatibilities = selectedCompat.value + const ctxWin = form.values.context_window ?? (isEdit ? fallback!.config?.context_window : undefined) + if (ctxWin) config.context_window = ctxWin + } const payload: Record = { type, model_id, llm_provider_id: id, - } - - if (type === 'chat' && client_type) { - payload.client_type = client_type + config, } if (name) { payload.name = name } - if (type === 'embedding' && dimensions) { - payload.dimensions = dimensions - } - - if (type === 'chat') { - payload.input_modalities = selectedModalities.value.length > 0 ? selectedModalities.value : ['text'] - payload.supports_reasoning = supportsReasoning.value - } - await run( () => { if (isEdit) { @@ -386,25 +335,24 @@ watch(open, async () => { await nextTick() if (editInfo?.value) { - const { client_type, type, model_id, name, dimensions, input_modalities } = editInfo.value - form.resetForm({ values: { type: type || 'chat', client_type: client_type || '', model_id, name, dimensions } }) - selectedModalities.value = input_modalities ?? ['text'] - supportsReasoning.value = !!editInfo.value.supports_reasoning + const { type, model_id, name, config } = editInfo.value + form.resetForm({ + values: { + type: type || 'chat', + model_id, + name, + dimensions: config?.dimensions, + context_window: config?.context_window, + }, + }) + selectedCompat.value = config?.compatibilities ?? [] userEditedName.value = !!(name && name !== model_id) } else { - form.resetForm({ values: { type: 'chat', client_type: '', model_id: '', name: '', dimensions: undefined } }) - selectedModalities.value = ['text'] - supportsReasoning.value = false + form.resetForm({ values: { type: 'chat', model_id: '', name: '', dimensions: undefined, context_window: undefined } }) + selectedCompat.value = [] userEditedName.value = false } }, { immediate: true, }) - -// Clear client_type when switching to embedding -watch(selectedType, (newType) => { - if (newType === 'embedding') { - form.setFieldValue('client_type', '') - } -}) diff --git a/apps/web/src/components/import-models-dialog/index.vue b/apps/web/src/components/import-models-dialog/index.vue index bd515fce..e77e53c7 100644 --- a/apps/web/src/components/import-models-dialog/index.vue +++ b/apps/web/src/components/import-models-dialog/index.vue @@ -4,7 +4,7 @@ :title="$t('models.importModels')" :cancel-text="$t('common.cancel')" :submit-text="$t('common.import')" - :submit-disabled="!clientType" + :submit-disabled="false" :loading="isLoading" @submit="handleImport" > @@ -19,17 +19,8 @@ - - {{ $t('models.importClientType') }} - - - - {{ $t('models.importClientTypeHint') }} + + {{ $t('models.importConfirmHint') }} @@ -37,15 +28,13 @@
- {{ $t('models.importClientTypeHint') }} -
- {{ $t('models.importClientTypeHint') }} +
+ {{ $t('models.importConfirmHint') }}