Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,10 @@ class ReadToolCardIntegrationTest {
messageLimit = 200,
repository = repository,
workspaceDirectory = null,
completedTurnActivities = emptyList(),
onLoadMore = {},
onFileClick = {},
onMarkdownLinkClick = {},
onForkFromMessage = {},
onEditFromMessage = {}
)
Expand Down
16 changes: 8 additions & 8 deletions app/src/main/java/com/yage/opencode_client/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -365,11 +365,11 @@ private fun TabletLayout(viewModel: MainViewModel) {
val filesWeight = if (sessionsPaneCollapsed) 0.5f else 0.375f
val chatWeight = if (sessionsPaneCollapsed) 0.5f else 0.375f

Row(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.statusBars)
) {
Row(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.statusBars)
) {
// Left panel: Session list or Settings — 25% when expanded.
if (!sessionsPaneCollapsed) {
Column(
Expand Down Expand Up @@ -446,9 +446,9 @@ private fun TabletLayout(viewModel: MainViewModel) {
Icons.AutoMirrored.Filled.KeyboardArrowRight,
contentDescription = stringResource(R.string.sessions_show)
)
}
}
}
}
}
}
}
}
}
Expand Down
125 changes: 83 additions & 42 deletions app/src/main/java/com/yage/opencode_client/ui/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ data class AppState(
val isLoadingMessages: Boolean = false,
val agents: List<AgentInfo> = emptyList(),
val selectedAgentName: String = "build",
val selectedModelIndex: Int = 2,
val selectedModel: Message.ModelInfo? = null,
val availableModels: List<ModelOption> = emptyList(),
val providerModelsIndex: Map<String, ProviderModel> = emptyMap(),
val providers: ProvidersResponse? = null,
val pendingPermissions: List<PermissionRequest> = emptyList(),
val pendingQuestions: List<QuestionRequest> = emptyList(),
Expand Down Expand Up @@ -110,20 +112,25 @@ data class AppState(
val aiUsageError: String? = null
) {
data class NfcPendingAction(val prompt: String, val autoSend: Boolean)
data class ModelOption(val displayName: String, val providerId: String, val modelId: String) {
data class ModelOption(
val displayName: String,
val providerId: String,
val modelId: String,
val modelLabel: String = displayName.substringBefore(" (")
) {
val shortName: String
get() = when {
displayName == "DeepSeek V4 Flash" -> "DS-Flash"
displayName == "DeepSeek Local" -> "DS-L"
displayName == "DeepSeek V4 Pro" -> "DS-Pro"
displayName == "Ollama GLM 5.2" -> "OGLM-5.2"
displayName == "GPT-5.6 Sol Fast" -> "GPT-F"
displayName == "GPT-5.6 Terra Fast" -> "GPT-TF"
"Haiku" in displayName -> "Haiku"
"Gemini" in displayName -> "Gemini"
"GPT" in displayName -> "GPT"
"Grok" in displayName -> "Grok"
else -> displayName.split(" ").firstOrNull() ?: displayName
modelLabel == "GPT-5.6 Sol Pro" -> "GPT-P"
modelLabel == "GPT-5.6 Sol Fast" -> "GPT-F"
"Haiku" in modelLabel -> "Haiku"
"Sonnet" in modelLabel -> "Sonnet"
"Opus" in modelLabel -> "Opus"
"Gemini" in modelLabel -> "Gemini"
"GPT" in modelLabel -> modelLabel.split(" ").firstOrNull() ?: modelLabel
"Grok" in modelLabel -> "Grok"
"DeepSeek" in modelLabel -> "DeepSeek"
"GLM" in modelLabel -> "GLM"
else -> modelLabel.split(" ").firstOrNull() ?: modelLabel
}
}

Expand Down Expand Up @@ -202,9 +209,9 @@ data class AppState(
val error: String? = null,
val themeMode: ThemeMode = ThemeMode.SYSTEM,
val languageMode: LanguageMode = LanguageMode.SYSTEM,
val selectedModelIndex: Int = 2,
val selectedModelIndex: Int = 0,
val selectedAgentName: String = "build",
val availableModels: List<ModelOption> = ModelPresets.list,
val availableModels: List<ModelOption> = emptyList(),
val contextUsage: ContextUsage? = null,
val agents: List<AgentInfo> = emptyList(),
val providers: ProvidersResponse? = null,
Expand Down Expand Up @@ -296,13 +303,12 @@ data class AppState(
val visibleAgents: List<AgentInfo>
get() = agents.filter { it.isVisible }

/** Curated model list (filtered like iOS), not the full API response. */
val availableModels: List<ModelOption>
get() = ModelPresets.list
val selectedModelIndex: Int
get() = modelIndexFor(availableModels, selectedModel) ?: 0

val selectedAIUsageQuota: AIUsageQuota?
get() {
val provider = when (availableModels.getOrNull(selectedModelIndex)?.providerId) {
val provider = when (selectedModel?.providerId) {
"openai" -> "codex"
"zai-coding-plan" -> "glm"
"ollama-cloud" -> "ollama"
Expand All @@ -313,19 +319,6 @@ data class AppState(
}
}

private val providerModelsIndex: Map<String, ProviderModel>
get() = providers?.providers?.flatMap { provider ->
provider.models.flatMap { (modelKey, model) ->
listOfNotNull(
"${provider.id}/$modelKey" to model,
model.id.takeIf { it.isNotEmpty() }?.let { "${provider.id}/$it" to model },
model.resolvedProviderId?.let { resolvedProvider ->
model.id.takeIf { it.isNotEmpty() }?.let { modelId -> "$resolvedProvider/$modelId" to model }
}
)
}
}?.toMap() ?: emptyMap()

val contextUsage: ContextUsage?
get() {
val lastAssistant = messages.lastOrNull { it.info.isAssistant && tokenTotal(it.info.tokens) != null }
Expand Down Expand Up @@ -408,6 +401,8 @@ class MainViewModel @Inject constructor(
private var hostRuntimeJob = SupervisorJob(viewModelScope.coroutineContext[Job])
private val hostRuntimeScope: CoroutineScope
get() = CoroutineScope(viewModelScope.coroutineContext + hostRuntimeJob)
private var modelCatalogRequestGeneration = 0L
private var configuredRepositoryTarget: RepositoryTarget? = null

init {
loadSettings()
Expand All @@ -422,7 +417,7 @@ class MainViewModel @Inject constructor(
settingsManager.serverUrl = url
settingsManager.username = username
settingsManager.password = password
repository.configure(url, username, password)
configureRepository(url, username, password)
}

fun getHostProfiles(): List<HostProfile> = hostProfileStore.profiles()
Expand Down Expand Up @@ -500,7 +495,7 @@ class MainViewModel @Inject constructor(
hostRuntimeScope.launch { configureRepositoryForProfileAsync(profile) }
return
}
repository.configure(profile.serverUrl, profile.basicAuth?.username, password)
configureRepository(profile.serverUrl, profile.basicAuth?.username, password)
}

private suspend fun configureRepositoryForProfileAsync(profile: HostProfile): Boolean {
Expand Down Expand Up @@ -528,10 +523,42 @@ class MainViewModel @Inject constructor(
}
}
}
repository.configure(baseUrl, profile.basicAuth?.username, password)
configureRepository(baseUrl, profile.basicAuth?.username, password)
return true
}

private data class RepositoryTarget(
val baseUrl: String,
val username: String?,
val password: String?
)

/**
* Points the repository at [baseUrl], clearing the model catalog only when the
* effective target actually changes so same-profile reconnects (e.g. the
* lifecycle-driven health check) keep the loaded catalog and selection.
*/
private fun configureRepository(baseUrl: String, username: String?, password: String?) {
val target = RepositoryTarget(baseUrl, username, password)
if (configuredRepositoryTarget != target) {
configuredRepositoryTarget = target
resetModelCatalogForCurrentProfile()
}
repository.configure(baseUrl, username, password)
}

private fun resetModelCatalogForCurrentProfile() {
modelCatalogRequestGeneration += 1
_state.update {
it.copy(
providers = null,
availableModels = emptyList(),
providerModelsIndex = emptyMap(),
selectedModel = null
)
}
}

fun getSavedConnectionSettings(): ConnectionFormSettings = ConnectionFormSettings(
serverUrl = settingsManager.serverUrl,
username = settingsManager.username ?: "",
Expand Down Expand Up @@ -1032,6 +1059,9 @@ class MainViewModel @Inject constructor(
sessionSendTimestamps = emptyMap(),
agents = emptyList(),
providers = null,
availableModels = emptyList(),
providerModelsIndex = emptyMap(),
selectedModel = null,
filePathToShowInFiles = null,
filePreviewOriginRoute = null,
pendingNfcAction = null,
Expand Down Expand Up @@ -1075,9 +1105,14 @@ class MainViewModel @Inject constructor(
}

private fun loadProviders() {
launchLoadProviders(hostRuntimeScope, repository, _state) { message, error ->
reportNonFatalIssue(TAG, message, error)
}
val requestGeneration = ++modelCatalogRequestGeneration
launchLoadProviders(
scope = hostRuntimeScope,
repository = repository,
state = _state,
settingsManager = settingsManager,
shouldApply = { requestGeneration == modelCatalogRequestGeneration }
) { message, error -> reportNonFatalIssue(TAG, message, error) }
}

fun createSession(title: String? = null) {
Expand Down Expand Up @@ -1263,10 +1298,16 @@ class MainViewModel @Inject constructor(
}

fun selectModel(index: Int) {
val clamped = index.coerceIn(0, ModelPresets.list.size - 1)
settingsManager.selectedModelIndex = clamped
_state.update { it.copy(selectedModelIndex = clamped) }
_state.value.currentSessionId?.let { settingsManager.setModelForSession(it, clamped) }
val models = _state.value.availableModels
if (models.isEmpty()) return
val clamped = index.coerceIn(0, models.size - 1)
val selected = models[clamped]
settingsManager.setSelectedModel(selected.providerId, selected.modelId)
val selectedModel = Message.ModelInfo(selected.providerId, selected.modelId)
_state.update { it.copy(selectedModel = selectedModel) }
_state.value.currentSessionId?.let {
settingsManager.setModelForSession(it, selected.providerId, selected.modelId)
}
}

fun setThemeMode(mode: ThemeMode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,11 @@ internal fun applySavedSettings(
password = password
)

val savedModelIndex = settingsManager.selectedModelIndex
val clampedModelIndex = savedModelIndex.coerceIn(0, ModelPresets.list.size - 1)
if (clampedModelIndex != savedModelIndex) {
settingsManager.selectedModelIndex = clampedModelIndex
}

state.update {
it.copy(
currentSessionId = settingsManager.currentSessionId,
hostProfiles = hostProfileStore.profiles(),
currentHostProfileId = currentProfile.id,
selectedModelIndex = clampedModelIndex,
selectedAgentName = settingsManager.selectedAgentName ?: "build",
themeMode = settingsManager.themeMode,
languageMode = settingsManager.languageMode
Expand Down
Loading