feat(#441): add profile cloning and description editing (#463)

* feat(#441): add profile cloning and description editing

* feat(#441): add profile builder wizard and API enhancements

Introduce 5-step Profile Builder wizard into ProfilesScreen and add support for create/clone endpoints.

* fix(#441): restore imports and add ProfileInfo constructor defaults

* style: run ktlint format on codebase to fix styling errors

* fix(#441): address PR review comments including provider slug bug, validation, and strings

* fix(#441): remove unused McpServerConfigInput import

---------

Co-authored-by: Hermes Agent <hermes@agent.local>
This commit is contained in:
M57andHermes Agent authored and GitHub committed 2026-07-03 22:24:42 +03:00
1 parent 846a9380e2
commit d181851670
37 files changed
+2299 -448

No files matched your search

@@ -336,7 +336,9 @@ private fun FormEditor(
val isSearching = searchQuery.isNotBlank()
val categoryCounts =
remember(schema) {
schema.fields.values.groupingBy { it.category ?: "general" }.eachCount()
schema.fields.values
.groupingBy { it.category ?: "general" }
.eachCount()
}
// Non-schema config values — all dot-paths in the config that aren't in the schema
@@ -400,17 +402,21 @@ private fun FormEditor(
remember(schema, activeCategory, searchQuery) {
if (isSearching) {
val query = searchQuery.lowercase()
schema.fields.filter { (key, field) ->
val label = key.split(".").last().replace("_", " ")
key.lowercase().contains(query) ||
label.contains(query) ||
(field.description?.lowercase()?.contains(query) == true) ||
(field.category?.lowercase()?.contains(query) == true)
}.entries.map { it.key to it.value }
schema.fields
.filter { (key, field) ->
val label = key.split(".").last().replace("_", " ")
key.lowercase().contains(query) ||
label.contains(query) ||
(field.description?.lowercase()?.contains(query) == true) ||
(field.category?.lowercase()?.contains(query) == true)
}.entries
.map { it.key to it.value }
} else {
schema.fields.filter { (_, field) ->
(field.category ?: "general") == activeCategory
}.entries.map { it.key to it.value }
schema.fields
.filter { (_, field) ->
(field.category ?: "general") == activeCategory
}.entries
.map { it.key to it.value }
}
}
visibleFields.forEach { (key, field) ->
@@ -521,7 +527,12 @@ private fun ConfigField(
isModified: Boolean,
onChange: (JsonElement) -> Unit,
) {
val label = key.split(".").last().replace("_", " ").replaceFirstChar { it.uppercase() }
val label =
key
.split(".")
.last()
.replace("_", " ")
.replaceFirstChar { it.uppercase() }
val description = field.description ?: key.replace(".", " → ").replace("_", " ").replaceFirstChar { it.uppercase() }
Card(
@@ -38,7 +38,9 @@ data class ConfigUiState(
val toastMessage: String? = null,
)
class ConfigViewModel : ViewModel(), ToastHost {
class ConfigViewModel :
ViewModel(),
ToastHost {
private val _uiState = MutableStateFlow(ConfigUiState())
val uiState: StateFlow<ConfigUiState> = _uiState.asStateFlow()
@@ -147,6 +149,7 @@ class ConfigViewModel : ViewModel(), ToastHost {
)
}
}
is NetworkResult.Failure -> {
_uiState.update {
it.copy(
@@ -193,6 +196,7 @@ class ConfigViewModel : ViewModel(), ToastHost {
)
}
}
is NetworkResult.Failure -> {
_uiState.update {
it.copy(
@@ -231,6 +235,7 @@ class ConfigViewModel : ViewModel(), ToastHost {
)
}
}
is NetworkResult.Failure -> {
_uiState.update {
it.copy(