diff --git a/app/src/main/java/com/m57/hermescontrol/ui/bots/group/GroupChatScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/bots/group/GroupChatScreen.kt
index 9900a1a0..424b9160 100644
--- a/app/src/main/java/com/m57/hermescontrol/ui/bots/group/GroupChatScreen.kt
+++ b/app/src/main/java/com/m57/hermescontrol/ui/bots/group/GroupChatScreen.kt
@@ -42,8 +42,10 @@ import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.Stop
import androidx.compose.material.icons.filled.Terminal
+import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -549,6 +551,11 @@ private fun GroupChatSettingsDialog(
var maxMessages by remember(currentMaxMessages) { mutableStateOf(currentMaxMessages.toFloat()) }
var maxPasses by remember(currentMaxPasses) { mutableStateOf(currentMaxPasses.toFloat()) }
var systemPrompt by remember(currentSystemPrompt) { mutableStateOf(currentSystemPrompt.orEmpty()) }
+ var showHighLimitsConfirmation by remember { mutableStateOf(false) }
+
+ val isHighLimit =
+ maxMessages.roundToInt() > WARN_MAX_BOT_MESSAGES ||
+ maxPasses.roundToInt() > WARN_MAX_CONTINUATION_PASSES
AlertDialog(
onDismissRequest = onDismiss,
@@ -579,7 +586,12 @@ private fun GroupChatSettingsDialog(
Text(
text = "${maxMessages.roundToInt()}",
style = MaterialTheme.typography.labelLarge,
- color = MaterialTheme.colorScheme.primary,
+ color =
+ if (maxMessages.roundToInt() > WARN_MAX_BOT_MESSAGES) {
+ MaterialTheme.colorScheme.error
+ } else {
+ MaterialTheme.colorScheme.primary
+ },
fontWeight = FontWeight.Bold,
)
}
@@ -595,8 +607,8 @@ private fun GroupChatSettingsDialog(
Slider(
value = maxMessages,
onValueChange = { maxMessages = it },
- valueRange = 1f..20f,
- steps = 18,
+ valueRange = 1f..MAX_ALLOWED_BOT_MESSAGES.toFloat(),
+ steps = MAX_ALLOWED_BOT_MESSAGES - 2,
modifier = Modifier.testTag("max_messages_slider"),
)
}
@@ -616,7 +628,12 @@ private fun GroupChatSettingsDialog(
Text(
text = "${maxPasses.roundToInt()}",
style = MaterialTheme.typography.labelLarge,
- color = MaterialTheme.colorScheme.primary,
+ color =
+ if (maxPasses.roundToInt() > WARN_MAX_CONTINUATION_PASSES) {
+ MaterialTheme.colorScheme.error
+ } else {
+ MaterialTheme.colorScheme.primary
+ },
fontWeight = FontWeight.Bold,
)
}
@@ -632,12 +649,52 @@ private fun GroupChatSettingsDialog(
Slider(
value = maxPasses,
onValueChange = { maxPasses = it },
- valueRange = 0f..6f,
- steps = 5,
+ valueRange = 0f..MAX_ALLOWED_CONTINUATION_PASSES.toFloat(),
+ steps = MAX_ALLOWED_CONTINUATION_PASSES - 1,
modifier = Modifier.testTag("max_handoffs_slider"),
)
}
+ // Live In-Dialog Warning Banner (Option C)
+ AnimatedVisibility(
+ visible = isHighLimit,
+ enter = fadeIn() + expandVertically(),
+ exit = fadeOut() + shrinkVertically(),
+ ) {
+ Surface(
+ color = MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.35f),
+ contentColor = MaterialTheme.colorScheme.onErrorContainer,
+ shape = RoundedCornerShape(12.dp),
+ border = BorderStroke(1.dp, MaterialTheme.colorScheme.error.copy(alpha = 0.5f)),
+ modifier =
+ Modifier
+ .fillMaxWidth()
+ .testTag("high_limits_warning_banner"),
+ ) {
+ Row(
+ modifier = Modifier.padding(12.dp),
+ verticalAlignment = Alignment.Top,
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ ) {
+ Icon(
+ imageVector = Icons.Filled.Warning,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.error,
+ modifier = Modifier.size(20.dp),
+ )
+ Text(
+ text =
+ stringResource(
+ R.string.group_chat_settings_high_limits_warning_desc,
+ maxMessages.roundToInt(),
+ maxPasses.roundToInt(),
+ ),
+ style = MaterialTheme.typography.bodySmall,
+ )
+ }
+ }
+ }
+
// Room Instructions / System Prompt
Column {
Text(
@@ -674,11 +731,15 @@ private fun GroupChatSettingsDialog(
confirmButton = {
Button(
onClick = {
- onSave(
- maxMessages.roundToInt(),
- maxPasses.roundToInt(),
- systemPrompt.trim().ifBlank { null },
- )
+ if (isHighLimit) {
+ showHighLimitsConfirmation = true
+ } else {
+ onSave(
+ maxMessages.roundToInt(),
+ maxPasses.roundToInt(),
+ systemPrompt.trim().ifBlank { null },
+ )
+ }
},
modifier = Modifier.testTag("save_settings_button"),
) {
@@ -702,6 +763,65 @@ private fun GroupChatSettingsDialog(
}
},
)
+
+ if (showHighLimitsConfirmation) {
+ AlertDialog(
+ onDismissRequest = { showHighLimitsConfirmation = false },
+ icon = {
+ Icon(
+ imageVector = Icons.Filled.Warning,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.error,
+ )
+ },
+ title = {
+ Text(
+ text = stringResource(R.string.group_chat_settings_high_limits_warning_title),
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.Bold,
+ )
+ },
+ text = {
+ Text(
+ text =
+ stringResource(
+ R.string.group_chat_settings_high_limits_warning_desc,
+ maxMessages.roundToInt(),
+ maxPasses.roundToInt(),
+ ),
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ },
+ confirmButton = {
+ Button(
+ onClick = {
+ showHighLimitsConfirmation = false
+ onSave(
+ maxMessages.roundToInt(),
+ maxPasses.roundToInt(),
+ systemPrompt.trim().ifBlank { null },
+ )
+ },
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.error,
+ contentColor = MaterialTheme.colorScheme.onError,
+ ),
+ modifier = Modifier.testTag("confirm_high_limits_button"),
+ ) {
+ Text(stringResource(R.string.group_chat_settings_high_limits_confirm))
+ }
+ },
+ dismissButton = {
+ TextButton(
+ onClick = { showHighLimitsConfirmation = false },
+ modifier = Modifier.testTag("dismiss_high_limits_button"),
+ ) {
+ Text(stringResource(R.string.group_chat_settings_high_limits_adjust))
+ }
+ },
+ )
+ }
}
@Composable
diff --git a/app/src/main/java/com/m57/hermescontrol/ui/bots/group/GroupChatViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/bots/group/GroupChatViewModel.kt
index 4b0157b7..22d407f2 100644
--- a/app/src/main/java/com/m57/hermescontrol/ui/bots/group/GroupChatViewModel.kt
+++ b/app/src/main/java/com/m57/hermescontrol/ui/bots/group/GroupChatViewModel.kt
@@ -52,6 +52,10 @@ data class MemberSession(
const val DEFAULT_MAX_BOT_MESSAGES = 6
const val DEFAULT_MAX_CONTINUATION_PASSES = 2
+const val MAX_ALLOWED_BOT_MESSAGES = 50
+const val MAX_ALLOWED_CONTINUATION_PASSES = 20
+const val WARN_MAX_BOT_MESSAGES = 20
+const val WARN_MAX_CONTINUATION_PASSES = 6
private const val LEASE_STEP_TTL_MS = 45_000L
private const val TURN_TIMEOUT_MS = 45_000L
private const val HISTORY_LIMIT = 10
@@ -424,8 +428,8 @@ class GroupChatViewModel(
maxPasses: Int,
systemPrompt: String? = null,
) {
- val clampedMessages = maxMessages.coerceIn(1, 20)
- val clampedPasses = maxPasses.coerceIn(0, 10)
+ val clampedMessages = maxMessages.coerceIn(1, MAX_ALLOWED_BOT_MESSAGES)
+ val clampedPasses = maxPasses.coerceIn(0, MAX_ALLOWED_CONTINUATION_PASSES)
val cleanPrompt = systemPrompt?.trim()?.ifBlank { null }
_uiState.update {
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 03f09da1..6a82b593 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -1181,6 +1181,10 @@
تعليمات / موجه الغرفة
قواعد مخصصة، أو أسلوب، أو سياق لهذه المجموعة (اختياري)…
إرشادات مخصصة لجميع البوتات في هذه الغرفة
+تحذير من الحدود المرتفعة
+قد يؤدي تعيين حدود مرتفعة للرسائل (%1$d) أو التمريرات (%2$d) إلى استهلاك كبير للرموز (Tokens) وحدوث حلقات نقاش طويلة ومستمرة بين البوتات.
+حفظ على أي حال
+تعديل الحدود
الإعدادات
تم إيقاف النقاش
إيقاف التوليد
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index a1e47513..9dd5cf74 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -863,6 +863,10 @@
방 지침 / 프롬프트
이 그룹을 위한 사용자 지정 규칙, 어조 또는 컨텍스트 (선택사항)…
이 방의 모든 봇에 적용되는 사용자 지정 가이드라인
+ 높은 제한 경고
+ 메시지 수(%1$d) 또는 전달 횟수(%2$d)를 높게 설정하면 API 토큰이 대량 소비되고 봇 간의 긴 토론 루프가 발생할 수 있습니다.
+ 그래도 저장
+ 제한 조정
설정
에이전트 계획
실시간 기록
diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml
index 0912dbd7..bdff2ba1 100644
--- a/app/src/main/res/values-zh/strings.xml
+++ b/app/src/main/res/values-zh/strings.xml
@@ -1164,5 +1164,9 @@
房间指令 / 提示词
此群聊的自定义规则、语气或上下文(可选)…
适用于此房间内所有 Bot 的自定义准则
+ 高上限警告
+ 设置较高的消息数 (%1$d) 或交接次数 (%2$d) 可能会消耗大量 API 令牌,并导致机器人之间产生长时间的连续讨论循环。
+ 仍要保存
+ 调整限制
设置
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9ab998eb..a3113b56 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1295,6 +1295,10 @@
Room Instructions / Prompt
Custom rules, tone, or context for this group (optional)…
Custom guidelines for all bots in this room
+ High Limits Warning
+ Setting high message (%1$d) or handoff (%2$d) limits can consume significant API tokens and lead to long continuous bot discussion loops.
+ Save Anyway
+ Adjust Limits
Settings
Discussion stopped
Stop generation
diff --git a/app/src/test/java/com/m57/hermescontrol/ui/bots/group/GroupChatViewModelTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/bots/group/GroupChatViewModelTest.kt
index a8bc0116..97943a25 100644
--- a/app/src/test/java/com/m57/hermescontrol/ui/bots/group/GroupChatViewModelTest.kt
+++ b/app/src/test/java/com/m57/hermescontrol/ui/bots/group/GroupChatViewModelTest.kt
@@ -420,6 +420,33 @@ class GroupChatViewModelTest {
assertEquals(15, viewModel.uiState.value.maxBotMessages)
assertEquals(4, viewModel.uiState.value.maxContinuationPasses)
assertEquals("Custom instructions for this group", viewModel.uiState.value.systemPrompt)
+
+ // Extended limits within new range
+ viewModel.updateGroupLimits(
+ maxMessages = 45,
+ maxPasses = 18,
+ )
+ testScheduler.runCurrent()
+ assertEquals(45, viewModel.uiState.value.maxBotMessages)
+ assertEquals(18, viewModel.uiState.value.maxContinuationPasses)
+
+ // Exceeding ceiling clamps to MAX_ALLOWED_BOT_MESSAGES (50) and MAX_ALLOWED_CONTINUATION_PASSES (20)
+ viewModel.updateGroupLimits(
+ maxMessages = 999,
+ maxPasses = 999,
+ )
+ testScheduler.runCurrent()
+ assertEquals(MAX_ALLOWED_BOT_MESSAGES, viewModel.uiState.value.maxBotMessages)
+ assertEquals(MAX_ALLOWED_CONTINUATION_PASSES, viewModel.uiState.value.maxContinuationPasses)
+
+ // Under floor clamps to 1 and 0
+ viewModel.updateGroupLimits(
+ maxMessages = -10,
+ maxPasses = -5,
+ )
+ testScheduler.runCurrent()
+ assertEquals(1, viewModel.uiState.value.maxBotMessages)
+ assertEquals(0, viewModel.uiState.value.maxContinuationPasses)
}
@Test