feat(admin): toggle "mode debug" — masque le bouton logs/métriques côté patient
Nouveau flag ConfigStore.debugEnabled (défaut false), propagé via le ContentProvider (/config colonne debug_enabled) et piloté par un Switch dans l'écran Système de l'app admin. - ConfigStore : champ debugEnabled + (de)sérialisation JSON - KazeiaTelemetryProvider : colonne debug_enabled (query + update) - KazeiaService : applique debugEnabled → _debugMode au boot et au reload config - ChatActivity : _debugMode gate désormais la VISIBILITÉ du bouton debug (applyDebugGate). Off → bouton masqué + panneau forcé fermé (écran patient épuré). On → bouton visible, ouvre logs + métriques CPU/GPU/NPU/RAM. - activity_chat.xml : btnDebugToggle visibility=gone par défaut (pas de flash) - app-admin : KazeiaConfigClient/ConfigRepository + DebugCard dans SystemScreen Vérifié device : round-trip provider debug_enabled 0→1→0 OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d1baacd096
commit
93a3953614
|
|
@ -22,6 +22,9 @@ class ConfigRepository(context: Context) {
|
|||
fun updateTts(enabled: Boolean): Boolean =
|
||||
client.updateConfig(ttsEnabled = enabled)
|
||||
|
||||
fun updateDebug(enabled: Boolean): Boolean =
|
||||
client.updateConfig(debugEnabled = enabled)
|
||||
|
||||
fun updateSpeakerModel(modelId: String): Boolean =
|
||||
client.updateConfig(speakerModelId = modelId)
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ class KazeiaConfigClient(private val resolver: ContentResolver) {
|
|||
val thinkerTemperature: Float,
|
||||
val ragEnabled: Boolean = false,
|
||||
val ragThreshold: Float = 0.82f,
|
||||
val ragTopK: Int = 3
|
||||
val ragTopK: Int = 3,
|
||||
val debugEnabled: Boolean = false
|
||||
)
|
||||
|
||||
data class ModelInfo(
|
||||
|
|
@ -57,7 +58,8 @@ class KazeiaConfigClient(private val resolver: ContentResolver) {
|
|||
thinkerTemperature = c.getFloat(c.getColumnIndexOrThrow("thinker_temperature")),
|
||||
ragEnabled = c.getColumnIndex("rag_enabled").let { if (it >= 0) c.getInt(it) == 1 else false },
|
||||
ragThreshold = c.getColumnIndex("rag_threshold").let { if (it >= 0) c.getFloat(it) else 0.82f },
|
||||
ragTopK = c.getColumnIndex("rag_top_k").let { if (it >= 0) c.getInt(it) else 3 }
|
||||
ragTopK = c.getColumnIndex("rag_top_k").let { if (it >= 0) c.getInt(it) else 3 },
|
||||
debugEnabled = c.getColumnIndex("debug_enabled").let { if (it >= 0) c.getInt(it) == 1 else false }
|
||||
)
|
||||
}
|
||||
}.getOrNull()
|
||||
|
|
@ -95,7 +97,8 @@ class KazeiaConfigClient(private val resolver: ContentResolver) {
|
|||
thinkerTemperature: Float? = null,
|
||||
ragEnabled: Boolean? = null,
|
||||
ragThreshold: Float? = null,
|
||||
ragTopK: Int? = null
|
||||
ragTopK: Int? = null,
|
||||
debugEnabled: Boolean? = null
|
||||
): Boolean {
|
||||
val v = ContentValues()
|
||||
cascadeEnabled?.let { v.put("cascade_enabled", it) }
|
||||
|
|
@ -109,6 +112,7 @@ class KazeiaConfigClient(private val resolver: ContentResolver) {
|
|||
ragEnabled?.let { v.put("rag_enabled", it) }
|
||||
ragThreshold?.let { v.put("rag_threshold", it) }
|
||||
ragTopK?.let { v.put("rag_top_k", it) }
|
||||
debugEnabled?.let { v.put("debug_enabled", it) }
|
||||
if (v.size() == 0) return true
|
||||
return runCatching {
|
||||
resolver.update(CONFIG_URI, v, null, null) > 0
|
||||
|
|
|
|||
|
|
@ -138,6 +138,21 @@ fun SystemScreen() {
|
|||
}
|
||||
)
|
||||
|
||||
DebugCard(
|
||||
enabled = cfg.debugEnabled,
|
||||
onToggle = { newValue ->
|
||||
val ok = repo.updateDebug(newValue)
|
||||
if (ok) config = repo.config()
|
||||
scope.launch {
|
||||
snackbar.showSnackbar(
|
||||
if (ok) "Mode debug ${if (newValue) "activé" else "désactivé"} — appliqué immédiatement côté patient."
|
||||
else "Échec : Kazeia patient injoignable.",
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
ModelSection(
|
||||
title = "Speaker",
|
||||
subtitle = "LLM qui répond au patient",
|
||||
|
|
@ -373,6 +388,45 @@ private fun TtsCard(enabled: Boolean, onToggle: (Boolean) -> Unit) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DebugCard(enabled: Boolean, onToggle: (Boolean) -> Unit) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Column(Modifier.padding(16.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(
|
||||
"Mode debug",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
if (enabled)
|
||||
"ACTIVÉ — le bouton debug (logs + métriques CPU/GPU/NPU/RAM) est visible dans l'app patient."
|
||||
else
|
||||
"DÉSACTIVÉ — écran patient épuré, bouton debug masqué.",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
Switch(
|
||||
checked = enabled,
|
||||
onCheckedChange = onToggle,
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = StateReady,
|
||||
checkedTrackColor = StateReady.copy(alpha = 0.4f)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ModelSection(
|
||||
|
|
|
|||
|
|
@ -64,7 +64,12 @@ class ConfigStore private constructor(private val file: File) {
|
|||
* pertinent ~0.88). Plus haut = plus strict. Tunable depuis l'admin. */
|
||||
val ragThreshold: Float = 0.82f,
|
||||
/** Nombre max de chunks récupérés/injectés par tour. */
|
||||
val ragTopK: Int = 3
|
||||
val ragTopK: Int = 3,
|
||||
/** Mode debug : quand true, l'app patient affiche le bouton qui ouvre le
|
||||
* panneau de logs + métriques système (CPU/GPU/NPU/RAM). Quand false
|
||||
* (défaut prod), le bouton est masqué — l'écran reste épuré pour le
|
||||
* patient. Pilotable depuis l'app admin. */
|
||||
val debugEnabled: Boolean = false
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
|
@ -172,6 +177,7 @@ class ConfigStore private constructor(private val file: File) {
|
|||
root.put("rag_enabled", cfg.ragEnabled)
|
||||
root.put("rag_threshold", cfg.ragThreshold.toDouble())
|
||||
root.put("rag_top_k", cfg.ragTopK)
|
||||
root.put("debug_enabled", cfg.debugEnabled)
|
||||
root.put("speaker", JSONObject().apply {
|
||||
put("model_id", cfg.speaker.modelId)
|
||||
put("system_prompt", cfg.speaker.systemPrompt)
|
||||
|
|
@ -197,6 +203,7 @@ class ConfigStore private constructor(private val file: File) {
|
|||
ragEnabled = root.optBoolean("rag_enabled", defaults.ragEnabled),
|
||||
ragThreshold = root.optDouble("rag_threshold", defaults.ragThreshold.toDouble()).toFloat(),
|
||||
ragTopK = root.optInt("rag_top_k", defaults.ragTopK),
|
||||
debugEnabled = root.optBoolean("debug_enabled", defaults.debugEnabled),
|
||||
speaker = root.optJSONObject("speaker")?.let { js ->
|
||||
ModelConfig(
|
||||
modelId = js.optString("model_id", defaults.speaker.modelId),
|
||||
|
|
|
|||
|
|
@ -703,6 +703,7 @@ Pas d'introduction, pas d'explication. Juste les 4 lignes en francais. /no_think
|
|||
// (admin app → ContentProvider.update → ConfigStore.save → listener)
|
||||
val store = com.kazeia.config.ConfigStore.get(this)
|
||||
runtimeConfig = store.current()
|
||||
_debugMode.value = runtimeConfig.debugEnabled // gate UI debug (bouton logs/métriques)
|
||||
store.addListener { new -> handleConfigChange(new) }
|
||||
registerConfigReloadReceiver()
|
||||
registerRagReloadReceiver()
|
||||
|
|
@ -2173,6 +2174,10 @@ Pas d'introduction, pas d'explication. Juste les 4 lignes en francais. /no_think
|
|||
private fun handleConfigChange(new: com.kazeia.config.ConfigStore.RuntimeConfig) {
|
||||
val old = runtimeConfig
|
||||
runtimeConfig = new
|
||||
if (new.debugEnabled != old.debugEnabled) {
|
||||
_debugMode.value = new.debugEnabled // affiche/masque le bouton debug côté patient
|
||||
log("[CONFIG] debug ${old.debugEnabled}→${new.debugEnabled}")
|
||||
}
|
||||
log("[CONFIG] applied : cascade ${old.cascadeEnabled}→${new.cascadeEnabled}, " +
|
||||
"speaker ${old.speaker.modelId}→${new.speaker.modelId}, " +
|
||||
"thinker ${old.thinker.modelId}→${new.thinker.modelId}")
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ class KazeiaTelemetryProvider : ContentProvider() {
|
|||
val cols = arrayOf(
|
||||
"cascade_enabled", "tts_enabled",
|
||||
"stt_engine", "llm_engine", "tts_engine", "rag_enabled",
|
||||
"rag_threshold", "rag_top_k",
|
||||
"rag_threshold", "rag_top_k", "debug_enabled",
|
||||
"speaker_model_id", "speaker_system_prompt", "speaker_temperature",
|
||||
"thinker_model_id", "thinker_system_prompt", "thinker_temperature"
|
||||
)
|
||||
|
|
@ -267,6 +267,7 @@ class KazeiaTelemetryProvider : ContentProvider() {
|
|||
cfg.sttEngine, cfg.llmEngine, cfg.ttsEngine,
|
||||
if (cfg.ragEnabled) 1 else 0,
|
||||
cfg.ragThreshold, cfg.ragTopK,
|
||||
if (cfg.debugEnabled) 1 else 0,
|
||||
cfg.speaker.modelId, cfg.speaker.systemPrompt, cfg.speaker.temperature,
|
||||
cfg.thinker.modelId, cfg.thinker.systemPrompt, cfg.thinker.temperature
|
||||
))
|
||||
|
|
@ -361,6 +362,8 @@ class KazeiaTelemetryProvider : ContentProvider() {
|
|||
values.getAsBoolean("rag_enabled") else cur.ragEnabled,
|
||||
ragThreshold = values.getAsFloat("rag_threshold") ?: cur.ragThreshold,
|
||||
ragTopK = values.getAsInteger("rag_top_k") ?: cur.ragTopK,
|
||||
debugEnabled = if (values.containsKey("debug_enabled"))
|
||||
values.getAsBoolean("debug_enabled") else cur.debugEnabled,
|
||||
speaker = com.kazeia.config.ConfigStore.ModelConfig(
|
||||
modelId = values.getAsString("speaker_model_id") ?: cur.speaker.modelId,
|
||||
systemPrompt = values.getAsString("speaker_system_prompt")
|
||||
|
|
|
|||
|
|
@ -319,8 +319,12 @@ class ChatActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
launch {
|
||||
// debugMode (piloté par l'admin via ConfigStore.debugEnabled) gate le
|
||||
// BOUTON debug. Si off : bouton masqué + panneau forcé fermé (écran
|
||||
// patient épuré). Si on : bouton visible, le patient/soignant ouvre le
|
||||
// panneau logs+métriques via le bouton.
|
||||
service.debugMode.collect { debug ->
|
||||
setDebugPanelVisible(debug)
|
||||
applyDebugGate(debug)
|
||||
}
|
||||
}
|
||||
launch {
|
||||
|
|
@ -404,6 +408,19 @@ class ChatActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
/** Mode debug (ConfigStore.debugEnabled, piloté admin) : affiche/masque le bouton
|
||||
* qui ouvre le panneau logs+métriques. Off → bouton caché + panneau forcé fermé. */
|
||||
private fun applyDebugGate(enabled: Boolean) {
|
||||
runOnUiThread {
|
||||
findViewById<View>(R.id.btnDebugToggle)?.visibility =
|
||||
if (enabled) View.VISIBLE else View.GONE
|
||||
if (!enabled) {
|
||||
debugPanelVisible = false
|
||||
setDebugPanelVisible(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var logFile: java.io.FileWriter? = null
|
||||
|
||||
private fun appendLog(msg: String) {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
android:src="@android:drawable/ic_menu_manage"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Debug"
|
||||
android:visibility="gone"
|
||||
android:tint="@android:color/white" />
|
||||
|
||||
<ImageButton
|
||||
|
|
|
|||
Loading…
Reference in New Issue