@@ -40,13 +40,15 @@ const showProviderModal = ref(false)
4040const editingProvider = ref <CloudProvider | null >(null )
4141const 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
5254const { 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
98112const setBackendMutation = useMutation ({
99113 mutationFn : (backend : string ) => llmApi .setBackend (backend , backend === ' gemini' && stopUnusedVllm .value ),
@@ -214,31 +228,39 @@ function saveParams() {
214228// Cloud provider methods
215229function 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
231247function 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
253277function 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' }}
0 commit comments