From 83de67301aca607babe7e42b9bdb6a9fac72cf9a Mon Sep 17 00:00:00 2001 From: Hai Nguyen <3423575+haiphucnguyen@users.noreply.github.com> Date: Sun, 26 Jul 2026 18:54:46 -0700 Subject: [PATCH] Show default model om the top of list selection, and auto select --- .../main/resources/i18n/messages.properties | 1 + .../resources/i18n/messages_de.properties | 1 + .../resources/i18n/messages_es.properties | 1 + .../resources/i18n/messages_fr.properties | 1 + .../resources/i18n/messages_ja_JP.properties | 1 + .../resources/i18n/messages_ko_KR.properties | 1 + .../resources/i18n/messages_pt_BR.properties | 1 + .../resources/i18n/messages_vi_VN.properties | 1 + .../resources/i18n/messages_zh_CN.properties | 1 + .../resources/i18n/messages_zh_TW.properties | 1 + .../desktop/shell/ProviderModelPanel.kt | 80 +++++++++++++++++-- 11 files changed, 84 insertions(+), 6 deletions(-) diff --git a/desktop-shared/src/main/resources/i18n/messages.properties b/desktop-shared/src/main/resources/i18n/messages.properties index 3f6bd4409..55ad21690 100644 --- a/desktop-shared/src/main/resources/i18n/messages.properties +++ b/desktop-shared/src/main/resources/i18n/messages.properties @@ -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 diff --git a/desktop-shared/src/main/resources/i18n/messages_de.properties b/desktop-shared/src/main/resources/i18n/messages_de.properties index 2641ddcf5..cc7d54330 100644 --- a/desktop-shared/src/main/resources/i18n/messages_de.properties +++ b/desktop-shared/src/main/resources/i18n/messages_de.properties @@ -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 diff --git a/desktop-shared/src/main/resources/i18n/messages_es.properties b/desktop-shared/src/main/resources/i18n/messages_es.properties index 097dde212..0b91f5caf 100644 --- a/desktop-shared/src/main/resources/i18n/messages_es.properties +++ b/desktop-shared/src/main/resources/i18n/messages_es.properties @@ -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}" diff --git a/desktop-shared/src/main/resources/i18n/messages_fr.properties b/desktop-shared/src/main/resources/i18n/messages_fr.properties index 159aba040..31534fa25 100644 --- a/desktop-shared/src/main/resources/i18n/messages_fr.properties +++ b/desktop-shared/src/main/resources/i18n/messages_fr.properties @@ -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à diff --git a/desktop-shared/src/main/resources/i18n/messages_ja_JP.properties b/desktop-shared/src/main/resources/i18n/messages_ja_JP.properties index 3fb2ac4b9..d8226712a 100644 --- a/desktop-shared/src/main/resources/i18n/messages_ja_JP.properties +++ b/desktop-shared/src/main/resources/i18n/messages_ja_JP.properties @@ -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}" という名前のプロバイダーはすでに存在します diff --git a/desktop-shared/src/main/resources/i18n/messages_ko_KR.properties b/desktop-shared/src/main/resources/i18n/messages_ko_KR.properties index eb1f795f9..c63a15718 100644 --- a/desktop-shared/src/main/resources/i18n/messages_ko_KR.properties +++ b/desktop-shared/src/main/resources/i18n/messages_ko_KR.properties @@ -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}" 이름의 프로바이더가 이미 존재합니다 diff --git a/desktop-shared/src/main/resources/i18n/messages_pt_BR.properties b/desktop-shared/src/main/resources/i18n/messages_pt_BR.properties index 8ec92557e..72821dd03 100644 --- a/desktop-shared/src/main/resources/i18n/messages_pt_BR.properties +++ b/desktop-shared/src/main/resources/i18n/messages_pt_BR.properties @@ -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}" diff --git a/desktop-shared/src/main/resources/i18n/messages_vi_VN.properties b/desktop-shared/src/main/resources/i18n/messages_vi_VN.properties index bd3078ef0..931c73872 100644 --- a/desktop-shared/src/main/resources/i18n/messages_vi_VN.properties +++ b/desktop-shared/src/main/resources/i18n/messages_vi_VN.properties @@ -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 diff --git a/desktop-shared/src/main/resources/i18n/messages_zh_CN.properties b/desktop-shared/src/main/resources/i18n/messages_zh_CN.properties index 4b096b2e8..1df225e0d 100644 --- a/desktop-shared/src/main/resources/i18n/messages_zh_CN.properties +++ b/desktop-shared/src/main/resources/i18n/messages_zh_CN.properties @@ -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}"的提供商已存在 diff --git a/desktop-shared/src/main/resources/i18n/messages_zh_TW.properties b/desktop-shared/src/main/resources/i18n/messages_zh_TW.properties index afb09f7c8..1b9b07eee 100644 --- a/desktop-shared/src/main/resources/i18n/messages_zh_TW.properties +++ b/desktop-shared/src/main/resources/i18n/messages_zh_TW.properties @@ -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}」的提供商已存在 diff --git a/desktop/src/main/kotlin/io/askimo/desktop/shell/ProviderModelPanel.kt b/desktop/src/main/kotlin/io/askimo/desktop/shell/ProviderModelPanel.kt index 2677a8cd3..31df11d73 100644 --- a/desktop/src/main/kotlin/io/askimo/desktop/shell/ProviderModelPanel.kt +++ b/desktop/src/main/kotlin/io/askimo/desktop/shell/ProviderModelPanel.kt @@ -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 @@ -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, @@ -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) }, ) @@ -300,6 +311,7 @@ internal fun providerModelPanel( state = state, searchQuery = searchQuery, filteredModels = filteredModels, + pendingDefaultModel = pendingDefaultModel, currentInstanceId = currentInstanceId, currentModel = currentModel, totalModelCount = pendingModels.size, @@ -325,6 +337,7 @@ private fun modelListColumn( state: ProviderModelPanelState, searchQuery: String, filteredModels: List, + pendingDefaultModel: String, currentInstanceId: String, currentModel: String, totalModelCount: Int, @@ -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 @@ -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}") {