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

@@ -88,14 +88,13 @@ import com.m57.hermescontrol.ui.common.ToastEffect
// ── Helpers ─────────────────────────────────────────────────────────────
private fun formatBytes(bytes: Long): String {
return when {
private fun formatBytes(bytes: Long): String =
when {
bytes < 1024 -> "$bytes B"
bytes < 1024 * 1024 -> "${bytes / 1024} KB"
bytes < 1024 * 1024 * 1024 -> "${bytes / (1024 * 1024)} MB"
else -> "${"%.1f".format(bytes.toDouble() / (1024 * 1024 * 1024))} GB"
}
}
private fun formatDuration(totalSeconds: Double): String {
val days = (totalSeconds / 86400).toInt()
@@ -341,8 +340,7 @@ private fun LazyListScope.hostSection(
stats.os?.let { InfoRow(stringResource(R.string.system_label_os), it) }
stats.arch?.let { InfoRow(stringResource(R.string.system_label_arch), it) }
stats.hostname?.let { InfoRow(stringResource(R.string.system_label_hostname), it) }
stats.python_version?.let {
python ->
stats.python_version?.let { python ->
val impl = stats.python_impl?.let { " ($it)" } ?: ""
InfoRow(stringResource(R.string.system_label_python), "$python$impl")
}
@@ -352,14 +350,21 @@ private fun LazyListScope.hostSection(
val updateBadge =
state.updateInfo?.let { info ->
when {
info.update_available == true && info.behind != null && info.behind > 0 ->
info.update_available == true && info.behind != null && info.behind > 0 -> {
" (${stringResource(
R.string.system_version_update_available,
)}: ${stringResource(R.string.system_version_behind, info.behind)})"
info.update_available == true -> " (${stringResource(
R.string.system_version_update_available,
)})"
else -> " (${stringResource(R.string.system_version_latest)})"
}
info.update_available == true -> {
" (${stringResource(
R.string.system_version_update_available,
)})"
}
else -> {
" (${stringResource(R.string.system_version_latest)})"
}
}
} ?: ""
InfoRow(stringResource(R.string.system_label_hermes_version), "$ver$updateBadge")
@@ -620,10 +625,12 @@ private fun LazyListScope.curatorSection(
statusText = stringResource(R.string.system_status_disabled)
statusType = StatusBadgeType.ERROR
}
curator.paused == true -> {
statusText = stringResource(R.string.system_status_paused)
statusType = StatusBadgeType.WARNING
}
else -> {
statusText = stringResource(R.string.system_status_active)
statusType = StatusBadgeType.SUCCESS
@@ -77,7 +77,9 @@ data class SystemUiState(
val updateConfirmOpen: Boolean = false,
)
class SystemViewModel : ViewModel(), ToastHost {
class SystemViewModel :
ViewModel(),
ToastHost {
companion object {
private const val TAG = "SystemViewModel"
}
@@ -186,6 +188,7 @@ class SystemViewModel : ViewModel(), ToastHost {
_uiState.update { it.copy(toastMessage = "Gateway ${name}ed successfully") }
loadAll()
}
is NetworkResult.Failure -> {
_uiState.update { it.copy(toastMessage = "Failed to $name gateway: ${result.error.message}") }
}
@@ -206,6 +209,7 @@ class SystemViewModel : ViewModel(), ToastHost {
is NetworkResult.Success -> {
_uiState.update { it.copy(checkingUpdate = false, updateInfo = result.data) }
}
is NetworkResult.Failure -> {
_uiState.update {
it.copy(
@@ -251,6 +255,7 @@ class SystemViewModel : ViewModel(), ToastHost {
}
loadAll()
}
is NetworkResult.Failure -> {
_uiState.update { it.copy(toastMessage = "Failed to toggle curator: ${result.error.message}") }
}
@@ -278,6 +283,7 @@ class SystemViewModel : ViewModel(), ToastHost {
_uiState.update { it.copy(toastMessage = "Memory ($target) reset successfully") }
loadAll()
}
is NetworkResult.Failure -> {
_uiState.update { it.copy(toastMessage = "Failed to reset memory: ${result.error.message}") }
}
@@ -329,6 +335,7 @@ class SystemViewModel : ViewModel(), ToastHost {
}
loadAll()
}
is NetworkResult.Failure -> {
_uiState.update {
it.copy(addingCred = false, toastMessage = "Failed to add credential: ${result.error.message}")
@@ -352,6 +359,7 @@ class SystemViewModel : ViewModel(), ToastHost {
_uiState.update { it.copy(toastMessage = "Credential removed") }
loadAll()
}
is NetworkResult.Failure -> {
_uiState.update { it.copy(toastMessage = "Failed to remove credential: ${result.error.message}") }
}
@@ -411,6 +419,7 @@ class SystemViewModel : ViewModel(), ToastHost {
}
loadAll()
}
is NetworkResult.Failure -> {
_uiState.update { it.copy(toastMessage = "Failed to trigger backup: ${result.error.message}") }
}
@@ -433,6 +442,7 @@ class SystemViewModel : ViewModel(), ToastHost {
is NetworkResult.Success -> {
_uiState.update { it.copy(toastMessage = "Backup downloaded") }
}
is NetworkResult.Failure -> {
_uiState.update { it.copy(toastMessage = "Failed to download backup: ${result.error.message}") }
}
@@ -475,6 +485,7 @@ class SystemViewModel : ViewModel(), ToastHost {
)
}
}
is NetworkResult.Failure -> {
_uiState.update {
it.copy(
@@ -556,6 +567,7 @@ class SystemViewModel : ViewModel(), ToastHost {
}
loadAll()
}
is NetworkResult.Failure -> {
_uiState.update {
it.copy(
@@ -582,6 +594,7 @@ class SystemViewModel : ViewModel(), ToastHost {
_uiState.update { it.copy(toastMessage = "Hook deleted") }
loadAll()
}
is NetworkResult.Failure -> {
_uiState.update { it.copy(toastMessage = "Failed to delete hook: ${result.error.message}") }
}
@@ -631,6 +644,7 @@ class SystemViewModel : ViewModel(), ToastHost {
result.data?.name?.let { pollActionStatus(it) }
_uiState.update { it.copy(toastMessage = "$label started") }
}
is NetworkResult.Failure -> {
_uiState.update { it.copy(toastMessage = "$label failed: ${result.error.message}") }
}