Files
view-mate/app-phone/src/main/kotlin/pw/binom/viewmate/phone/MainActivity.kt
T
subochev bbf7a020d4 Ассистент Порфирий: тула времени, сессии, чат-UI, голосовой цикл; BT-транспорт очки↔телефон
- BT (RFCOMM SPP 00001105) вместо WiFi: BtGlassesTransport/BtServerTransport, реконнект 3с
- PlaybackService (foreground, WAKE_LOCK) против морозов HONOR
- Ассистент: AgentTools (TimeTool ISO 8601), TOOL_CALL-протокол, сессии SQLite (UUID)
- Чат-UI: создание/сброс/переключение сессий, автообновление ленты 2с
- LlmClient на прямом OkHttp (Koog давал пустой content на длинных историях)
- Голосовой цикл: тройной тап на очках → STT → активная сессия → ShowText
- Тесты: AgentToolsTest (3), GlassesServerTest переписан (6) — зелёные
2026-08-22 16:31:04 +03:00

97 lines
3.6 KiB
Kotlin

package pw.binom.viewmate.phone
import android.Manifest
import android.content.pm.PackageManager
import android.graphics.Color
import android.os.Bundle
import android.view.Gravity
import android.widget.FrameLayout
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.core.content.ContextCompat
import pw.binom.viewmate.phone.stt.SttDebug
import pw.binom.viewmate.phone.agent.LlmDebug
import pw.binom.viewmate.phone.ui.ViewMateApp
import java.io.File
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestBtPermissions()
setContent {
ViewMateApp()
}
if (intent?.getBooleanExtra("stt_debug", false) == true) {
runSttDebug()
}
if (intent?.getBooleanExtra("llm_debug", false) == true) {
runLlmDebug()
}
}
/**
* Runtime-запрос Bluetooth-пермишенов (TASK-transport.md п.5): только при
* вкл. SPP ([PhoneConfig.USE_BLUETOOTH]). На 31+ — BLUETOOTH_CONNECT/SCAN,
* на ≤30 — BLUETOOTH/BLUETOOTH_ADMIN. Если уже выданы — молча пропускаем.
*/
private fun requestBtPermissions() {
if (!PhoneConfig.USE_BLUETOOTH) return
val needed = BtServerTransport.runtimePermissions()
val missing = needed.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}
if (missing.isNotEmpty()) {
requestPermissions(missing.toTypedArray(), 1009)
log("bt", "запрошены Bluetooth-пермишены (SPP): ${missing.joinToString()}")
}
}
/** Smoke Koog-клиента: один вызов llm.binom.pw, ответ в лог [llm]. */
private fun runLlmDebug() {
showOverlay("[llm] вызываю llm.binom.pw...")
Thread {
LlmDebug.run(BuildConfig.LLM_API_KEY, BuildConfig.LLM_MODEL)
runOnUiThread { showOverlay("[llm] готово — смотри лог") }
}.start()
}
/** Отладочный прогон распознавания: берёт первый wav из filesDir/samples. */
private fun runSttDebug() {
val samplesDir = File(filesDir, "samples")
val wav = samplesDir.listFiles()
?.filter { it.isFile && it.extension.equals("wav", true) }
?.firstOrNull()
if (wav == null) {
val msg = "[stt] нет wav-файлов в ${samplesDir.absolutePath}"
log("stt", msg)
showOverlay(msg)
return
}
showOverlay("[stt] распознаю ${wav.name}...")
Thread {
val text = SttDebug.run(this, wav.absolutePath)
runOnUiThread { showOverlay(text ?: "[stt] не удалось распознать") }
}.start()
}
/** TextView поверх экрана — чтобы видеть результат на эмуляторе. */
private fun showOverlay(text: String) {
val tv = TextView(this).apply {
this.text = text
setTextColor(Color.WHITE)
setBackgroundColor(Color.argb(200, 0, 0, 0))
textSize = 16f
setPadding(24, 24, 24, 24)
}
addContentView(
tv,
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.TOP,
),
)
}
}