feat(#445): full Models tab parity — settings, aux tasks, MOA config

* feat(#445): full Models tab — settings panel, aux tasks, MOA config, model assignment

Main features added:
- Model Settings card at top of screen with main model, aux tasks, MOA summary
- Main model assignment via /api/model/set with expensive model confirm flow
- Auxiliary tasks dialog with per-task model override and reset-all
- MOA config dialog with preset management and reference/aggregator model picker
- Model picker dialog reused for main model, aux tasks, and MOA slot selection
- Kept existing provider browse + pin/unpin/search

New data models: AuxiliaryModelsResponse, MoaConfigResponse,
ModelAssignmentRequest/Response
New API endpoints: GET /api/model/auxiliary, GET/PUT /api/model/moa,
POST /api/model/set

* test(#445): add aux + MOA mock endpoints to E2eIntegrationTest

testModelOptionsSelection_success was failing because the ViewModel's init
block now calls getAuxiliaryModels() and getMoaConfig() which weren't mocked.
Added proper mock returns + imports for the new types.

* fix(#445): correct mock endpoint name in E2eIntegrationTest

- getMoaConfig -> getMoaModels (matches API service method name)
- Add explicit type parameter Response.success<MoaConfigResponse>
  to fix Kotlin type inference

* fix(#445): add missing main param to AuxiliaryModelsResponse mock

AuxiliaryModelsResponse requires a MainModelAssignment for its
'main' field. Added the parameter with a dummy value and the
corresponding import.

* fix(#445): update toast assertion in test to match new message format

ViewModel now produces 'Successfully set profile model to ronia'
instead of the old 'Successfully set model to ronia'.

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-07-03 11:59:32 +03:00
1 parent efb8e528ef
commit dc808daee8
5 files changed
+1674 -397

No files matched your search

@@ -0,0 +1,64 @@
package com.m57.hermescontrol.data.model
data class AuxiliaryTaskAssignment(
val task: String,
val provider: String,
val model: String,
val base_url: String? = null,
)
data class AuxiliaryModelsResponse(
val tasks: List<AuxiliaryTaskAssignment>,
val main: MainModelAssignment,
)
data class MainModelAssignment(
val provider: String,
val model: String,
)
data class MoaModelSlot(
val provider: String,
val model: String,
)
data class MoaConfigPreset(
val reference_models: List<MoaModelSlot>,
val aggregator: MoaModelSlot,
val reference_temperature: Double = 0.7,
val aggregator_temperature: Double = 0.3,
val max_tokens: Int = 4096,
val enabled: Boolean = true,
)
data class MoaConfigResponse(
val default_preset: String = "",
val active_preset: String = "",
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,
val max_tokens: Int = 4096,
val enabled: Boolean = true,
)
data class ModelAssignmentRequest(
val confirm_expensive_model: Boolean = false,
val scope: String,
val provider: String,
val model: String,
val base_url: String? = null,
val task: String? = null,
)
data class ModelAssignmentResponse(
val confirm_message: String? = null,
val confirm_required: Boolean? = null,
val ok: Boolean = false,
val scope: String? = null,
val provider: String? = null,
val model: String? = null,
val tasks: List<String>? = null,
val reset: Boolean? = null,
)
@@ -4,6 +4,7 @@ import com.google.gson.JsonElement
import com.m57.hermescontrol.data.model.AchievementsResponse
import com.m57.hermescontrol.data.model.ActiveProfileResponse
import com.m57.hermescontrol.data.model.AgentPluginInstallBody
import com.m57.hermescontrol.data.model.AuxiliaryModelsResponse
import com.m57.hermescontrol.data.model.ConfigSchemaResponse
import com.m57.hermescontrol.data.model.ConfigUpdateRequest
import com.m57.hermescontrol.data.model.CreateCronJobRequest
@@ -25,6 +26,9 @@ import com.m57.hermescontrol.data.model.McpServersResponse
import com.m57.hermescontrol.data.model.MessagingPlatformResponse
import com.m57.hermescontrol.data.model.MessagingPlatformTestResult
import com.m57.hermescontrol.data.model.MessagingPlatformUpdate
import com.m57.hermescontrol.data.model.MoaConfigResponse
import com.m57.hermescontrol.data.model.ModelAssignmentRequest
import com.m57.hermescontrol.data.model.ModelAssignmentResponse
import com.m57.hermescontrol.data.model.ModelOptionsResponse
import com.m57.hermescontrol.data.model.PairingApproveRequest
import com.m57.hermescontrol.data.model.PairingResponse
@@ -285,6 +289,22 @@ interface HermesApiService {
@GET("api/model/options")
suspend fun getModelOptions(): Response<ModelOptionsResponse>
@GET("api/model/auxiliary")
suspend fun getAuxiliaryModels(): Response<AuxiliaryModelsResponse>
@GET("api/model/moa")
suspend fun getMoaModels(): Response<MoaConfigResponse>
@PUT("api/model/moa")
suspend fun saveMoaModels(
@Body body: MoaConfigResponse,
): Response<MoaConfigResponse>
@POST("api/model/set")
suspend fun setModelAssignment(
@Body body: ModelAssignmentRequest,
): Response<ModelAssignmentResponse>
@GET("api/logs")
suspend fun getLogs(
@Query("limit") limit: Int? = null,