Skip to content

Commit de0178b

Browse files
authored
Merge pull request #60 from ShaerWare/feature/openrouter-models-update
feat(llm): Update OpenRouter models and improve provider UI
2 parents cfd4721 + 3468958 commit de0178b

5 files changed

Lines changed: 137 additions & 47 deletions

File tree

admin/src/views/LlmView.vue

Lines changed: 78 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,15 @@ const showProviderModal = ref(false)
4040
const editingProvider = ref<CloudProvider | null>(null)
4141
const providerForm = ref({
4242
name: '',
43-
provider_type: 'kimi',
43+
provider_type: 'openrouter',
4444
api_key: '',
4545
base_url: '',
4646
model_name: '',
4747
description: '',
4848
enabled: true,
4949
})
50+
const useCustomModel = ref(false)
51+
const customModelName = ref('')
5052
5153
// Queries
5254
const { data: backendData, isLoading: backendLoading } = useQuery({
@@ -94,6 +96,18 @@ watch(paramsData, (data) => {
9496
}
9597
}, { immediate: true })
9698
99+
// Computed для списка моделей текущего провайдера
100+
const currentProviderModels = computed(() => {
101+
return providersData.value?.provider_types?.[providerForm.value.provider_type]?.default_models || []
102+
})
103+
104+
// Переключение на кастомную модель когда выбрано "Other"
105+
watch(() => providerForm.value.model_name, (newVal) => {
106+
if (newVal === '' && !useCustomModel.value && currentProviderModels.value.length > 0) {
107+
useCustomModel.value = true
108+
}
109+
})
110+
97111
// Mutations
98112
const setBackendMutation = useMutation({
99113
mutationFn: (backend: string) => llmApi.setBackend(backend, backend === 'gemini' && stopUnusedVllm.value),
@@ -214,31 +228,39 @@ function saveParams() {
214228
// Cloud provider methods
215229
function openCreateProviderModal() {
216230
editingProvider.value = null
217-
const defaultType = 'kimi'
231+
const defaultType = 'openrouter'
218232
const typeConfig = providersData.value?.provider_types?.[defaultType]
219233
providerForm.value = {
220234
name: '',
221235
provider_type: defaultType,
222236
api_key: '',
223-
base_url: typeConfig?.default_base_url || 'https://api.moonshot.ai/v1',
224-
model_name: typeConfig?.default_models?.[0] || 'kimi-k2',
237+
base_url: typeConfig?.default_base_url || 'https://openrouter.ai/api/v1',
238+
model_name: typeConfig?.default_models?.[0] || '',
225239
description: '',
226240
enabled: true,
227241
}
242+
useCustomModel.value = false
243+
customModelName.value = ''
228244
showProviderModal.value = true
229245
}
230246
231247
function openEditProviderModal(provider: CloudProvider) {
232248
editingProvider.value = provider
249+
const typeConfig = providersData.value?.provider_types?.[provider.provider_type]
250+
const defaultModels = typeConfig?.default_models || []
251+
const isCustomModel = Boolean(provider.model_name && !defaultModels.includes(provider.model_name))
252+
233253
providerForm.value = {
234254
name: provider.name,
235255
provider_type: provider.provider_type,
236256
api_key: '', // Don't prefill key
237257
base_url: provider.base_url || '',
238-
model_name: provider.model_name,
258+
model_name: isCustomModel ? '' : provider.model_name,
239259
description: provider.description || '',
240260
enabled: provider.enabled,
241261
}
262+
useCustomModel.value = isCustomModel
263+
customModelName.value = isCustomModel ? provider.model_name : ''
242264
showProviderModal.value = true
243265
}
244266
@@ -247,11 +269,19 @@ function onProviderTypeChange() {
247269
if (typeConfig) {
248270
providerForm.value.base_url = typeConfig.default_base_url || ''
249271
providerForm.value.model_name = typeConfig.default_models?.[0] || ''
272+
useCustomModel.value = false
273+
customModelName.value = ''
250274
}
251275
}
252276
253277
function saveProvider() {
254278
const data = { ...providerForm.value }
279+
280+
// Используем кастомную модель если выбрана опция "Другая"
281+
if (useCustomModel.value && customModelName.value) {
282+
data.model_name = customModelName.value
283+
}
284+
255285
if (!data.api_key) {
256286
// eslint-disable-next-line @typescript-eslint/no-explicit-any
257287
delete (data as any).api_key // Don't update if empty
@@ -287,25 +317,25 @@ function switchToCloudProvider(providerId: string) {
287317
<div class="flex items-center gap-4">
288318
<div class="grid grid-cols-2 gap-2 p-1 bg-secondary rounded-lg">
289319
<button
290-
@click="setBackendMutation.mutate('vllm')"
291320
:disabled="setBackendMutation.isPending.value"
292321
:class="[
293322
'px-4 py-2 rounded-lg transition-colors flex items-center justify-center gap-2',
294323
isVllm ? 'bg-primary text-primary-foreground' : 'hover:bg-secondary/80',
295324
setBackendMutation.isPending.value && 'opacity-50 cursor-not-allowed'
296325
]"
326+
@click="setBackendMutation.mutate('vllm')"
297327
>
298328
<Loader2 v-if="setBackendMutation.isPending.value && !isVllm" class="w-4 h-4 animate-spin" />
299329
vLLM (Local)
300330
</button>
301331
<button
302-
@click="setBackendMutation.mutate('gemini')"
303332
:disabled="setBackendMutation.isPending.value"
304333
:class="[
305334
'px-4 py-2 rounded-lg transition-colors flex items-center justify-center gap-2',
306335
!isVllm ? 'bg-primary text-primary-foreground' : 'hover:bg-secondary/80',
307336
setBackendMutation.isPending.value && 'opacity-50 cursor-not-allowed'
308337
]"
338+
@click="setBackendMutation.mutate('gemini')"
309339
>
310340
<Loader2 v-if="setBackendMutation.isPending.value && isVllm" class="w-4 h-4 animate-spin" />
311341
Gemini (Cloud)
@@ -331,9 +361,9 @@ function switchToCloudProvider(providerId: string) {
331361
<!-- Stop vLLM checkbox (show when using vLLM) -->
332362
<div v-if="isVllm" class="flex items-center gap-2">
333363
<input
334-
type="checkbox"
335364
id="stopVllm"
336365
v-model="stopUnusedVllm"
366+
type="checkbox"
337367
class="w-4 h-4 rounded border-border"
338368
/>
339369
<label for="stopVllm" class="text-sm text-muted-foreground">
@@ -359,15 +389,15 @@ function switchToCloudProvider(providerId: string) {
359389
</h2>
360390
<div class="flex gap-2">
361391
<button
362-
@click="() => refetchProviders()"
363392
class="p-2 bg-secondary rounded-lg hover:bg-secondary/80 transition-colors"
364393
title="Refresh"
394+
@click="() => refetchProviders()"
365395
>
366396
<RefreshCw class="w-4 h-4" />
367397
</button>
368398
<button
369-
@click="openCreateProviderModal"
370399
class="flex items-center gap-2 px-3 py-1.5 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors"
400+
@click="openCreateProviderModal"
371401
>
372402
<Plus class="w-4 h-4" />
373403
Add Provider
@@ -420,31 +450,31 @@ function switchToCloudProvider(providerId: string) {
420450

421451
<div class="flex items-center gap-2">
422452
<button
423-
@click="testProviderMutation.mutate(provider.id)"
424453
:disabled="testProviderMutation.isPending.value"
425454
class="p-2 bg-secondary rounded-lg hover:bg-secondary/80 transition-colors"
426455
title="Test connection"
456+
@click="testProviderMutation.mutate(provider.id)"
427457
>
428458
<Loader2 v-if="testProviderMutation.isPending.value" class="w-4 h-4 animate-spin" />
429459
<Play v-else class="w-4 h-4" />
430460
</button>
431461
<button
432-
@click="switchToCloudProvider(provider.id)"
433462
:disabled="!provider.enabled || setBackendMutation.isPending.value"
434463
class="px-3 py-1.5 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-50 transition-colors text-sm"
464+
@click="switchToCloudProvider(provider.id)"
435465
>
436466
Use
437467
</button>
438468
<button
439-
@click="openEditProviderModal(provider)"
440469
class="p-2 bg-secondary rounded-lg hover:bg-secondary/80 transition-colors"
470+
@click="openEditProviderModal(provider)"
441471
>
442472
<Edit2 class="w-4 h-4" />
443473
</button>
444474
<button
445-
@click="deleteProviderMutation.mutate(provider.id)"
446475
:disabled="deleteProviderMutation.isPending.value"
447476
class="p-2 bg-red-600/20 text-red-600 rounded-lg hover:bg-red-600/30 transition-colors"
477+
@click="deleteProviderMutation.mutate(provider.id)"
448478
>
449479
<Trash2 class="w-4 h-4" />
450480
</button>
@@ -551,19 +581,19 @@ function switchToCloudProvider(providerId: string) {
551581
<div
552582
v-for="(persona, id) in personas"
553583
:key="id"
554-
@click="setPersonaMutation.mutate(id as string)"
555584
:class="[
556585
'p-4 rounded-lg border cursor-pointer transition-all',
557586
currentPersonaData?.id === id
558587
? 'border-primary bg-primary/10'
559588
: 'border-border hover:border-primary/50'
560589
]"
590+
@click="setPersonaMutation.mutate(id as string)"
561591
>
562592
<div class="font-medium">{{ persona.name }}</div>
563593
<div class="text-sm text-muted-foreground">{{ persona.full_name }}</div>
564594
<button
565-
@click.stop="loadPromptForEdit(id as string)"
566595
class="mt-2 text-xs text-primary hover:underline"
596+
@click.stop="loadPromptForEdit(id as string)"
567597
>
568598
Edit Prompt
569599
</button>
@@ -580,9 +610,9 @@ function switchToCloudProvider(providerId: string) {
580610
Generation Parameters
581611
</h2>
582612
<button
583-
@click="saveParams"
584613
:disabled="setParamsMutation.isPending.value"
585614
class="flex items-center gap-2 px-3 py-1.5 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-50 transition-colors"
615+
@click="saveParams"
586616
>
587617
<Save class="w-4 h-4" />
588618
Save
@@ -671,15 +701,15 @@ function switchToCloudProvider(providerId: string) {
671701
</h2>
672702
<div class="flex items-center gap-2">
673703
<button
674-
@click="() => refetchHistory()"
675704
class="p-2 rounded-lg bg-secondary hover:bg-secondary/80 transition-colors"
705+
@click="() => refetchHistory()"
676706
>
677707
<RotateCw class="w-4 h-4" />
678708
</button>
679709
<button
680-
@click="clearHistoryMutation.mutate()"
681710
:disabled="clearHistoryMutation.isPending.value || !historyData?.count"
682711
class="flex items-center gap-2 px-3 py-1.5 bg-red-600 text-white rounded-lg hover:bg-red-700 disabled:opacity-50 transition-colors"
712+
@click="clearHistoryMutation.mutate()"
683713
>
684714
<Trash2 class="w-4 h-4" />
685715
Clear
@@ -726,15 +756,15 @@ function switchToCloudProvider(providerId: string) {
726756

727757
<div class="flex justify-end gap-2 mt-4">
728758
<button
729-
@click="editingPrompt = false"
730759
class="px-4 py-2 bg-secondary rounded-lg hover:bg-secondary/80 transition-colors"
760+
@click="editingPrompt = false"
731761
>
732762
Cancel
733763
</button>
734764
<button
735-
@click="savePromptMutation.mutate({ persona: selectedPersonaForPrompt, prompt: promptText })"
736765
:disabled="savePromptMutation.isPending.value"
737766
class="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-50 transition-colors"
767+
@click="savePromptMutation.mutate({ persona: selectedPersonaForPrompt, prompt: promptText })"
738768
>
739769
Save Prompt
740770
</button>
@@ -799,21 +829,35 @@ function switchToCloudProvider(providerId: string) {
799829
</div>
800830

801831
<div>
802-
<label class="block text-sm font-medium mb-1">Model Name</label>
803-
<input
832+
<label class="block text-sm font-medium mb-1">Model</label>
833+
<select
834+
v-if="!useCustomModel"
804835
v-model="providerForm.model_name"
805-
type="text"
806-
list="model-suggestions"
807836
class="w-full px-3 py-2 bg-secondary rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
808-
placeholder="kimi-k2"
809-
/>
810-
<datalist id="model-suggestions">
837+
>
811838
<option
812-
v-for="model in providersData?.provider_types?.[providerForm.provider_type]?.default_models"
839+
v-for="model in currentProviderModels"
813840
:key="model"
814841
:value="model"
815-
/>
816-
</datalist>
842+
>
843+
{{ model }}
844+
</option>
845+
<option value="">— Other (custom) —</option>
846+
</select>
847+
<input
848+
v-else
849+
v-model="customModelName"
850+
type="text"
851+
class="w-full px-3 py-2 bg-secondary rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
852+
placeholder="e.g. anthropic/claude-3.5-sonnet"
853+
/>
854+
<button
855+
v-if="useCustomModel"
856+
class="mt-1 text-xs text-primary hover:underline"
857+
@click="useCustomModel = false; customModelName = ''"
858+
>
859+
← Back to list
860+
</button>
817861
</div>
818862

819863
<div>
@@ -828,9 +872,9 @@ function switchToCloudProvider(providerId: string) {
828872

829873
<div class="flex items-center gap-2">
830874
<input
831-
type="checkbox"
832875
id="provider-enabled"
833876
v-model="providerForm.enabled"
877+
type="checkbox"
834878
class="w-4 h-4 rounded border-border"
835879
/>
836880
<label for="provider-enabled" class="text-sm">Enabled</label>
@@ -839,15 +883,15 @@ function switchToCloudProvider(providerId: string) {
839883

840884
<div class="flex justify-end gap-2 mt-6">
841885
<button
842-
@click="showProviderModal = false"
843886
class="px-4 py-2 bg-secondary rounded-lg hover:bg-secondary/80 transition-colors"
887+
@click="showProviderModal = false"
844888
>
845889
Cancel
846890
</button>
847891
<button
848-
@click="saveProvider"
849892
:disabled="!providerForm.name || createProviderMutation.isPending.value || updateProviderMutation.isPending.value"
850893
class="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-50 transition-colors"
894+
@click="saveProvider"
851895
>
852896
<Loader2 v-if="createProviderMutation.isPending.value || updateProviderMutation.isPending.value" class="w-4 h-4 inline mr-1 animate-spin" />
853897
{{ editingProvider ? 'Update' : 'Create' }}

app/routers/llm.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,17 @@ async def admin_set_llm_backend(
325325
)
326326

327327
llm_service = container.llm_service
328-
current_backend = "vllm" if (llm_service and hasattr(llm_service, "api_url")) else "gemini"
328+
329+
# Определяем текущий бэкенд правильно (cloud, vllm, gemini)
330+
if llm_service and getattr(llm_service, "backend_type", None) == "cloud":
331+
current_backend = f"cloud:{getattr(llm_service, 'provider_id', 'unknown')}"
332+
elif (
333+
llm_service and hasattr(llm_service, "api_url") and not hasattr(llm_service, "backend_type")
334+
):
335+
current_backend = "vllm"
336+
else:
337+
current_backend = "gemini"
338+
329339
if request.backend == current_backend:
330340
return {
331341
"status": "ok",

0 commit comments

Comments
 (0)