fix(jetbrains): render provider descriptions

This commit is contained in:
kirillk
2026-06-14 13:41:39 -04:00
parent 987da27287
commit 887f58ccc7
12 changed files with 52 additions and 11 deletions
+1
View File
@@ -105,6 +105,7 @@ export type Model = Schema.Schema.Type<typeof Model>
export const Provider = Schema.Struct({
api: Schema.optional(Schema.String),
name: Schema.String,
description: Schema.optional(Schema.String), // kilocode_change
env: Schema.Array(Schema.String),
id: Schema.String,
npm: Schema.optional(Schema.String),
@@ -326,6 +326,7 @@ object KiloCliDataParser {
ProviderSettingsProviderDto(
id = item.str("id") ?: "",
name = item.str("name") ?: item.str("id") ?: "",
description = item.str("description"),
source = item.str("source"),
key = item.str("key"),
metadata = parseProviderMetadata(item["metadata"].obj()),
@@ -75,7 +75,7 @@ internal class KiloBackendProviderSettingsManager(
result.providers.forEach { provider ->
val configured = provider.id in result.connected || provider.key != null || provider.source == "config" || provider.id in result.config
LOG.debug {
"provider settings provider: id=${provider.id} source=${provider.source} connected=${provider.id in result.connected} configured=$configured disabled=${provider.id in result.disabled} enabled=${provider.id in result.enabled} hasKey=${provider.key != null} auth=${result.auth[provider.id].orEmpty().map { it.type }.distinct().joinToString(",")} config=${provider.id in result.config} models=${provider.models.size}"
"provider settings provider: id=${provider.id} source=${provider.source} connected=${provider.id in result.connected} configured=$configured disabled=${provider.id in result.disabled} enabled=${provider.id in result.enabled} hasKey=${provider.key != null} auth=${result.auth[provider.id].orEmpty().map { it.type }.distinct().joinToString(",")} config=${provider.id in result.config} models=${provider.models.size} description=${provider.description?.isNotBlank() == true} note=${provider.metadata?.note?.isNotBlank() == true} noteKey=${provider.metadata?.noteKey} icon=${provider.metadata?.icon}"
}
}
LOG.debug { "provider settings state: completed dir=$directory providers=${result.providers.size} connected=${result.connected.size} auth=${result.auth.size} errors=${result.errors.size} durationMs=${System.currentTimeMillis() - start}" }
@@ -1183,6 +1183,7 @@ class KiloCliDataParserTest {
"all": [{
"id": "openai",
"name": "OpenAI",
"description": "Build with OpenAI models",
"source": "api",
"metadata": {
"noteKey": "settings.providers.note.openai",
@@ -1201,6 +1202,7 @@ class KiloCliDataParserTest {
val provider = result.first.single()
assertEquals("settings.providers.note.openai", provider.metadata?.noteKey)
assertEquals("Build with OpenAI models", provider.description)
assertEquals("GPT and Codex models with API key or ChatGPT login", provider.metadata?.note)
assertEquals("openai", provider.metadata?.icon)
assertEquals(listOf("openai"), result.second)
@@ -28,11 +28,10 @@ internal fun popularProviderIndex(id: String): Int {
}
internal fun providerDescription(provider: ProviderSettingsProviderDto): String {
provider.description?.takeIf { it.isNotBlank() }?.let { return it }
provider.metadata?.noteKey?.let { key -> KiloBundle.optional(key)?.let { return it } }
provider.metadata?.note?.let { return it }
val source = provider.source ?: "catalog"
val models = provider.models.size
return "$source · $models models"
return ""
}
internal fun providerIcon(provider: ProviderSettingsProviderDto): Icon {
@@ -3,7 +3,10 @@ package ai.kilocode.client.settings.providers
import ai.kilocode.client.plugin.KiloBundle
import ai.kilocode.client.session.ui.PickerRow
import ai.kilocode.client.ui.UiStyle
import ai.kilocode.client.ui.layout.HAlign
import ai.kilocode.client.ui.layout.Stack
import ai.kilocode.client.ui.layout.VAlign
import ai.kilocode.client.ui.layout.align
import com.intellij.ui.CollectionListModel
import com.intellij.ui.GroupHeaderSeparator
import com.intellij.ui.SimpleColoredComponent
@@ -86,6 +89,7 @@ internal class ProviderListRenderer(
add(sep, BorderLayout.NORTH)
}
private val icon = JBLabel()
private val mark = icon.align(HAlign.CENTER, VAlign.TOP)
private val title = SimpleColoredComponent()
private val desc = JBLabel()
private val text = JPanel(BorderLayout()).apply {
@@ -93,13 +97,13 @@ internal class ProviderListRenderer(
add(desc, BorderLayout.SOUTH)
}
private val actions = Stack.horizontal(JBUI.scale(ACTION_GAP))
private val row = Stack.horizontal(UiStyle.Gap.md()).next(icon).next(text)
private val row = Stack.horizontal(UiStyle.Gap.md()).next(mark).next(text)
private val wrap = PickerRow()
init {
isOpaque = true
top.isOpaque = true
UiStyle.Components.transparent(row, icon, title, text, desc, actions)
UiStyle.Components.transparent(row, mark, icon, title, text, desc, actions)
row.border = JBUI.Borders.empty(
UiStyle.Gap.md(),
UiStyle.Gap.lg(),
@@ -134,7 +138,9 @@ internal class ProviderListRenderer(
title.clear()
title.append(value.provider.name, SimpleTextAttributes(SimpleTextAttributes.STYLE_BOLD, fg))
icon.icon = providerIcon(value.provider)
desc.text = providerDescription(value.provider)
val note = providerDescription(value.provider)
desc.text = note
desc.isVisible = note.isNotEmpty()
desc.foreground = weak
actions.removeAll()
@@ -303,7 +303,8 @@ internal class ProvidersContent(
@RequiresEdt
fun update(state: ProviderSettingsDto) {
checkEdt()
ProvidersSettingsUi.LOG.info("provider settings content update: start providers=${state.providers.size} connected=${state.connected.size} disabled=${state.disabled.size}")
val notes = state.providers.count { providerDescription(it).isNotBlank() }
ProvidersSettingsUi.LOG.info("provider settings content update: start providers=${state.providers.size} connected=${state.connected.size} disabled=${state.disabled.size} descriptions=$notes")
this.state = state
sync()
ProvidersSettingsUi.LOG.info("provider settings content update: completed rows=${model.size}")
@@ -357,10 +357,32 @@ class ProvidersSettingsUiTest : BasePlatformTestCase() {
assertTrue(renderer.providerIconVisible())
assertEquals(Dimension(JBUI.scale(20), JBUI.scale(20)), renderer.providerIconSize())
assertEquals("GPT and Codex models with API key or ChatGPT login", renderer.descriptionText())
assertTrue(renderer.preferredSize.height > JBUI.scale(44))
}
}
fun `test renderer falls back to generic description without metadata`() {
fun `test renderer prefers provider description from cli`() {
edt {
val row = ProviderListRow(
provider(
"openai",
"OpenAI",
description = "Build with OpenAI models",
metadata = ProviderMetadataDto(note = "Fallback metadata note"),
),
"Popular providers",
listOf(ProviderListAction.CONNECT),
)
val list = JBList(listOf(row))
val renderer = ProviderListRenderer(com.intellij.ui.CollectionListModel(listOf(row)))
renderer.getListCellRendererComponent(list, row, 0, true, false)
assertEquals("Build with OpenAI models", renderer.descriptionText())
}
}
fun `test renderer does not invent provider description without metadata`() {
edt {
val row = ProviderListRow(provider("openai", "OpenAI"), "Popular providers", listOf(ProviderListAction.CONNECT))
val list = JBList(listOf(row))
@@ -368,7 +390,7 @@ class ProvidersSettingsUiTest : BasePlatformTestCase() {
renderer.getListCellRendererComponent(list, row, 0, true, false)
assertEquals("catalog · 1 models", renderer.descriptionText())
assertEquals("", renderer.descriptionText())
}
}
@@ -496,11 +518,13 @@ class ProvidersSettingsUiTest : BasePlatformTestCase() {
private fun provider(
id: String,
name: String,
description: String? = null,
source: String? = null,
metadata: ProviderMetadataDto? = null,
) = ProviderSettingsProviderDto(
id = id,
name = name,
description = description,
source = source,
metadata = metadata,
models = mapOf("model" to ModelDto("model", "Model")),
@@ -18,6 +18,7 @@ data class ProviderSettingsDto(
data class ProviderSettingsProviderDto(
val id: String,
val name: String,
val description: String? = null,
val source: String? = null,
val key: String? = null,
val metadata: ProviderMetadataDto? = null,
+3 -1
View File
@@ -964,9 +964,10 @@ export type Model = Types.DeepMutable<Schema.Schema.Type<typeof Model>>
export const Info = Schema.Struct({
id: ProviderID,
name: Schema.String,
description: optionalOmitUndefined(Schema.String), // kilocode_change
source: Schema.Literals(["env", "config", "custom", "api"]),
env: Schema.Array(Schema.String),
key: optionalOmitUndefined(Schema.String),
key: optionalOmitUndefined(Schema.String), // kilocode_change
metadata: optionalOmitUndefined(ProviderMetadata), // kilocode_change
options: Schema.Record(Schema.String, Schema.Any),
models: Schema.Record(Schema.String, Model),
@@ -1163,6 +1164,7 @@ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
id: ProviderID.make(provider.id),
source: "custom",
name: provider.name,
description: provider.description, // kilocode_change
env: [...(provider.env ?? [])],
options: {},
models,
+1
View File
@@ -1522,6 +1522,7 @@ export type Model = {
export type Provider = {
id: string
name: string
description?: string
source: "env" | "config" | "custom" | "api"
env: Array<string>
key?: string
+3
View File
@@ -16481,6 +16481,9 @@
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"source": {
"type": "object",
"properties": {