Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions apps/web/src/components/create-model/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
<SelectItem value="embedding">
Embedding
</SelectItem>
<SelectItem value="image">
Image
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
Expand Down Expand Up @@ -107,14 +110,14 @@
</FormItem>
</FormField>

<!-- Compatibilities (chat only) -->
<div v-if="selectedType === 'chat'">
<!-- Compatibilities -->
<div v-if="selectedCompatibilityOptions.length > 0">
<Label class="mb-4">
{{ $t('models.compatibilities') }}
</Label>
<div class="flex flex-wrap gap-3 mt-2">
<label
v-for="opt in COMPATIBILITY_OPTIONS"
v-for="opt in selectedCompatibilityOptions"
:key="opt.value"
class="flex items-center gap-1.5 text-xs"
>
Expand Down Expand Up @@ -177,7 +180,7 @@ import { useMutation, useQueryCache } from '@pinia/colada'
import { postModels, putModelsById, putModelsModelByModelId } from '@memohai/sdk'
import type { ModelsGetResponse, ModelsAddRequest, ModelsUpdateRequest } from '@memohai/sdk'
import { useI18n } from 'vue-i18n'
import { COMPATIBILITY_OPTIONS } from '@/constants/compatibilities'
import { CHAT_COMPATIBILITY_OPTIONS, IMAGE_COMPATIBILITY_OPTIONS } from '@/constants/compatibilities'
import FormDialogShell from '@/components/form-dialog-shell/index.vue'
import { useDialogMutation } from '@/composables/useDialogMutation'

Expand All @@ -201,6 +204,21 @@ const form = useForm({
})

const selectedType = computed(() => form.values.type || 'chat')
const selectedCompatibilityOptions = computed(() => {
switch (selectedType.value) {
case 'chat':
return CHAT_COMPATIBILITY_OPTIONS
case 'image':
return IMAGE_COMPATIBILITY_OPTIONS
default:
return []
}
})

watch(selectedCompatibilityOptions, (options) => {
const allowed = new Set(options.map(option => option.value))
selectedCompat.value = selectedCompat.value.filter(value => allowed.has(value))
}, { immediate: true })

const open = inject<Ref<boolean>>('openModel', ref(false))
const title = inject<Ref<'edit' | 'title'>>('openModelTitle', ref('title'))
Expand Down Expand Up @@ -288,8 +306,11 @@ async function addModel() {
if (dim) config.dimensions = dim
}

if (type === 'chat') {
if (type === 'chat' || type === 'image') {
config.compatibilities = selectedCompat.value
}

if (type === 'chat') {
const ctxWin = form.values.context_window ?? (isEdit ? fallback!.config?.context_window : undefined)
if (ctxWin) config.context_window = ctxWin
}
Expand Down
6 changes: 5 additions & 1 deletion apps/web/src/components/model-capabilities/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

<script setup lang="ts">
import type { Component } from 'vue'
import { Wrench, Eye, Image, Brain } from 'lucide-vue-next'
import { Wrench, Eye, Image, Brain, Pencil } from 'lucide-vue-next'

defineProps<{
compatibilities: string[]
Expand All @@ -25,13 +25,17 @@ const ICONS: Record<string, Component> = {
'tool-call': Wrench,
'vision': Eye,
'image-output': Image,
'generate': Image,
'edit': Pencil,
'reasoning': Brain,
}

const CLASSES: Record<string, string> = {
'tool-call': 'bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300',
'vision': 'bg-purple-50 text-purple-700 dark:bg-purple-950 dark:text-purple-300',
'image-output': 'bg-pink-50 text-pink-700 dark:bg-pink-950 dark:text-pink-300',
'generate': 'bg-pink-50 text-pink-700 dark:bg-pink-950 dark:text-pink-300',
'edit': 'bg-emerald-50 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300',
'reasoning': 'bg-amber-50 text-amber-700 dark:bg-amber-950 dark:text-amber-300',
}

Expand Down
12 changes: 11 additions & 1 deletion apps/web/src/constants/compatibilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@ export interface CompatibilityMeta {
label: string
}

export const COMPATIBILITY_OPTIONS: CompatibilityMeta[] = [
export const CHAT_COMPATIBILITY_OPTIONS: CompatibilityMeta[] = [
{ value: 'vision', label: 'Vision' },
{ value: 'tool-call', label: 'Tool Call' },
{ value: 'image-output', label: 'Image Output' },
{ value: 'reasoning', label: 'Reasoning' },
]

export const IMAGE_COMPATIBILITY_OPTIONS: CompatibilityMeta[] = [
{ value: 'generate', label: 'Generate' },
{ value: 'edit', label: 'Edit' },
]

export const COMPATIBILITY_OPTIONS: CompatibilityMeta[] = [
...CHAT_COMPATIBILITY_OPTIONS,
...IMAGE_COMPATIBILITY_OPTIONS,
]
4 changes: 3 additions & 1 deletion apps/web/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@
"vision": "Vision",
"tool-call": "Tool Call",
"image-output": "Image Output",
"generate": "Generate",
"edit": "Edit",
"reasoning": "Reasoning"
},
"contextWindow": "Context Window",
Expand Down Expand Up @@ -916,7 +918,7 @@
"ttsModel": "TTS Model",
"ttsModelPlaceholder": "Select TTS model",
"imageModel": "Image Generation Model",
"imageModelDescription": "Model used for the generate_image tool. Must support image-output compatibility.",
"imageModelDescription": "Model used for image tools. Supports chat models with image-output or image models with generate/edit compatibility.",
"imageModelPlaceholder": "Select image model (optional)",
"language": "Language",
"reasoningEnabled": "Enable Reasoning",
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@
"vision": "视觉",
"tool-call": "工具调用",
"image-output": "图片生成",
"generate": "生成",
"edit": "编辑",
"reasoning": "推理"
},
"contextWindow": "上下文窗口",
Expand Down Expand Up @@ -912,7 +914,7 @@
"ttsModel": "语音合成模型",
"ttsModelPlaceholder": "选择语音合成模型",
"imageModel": "图片生成模型",
"imageModelDescription": "用于 generate_image 工具的模型,必须支持 image-output 兼容性。",
"imageModelDescription": "用于图片工具的模型。可选择支持 image-output 的 chat 模型,或支持 generate/edit 的 image 模型。",
"imageModelPlaceholder": "选择图片模型(可选)",
"language": "语言",
"reasoningEnabled": "启用推理",
Expand Down
11 changes: 9 additions & 2 deletions apps/web/src/pages/bots/components/bot-settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,6 @@
v-model="form.image_model_id"
:models="imageCapableModels"
:providers="providers"
model-type="chat"
:placeholder="$t('bots.settings.imageModelPlaceholder')"
/>
</div>
Expand Down Expand Up @@ -488,7 +487,15 @@ const { mutateAsync: deleteBot, isLoading: deleteLoading } = useMutation({
const models = computed(() => modelData.value ?? [])
const providers = computed(() => providerData.value ?? [])
const imageCapableModels = computed(() =>
models.value.filter((m) => m.config?.compatibilities?.includes('image-output')),
models.value.filter((m) => {
if (m.type === 'chat') {
return m.config?.compatibilities?.includes('image-output')
}
if (m.type === 'image') {
return m.config?.compatibilities?.includes('generate') || m.config?.compatibilities?.includes('edit')
}
return false
}),
)
const searchProviders = computed(() => (searchProviderData.value ?? []).filter((p) => p.enable !== false))
const memoryProviders = computed(() => memoryProviderData.value ?? [])
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/pages/bots/components/model-options.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export interface ModelOption {
const props = defineProps<{
models: ModelsGetResponse[]
providers: ProvidersGetResponse[]
modelType: 'chat' | 'embedding'
modelType?: 'chat' | 'embedding' | 'image'
open?: boolean
}>()

Expand All @@ -118,7 +118,7 @@ const providerMap = computed(() => {
})

const typeFilteredModels = computed(() =>
props.models.filter((m) => m.type === props.modelType),
props.modelType ? props.models.filter((m) => m.type === props.modelType) : props.models,
)

const options = computed<ModelOption[]>(() =>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/pages/bots/components/model-select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import ModelOptions from './model-options.vue'
const props = defineProps<{
models: ModelsGetResponse[]
providers: ProvidersGetResponse[]
modelType: 'chat' | 'embedding'
modelType?: 'chat' | 'embedding' | 'image'
placeholder?: string
}>()

Expand Down
11 changes: 9 additions & 2 deletions apps/web/src/pages/providers/components/model-item.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ import {
Button,
Spinner,
} from '@memohai/ui'
import { RefreshCw, Settings, Trash2, MessageSquare, Binary } from 'lucide-vue-next'
import { RefreshCw, Settings, Trash2, MessageSquare, Binary, Image } from 'lucide-vue-next'
import ConfirmPopover from '@/components/confirm-popover/index.vue'
import ModelCapabilities from '@/components/model-capabilities/index.vue'
import ContextWindowBadge from '@/components/context-window-badge/index.vue'
Expand All @@ -128,7 +128,14 @@ const testResult = ref<ModelsTestResponse | null>(null)
const reasoningEfforts = computed(() => ((props.model.config as ModelConfigWithReasoning | undefined)?.reasoning_efforts ?? []))

const typeIcon = computed(() => {
return props.model.type === 'embedding' ? Binary : MessageSquare
switch (props.model.type) {
case 'embedding':
return Binary
case 'image':
return Image
default:
return MessageSquare
}
})

const statusDotClass = computed(() => {
Expand Down
2 changes: 1 addition & 1 deletion db/migrations/0001_init.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ CREATE TABLE IF NOT EXISTS models (
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT models_provider_id_model_id_unique UNIQUE (provider_id, model_id),
CONSTRAINT models_type_check CHECK (type IN ('chat', 'embedding', 'speech'))
CONSTRAINT models_type_check CHECK (type IN ('chat', 'embedding', 'image', 'speech'))
);

CREATE TABLE IF NOT EXISTS model_variants (
Expand Down
7 changes: 7 additions & 0 deletions db/migrations/0067_add_image_model_type.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- 0067_add_image_model_type (rollback)
-- Remove image from the supported model types.
ALTER TABLE models DROP CONSTRAINT IF EXISTS models_type_check;

ALTER TABLE models
ADD CONSTRAINT models_type_check
CHECK (type IN ('chat', 'embedding', 'speech'));
7 changes: 7 additions & 0 deletions db/migrations/0067_add_image_model_type.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- 0067_add_image_model_type
-- Add image as a supported model type.
ALTER TABLE models DROP CONSTRAINT IF EXISTS models_type_check;

ALTER TABLE models
ADD CONSTRAINT models_type_check
CHECK (type IN ('chat', 'embedding', 'image', 'speech'));
Loading
Loading