d9d34e9277
SQLite (голый SQLiteOpenHelper, без ORM — как решили): - sessions (id, title, created_at, updated_at) + messages (role, content, tool_call_id, tool_calls JSON, attachments JSON, created_at) по утверждённой схеме; FK-каскад; CHECK на роли. - ChatDb + ChatDao (createSession/listSessions/rename/touch/delete, addMessage, messagesForSession), доступ через PhoneApp.chatDao. - Unit: ModelsTest (роли, JSON tool_calls/attachments). androidTest: ChatDaoTest — CRUD-круг + каскад — OK (3 tests) на эмуляторе. Koog (JetBrains) — базовый LLM-клиент, без агентного слоя: - ai.koog:prompt-executor-openai-client:1.1.1 + http-client-okhttp - LlmClient: OpenAILLMClient(apiKey, baseUrl=llm.binom.pw) → chat(история) - BuildConfig LLM_API_KEY/LLM_MODEL из local.properties (вне git); llm_debug smoke (--ez llm_debug true) — проверен на эмуляторе: реальный ответ от llm.binom.pw (deepseek-v4-flash). - ЗАГЛУШКА Coroutines_jvmKt: дефект публикации Koog 1.1.1 — utils-android не включает jvm-класс, который зовёт OkHttpKoogHttpClient (без неё NoClassDefFoundError на первом вызове). Сигнатура сверена с utils-jvm. Убрать после фикса JetBrains.
76 lines
2.6 KiB
Kotlin
76 lines
2.6 KiB
Kotlin
package pw.binom.viewmate.phone
|
|
|
|
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 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)
|
|
setContent {
|
|
ViewMateApp()
|
|
}
|
|
if (intent?.getBooleanExtra("stt_debug", false) == true) {
|
|
runSttDebug()
|
|
}
|
|
if (intent?.getBooleanExtra("llm_debug", false) == true) {
|
|
runLlmDebug()
|
|
}
|
|
}
|
|
|
|
/** 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,
|
|
),
|
|
)
|
|
}
|
|
}
|