fix(model): MoA row shows 'not loaded' on null temperatures (#1421)

This commit is contained in:
m57 authored and GitHub committed 2026-09-30 22:16:32 +03:00
1 parent a8dd055426
commit 6e14c49df1
3 files changed
+45 -5

No files matched your search

@@ -25,14 +25,20 @@ data class MainModelAssignment(
data class MoaModelSlot(
val provider: String,
val model: String,
val reasoning_effort: String? = null,
val enabled: Boolean = true,
)
@Serializable
data class MoaConfigPreset(
val reference_models: List<MoaModelSlot>,
val aggregator: MoaModelSlot,
val reference_temperature: Double = 0.7,
val aggregator_temperature: Double = 0.3,
// null = omitted from API calls (provider default); the backend sends null when unset.
val reference_temperature: Double? = null,
val aggregator_temperature: Double? = null,
val reference_timeout: Double? = null,
val degraded_reference_policy: String = "loud",
val fanout: String? = null,
val max_tokens: Int = 4096,
val enabled: Boolean = true,
)
@@ -44,8 +50,12 @@ data class MoaConfigResponse(
val presets: Map<String, MoaConfigPreset> = emptyMap(),
val reference_models: List<MoaModelSlot> = emptyList(),
val aggregator: MoaModelSlot = MoaModelSlot("", ""),
val reference_temperature: Double = 0.7,
val aggregator_temperature: Double = 0.3,
// null = omitted from API calls (provider default); the backend sends null when unset.
val reference_temperature: Double? = null,
val aggregator_temperature: Double? = null,
val reference_timeout: Double? = null,
val degraded_reference_policy: String = "loud",
val fanout: String? = null,
val max_tokens: Int = 4096,
val enabled: Boolean = true,
)
@@ -19,6 +19,7 @@ import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
@@ -146,7 +147,7 @@ fun ChatLifecycleEffects(
// Show error as snackbar
val clipboard = LocalClipboard.current
val copyLabel = context.getString(R.string.action_copy_error)
val copyLabel = stringResource(R.string.action_copy_error)
// Own scope + immediate clear: a later `errorMessage = null` elsewhere must not cancel a visible popup.
val snackbarScope = rememberCoroutineScope()
LaunchedEffect(errorMessage) {
@@ -622,4 +622,33 @@ class ModelSerializationTest {
json.decodeFromString<ToggleSkillRequest>(jsonInput)
}
}
@Test
fun moaConfigDecodesNullTemperaturesAndNewFields() {
val payload =
"""
{"default_preset":"default","active_preset":"","privacy_filter":"",
"presets":{"default":{"enabled":true,
"reference_models":[{"provider":"openrouter","model":"x/y","enabled":false,"reasoning_effort":"high"}],
"aggregator":{"provider":"openrouter","model":"a/b"},
"reference_temperature":null,"aggregator_temperature":null,
"reference_timeout":null,"degraded_reference_policy":"silent","fanout":"every_n:3"}},
"reference_models":[{"provider":"openrouter","model":"x/y","enabled":true}],
"aggregator":{"provider":"openrouter","model":"a/b"},
"reference_temperature":null,"aggregator_temperature":null,
"reference_timeout":null,"degraded_reference_policy":"silent","fanout":"every_n:3","enabled":true}
""".trimIndent()
val cfg = json.decodeFromString<MoaConfigResponse>(payload)
assertNull(cfg.reference_temperature)
assertNull(cfg.aggregator_temperature)
assertEquals("every_n:3", cfg.fanout)
val preset = cfg.presets.getValue("default")
assertEquals("silent", preset.degraded_reference_policy)
assertEquals(false, preset.reference_models.first().enabled)
assertEquals("high", preset.reference_models.first().reasoning_effort)
// Round-trip keeps the non-default knobs so a save doesn't reset them.
val back = json.encodeToString(cfg)
assertTrue(back.contains("every_n:3"))
assertTrue(back.contains("silent"))
}
}