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
1 change: 1 addition & 0 deletions desktop-shared/src/main/resources/i18n/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,7 @@ provider.add.new=Add Provider
provider.no.instances.hint=No providers configured yet. Click "Add Provider" to get started.
provider.model.panel.select.hint=Select a provider on the left to browse its models
provider.model.panel.model.count={0} models
provider.model.default.badge=Default
provider.instance.name.label=Instance name
provider.instance.name.placeholder=e.g. "Work OpenAI", "ollama-macbook"
provider.instance.name.duplicate=A provider named "{0}" already exists
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,7 @@ provider.add.new=Anbieter hinzufügen
provider.no.instances.hint=Noch keine Anbieter konfiguriert. Klicken Sie auf "Anbieter hinzufügen", um loszulegen.
provider.model.panel.select.hint=Wählen Sie links einen Anbieter aus, um seine Modelle zu durchsuchen
provider.model.panel.model.count={0} Modelle
provider.model.default.badge=Standard
provider.instance.name.label=Instanzname
provider.instance.name.placeholder=z. B. "Work OpenAI", "ollama-macbook"
provider.instance.name.duplicate=Ein Anbieter mit dem Namen "{0}" existiert bereits
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@ provider.add.new=Añadir proveedor
provider.no.instances.hint=Aún no hay proveedores configurados. Haga clic en "Añadir proveedor" para comenzar.
provider.model.panel.select.hint=Selecciona un proveedor a la izquierda para explorar sus modelos
provider.model.panel.model.count={0} modelos
provider.model.default.badge=Predeterminado
provider.instance.name.label=Nombre de la instancia
provider.instance.name.placeholder=p. ej., "Work OpenAI", "ollama-macbook"
provider.instance.name.duplicate=Ya existe un proveedor llamado "{0}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@ provider.add.new=Ajouter un fournisseur
provider.no.instances.hint=Aucun fournisseur configuré pour l'instant. Cliquez sur "Ajouter un fournisseur" pour commencer.
provider.model.panel.select.hint=Sélectionnez un fournisseur à gauche pour parcourir ses modèles
provider.model.panel.model.count={0} modèles
provider.model.default.badge=Par défaut
provider.instance.name.label=Nom de l'instance
provider.instance.name.placeholder=par ex. "Work OpenAI", "ollama-macbook"
provider.instance.name.duplicate=Un fournisseur nommé "{0}" existe déjà
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@ provider.add.new=プロバイダーを追加
provider.no.instances.hint=プロバイダーがまだ設定されていません。"プロバイダーを追加" をクリックして開始してください。
provider.model.panel.select.hint=左側のプロバイダーを選択してモデルを参照してください
provider.model.panel.model.count={0} モデル
provider.model.default.badge=デフォルト
provider.instance.name.label=インスタンス名
provider.instance.name.placeholder=例: "Work OpenAI", "ollama-macbook"
provider.instance.name.duplicate="{0}" という名前のプロバイダーはすでに存在します
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@ provider.add.new=제공업체 추가
provider.no.instances.hint=구성된 제공업체가 없습니다. 시작하려면 "제공업체 추가"를 클릭하세요.
provider.model.panel.select.hint=왼쪽에서 공급자를 선택하여 모델을 탐색하세요
provider.model.panel.model.count={0}개 모델
provider.model.default.badge=기본
provider.instance.name.label=인스턴스 이름
provider.instance.name.placeholder=예: "Work OpenAI", "ollama-macbook"
provider.instance.name.duplicate="{0}" 이름의 프로바이더가 이미 존재합니다
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,7 @@ provider.add.new=Adicionar provedor
provider.no.instances.hint=Nenhum provedor configurado ainda. Clique em "Adicionar provedor" para começar.
provider.model.panel.select.hint=Selecione um provedor à esquerda para explorar seus modelos
provider.model.panel.model.count={0} modelos
provider.model.default.badge=Padrão
provider.instance.name.label=Nome da instância
provider.instance.name.placeholder=ex: "Work OpenAI", "ollama-macbook"
provider.instance.name.duplicate=Já existe um provedor chamado "{0}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,7 @@ provider.add.new=Thêm nhà cung cấp
provider.no.instances.hint=Chưa có nhà cung cấp nào được cấu hình. Nhấp vào "Thêm nhà cung cấp" để bắt đầu.
provider.model.panel.select.hint=Chọn nhà cung cấp ở bên trái để duyệt các mô hình
provider.model.panel.model.count={0} mô hình
provider.model.default.badge=Mặc định
provider.instance.name.label=Tên phiên bản
provider.instance.name.placeholder=Ví dụ: "Work OpenAI", "ollama-macbook"
provider.instance.name.duplicate=Nhà cung cấp có tên "{0}" đã tồn tại
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@ provider.add.new=添加服务商
provider.no.instances.hint=尚未配置任何服务商。点击 "添加服务商" 以开始。
provider.model.panel.select.hint=在左侧选择一个提供商以浏览其模型
provider.model.panel.model.count={0} 个模型
provider.model.default.badge=默认
provider.instance.name.label=实例名称
provider.instance.name.placeholder=例如 "Work OpenAI", "ollama-macbook"
provider.instance.name.duplicate=名为"{0}"的提供商已存在
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@ provider.add.new=新增服務商
provider.no.instances.hint=尚未設定任何服務商。點擊 "新增服務商" 以開始。
provider.model.panel.select.hint=在左側選擇一個提供商以瀏覽其模型
provider.model.panel.model.count={0} 個模型
provider.model.default.badge=預設
provider.instance.name.label=執行個體名稱
provider.instance.name.placeholder=例如 "Work OpenAI", "ollama-macbook"
provider.instance.name.duplicate=名為「{0}」的提供商已存在
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
Expand Down Expand Up @@ -152,6 +153,11 @@ internal fun providerModelPanel(
}
}

val pendingDefaultModel = remember(state.pendingInstanceId, state.availableInstances) {
state.availableInstances.firstOrNull { it.id == state.pendingInstanceId }
?.settings?.defaultModel ?: ""
}

dropdownMenu(
expanded = expanded,
onDismissRequest = onDismiss,
Expand Down Expand Up @@ -216,7 +222,12 @@ internal fun providerModelPanel(
instance = instance,
isActive = instance.id == currentInstanceId,
isPending = instance.id == state.pendingInstanceId,
onSelect = { state.selectInstanceForPreview(instance.id) },
onSelect = {
state.selectInstanceForPreview(instance.id)
if (instance.settings.defaultModel.isNotBlank()) {
state.commitSelection(instance.id, instance.settings.defaultModel)
}
},
onEditOpen = { state.openEditForm(instance.id) },
onDelete = { state.deleteInstance(instance.id) },
)
Expand Down Expand Up @@ -300,6 +311,7 @@ internal fun providerModelPanel(
state = state,
searchQuery = searchQuery,
filteredModels = filteredModels,
pendingDefaultModel = pendingDefaultModel,
currentInstanceId = currentInstanceId,
currentModel = currentModel,
totalModelCount = pendingModels.size,
Expand All @@ -325,6 +337,7 @@ private fun modelListColumn(
state: ProviderModelPanelState,
searchQuery: String,
filteredModels: List<ModelDTO>,
pendingDefaultModel: String,
currentInstanceId: String,
currentModel: String,
totalModelCount: Int,
Expand Down Expand Up @@ -415,16 +428,30 @@ private fun modelListColumn(

val modelListState = rememberLazyListState()

// Scroll to the active model when first browsing the active instance
// Pin the default model to the top when not actively searching
val pinnedModel = remember(filteredModels, pendingDefaultModel, searchQuery) {
if (pendingDefaultModel.isNotBlank() && searchQuery.isBlank()) {
filteredModels.firstOrNull { it.modelId == pendingDefaultModel }
} else {
null
}
}
val groupedModels = remember(filteredModels, pinnedModel) {
val remaining = if (pinnedModel != null) filteredModels.filter { it.modelId != pinnedModel.modelId } else filteredModels
remaining.groupBy { it.provider }
}

// Scroll to pinned (index 0) or find currentModel in grouped list
val isActiveInstance = state.pendingInstanceId == currentInstanceId
LaunchedEffect(state.pendingInstanceId, filteredModels) {
if (isActiveInstance && currentModel.isNotBlank() && filteredModels.isNotEmpty()) {
val groupedModels = filteredModels.groupBy { it.provider }
if (pinnedModel != null) {
modelListState.scrollToItem(0)
} else if (isActiveInstance && currentModel.isNotBlank() && filteredModels.isNotEmpty()) {
val showHeaders = groupedModels.size > 1
var flatIndex = 0
var found = false
for ((_, providerModels) in groupedModels) {
if (showHeaders) flatIndex++ // header item
if (showHeaders) flatIndex++
for (dto in providerModels) {
if (dto.modelId == currentModel) {
found = true
Expand All @@ -450,8 +477,49 @@ private fun modelListColumn(
}
} else {
LazyColumn(state = modelListState, modifier = Modifier.fillMaxWidth()) {
val groupedModels = filteredModels.groupBy { it.provider }
val showHeaders = groupedModels.size > 1

// ── Pinned default model ──────────────────────────────────────
if (pinnedModel != null) {
item(key = "pinned_${pinnedModel.modelId}") {
val isCurrent = pinnedModel.modelId == currentModel &&
state.pendingInstanceId == currentInstanceId
AppComponents.themedDropdownMenuItem(
text = {
Row(
horizontalArrangement = Arrangement.spacedBy(Spacing.small),
verticalAlignment = Alignment.CenterVertically,
) {
Text(pinnedModel.displayName, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.weight(1f, fill = false))
Box(
modifier = Modifier
.background(
MaterialTheme.colorScheme.tertiaryContainer,
RoundedCornerShape(4.dp),
)
.padding(horizontal = 4.dp, vertical = 1.dp),
) {
Text(
text = stringResource("provider.model.default.badge"),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onTertiaryContainer,
)
}
}
},
onClick = {
state.commitSelection(state.pendingInstanceId, pinnedModel.modelId)
onModelSelected()
},
isSelected = isCurrent,
)
}
item(key = "pinned_divider") {
HorizontalDivider(modifier = Modifier.padding(horizontal = 8.dp))
}
}

// ── Grouped remaining models ──────────────────────────────────
groupedModels.forEach { (provider, providerModels) ->
if (showHeaders && providerModels.isNotEmpty()) {
item(key = "mhdr_${provider.name}") {
Expand Down