fix(kilo-jetbrains): show sign-in banner in models settings

This commit is contained in:
kirillk
2026-06-01 18:23:32 -04:00
parent 253f8351ec
commit 910f4a6c3d
4 changed files with 73 additions and 3 deletions
@@ -70,8 +70,10 @@ internal fun modelsStatus(
if (saving) return ModelsStatus.SAVING
if (!ready) return ModelsStatus.UNAVAILABLE
if (loading) return ModelsStatus.LOADING
if (providers == null) return if (errors.isNotEmpty()) ModelsStatus.LOAD_FAILED else ModelsStatus.LOADING
if (providers == null) return ModelsStatus.LOAD_FAILED
if (items == 0) return ModelsStatus.NO_PROVIDERS
if (errors.any { it.resource == "agents" }) return ModelsStatus.MODES_FAILED
return ModelsStatus.READY
}
internal fun modelsLoginBannerVisible(ready: Boolean, authenticated: Boolean): Boolean = ready && !authenticated
@@ -6,6 +6,7 @@ import ai.kilocode.client.plugin.KiloBundle
import ai.kilocode.client.session.ui.ReasoningPicker
import ai.kilocode.client.session.ui.model.ModelPicker
import ai.kilocode.client.session.ui.model.ModelText
import ai.kilocode.client.settings.profile.UserProfileConfigurable
import ai.kilocode.client.settings.profile.edt
import ai.kilocode.client.settings.ui.SettingsRow
import ai.kilocode.client.settings.ui.SettingsRows
@@ -20,8 +21,16 @@ import ai.kilocode.rpc.dto.KiloAppStatusDto
import ai.kilocode.rpc.dto.LoadErrorDto
import ai.kilocode.rpc.dto.ModelsWorkspaceDto
import ai.kilocode.rpc.dto.ProvidersDto
import com.intellij.ide.DataManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.options.ConfigurableWithId
import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.options.ex.Settings
import com.intellij.openapi.project.ProjectManager
import com.intellij.ui.EditorNotificationPanel
import com.intellij.ui.InlineBanner
import com.intellij.ui.TitledSeparator
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBScrollPane
@@ -33,6 +42,8 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.awt.BorderLayout
import java.util.function.Predicate
import javax.swing.JComponent
import javax.swing.JPanel
internal class ModelsSettingsUi(
@@ -43,6 +54,10 @@ internal class ModelsSettingsUi(
) : JPanel(BorderLayout()) {
private val status = JBLabel(KiloBundle.message("settings.models.loading"))
private val banner = InlineBanner(
KiloBundle.message("settings.models.login.message"),
EditorNotificationPanel.Status.Warning,
).showCloseButton(false)
private val content = Stack.vertical()
private val rows = SettingsRows()
private val modes = SettingsRows()
@@ -76,6 +91,9 @@ internal class ModelsSettingsUi(
picker.picker.onFavoriteToggle = { app.toggleModelFavorite(it.provider, it.id) }
}
banner.addAction(KiloBundle.message("settings.models.login.action"), Runnable { openProfile(banner) })
content.next(banner)
content.gap(UiStyle.Gap.md())
content.next(status)
content.gap(UiStyle.Gap.lg())
content.next(TitledSeparator(KiloBundle.message("settings.models.displayName")))
@@ -240,15 +258,20 @@ internal class ModelsSettingsUi(
checkEdt()
allItems = items(false)
val smallItems = items(true)
val hasDir = dir != null || directory != null
val state = modelsStatus(
ready = appState.status == KiloAppStatusDto.READY,
loading = loading || (appState.status == KiloAppStatusDto.READY && !loaded && dir != null),
ready = appState.status == KiloAppStatusDto.READY && hasDir,
loading = loading || (appState.status == KiloAppStatusDto.READY && !loaded && hasDir),
providers = providers,
items = allItems.size,
errors = errors,
saving = saving,
)
val ready = state == ModelsStatus.READY || state == ModelsStatus.MODES_FAILED
banner.isVisible = modelsLoginBannerVisible(
ready = appState.status == KiloAppStatusDto.READY,
authenticated = appState.profile != null,
)
if (!saving) {
when (state) {
ModelsStatus.UNAVAILABLE -> {
@@ -348,6 +371,26 @@ internal class ModelsSettingsUi(
update { copy(subagent = item.key, variant = variant) }
}
private fun openProfile(src: JComponent) {
val settings = Settings.KEY.getData(DataManager.getInstance().getDataContext(src))
if (settings != null) {
val cfg = settings.find(UserProfileConfigurable.ID)
if (cfg != null) {
settings.select(cfg)
return
}
}
val project = ProjectManager.getInstance().openProjects.firstOrNull { !it.isDefault }
ShowSettingsUtil.getInstance().showSettingsDialog(
project,
Predicate { cfg: Configurable ->
cfg is ConfigurableWithId && cfg.getId() == UserProfileConfigurable.ID
},
{ cfg: Configurable -> cfg.focusOn(UserProfileConfigurable.FOCUS_ACCOUNT_COMBO) },
)
}
@RequiresEdt
private fun update(fn: ModelsDraft.() -> ModelsDraft) {
checkEdt()
@@ -207,6 +207,8 @@ settings.models.unavailable=Models are unavailable until Kilo finishes loading.
settings.models.load.failed=Failed to load model settings
settings.models.noProviders=No providers available
settings.models.modes.failed=Failed to load per-mode settings
settings.models.login.message=Sign in to Kilo Code to access account-backed models and manage billing.
settings.models.login.action=Open User Profile
settings.models.save.failed=Failed to save model settings
settings.models.save.pending=Saving model settings...
settings.models.reset=Reset
@@ -7,7 +7,9 @@ import ai.kilocode.rpc.dto.LoadErrorDto
import ai.kilocode.rpc.dto.ProvidersDto
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue
class ModelsSettingsStateTest {
@@ -96,6 +98,20 @@ class ModelsSettingsStateTest {
assertEquals(ModelsStatus.LOAD_FAILED, status)
}
@Test
fun `models status reports missing providers after loading completes`() {
val status = modelsStatus(
ready = true,
loading = false,
providers = null,
items = 0,
errors = emptyList(),
saving = false,
)
assertEquals(ModelsStatus.LOAD_FAILED, status)
}
@Test
fun `models status reports app unavailable`() {
val status = modelsStatus(
@@ -109,4 +125,11 @@ class ModelsSettingsStateTest {
assertEquals(ModelsStatus.UNAVAILABLE, status)
}
@Test
fun `login banner shows only when ready and unauthenticated`() {
assertTrue(modelsLoginBannerVisible(ready = true, authenticated = false))
assertFalse(modelsLoginBannerVisible(ready = true, authenticated = true))
assertFalse(modelsLoginBannerVisible(ready = false, authenticated = false))
}
}