feat(设置页): 添加 Agent 过滤配置字段
- 在设置页添加 Agent ID 输入框,用于过滤流式播报消息 - 新增字符串资源 label_agent_filter 和 hint_agent_id - 在 MQTT 管理器中处理 soul2user 主题消息,根据配置的 demp_id 进行过滤 - 更新 README 文档结构,将详细技术说明移至 technique.md 文件
This commit is contained in:
@@ -28,6 +28,8 @@ class MqttManager(
|
||||
private val job = SupervisorJob()
|
||||
private val scope = CoroutineScope(Dispatchers.IO + job)
|
||||
private var reconnectJob: Job? = null
|
||||
private val prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
|
||||
private val agentDempIdKey = "agent_demp_id"
|
||||
|
||||
// Streaming text buffer
|
||||
private val speechBuffer = StringBuilder()
|
||||
@@ -42,6 +44,8 @@ class MqttManager(
|
||||
private var interruptedLanguage: TtsRequest.Language? = null
|
||||
private var lastStreamLangCode: String? = null
|
||||
private val ttsLanguageMap = mutableMapOf<TtsRequest, TtsRequest.Language>()
|
||||
private var currentStreamSessionId: String? = null
|
||||
private var currentStreamMessageId: String? = null
|
||||
|
||||
init {
|
||||
try {
|
||||
@@ -50,6 +54,7 @@ class MqttManager(
|
||||
override fun connectComplete(reconnect: Boolean, serverURI: String?) {
|
||||
Log.i(TAG, "MQTT connection complete. Reconnect: $reconnect")
|
||||
subscribeTopic("robot/cmd")
|
||||
subscribeTopic("soul2user")
|
||||
updateConnectionStatus(true)
|
||||
}
|
||||
|
||||
@@ -192,7 +197,11 @@ class MqttManager(
|
||||
}
|
||||
try {
|
||||
val obj = JSONObject(trimmed)
|
||||
handleJsonCommand(obj)
|
||||
if (topic == "soul2user") {
|
||||
handleSoulStream(obj)
|
||||
} else {
|
||||
handleJsonCommand(obj)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Invalid JSON payload: $payload", e)
|
||||
val ttsRequest = TtsRequest.create("指令格式错误,请检查指令格式", false, language = TtsRequest.Language.ZH_CN)
|
||||
@@ -200,6 +209,63 @@ class MqttManager(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSoulStream(obj: JSONObject) {
|
||||
val configuredDempId = prefs.getString(agentDempIdKey, "").orEmpty().trim()
|
||||
val dempId = obj.optString("demp_id", "").trim()
|
||||
if (configuredDempId.isNotEmpty() && configuredDempId != dempId) {
|
||||
Log.i(TAG, "Stream ignored: demp_id mismatch ($dempId)")
|
||||
return
|
||||
}
|
||||
val sessionId = obj.optString("session_id", "").trim()
|
||||
val messageId = obj.optString("message_id", "").trim()
|
||||
ensureStreamContext(sessionId, messageId)
|
||||
val text = obj.optString("text", "")
|
||||
val isFinal = obj.optBoolean("is_final", false)
|
||||
val lang = obj.optString("lang", "").trim()
|
||||
scope.launch(Dispatchers.Main) {
|
||||
(context as? MainActivity)?.markSpeechTaskActive()
|
||||
}
|
||||
processStreamText(text, lang)
|
||||
if (isFinal) {
|
||||
flushStreamRemainder(lang)
|
||||
currentStreamSessionId = null
|
||||
currentStreamMessageId = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun ensureStreamContext(sessionId: String, messageId: String) {
|
||||
if (sessionId.isEmpty() && messageId.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val sessionChanged = currentStreamSessionId != null && sessionId.isNotEmpty() && sessionId != currentStreamSessionId
|
||||
val messageChanged = currentStreamMessageId != null && messageId.isNotEmpty() && messageId != currentStreamMessageId
|
||||
if (currentStreamSessionId == null && currentStreamMessageId == null) {
|
||||
currentStreamSessionId = sessionId.ifEmpty { null }
|
||||
currentStreamMessageId = messageId.ifEmpty { null }
|
||||
return
|
||||
}
|
||||
if (sessionChanged || messageChanged) {
|
||||
stopTts()
|
||||
currentStreamSessionId = sessionId.ifEmpty { null }
|
||||
currentStreamMessageId = messageId.ifEmpty { null }
|
||||
return
|
||||
}
|
||||
if (currentStreamSessionId == null && sessionId.isNotEmpty()) {
|
||||
currentStreamSessionId = sessionId
|
||||
}
|
||||
if (currentStreamMessageId == null && messageId.isNotEmpty()) {
|
||||
currentStreamMessageId = messageId
|
||||
}
|
||||
}
|
||||
|
||||
private fun flushStreamRemainder(langCode: String?) {
|
||||
val remaining = speechBuffer.toString().trim()
|
||||
if (remaining.isNotEmpty()) {
|
||||
speechBuffer.setLength(0)
|
||||
speak(remaining, langCode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleJsonCommand(obj: JSONObject) {
|
||||
val action = obj.optString("action", obj.optString("cmd", obj.optString("type", ""))).lowercase()
|
||||
when (action) {
|
||||
|
||||
@@ -33,6 +33,7 @@ class SettingsActivity : AppCompatActivity() {
|
||||
private val liveKitRoomKey = "livekit_room"
|
||||
private val liveKitTokenKey = "livekit_token"
|
||||
private val liveKitEnabledKey = "livekit_enabled"
|
||||
private val agentDempIdKey = "agent_demp_id"
|
||||
private val liveKitUrlDefault = "ws://192.168.2.236:7880"
|
||||
private val liveKitRoomDefault = "temi-room"
|
||||
|
||||
@@ -47,6 +48,8 @@ class SettingsActivity : AppCompatActivity() {
|
||||
prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
|
||||
val savedIp = prefs.getString("network_ip", "")
|
||||
binding.etIpAddress.setText(savedIp)
|
||||
val savedDempId = prefs.getString(agentDempIdKey, "")
|
||||
binding.etAgentDempId.setText(savedDempId)
|
||||
val savedLiveKitUrl = prefs.getString(liveKitUrlKey, resolveLiveKitUrl())
|
||||
val savedLiveKitRoom = prefs.getString(liveKitRoomKey, liveKitRoomDefault)
|
||||
val savedLiveKitToken = prefs.getString(liveKitTokenKey, "")
|
||||
@@ -66,8 +69,12 @@ class SettingsActivity : AppCompatActivity() {
|
||||
binding.btnSave.setOnClickListener {
|
||||
hideKeyboard()
|
||||
val ip = binding.etIpAddress.text.toString().trim()
|
||||
val dempId = binding.etAgentDempId.text?.toString()?.trim().orEmpty()
|
||||
if (ip.isNotEmpty()) {
|
||||
prefs.edit().putString("network_ip", ip).apply()
|
||||
prefs.edit()
|
||||
.putString("network_ip", ip)
|
||||
.putString(agentDempIdKey, dempId)
|
||||
.apply()
|
||||
Toast.makeText(this, getString(R.string.msg_ip_saved, ip), Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
} else {
|
||||
|
||||
@@ -102,6 +102,22 @@
|
||||
android:textColor="@color/text_primary" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/Widget.App.TextInputLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:hint="@string/hint_agent_id">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etAgentDempId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="72dp"
|
||||
android:inputType="text"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/text_primary" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSave"
|
||||
style="@style/Widget.App.Button"
|
||||
|
||||
@@ -93,6 +93,22 @@
|
||||
android:textColor="@color/text_primary" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/Widget.App.TextInputLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:hint="@string/hint_agent_id">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etAgentDempId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="72dp"
|
||||
android:inputType="text"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/text_primary" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSave"
|
||||
style="@style/Widget.App.Button"
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
<string name="btn_restart_app">长按重启应用</string>
|
||||
<string name="label_location_config">当前位置</string>
|
||||
<string name="hint_current_location">请选择当前地点</string>
|
||||
<string name="label_agent_filter">Agent 过滤</string>
|
||||
<string name="hint_agent_id">请输入 demp_id(留空接收全部)</string>
|
||||
<string name="location_unknown">未知</string>
|
||||
<string name="label_livekit_config">LiveKit 配置</string>
|
||||
<string name="hint_livekit_url">请输入 LiveKit 地址 (wss://your-host)</string>
|
||||
|
||||
Reference in New Issue
Block a user