Skip to content
Merged
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
165 changes: 125 additions & 40 deletions src/renderer/src/components/settings/ModelProviderSettings.vue
Original file line number Diff line number Diff line change
@@ -1,41 +1,92 @@
<template>
<div class="w-full h-full flex flex-row">
<ScrollArea class="w-64 border-r h-full px-2">
<div class="py-2">
<draggable
v-model="sortedProviders"
item-key="id"
handle=".drag-handle"
class="space-y-2"
@end="handleDragEnd"
>
<template #item="{ element: provider }">
<div
:data-provider-id="provider.id"
:class="[
'flex flex-row hover:bg-accent items-center gap-2 rounded-lg p-2 cursor-pointer group',
route.params?.providerId === provider.id
? 'bg-secondary text-secondary-foreground'
: ''
]"
@click="setActiveProvider(provider.id)"
>
<Icon
icon="lucide:grip-vertical"
class="w-4 h-4 text-muted-foreground opacity-0 group-hover:opacity-100 cursor-move drag-handle"
/>
<ModelIcon
:model-id="provider.id"
:custom-class="'w-4 h-4 text-muted-foreground'"
:is-dark="themeStore.isDark"
/>
<span class="text-sm font-medium flex-1" :dir="languageStore.dir">{{
t(provider.name)
}}</span>
<Switch :checked="provider.enable" @click.stop="toggleProviderStatus(provider)" />
</div>
</template>
</draggable>
<div class="py-2 space-y-4">
<!-- 启用的服务商区域 -->
<div v-if="enabledProviders.length > 0">
<div class="text-xs font-medium text-muted-foreground mb-2 px-2">
{{ t('settings.provider.enabled') }} ({{ enabledProviders.length }})
</div>
<draggable
v-model="enabledProviders"
item-key="id"
handle=".drag-handle"
class="space-y-2"
group="providers"
:move="onMoveEnabled"
@end="handleDragEnd"
>
<template #item="{ element: provider }">
<div
:data-provider-id="provider.id"
:class="[
'flex flex-row hover:bg-accent items-center gap-2 rounded-lg p-2 cursor-pointer group',
route.params?.providerId === provider.id
? 'bg-secondary text-secondary-foreground'
: ''
]"
@click="setActiveProvider(provider.id)"
>
<Icon
icon="lucide:grip-vertical"
class="w-4 h-4 text-muted-foreground opacity-0 group-hover:opacity-100 cursor-move drag-handle"
/>
<ModelIcon
:model-id="provider.id"
:custom-class="'w-4 h-4 text-muted-foreground'"
:is-dark="themeStore.isDark"
/>
<span class="text-sm font-medium flex-1" :dir="languageStore.dir">{{
t(provider.name)
}}</span>
<Switch :checked="provider.enable" @click.stop="toggleProviderStatus(provider)" />
</div>
</template>
</draggable>
</div>

<!-- 禁用的服务商区域 -->
<div v-if="disabledProviders.length > 0">
<div class="text-xs font-medium text-muted-foreground mb-2 px-2">
{{ t('settings.provider.disabled') }} ({{ disabledProviders.length }})
</div>
<draggable
v-model="disabledProviders"
item-key="id"
handle=".drag-handle"
class="space-y-2"
group="providers"
:move="onMoveDisabled"
@end="handleDragEnd"
>
<template #item="{ element: provider }">
<div
:data-provider-id="provider.id"
:class="[
'flex flex-row hover:bg-accent items-center gap-2 rounded-lg p-2 cursor-pointer group opacity-60',
route.params?.providerId === provider.id
? 'bg-secondary text-secondary-foreground'
: ''
]"
@click="setActiveProvider(provider.id)"
>
<Icon
icon="lucide:grip-vertical"
class="w-4 h-4 text-muted-foreground opacity-0 group-hover:opacity-100 cursor-move drag-handle"
/>
<ModelIcon
:model-id="provider.id"
:custom-class="'w-4 h-4 text-muted-foreground'"
:is-dark="themeStore.isDark"
/>
<span class="text-sm font-medium flex-1" :dir="languageStore.dir">{{
t(provider.name)
}}</span>
<Switch :checked="provider.enable" @click.stop="toggleProviderStatus(provider)" />
</div>
</template>
</draggable>
</div>

<div
class="flex flex-row items-center gap-2 rounded-lg p-2 cursor-pointer hover:bg-accent mt-2"
Expand Down Expand Up @@ -93,12 +144,20 @@ const settingsStore = useSettingsStore()
const themeStore = useThemeStore()
const isAddProviderDialogOpen = ref(false)

// 创建一个计算属性来处理排序后的providers
const sortedProviders = computed({
get: () => settingsStore.sortedProviders,
// 分别处理启用和禁用的 providers
const enabledProviders = computed({
get: () => settingsStore.sortedProviders.filter((p) => p.enable),
set: (newProviders) => {
const allProviders = [...newProviders, ...disabledProviders.value]
settingsStore.updateProvidersOrder(allProviders)
}
})

const disabledProviders = computed({
get: () => settingsStore.sortedProviders.filter((p) => !p.enable),
set: (newProviders) => {
// 更新 store 中的 providers 顺序
settingsStore.updateProvidersOrder(newProviders)
const allProviders = [...enabledProviders.value, ...newProviders]
settingsStore.updateProvidersOrder(allProviders)
}
})

Expand Down Expand Up @@ -152,6 +211,32 @@ const handleProviderAdded = (provider: LLM_PROVIDER) => {
const handleDragEnd = () => {
// 可以在这里添加额外的处理逻辑
}

// 处理启用区域的拖拽移动事件
const onMoveEnabled = (evt: any) => {
const draggedProvider = evt.draggedContext.element
const relatedProvider = evt.relatedContext?.element
if (!draggedProvider || !draggedProvider.enable) {
return false
}
if (relatedProvider && !relatedProvider.enable) {
return false
}
return true
}

// 处理禁用区域的拖拽移动事件
const onMoveDisabled = (evt: any) => {
const draggedProvider = evt.draggedContext.element
const relatedProvider = evt.relatedContext?.element
if (!draggedProvider || draggedProvider.enable) {
return false
}
if (relatedProvider && relatedProvider.enable) {
return false
}
return true
}
</script>

<style scoped>
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/en-US/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@
},
"provider": {
"enable": "Enable Service",
"enabled": "Enabled",
"disabled": "Disabled",
"urlPlaceholder": "Please enter API URL",
"keyPlaceholder": "Please enter API Key",
"verifyKey": "Verify Key",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/fa-IR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@
},
"provider": {
"enable": "روشن کردن خدمات",
"enabled": "فعال",
"disabled": "غیرفعال",
"urlPlaceholder": "لطفاً نشانی API را وارد کنید",
"keyPlaceholder": "لطفاً کلید API را وارد کنید",
"verifyKey": "پذیرش کلید",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/fr-FR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@
},
"provider": {
"enable": "Activer le service",
"enabled": "Activé",
"disabled": "Désactivé",
"urlPlaceholder": "Veuillez entrer l'URL de l'API",
"keyPlaceholder": "Veuillez entrer la clé API",
"verifyKey": "Vérifier la clé",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/ja-JP/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@
},
"provider": {
"enable": "サービスを有効にする",
"enabled": "有効",
"disabled": "無効",
"urlPlaceholder": "API URLを入力してください",
"keyPlaceholder": "API Keyを入力してください",
"verifyKey": "キーを検証",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/ko-KR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@
},
"provider": {
"enable": "서비스 활성화",
"enabled": "활성화됨",
"disabled": "비활성화됨",
"urlPlaceholder": "API URL을 입력하세요",
"keyPlaceholder": "API 키를 입력하세요",
"verifyKey": "키 확인",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/ru-RU/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@
},
"provider": {
"enable": "Включить сервис",
"enabled": "Включен",
"disabled": "Отключен",
"urlPlaceholder": "Введите API URL",
"keyPlaceholder": "Введите API Key",
"verifyKey": "Проверить ключ",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/zh-CN/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@
},
"provider": {
"enable": "开启服务",
"enabled": "已启用",
"disabled": "已禁用",
"urlPlaceholder": "请输入API URL",
"keyPlaceholder": "请输入API Key",
"verifyKey": "验证密钥",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/zh-HK/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@
},
"provider": {
"enable": "開啟服務",
"enabled": "已啟用",
"disabled": "已禁用",
"urlPlaceholder": "請輸入API URL",
"keyPlaceholder": "請輸入API Key",
"verifyKey": "驗證密鑰",
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/i18n/zh-TW/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@
},
"provider": {
"enable": "啟用服務",
"enabled": "已啟用",
"disabled": "已停用",
"urlPlaceholder": "請輸入 API URL",
"keyPlaceholder": "請輸入 API 金鑰",
"verifyKey": "驗證金鑰",
Expand Down
85 changes: 69 additions & 16 deletions src/renderer/src/stores/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,19 +265,27 @@ export const useSettingsStore = defineStore('settings', () => {
}
})

// Enabled:按时间戳升序排列
const sortedEnabled = enabledProviders.sort((a, b) => {
const aTime = providerTimestamps.value[a.id] || 0
const bTime = providerTimestamps.value[b.id] || 0
return aTime - bTime
})

// Disabled:按时间戳降序排列
const sortedDisabled = disabledProviders.sort((a, b) => {
const aTime = providerTimestamps.value[a.id] || 0
const bTime = providerTimestamps.value[b.id] || 0
return bTime - aTime
})
// 排序函数:优先使用拖拽顺序,其次使用时间戳
const sortProviders = (providerList: LLM_PROVIDER[], useAscendingTime: boolean) => {
return providerList.sort((a, b) => {
const aOrderIndex = providerOrder.value.indexOf(a.id)
const bOrderIndex = providerOrder.value.indexOf(b.id)
if (aOrderIndex !== -1 && bOrderIndex !== -1) {
return aOrderIndex - bOrderIndex
}
if (aOrderIndex !== -1 && bOrderIndex === -1) {
return -1
}
if (aOrderIndex === -1 && bOrderIndex !== -1) {
return 1
}
const aTime = providerTimestamps.value[a.id] || 0
const bTime = providerTimestamps.value[b.id] || 0
return useAscendingTime ? aTime - bTime : bTime - aTime
})
}
const sortedEnabled = sortProviders(enabledProviders, true)
const sortedDisabled = sortProviders(disabledProviders, false)

return [...sortedEnabled, ...sortedDisabled]
})
Expand Down Expand Up @@ -870,6 +878,42 @@ export const useSettingsStore = defineStore('settings', () => {
// 保存时间戳
await saveProviderTimestamps()
await updateProviderConfig(providerId, { enable })

await optimizeProviderOrder(providerId, enable)
}

const optimizeProviderOrder = async (providerId: string, enable: boolean): Promise<void> => {
try {
const currentOrder = [...providerOrder.value]
const providerIndex = currentOrder.indexOf(providerId)
if (providerIndex === -1) return
currentOrder.splice(providerIndex, 1)
const allProviders = providers.value
const enabledInOrder: string[] = []
const disabledInOrder: string[] = []
currentOrder.forEach((id) => {
const provider = allProviders.find((p) => p.id === id)
if (!provider || provider.id === providerId) return
if (provider.enable) {
enabledInOrder.push(id)
} else {
disabledInOrder.push(id)
}
})
let newOrder: string[]
if (enable) {
newOrder = [...enabledInOrder, providerId, ...disabledInOrder]
} else {
newOrder = [...enabledInOrder, providerId, ...disabledInOrder]
}
const existingIds = providers.value.map((p) => p.id)
const missingIds = existingIds.filter((id) => !newOrder.includes(id))
const finalOrder = [...newOrder, ...missingIds]
providerOrder.value = finalOrder
await configP.setSetting('providerOrder', finalOrder)
} catch (error) {
console.error('Failed to optimize provider order:', error)
}
}

const setSearchEngine = async (engineId: string) => {
Expand Down Expand Up @@ -1382,11 +1426,20 @@ export const useSettingsStore = defineStore('settings', () => {
}
}

// 更新 provider 顺序
// 更新 provider 顺序 - 支持分区域拖拽
const updateProvidersOrder = async (newProviders: LLM_PROVIDER[]) => {
try {
// 从新的 provider 数组创建顺序数组
const newOrder = newProviders.map((provider) => provider.id)
const enabledProviders: LLM_PROVIDER[] = []
const disabledProviders: LLM_PROVIDER[] = []
newProviders.forEach((provider) => {
if (provider.enable) {
enabledProviders.push(provider)
} else {
disabledProviders.push(provider)
}
})
const newOrder = [...enabledProviders.map((p) => p.id), ...disabledProviders.map((p) => p.id)]

// 确保所有现有的 provider 都在顺序中
const existingIds = providers.value.map((p) => p.id)
const missingIds = existingIds.filter((id) => !newOrder.includes(id))
Expand Down