{"id":1753,"date":"2026-10-05T12:20:07","date_gmt":"2026-10-05T09:20:07","guid":{"rendered":"https:\/\/devhorizon.online\/wordpress\/com-devhorizon-online-ggufchat\/"},"modified":"2026-10-06T04:09:43","modified_gmt":"2026-10-06T01:09:43","slug":"com-devhorizon-online-ggufchat","status":"publish","type":"post","link":"https:\/\/devhorizon.online\/wordpress\/com-devhorizon-online-ggufchat\/","title":{"rendered":"GGUF Chat \u2014 Android LLM client (com.devhorizon.online.ggufchat)"},"content":{"rendered":"<h1>GGUF Chat \u2014 Android LLM client<\/h1>\n<p><strong>RU<\/strong> <a href=\"README.ru.md\">\u0420\u0443\u0441\u0441\u043a\u0438\u0439<\/a> \u00b7 <strong>EN<\/strong> <a href=\"README.md\">English<\/a> \u00b7 <strong>DE<\/strong> <a href=\"README.de.md\">Deutsch<\/a><\/p>\n<hr \/>\n<h2>1. \u0427\u0442\u043e \u044d\u0442\u043e<\/h2>\n<p><code>GGUF Chat<\/code> \u2014 \u043d\u0430\u0442\u0438\u0432\u043d\u043e\u0435 Android-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 (\u043f\u0430\u043a\u0435\u0442 <code>com.devhorizon.online.ggufchat<\/code>), \u043a\u043e\u0442\u043e\u0440\u043e\u0435:<\/p>\n<ul>\n<li>\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 GGUF-\u043c\u043e\u0434\u0435\u043b\u0438 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044e \u043d\u0430 CPU \u0447\u0435\u0440\u0435\u0437 \u0432\u0435\u043d\u0434\u043e\u0440\u0435\u043d\u043d\u044b\u0439 <code>llama.cpp<\/code>;<\/li>\n<li>\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 <strong>\u0437\u0440\u0435\u043d\u0438\u0435<\/strong> (\u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f) \u0447\u0435\u0440\u0435\u0437 <code>mtmd<\/code> + <code>mmproj<\/code>-\u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440 (Qwen2-VL, Gemma 4 E2B \u0438 \u0434\u0440.);<\/li>\n<li>\u0443\u043c\u0435\u0435\u0442 \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u0447\u0430\u0442 \u043f\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043c, \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u0432\u0432\u043e\u0434 (STT) \u0438 \u043e\u0437\u0432\u0443\u0447\u043a\u0443 (TTS), \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438-\u0447\u0438\u043f\u044b, \u043f\u043e\u0438\u0441\u043a\/\u0441\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0441 Hugging Face, \u043c\u043e\u043d\u0438\u0442\u043e\u0440 \u041e\u0417\u0423 \u0438 \u043a\u043d\u043e\u043f\u043a\u0443 \u00ab\u041e\u0441\u0432\u043e\u0431\u043e\u0434\u0438\u0442\u044c \u041e\u0417\u0423\u00bb;<\/li>\n<li>\u043b\u043e\u043a\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d \u043d\u0430 <strong>en \/ ru \/ de<\/strong> \u0438 \u0438\u043c\u0435\u0435\u0442 6 \u0442\u0435\u043c.<\/li>\n<\/ul>\n<h2>2. \u041f\u043e\u043b\u043d\u044b\u0439 \u0441\u0442\u0435\u043a<\/h2>\n<table>\n<thead>\n<tr>\n<th>\u0421\u043b\u043e\u0439<\/th>\n<th>\u0422\u0435\u0445\u043d\u043e\u043b\u043e\u0433\u0438\u0438<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>\u042f\u0437\u044b\u043a<\/td>\n<td>Kotlin <code>2.2.10<\/code> (JVM target 17)<\/td>\n<\/tr>\n<tr>\n<td>\u0421\u0431\u043e\u0440\u043a\u0430<\/td>\n<td>Gradle + AGP <code>9.1.1<\/code>, Kotlin Compose plugin<\/td>\n<\/tr>\n<tr>\n<td>UI<\/td>\n<td>Jetpack Compose (BOM <code>2024.09.00<\/code>), Material3, Material Icons (core+extended), Navigation Compose <code>2.8.9<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435<\/td>\n<td>AndroidX Lifecycle\/ViewModel <code>2.8.7<\/code>, Kotlin Coroutines\/Flow <code>1.10.2<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u0414\u0430\u043d\u043d\u044b\u0435<\/td>\n<td><code>org.json<\/code> (JSONObject\/JSONArray), <code>SharedPreferences<\/code>, \u0444\u0430\u0439\u043b\u044b \u0441\u0435\u0441\u0441\u0438\u0439 \u0432 <code>files\/sessions\/*.json<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u0421\u0435\u0442\u044c<\/td>\n<td><code>HttpURLConnection<\/code>, REST API Hugging Face<\/td>\n<\/tr>\n<tr>\n<td>\u041c\u0435\u0434\u0438\u0430<\/td>\n<td>Android <code>SpeechRecognizer<\/code> (STT), <code>TextToSpeech<\/code> (TTS), <code>ContentResolver<\/code>\/document picker, <code>FileProvider<\/code> (\u043a\u0430\u043c\u0435\u0440\u0430), <code>BitmapFactory<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u041d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u0441\u043b\u043e\u0439<\/td>\n<td>C++17, JNI, <strong>llama.cpp<\/strong> (<code>app\/src\/main\/cpp\/llama.cpp-new<\/code>) + <code>tools\/mtmd<\/code>, CMake <code>3.22.1<\/code>, NDK <code>28.2.13676358<\/code>, \u0442\u043e\u043b\u044c\u043a\u043e <code>arm64-v8a<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u041f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430<\/td>\n<td><code>minSdk 26<\/code>, <code>targetSdk 36<\/code>, <code>compileSdk 36<\/code>, <code>ANDROID_STL=c++_shared<\/code>, \u043d\u0430\u0442\u0438\u0432\u043d\u0430\u044f \u0441\u0431\u043e\u0440\u043a\u0430 \u0432\u0441\u0435\u0433\u0434\u0430 <code>Release<\/code> (<code>-DCMAKE_BUILD_TYPE=Release<\/code>)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>\u041a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 (<code>app\/build.gradle.kts<\/code>): <code>androidx.compose.material3<\/code>, <code>androidx.navigation.compose<\/code>, <code>kotlinx.coroutines.android<\/code>, <code>kotlinx.serialization.json<\/code>. \u041c\u0430\u043d\u0438\u0444\u0435\u0441\u0442: \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u044f <code>RECORD_AUDIO<\/code>, <code>INTERNET<\/code>; <code>FileProvider<\/code> \u0441 authority <code>${applicationId}.fileprovider<\/code>.<\/p>\n<h2>3. \u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 APK<\/h2>\n<pre><code>\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 UI (Compose) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 MainActivity \/ AppNavigation (NavHost: chat, models, settings)          \u2502\n\u2502 ChatScreen &middot; ModelPickerScreen &middot; SettingsScreen &middot; DrawerContent         \u2502\n\u2502 MarkdownText &middot; Theme &middot; Localization                                     \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25b2\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25b2\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                \u2502 collectAsState()                          \u2502 l(key, lang)\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 ViewModel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 ChatViewModel (AndroidViewModel): StateFlow-\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435, sendMessage,      \u2502\n\u2502 loadModel, freeRam, suggestions, documents, STT\/TTS, \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438          \u2502\n\u2514\u2500\u2500\u2500\u25b2\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25b2\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25b2\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25b2\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n    \u2502          \u2502               \u2502                   \u2502\n\u250c\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Chat     \u2502 \u2502 LlmEngine   \u2502 \u2502 Suggestion     \u2502 \u2502 SttManager \/     \u2502\n\u2502 Repo     \u2502 \u2502 (inference, \u2502 \u2502 Engine         \u2502 \u2502 TtsManager       \u2502\n\u2502 (sessions\u2502 \u2502  models,    \u2502 \u2502 (dedup, retry) \u2502 \u2502 (voice)          \u2502\n\u2502  docs)   \u2502 \u2502  mmproj,    \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502  settings)  \u2502\n             \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                    \u2502 JNI (LocalLlmNative)\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 llama_jni.cpp  &rarr;  llama.cpp (llama) + mtmd (vision) + ggml (CPU)        \u2502\n\u2502 libllama_jni.so (arm64-v8a)                                             \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/code><\/pre>\n<p>\u041f\u043e\u0442\u043e\u043a \u0434\u0430\u043d\u043d\u044b\u0445: UI \u2192 <code>ChatViewModel<\/code> \u2192 <code>LlmEngine.generate\/generateVision<\/code> \u2192 JNI \u2192 <code>llama_decode<\/code> \u2192 \u0442\u043e\u043a\u0435\u043d\u044b \u0441\u0442\u0440\u0438\u043c\u044f\u0442\u0441\u044f \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u0447\u0435\u0440\u0435\u0437 <code>TokenCallback<\/code> \u2192 <code>StateFlow<\/code> \u2192 Compose. \u0421\u0435\u0441\u0441\u0438\u0438\/\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u2014 <code>ChatRepository<\/code> (JSON \u043d\u0430 \u0434\u0438\u0441\u043a\u0435); \u043c\u043e\u0434\u0435\u043b\u0438\/\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u2014 <code>SharedPreferences<\/code> (<code>gguf_chat_prefs<\/code>).<\/p>\n<h2>4. \u0424\u0443\u043d\u043a\u0446\u0438\u0438 \u0438 \u043a\u043e\u0434<\/h2>\n<h3>4.1 \u0422\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u0438 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u2014 <code>MainActivity.kt<\/code><\/h3>\n<table>\n<thead>\n<tr>\n<th>\u0424\u0443\u043d\u043a\u0446\u0438\u044f<\/th>\n<th>\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442<\/th>\n<th>\u041a\u043e\u0434<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>MainActivity.onCreate<\/code><\/td>\n<td>\u0422\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430, \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 edge-to-edge \u0438 \u043a\u043e\u043d\u0442\u0435\u043d\u0442 Compose<\/td>\n<td><code>setContent { AppNavigation() }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>AppNavigation()<\/code><\/td>\n<td>\u0421\u043e\u0437\u0434\u0430\u0451\u0442 <code>ChatViewModel<\/code>, <code>NavHost<\/code>, \u0442\u0435\u043c\u0443, \u043b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440 <code>l<\/code><\/td>\n<td><code>val l = { key -&amp;gt; Localization.getString(key, lang) }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>AppContent(...)<\/code><\/td>\n<td>Drawer + <code>NavHost<\/code> \u0441 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0430\u043c\u0438 <code>chat<\/code>\/<code>models<\/code>\/<code>settings<\/code><\/td>\n<td><code>navController.navigate(&quot;models&quot;)<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u043c\u0430\u0440\u0448\u0440\u0443\u0442 <code>chat<\/code><\/td>\n<td>\u042d\u043a\u0440\u0430\u043d \u0447\u0430\u0442\u0430<\/td>\n<td><code>composable(&quot;chat&quot;) { ChatScreen(...) }<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>4.2 ViewModel \u2014 <code>ui\/viewmodel\/ChatViewModel.kt<\/code><\/h3>\n<table>\n<thead>\n<tr>\n<th>\u0424\u0443\u043d\u043a\u0446\u0438\u044f<\/th>\n<th>\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442<\/th>\n<th>\u041a\u043e\u0434<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>importModel(uri)<\/code><\/td>\n<td>\u0418\u043c\u043f\u043e\u0440\u0442 \u043c\u043e\u0434\u0435\u043b\u0438 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430<\/td>\n<td><code>val result = llmEngine.importModel(uri, displayName)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>downloadModelFromUrl(url)<\/code><\/td>\n<td>\u0421\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435 \u043c\u043e\u0434\u0435\u043b\u0438; mmproj-URL \u0440\u043e\u0443\u0442\u0438\u0442\u0441\u044f \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440<\/td>\n<td><code>if (rawName.contains(&quot;mmproj&quot;, ignoreCase = true)) { ... }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>searchModels(q, maxMB, includeProj)<\/code><\/td>\n<td>\u041f\u043e\u0438\u0441\u043a \u043d\u0430 HF \u0432 IO-\u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u0435<\/td>\n<td><code>HuggingFaceClient.searchGgufModels(...)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>loadModel(model)<\/code><\/td>\n<td>\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043c\u043e\u0434\u0435\u043b\u0438 + \u0435\u0451 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440\u0430<\/td>\n<td><code>llmEngine.loadModel(modelInfo, modelInfo.mmprojPath)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>attachImage(uri)<\/code><\/td>\n<td>\u041a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u0443\u044e \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0443 \u0432 <code>files\/images<\/code><\/td>\n<td><code>java.io.File(dir, &quot;img_${UUID.randomUUID()}.jpg&quot;)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>clearPendingImage()<\/code><\/td>\n<td>\u0421\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u0443\u044e \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0443<\/td>\n<td><code>_pendingImage.value = null<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>unloadModel()<\/code><\/td>\n<td>\u0412\u044b\u0433\u0440\u0443\u0437\u043a\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u043c\u043e\u0434\u0435\u043b\u0438<\/td>\n<td><code>llmEngine.unloadModel()<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>refreshMemory()<\/code><\/td>\n<td>\u0427\u0438\u0442\u0430\u0435\u0442 total\/avail \u041e\u0417\u0423<\/td>\n<td><code>am.getMemoryInfo(mi)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>freeRam()<\/code><\/td>\n<td><strong>\u0412\u044b\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u0432\u0441\u0451<\/strong> \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043f\u0430\u043c\u044f\u0442\u044c \u041e\u0421<\/td>\n<td><code>llmEngine.unloadModel(); System.gc(); LocalLlmNative.nativeFreeMemory()<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>deleteModel(modelInfo)<\/code><\/td>\n<td>\u0423\u0434\u0430\u043b\u044f\u0435\u0442 \u043c\u043e\u0434\u0435\u043b\u044c \u0438 \u0437\u0430\u043f\u0438\u0441\u044c<\/td>\n<td><code>llmEngine.deleteModel(modelInfo)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>createNewSession()<\/code><\/td>\n<td>\u041d\u043e\u0432\u0430\u044f \u0441\u0435\u0441\u0441\u0438\u044f \u0447\u0430\u0442\u0430<\/td>\n<td><code>chatRepository.createSession()<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>selectSession(id)<\/code><\/td>\n<td>\u041f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0441\u0435\u0441\u0441\u0438\u0438<\/td>\n<td><code>chatRepository.selectSession(id)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>addDocument(uri)<\/code><\/td>\n<td>\u0427\u0438\u0442\u0430\u0435\u0442 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0438 \u0440\u0435\u0436\u0435\u0442 \u043f\u043e\u0434 \u0431\u044e\u0434\u0436\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430<\/td>\n<td><code>val (text, truncated) = DocumentReader.truncate(raw.text, remaining)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>removeDocument(id)<\/code><\/td>\n<td>\u0423\u0434\u0430\u043b\u044f\u0435\u0442 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0438\u0437 \u0441\u0435\u0441\u0441\u0438\u0438<\/td>\n<td><code>chatRepository.removeDocument(sessionId, id)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>documentBudgetTokens()<\/code><\/td>\n<td>\u0411\u044e\u0434\u0436\u0435\u0442 \u0442\u043e\u043a\u0435\u043d\u043e\u0432 \u043f\u043e\u0434 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b<\/td>\n<td><code>DocumentReader.budgetTokens(...)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>sendMessage()<\/code><\/td>\n<td>\u0413\u043b\u0430\u0432\u043d\u044b\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439: \u0432\u044b\u0431\u043e\u0440 vision\/text, \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f, \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435, \u0430\u0432\u0442\u043e\u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438; \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u044b TTS \u0441\u0442\u0430\u0432\u044f\u0442\u0441\u044f \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043f\u043e \u0437\u043d\u0430\u043a\u0430\u043c \u043f\u0440\u0435\u043f\u0438\u043d\u0430\u043d\u0438\u044f<\/td>\n<td><code>val useVision = imagePath != null &amp;amp;&amp;amp; llmEngine.hasVision<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>lastTtsDelimiter(sb)<\/code><\/td>\n<td>\u0418\u043d\u0434\u0435\u043a\u0441 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0437\u043d\u0430\u043a\u0430 \u043f\u0440\u0435\u043f\u0438\u043d\u0430\u043d\u0438\u044f \u0432 \u0431\u0443\u0444\u0435\u0440\u0435 TTS<\/td>\n<td><code>&#039;.&#039;, &#039;!&#039;, &#039;?&#039;, &#039;;&#039;, &#039;:&#039;, &#039;,&#039;, &#039;&hellip;&#039; -&amp;gt; return i<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>attachMmprojToModel(model, uri)<\/code><\/td>\n<td>\u0418\u043c\u043f\u043e\u0440\u0442 \u0438 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440\u0430<\/td>\n<td><code>llmEngine.importMmproj(uri).onSuccess { llmEngine.setModelMmproj(...) }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>downloadMmprojForModel(...)<\/code><\/td>\n<td>\u0421\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440\u0430 \u0438 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0430<\/td>\n<td><code>llmEngine.downloadMmprojFromUrl(url, name)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>removeMmprojFromModel(model)<\/code><\/td>\n<td>\u0421\u043d\u0438\u043c\u0430\u0435\u0442 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440\u0430<\/td>\n<td><code>llmEngine.setModelMmproj(model.path, null)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>stopGeneration()<\/code><\/td>\n<td>\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438<\/td>\n<td><code>llmEngine.cancelGeneration()<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>generateSuggestions(onlyNew)<\/code><\/td>\n<td>\u0417\u0430\u043f\u0443\u0441\u043a \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043e\u043a \u0447\u0435\u0440\u0435\u0437 <code>SuggestionEngine<\/code><\/td>\n<td><code>suggestionEngine.generate(...)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>onSuggestionClicked(s)<\/code><\/td>\n<td>\u041f\u043e\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0443 \u0432 \u043f\u043e\u043b\u0435 \u0432\u0432\u043e\u0434\u0430<\/td>\n<td><code>_inputText.value = suggestion<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>startVoiceInput()\/stopVoiceInput()<\/code><\/td>\n<td>STT<\/td>\n<td><code>sttManager.startListening(lang) { ... }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>speakText()\/stopSpeaking()<\/code><\/td>\n<td>TTS<\/td>\n<td><code>ttsManager.speak(text)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>updateSettings(new)<\/code> \/ <code>setAppLanguage(lang)<\/code><\/td>\n<td>\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a<\/td>\n<td><code>llmEngine.saveSettings(newSettings)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>onCleared()<\/code><\/td>\n<td>\u041e\u0441\u0432\u043e\u0431\u043e\u0436\u0434\u0435\u043d\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432<\/td>\n<td><code>llmEngine.unloadModel()<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>4.3 \u0414\u0430\u043d\u043d\u044b\u0435 \u0447\u0430\u0442\u0430 \u2014 <code>data\/chat\/<\/code><\/h3>\n<table>\n<thead>\n<tr>\n<th>\u0424\u0443\u043d\u043a\u0446\u0438\u044f<\/th>\n<th>\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442<\/th>\n<th>\u041a\u043e\u0434<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>ChatRepository.loadSessions()<\/code><\/td>\n<td>\u0427\u0438\u0442\u0430\u0435\u0442 <code>files\/sessions\/*.json<\/code><\/td>\n<td><code>sessionsDir.listFiles { f -&amp;gt; f.extension == &quot;json&quot; }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>parseSession(json)<\/code><\/td>\n<td>\u041f\u0430\u0440\u0441\u0438\u0442 \u0441\u0435\u0441\u0441\u0438\u044e\/\u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f<\/td>\n<td><code>val arr = o.getJSONArray(&quot;messages&quot;)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>parseDocuments(o)<\/code><\/td>\n<td>\u041f\u0430\u0440\u0441\u0438\u0442 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0441\u0435\u0441\u0441\u0438\u0438<\/td>\n<td><code>val arr = o.optJSONArray(&quot;documents&quot;) ?: return emptyList()<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>saveSession(session)<\/code><\/td>\n<td>\u041f\u0438\u0448\u0435\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \u0432 JSON<\/td>\n<td><code>File(sessionsDir, &quot;${session.id}.json&quot;).writeText(o.toString())<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>createSession()<\/code><\/td>\n<td>\u0421\u043e\u0437\u0434\u0430\u0451\u0442 \u0441\u0435\u0441\u0441\u0438\u044e \u0438 \u0434\u0435\u043b\u0430\u0435\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0439<\/td>\n<td><code>_currentSessionId.value = session.id<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>addMessage(id, msg)<\/code><\/td>\n<td>\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 + \u0430\u0432\u0442\u043e-\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a<\/td>\n<td><code>message.content.take(40).replace(&quot;\\n&quot;, &quot; &quot;)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>addDocument<\/code> \/ <code>removeDocument<\/code><\/td>\n<td>\u0418\u0437\u043c\u0435\u043d\u044f\u044e\u0442 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0441\u0435\u0441\u0441\u0438\u0438<\/td>\n<td><code>session.copy(documents = session.documents + document)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>deleteSession(id)<\/code> \/ <code>clearAll()<\/code><\/td>\n<td>\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0441\u0435\u0441\u0441\u0438\u0439<\/td>\n<td><code>File(sessionsDir, &quot;$id.json&quot;).delete()<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>DocumentReader.estimateTokens(t)<\/code><\/td>\n<td>\u042d\u0432\u0440\u0438\u0441\u0442\u0438\u043a\u0430 \u0442\u043e\u043a\u0435\u043d\u043e\u0432 (\u043a\u0438\u0440\u0438\u043b\u043b\u0438\u0446\u0430 \u0434\u043e\u0440\u043e\u0436\u0435)<\/td>\n<td><code>val charsPerToken = if (cyrRatio &amp;gt; 0.2) 1.8 else 3.5<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>DocumentReader.truncate(t, n)<\/code><\/td>\n<td>\u041e\u0431\u0440\u0435\u0437\u043a\u0430 \u043f\u043e\u0434 \u043b\u0438\u043c\u0438\u0442<\/td>\n<td><code>(remainingTokens * charsPerToken(text)).toInt()<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>DocumentReader.read(ctx, uri)<\/code><\/td>\n<td>\u0427\u0438\u0442\u0430\u0435\u0442 txt\/md\/json\/html\/csv (\u22648 \u041c\u0411)<\/td>\n<td><code>val ALLOWED_EXTENSIONS = setOf(&quot;txt&quot;,&quot;md&quot;,&quot;markdown&quot;,&quot;json&quot;,&quot;htm&quot;,&quot;html&quot;,&quot;csv&quot;)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>stripHtml(html)<\/code><\/td>\n<td>\u041f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u0442 HTML \u0432 \u0442\u0435\u043a\u0441\u0442<\/td>\n<td><code>s.replace(Regex(&quot;]+&amp;gt;&quot;), &quot; &quot;)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>SuggestionEngine.generate(...)<\/code><\/td>\n<td>\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438 \u0441 \u0434\u0435\u0434\u0443\u043f\u043e\u043c \u0438 \u0440\u0435\u0442\u0440\u0430\u044f\u043c\u0438 \u043f\u043e \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0435<\/td>\n<td><code>val temperatures = listOf(0.7f, 0.85f, 1.0f)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>SuggestionEngine.normalize\/markShown\/clearHistory<\/code><\/td>\n<td>\u0414\u0435\u0434\u0443\u043f \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043e\u043a<\/td>\n<td><code>shownSuggestions.add(normalize(it))<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>4.4 \u0414\u0432\u0438\u0436\u043e\u043a \u0438\u043d\u0444\u0435\u0440\u0435\u043d\u0441\u0430 \u2014 <code>data\/llm\/LlmEngine.kt<\/code><\/h3>\n<table>\n<thead>\n<tr>\n<th>\u0424\u0443\u043d\u043a\u0446\u0438\u044f<\/th>\n<th>\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442<\/th>\n<th>\u041a\u043e\u0434<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>loadModelsFromPrefs()<\/code><\/td>\n<td>\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0441\u043f\u0438\u0441\u043a\u0430 \u043c\u043e\u0434\u0435\u043b\u0435\u0439; \u0434\u0440\u043e\u043f \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445; \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u044f stale-mmproj<\/td>\n<td><code>if (m.mmprojPath != null &amp;amp;&amp;amp; !ModelCompatibility.isVisionArch(m.architecture)) m = m.copy(mmprojPath = null)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>saveModelsToPrefs()<\/code><\/td>\n<td>\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0441\u043f\u0438\u0441\u043a\u0430<\/td>\n<td><code>prefs.edit().putString(KEY_MODELS, arr.toString()).apply()<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>loadSettingsFromPrefs()<\/code> \/ <code>saveSettings()<\/code><\/td>\n<td>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438<\/td>\n<td><code>o.put(&quot;temperature&quot;, ...)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>importModel(uri)<\/code><\/td>\n<td>\u041a\u043e\u043f\u0438\u0440\u0443\u0435\u0442 GGUF \u0432 <code>files\/models<\/code> \u0441 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u043e\u043c<\/td>\n<td><code>context.contentResolver.openInputStream(uri)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>downloadModelFromUrl(url)<\/code><\/td>\n<td>\u0421\u043a\u0430\u0447\u0438\u0432\u0430\u0435\u0442 \u0441 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u043e\u0439 \u0440\u0430\u0437\u043c\u0435\u0440\u0430\/\u0432\u0430\u043b\u0438\u0434\u043d\u043e\u0441\u0442\u0438<\/td>\n<td><code>if (totalBytes &amp;gt; 0 &amp;amp;&amp;amp; totalRead != totalBytes)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>loadModel(model, mmproj)<\/code><\/td>\n<td>\u0413\u0440\u0443\u0437\u0438\u0442 \u043c\u043e\u0434\u0435\u043b\u044c, \u0447\u0438\u0441\u0442\u0438\u0442 RAM-\u0433\u0435\u0439\u0442, \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442 vision-\u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440<\/td>\n<td><code>val isVision = ModelCompatibility.isVisionArch(arch)<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u042d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u044b\u0439 ctx<\/td>\n<td>\u0417\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043d\u044b\u0439 ctx \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c\u043e\u043c \u043c\u043e\u0434\u0435\u043b\u0438 \u0438 \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435<\/td>\n<td><code>val effectiveCtx = s.contextLength.coerceIn(512, modelMaxCtx)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>unloadModel()<\/code><\/td>\n<td><code>nativeFreeModel<\/code> + \u0441\u0431\u0440\u043e\u0441 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f<\/td>\n<td><code>LocalLlmNative.nativeFreeModel(handle)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>loadMmproj(path)<\/code><\/td>\n<td>\u041f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440\u0430 \u043a \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438<\/td>\n<td><code>LocalLlmNative.nativeLoadMmproj(handle, path)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>setModelMmproj(path, mmproj)<\/code><\/td>\n<td>\u041f\u0435\u0440\u0441\u0438\u0441\u0442\u0435\u043d\u0442\u043d\u0430\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440\u0430<\/td>\n<td><code>it.copy(mmprojPath = mmprojPath)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>autoFindMmproj(modelPath)<\/code><\/td>\n<td>\u0418\u0449\u0435\u0442 sibling <code>mmproj*.gguf<\/code><\/td>\n<td><code>n.endsWith(&quot;.gguf&quot;) &amp;amp;&amp;amp; n.startsWith(&quot;mmproj&quot;)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>importMmproj(uri)<\/code> \/ <code>downloadMmprojFromUrl(url)<\/code><\/td>\n<td>\u0418\u043c\u043f\u043e\u0440\u0442\/\u0441\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440\u0430<\/td>\n<td><code>ModelCompatibility.isProjectorFileName(name)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>deleteModel(model)<\/code><\/td>\n<td>\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430+\u0437\u0430\u043f\u0438\u0441\u0438<\/td>\n<td><code>File(modelInfo.path).delete()<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>generate(messages, onToken, docs)<\/code><\/td>\n<td>\u0422\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u0447\u0430\u0442: chat-template + \u044f\u0437\u044b\u043a + \u0441\u0442\u0440\u0438\u043c<\/td>\n<td><code>val templated = LocalLlmNative.nativeApplyChatTemplate(...)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>generateVision(messages, imagePath, ...)<\/code><\/td>\n<td>Vision: RGB \u2192 mtmd<\/td>\n<td><code>LocalLlmNative.nativeGenerateStreamingVision(handle, prompt, rgb, w, h, ...)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>buildVisionPrompt()<\/code><\/td>\n<td>\u0412\u044b\u0431\u043e\u0440 \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u043f\u043e \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0435<\/td>\n<td><code>if (arch.startsWith(&quot;gemma&quot;)) buildGemmaTurnPrompt(...) else buildChatmlPrompt(...)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>buildChatmlPrompt()<\/code> \/ <code>buildGemmaTurnPrompt()<\/code><\/td>\n<td>\u0420\u0443\u0447\u043d\u044b\u0435 \u0448\u0430\u0431\u043b\u043e\u043d\u044b Qwen\/Gemma \u0441 \u043c\u0435\u0434\u0438\u0430-\u043c\u0430\u0440\u043a\u0435\u0440\u043e\u043c<\/td>\n<td><code>sb.append(mediaMarker)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>buildManualPrompt()<\/code><\/td>\n<td>Fallback, \u0435\u0441\u043b\u0438 \u0443 \u043c\u043e\u0434\u0435\u043b\u0438 \u043d\u0435\u0442 chat-template<\/td>\n<td><code>when { arch.startsWith(&quot;qwen&quot;) -&amp;gt; ... }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>cancelGeneration()<\/code><\/td>\n<td>\u041e\u0442\u043c\u0435\u043d\u0430<\/td>\n<td><code>LocalLlmNative.nativeCancel(handle)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>resolveAnswerLanguageCode()\/answerLanguageName()<\/code><\/td>\n<td>\u042f\u0437\u044b\u043a \u043e\u0442\u0432\u0435\u0442\u0430 (auto \u2192 \u044f\u0437\u044b\u043a \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f)<\/td>\n<td><code>if (code.isBlank() \\|\\| code == &quot;auto&quot;) s.appLanguage else code<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>languageReminder()\/languageDirective()<\/code><\/td>\n<td>\u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 \u044f\u0437\u044b\u043a\u0430 \u0434\u043b\u044f \u043c\u043e\u0434\u0435\u043b\u0438<\/td>\n<td><code>&quot;\u041e\u0442\u0432\u0435\u0447\u0430\u0439 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u043e\u043c \u044f\u0437\u044b\u043a\u0435&hellip;&quot;<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>generateSuggestions(...)<\/code><\/td>\n<td>JSON-\u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438 + \u0444\u0438\u043b\u044c\u0442\u0440 \u044f\u0437\u044b\u043a\u0430<\/td>\n<td><code>.filter { suggestionMatchesLanguage(it, langCode) }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>buildSuggestionPrompt(...)<\/code><\/td>\n<td>\u041f\u0440\u043e\u043c\u043f\u0442 \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043e\u043a <strong>\u043d\u0430 \u044f\u0437\u044b\u043a\u0435-\u0446\u0435\u043b\u0438<\/strong> (ru\/de\/en)<\/td>\n<td><code>private data class SuggestionStrings(...)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>suggestionMatchesLanguage(t, code)<\/code><\/td>\n<td>\u041e\u0442\u0441\u0435\u0432 \u0438\u043d\u043e\u044f\u0437\u044b\u0447\u043d\u044b\u0445 \u0447\u0438\u043f\u043e\u0432<\/td>\n<td><code>&quot;ru&quot; -&amp;gt; hasCyr<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>parsePartial()<\/code> \/ <code>parseSuggestions()<\/code> \/ <code>unescape()<\/code><\/td>\n<td>\u041f\u0430\u0440\u0441\u0438\u043d\u0433 JSON-\u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043e\u043a (\u0442\u0435\u0440\u043f\u0438\u043c\u044b\u0439)<\/td>\n<td><code>Regex(&quot;\\\\[(.*?)\\\\]&quot;, DOT_MATCHES_ALL)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>formatBytes()<\/code><\/td>\n<td>\u0427\u0435\u043b\u043e\u0432\u0435\u043a\u043e\u0447\u0438\u0442\u0430\u0435\u043c\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440<\/td>\n<td><code>&quot;%.1f GB&quot;.format(bytes \/ 1_073_741_824.0)<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>4.5 GGUF-\u043f\u0430\u0440\u0441\u0435\u0440 \u0438 \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u044c \u2014 <code>data\/llm\/<\/code><\/h3>\n<table>\n<thead>\n<tr>\n<th>\u0424\u0443\u043d\u043a\u0446\u0438\u044f<\/th>\n<th>\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442<\/th>\n<th>\u041a\u043e\u0434<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>GgufReader.readHeaders(ctx, uri)<\/code> \/ <code>readHeadersFromFile(file)<\/code><\/td>\n<td>\u0427\u0442\u0435\u043d\u0438\u0435 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 GGUF<\/td>\n<td><code>context.contentResolver.openInputStream(uri)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>readHeadersFromStream()<\/code><\/td>\n<td>Magic\/version\/tensors\/KV<\/td>\n<td><code>if (String(magic) != &quot;GGUF&quot;)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>readGgufValue(buf, type)<\/code><\/td>\n<td>\u0420\u0430\u0437\u0431\u043e\u0440 \u0442\u0438\u043f\u043e\u0432 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 GGUF<\/td>\n<td><code>8 -&amp;gt; { val len = buffer.long ... }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>deriveArchitecture(meta)<\/code><\/td>\n<td>Fallback-\u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u043f\u043e \u043a\u043b\u044e\u0447\u0430\u043c<\/td>\n<td><code>metadata.keys.any { it.startsWith(&quot;$arch.&quot;) }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>ModelCompatibility.isArchSupported(arch)<\/code><\/td>\n<td>\u0413\u0435\u0439\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0445 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440<\/td>\n<td><code>arch.lowercase() in SUPPORTED_ARCHS<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>isModelFileName(name)<\/code><\/td>\n<td>\u041e\u0442\u0441\u0435\u0432 mmproj\/lora\/split<\/td>\n<td><code>NON_MODEL_MARKERS.any { n.contains(it) }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>isProjectorFileName(name)<\/code><\/td>\n<td>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 mmproj<\/td>\n<td><code>n.endsWith(&quot;.gguf&quot;) &amp;amp;&amp;amp; n.contains(&quot;mmproj&quot;)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>isVisionArch(arch)<\/code><\/td>\n<td>Vision-\u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b<\/td>\n<td><code>arch.lowercase() in VISION_ARCHS<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>4.6 \u0421\u0435\u0442\u044c \u2014 <code>data\/llm\/HuggingFaceClient.kt<\/code><\/h3>\n<table>\n<thead>\n<tr>\n<th>\u0424\u0443\u043d\u043a\u0446\u0438\u044f<\/th>\n<th>\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442<\/th>\n<th>\u041a\u043e\u0434<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>searchGgufModels(q, maxBytes, ...)<\/code><\/td>\n<td>\u041f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0435\u0432<\/td>\n<td><code>Semaphore(MAX_CONCURRENT_REQUESTS)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>collectRepoFiles(repo, ...)<\/code><\/td>\n<td>\u041e\u0431\u0445\u043e\u0434\u0438\u0442 \u0434\u0435\u0440\u0435\u0432\u043e \u0440\u0435\u043f\u043e, \u0444\u0438\u043b\u044c\u0442\u0440\u0443\u0435\u0442 .gguf<\/td>\n<td><code>$BASE\/api\/models\/${encodePath(repoId)}\/tree\/main?recursive=true<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>encodePath(path)<\/code><\/td>\n<td>URL-\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0443\u0442\u0438<\/td>\n<td><code>URLEncoder.encode(it, &quot;UTF-8&quot;).replace(&quot;+&quot;, &quot;%20&quot;)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>httpGetJsonArray(url)<\/code><\/td>\n<td>HTTP GET \u2192 JSONArray<\/td>\n<td><code>conn.setRequestProperty(&quot;User-Agent&quot;, USER_AGENT)<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>4.7 \u0413\u043e\u043b\u043e\u0441 \u2014 <code>data\/voice\/<\/code><\/h3>\n<table>\n<thead>\n<tr>\n<th>\u0424\u0443\u043d\u043a\u0446\u0438\u044f<\/th>\n<th>\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442<\/th>\n<th>\u041a\u043e\u0434<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>SttManager.startListening(lang, onResult)<\/code><\/td>\n<td>\u0420\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u0435 \u0440\u0435\u0447\u0438<\/td>\n<td><code>Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>SttManager.stopListening()\/destroy()<\/code><\/td>\n<td>\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430\/\u043e\u0441\u0432\u043e\u0431\u043e\u0436\u0434\u0435\u043d\u0438\u0435 STT<\/td>\n<td><code>speechRecognizer.destroy()<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>onResults<\/code> \/ <code>onPartialResults<\/code><\/td>\n<td>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u044f<\/td>\n<td><code>results?.getStringArrayList(RESULTS_RECOGNITION)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>TtsManager.initialize()<\/code><\/td>\n<td>\u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f TTS<\/td>\n<td><code>TextToSpeech(context) { status -&amp;gt; ... }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>TtsManager.setLanguage(lang)<\/code><\/td>\n<td>\u041b\u043e\u043a\u0430\u043b\u044c ru\/de\/en<\/td>\n<td><code>&quot;ru&quot; -&amp;gt; Locale(&quot;ru&quot;, &quot;RU&quot;)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>TtsManager.speak(text)<\/code><\/td>\n<td>\u041e\u0437\u0432\u0443\u0447\u043a\u0430 \u043f\u043e\u043b\u043d\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430 (\u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u0435\u0442 \u043e\u0447\u0435\u0440\u0435\u0434\u044c)<\/td>\n<td><code>fun speak(text: String) = speakChunk(text, flush = true)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>TtsManager.speakChunk(text, flush)<\/code><\/td>\n<td>\u041f\u043e\u0442\u043e\u043a\u043e\u0432\u0430\u044f \u043e\u0437\u0432\u0443\u0447\u043a\u0430: \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442 \u0438\u043b\u0438 \u0441\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u043e\u0447\u0435\u0440\u0435\u0434\u044c<\/td>\n<td><code>val mode = if (flush) TextToSpeech.QUEUE_FLUSH else TextToSpeech.QUEUE_ADD<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>TtsManager.stop()\/shutdown()<\/code><\/td>\n<td>\u0421\u0442\u043e\u043f\/\u043e\u0441\u0432\u043e\u0431\u043e\u0436\u0434\u0435\u043d\u0438\u0435<\/td>\n<td><code>tts?.shutdown()<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>4.8 UI-\u044d\u043a\u0440\u0430\u043d\u044b \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b<\/h3>\n<table>\n<thead>\n<tr>\n<th>\u0424\u0443\u043d\u043a\u0446\u0438\u044f<\/th>\n<th>\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442<\/th>\n<th>\u041a\u043e\u0434<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>ChatScreen(...)<\/code><\/td>\n<td>\u0427\u0430\u0442: \u0448\u0430\u043f\u043a\u0430 \u0441 \u041e\u0417\u0423, \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u0432\u0432\u043e\u0434, \u0447\u0438\u043f\u044b<\/td>\n<td><code>val ramFree by viewModel.ramFree.collectAsState()<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u0410\u0432\u0442\u043e\u0441\u043a\u0440\u043e\u043b\u043b \u0447\u0430\u0442\u0430<\/td>\n<td>\u0412\u043e \u0432\u0440\u0435\u043c\u044f \u0441\u0442\u0440\u0438\u043c\u0430 \u0434\u0435\u0440\u0436\u0438\u0442 \u043a\u043e\u043d\u0435\u0446 \u043e\u0442\u0432\u0435\u0442\u0430, \u043f\u043e\u0441\u043b\u0435 \u0433\u043e\u0442\u043e\u0432\u043d\u043e\u0441\u0442\u0438 \u0447\u0438\u043f\u043e\u0432 \u043f\u0440\u044b\u0433\u0430\u0435\u0442 \u043a \u043d\u0430\u0447\u0430\u043b\u0443 \u043e\u0442\u0432\u0435\u0442\u0430<\/td>\n<td><code>listState.scrollToItem(index, overflow)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>AnswerFooter(message)<\/code><\/td>\n<td>\u041f\u043e\u0434 \u043a\u0430\u0436\u0434\u044b\u043c \u043e\u0442\u0432\u0435\u0442\u043e\u043c: copy \u0432 \u0431\u0443\u0444\u0435\u0440, \u043f\u043e\u043b\u043d\u044b\u0439 timestamp (\u0434\u0430\u0442\u0430\/\u0432\u0440\u0435\u043c\u044f\/\u0441\u0435\u043a\u0443\u043d\u0434\u044b), \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 (\u043c\u043c:\u0441\u0441), \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c (t\/s); \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0430 \u0438\u0437 <code>ChatMessage.generationMs<\/code>\/<code>tokenCount<\/code><\/td>\n<td><code>clipboard.setText(AnnotatedString(message.content))<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>MarkdownMessage(text)<\/code> \/ <code>CodeBlock<\/code><\/td>\n<td>\u041a\u043e\u0434-\u0431\u043b\u043e\u043a\u0438 \u0441 \u043f\u043e\u0434\u043f\u0438\u0441\u044c\u044e \u044f\u0437\u044b\u043a\u0430, \u043f\u043e\u0434\u0441\u0432\u0435\u0442\u043a\u043e\u0439 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441\u0430 \u0438 \u043a\u043d\u043e\u043f\u043a\u043e\u0439 copy \u0443 \u0431\u043b\u043e\u043a\u0430<\/td>\n<td><code>FENCE.findAll(text)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>takePhoto()<\/code><\/td>\n<td>\u0421\u044a\u0451\u043c\u043a\u0430 \u0447\u0435\u0440\u0435\u0437 <code>FileProvider<\/code><\/td>\n<td><code>FileProvider.getUriForFile(appContext, &quot;${appContext.packageName}.fileprovider&quot;, file)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>sendCurrent()<\/code><\/td>\n<td>\u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 + \u0441\u043a\u0440\u044b\u0442\u0438\u0435 \u043a\u043b\u0430\u0432\u0438\u0430\u0442\u0443\u0440\u044b<\/td>\n<td><code>viewModel.sendMessage()<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>MessageBubble(...)<\/code><\/td>\n<td>\u041f\u0443\u0437\u044b\u0440\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f (+\u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0430)<\/td>\n<td><code>val isUser = message.role == &quot;user&quot;<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>chipsContent<\/code>\/<code>suggestionsArea<\/code><\/td>\n<td>\u0427\u0438\u043f\u044b \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043e\u043a (wrap\/single-row)<\/td>\n<td><code>AssistChip(onClick = { viewModel.onSuggestionClicked(suggestion) })<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u043c\u0435\u043d\u044e \u00ab+\u00bb<\/td>\n<td>\u041e\u0434\u043d\u0430 \u043a\u043d\u043e\u043f\u043a\u0430 \u2192 \u043c\u0435\u043d\u044e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\/\u0433\u0430\u043b\u0435\u0440\u0435\u044f\/\u043a\u0430\u043c\u0435\u0440\u0430<\/td>\n<td><code>DropdownMenu(expanded = showAttachMenu, ...)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>ModelPickerScreen(...)<\/code><\/td>\n<td>\u0421\u043f\u0438\u0441\u043e\u043a \u043c\u043e\u0434\u0435\u043b\u0435\u0439, \u0438\u043c\u043f\u043e\u0440\u0442\/\u0441\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435\/\u043f\u043e\u0438\u0441\u043a\/\u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440\u044b<\/td>\n<td><code>filePickerLauncher.launch(arrayOf(&quot;*\/*&quot;))<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>ModelCard(...)<\/code><\/td>\n<td>\u041a\u0430\u0440\u0442\u043e\u0447\u043a\u0430 \u043c\u043e\u0434\u0435\u043b\u0438 + \u0441\u0442\u0430\u0442\u0443\u0441 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440\u0430<\/td>\n<td><code>val projectorName = model.mmprojPath?.let { File(it).name }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>DownloadModelDialog<\/code> \/ <code>SearchModelsDialog<\/code> \/ <code>ProjectorDownloadDialog<\/code><\/td>\n<td>\u0414\u0438\u0430\u043b\u043e\u0433\u0438 URL\/\u043f\u043e\u0438\u0441\u043a\u0430\/\u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440\u0430<\/td>\n<td><code>viewModel.downloadModelFromUrl(url, name)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>SettingsScreen(...)<\/code><\/td>\n<td>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 (\u0442\u0435\u043c\u043f., \u044f\u0437\u044b\u043a\u0438, \u0442\u0435\u043c\u0430, layout)<\/td>\n<td><code>viewModel.updateSettings(currentSettings)<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u041f\u043e\u043b\u0435 ctx size<\/td>\n<td>\u0427\u0438\u0441\u043b\u043e\u0432\u043e\u0439 \u0432\u0432\u043e\u0434 512..131072; \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043c\u043e\u0434\u0435\u043b\u044c\u044e<\/td>\n<td><code>currentSettings.copy(contextLength = v.coerceIn(512, 131072))<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u0414\u0438\u0430\u043b\u043e\u0433 \u0431\u044b\u0441\u0442\u0440\u044b\u0445 \u0437\u0430\u0433\u0440\u0443\u0437\u043e\u043a<\/td>\n<td>\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0432 \u043e\u0434\u0438\u043d \u0442\u0430\u043f: Gemma 4 E2B + mmproj Q8 \u0438 Qwen2-VL 2B + mmproj Q8<\/td>\n<td><code>QUICK_DOWNLOADS<\/code> + <code>viewModel.downloadModelFromUrl(url, fileName)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>DropdownSelector\/SliderSetting\/SwitchSetting\/SectionTitle<\/code><\/td>\n<td>\u0412\u0438\u0434\u0436\u0435\u0442\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a<\/td>\n<td><code>var expanded by remember { mutableStateOf(false) }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>DrawerContent(...)<\/code><\/td>\n<td>\u042f\u0449\u0438\u043a: \u043d\u043e\u0432\u044b\u0439 \u0447\u0430\u0442, \u043c\u043e\u0434\u0435\u043b\u0438, \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438, \u0438\u0441\u0442\u043e\u0440\u0438\u044f<\/td>\n<td><code>Text(l(&quot;models&quot;), style = MaterialTheme.typography.bodyLarge)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>MarkdownText(...)<\/code> \/ <code>parseMarkdown<\/code> \/ <code>parseInline<\/code><\/td>\n<td>\u041b\u0451\u0433\u043a\u0438\u0439 markdown-\u0440\u0435\u043d\u0434\u0435\u0440<\/td>\n<td><code>val annotated = parseMarkdown(text, textColor)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>GGUFChatTemplateTheme(...)<\/code><\/td>\n<td>\u0422\u0435\u043c\u044b (light\/dark\/green\/orange\/light_blue\/yellow)<\/td>\n<td><code>enum class AppTheme { LIGHT, DARK, ... }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>Localization.getString(key, lang)<\/code><\/td>\n<td>\u041f\u0435\u0440\u0435\u0432\u043e\u0434 \u0441\u0442\u0440\u043e\u043a en\/ru\/de<\/td>\n<td><code>val selectedLang = if (translations.containsKey(lang)) lang else &quot;en&quot;<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>fmtRam(bytes)<\/code><\/td>\n<td>\u0424\u043e\u0440\u043c\u0430\u0442 \u041e\u0417\u0423 \u0432 \u0448\u0430\u043f\u043a\u0435<\/td>\n<td><code>&quot;%.1f GB&quot;.format(bytes \/ 1_073_741_824.0)<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>4.9 \u041d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 \u2014 <code>app\/src\/main\/cpp\/llama_jni.cpp<\/code><\/h3>\n<table>\n<thead>\n<tr>\n<th>JNI-\u0444\u0443\u043d\u043a\u0446\u0438\u044f<\/th>\n<th>\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442<\/th>\n<th>\u041a\u043e\u0434<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>nativeLoadModel<\/code><\/td>\n<td>\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043c\u043e\u0434\u0435\u043b\u0438 + \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0430<\/td>\n<td><code>session-&amp;gt;model = llama_model_load_from_file(path.c_str(), mparams)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>nativeApplyChatTemplate<\/code><\/td>\n<td>\u041f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435 chat-template \u043c\u043e\u0434\u0435\u043b\u0438<\/td>\n<td><code>llama_chat_apply_template(tmpl, messages.data(), messages.size(), true, ...)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>nativeGenerateStreaming<\/code><\/td>\n<td>\u0422\u0435\u043a\u0441\u0442\u043e\u0432\u0430\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0441\u0442\u0440\u0438\u043c\u043e\u043c<\/td>\n<td><code>llama_tokenize(...)<\/code>, <code>llama_decode(s-&amp;gt;ctx, batch)<\/code>, <code>llama_sampler_sample(...)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>nativeLoadMmproj<\/code><\/td>\n<td>\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 mmproj (mtmd)<\/td>\n<td><code>s-&amp;gt;mtmd = mtmd_init_from_file(path.c_str(), s-&amp;gt;model, mp)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>nativeHasMmproj<\/code><\/td>\n<td>\u0415\u0441\u0442\u044c \u043b\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440<\/td>\n<td><code>s-&amp;gt;mtmd != nullptr<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>nativeMediaMarker<\/code><\/td>\n<td>\u041c\u0430\u0440\u043a\u0435\u0440 \u043c\u0435\u0441\u0442\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438<\/td>\n<td><code>mtmd_default_marker()<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>nativeGenerateStreamingVision<\/code><\/td>\n<td>Vision-\u0438\u043d\u0444\u0435\u0440\u0435\u043d\u0441<\/td>\n<td><code>mtmd_tokenize(...)<\/code>, <code>mtmd_helper_eval_chunks(...)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>nativeCancel<\/code><\/td>\n<td>\u0424\u043b\u0430\u0433 \u043e\u0442\u043c\u0435\u043d\u044b<\/td>\n<td><code>s-&amp;gt;cancel_flag.store(true, ...)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>nativeFreeModel<\/code><\/td>\n<td>\u041e\u0441\u0432\u043e\u0431\u043e\u0436\u0434\u0435\u043d\u0438\u0435 \u043c\u043e\u0434\u0435\u043b\u0438\/\u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440\u0430<\/td>\n<td><code>mtmd_free(s-&amp;gt;mtmd)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>nativeFreeMemory<\/code><\/td>\n<td>\u0412\u043e\u0437\u0432\u0440\u0430\u0442 \u0441\u0432\u043e\u0431\u043e\u0434\u043d\u043e\u0439 \u043d\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438 \u041e\u0421<\/td>\n<td><code>mallopt(M_PURGE, 0)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>nativeIsModelLoaded<\/code><\/td>\n<td>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438<\/td>\n<td><code>s-&amp;gt;ready<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>\u0421\u044d\u043c\u043f\u043b\u0435\u0440 (<code>make_sampler<\/code>): <code>llama_sampler_init_penalties(...)<\/code> \u2192 <code>llama_sampler_init_temp(temp)<\/code> \u2192 <code>llama_sampler_init_top_p(topP, 1)<\/code> \u2192 <code>llama_sampler_init_dist(LLAMA_DEFAULT_SEED)<\/code>.<\/p>\n<h3>4.10 \u0421\u0431\u043e\u0440\u043a\u0430 \u2014 <code>CMakeLists.txt<\/code> \/ Gradle<\/h3>\n<table>\n<thead>\n<tr>\n<th>\u0424\u0430\u0439\u043b<\/th>\n<th>\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442<\/th>\n<th>\u041a\u043e\u0434<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>app\/src\/main\/cpp\/CMakeLists.txt<\/code><\/td>\n<td>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442 llama.cpp-new + mtmd<\/td>\n<td><code>set(LLAMA_CPP_DIR &quot;...\/llama.cpp-new&quot;)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>add_library(llama_jni ...)<\/code><\/td>\n<td>\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u0442 JNI-\u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443<\/td>\n<td><code>target_link_libraries(llama_jni mtmd llama ggml android log)<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>app\/build.gradle.kts<\/code><\/td>\n<td>\u041a\u043e\u043d\u0444\u0438\u0433 Android\/NDK\/Release<\/td>\n<td><code>arguments += &quot;-DCMAKE_BUILD_TYPE=Release&quot;<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>settings.gradle.kts<\/code><\/td>\n<td>\u041c\u043e\u0434\u0443\u043b\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/td>\n<td><code>include(&quot;:app&quot;)<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>5. TODO (\u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u2014 \u0441\u0442\u0430\u0442\u0443\u0441 \u043d\u0430 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442)<\/h2>\n<table>\n<thead>\n<tr>\n<th>#<\/th>\n<th>\u0424\u0438\u0447\u0430<\/th>\n<th>\u0421\u0442\u0430\u0442\u0443\u0441<\/th>\n<th>\u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>1<\/td>\n<td>\u0427\u0430\u0442 (Compose, \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u0430\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f)<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>ChatScreen<\/code> + <code>nativeGenerateStreaming<\/code><\/td>\n<\/tr>\n<tr>\n<td>2<\/td>\n<td>\u0418\u043c\u043f\u043e\u0440\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>importModel<\/code><\/td>\n<\/tr>\n<tr>\n<td>3<\/td>\n<td>\u0421\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u043f\u043e URL<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>downloadModelFromUrl<\/code> + \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0446\u0435\u043b\u043e\u0441\u0442\u043d\u043e\u0441\u0442\u0438<\/td>\n<\/tr>\n<tr>\n<td>4<\/td>\n<td>\u041f\u043e\u0438\u0441\u043a \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u043d\u0430 Hugging Face<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>HuggingFaceClient.searchGgufModels<\/code><\/td>\n<\/tr>\n<tr>\n<td>5<\/td>\n<td>Vision-\u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0440 (mmproj): \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0430\/\u0438\u043c\u043f\u043e\u0440\u0442\/\u0441\u043a\u0430\u0447\u0438\u0432\u0430\u043d\u0438\u0435<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>attachMmprojToModel<\/code>, <code>importMmproj<\/code><\/td>\n<\/tr>\n<tr>\n<td>6<\/td>\n<td>\u0410\u0432\u0442\u043e-\u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 mmproj \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f vision-\u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440 + \u0447\u0438\u0441\u0442\u043a\u0430 stale-\u043f\u0440\u0438\u0432\u044f\u0437\u043e\u043a<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>isVisionArch<\/code> \u0432 <code>loadModel<\/code>\/<code>loadModelFromPrefs<\/code><\/td>\n<\/tr>\n<tr>\n<td>7<\/td>\n<td>Multimodal-\u0438\u043d\u0444\u0435\u0440\u0435\u043d\u0441 (mtmd) Qwen2-VL \/ Gemma 4 E2B<\/td>\n<td>\u2705 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043e<\/td>\n<td>\u043e\u0442\u0432\u0435\u0442 \u00ab7391\u00bb \u043d\u0430 \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0439 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0435<\/td>\n<\/tr>\n<tr>\n<td>8<\/td>\n<td>Chat-templates: native + \u0440\u0443\u0447\u043d\u043e\u0439 fallback (Qwen\/Gemma\/Llama\/Phi)<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>buildManualPrompt<\/code><\/td>\n<\/tr>\n<tr>\n<td>9<\/td>\n<td>\u042f\u0437\u044b\u043a \u043e\u0442\u0432\u0435\u0442\u0430 \u043c\u043e\u0434\u0435\u043b\u0438 = \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 (text + vision)<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>languageReminder<\/code> \u0432 \u043e\u0431\u043e\u0438\u0445 \u043f\u0443\u0442\u044f\u0445<\/td>\n<\/tr>\n<tr>\n<td>10<\/td>\n<td>\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438-\u0447\u0438\u043f\u044b (\u0441\u0442\u0440\u0438\u043c, \u0434\u0435\u0434\u0443\u043f, \u044f\u0437\u044b\u043a-\u0446\u0435\u043b\u044c, \u0444\u0438\u043b\u044c\u0442\u0440 \u044f\u0437\u044b\u043a\u0430)<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>buildSuggestionPrompt<\/code>, <code>suggestionMatchesLanguage<\/code><\/td>\n<\/tr>\n<tr>\n<td>11<\/td>\n<td>\u0427\u0430\u0442 \u0441 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043c\u0438 (txt\/md\/json\/html\/csv)<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>DocumentReader<\/code><\/td>\n<\/tr>\n<tr>\n<td>12<\/td>\n<td>STT (\u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u0432\u0432\u043e\u0434)<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>SttManager<\/code><\/td>\n<\/tr>\n<tr>\n<td>13<\/td>\n<td>TTS (\u043e\u0437\u0432\u0443\u0447\u043a\u0430)<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>TtsManager<\/code> \u2014 \u043f\u043e\u0442\u043e\u043a\u043e\u0432\u0430\u044f, \u043f\u043e \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u0430\u043c \u043f\u0440\u0438 \u043f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u0438 \u0437\u043d\u0430\u043a\u043e\u0432 \u043f\u0440\u0435\u043f\u0438\u043d\u0430\u043d\u0438\u044f<\/td>\n<\/tr>\n<tr>\n<td>14<\/td>\n<td>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 (temp, top_p, max tokens, ctx, threads, \u044f\u0437\u044b\u043a\u0438, \u0442\u0435\u043c\u044b, layout)<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>SettingsScreen<\/code><\/td>\n<\/tr>\n<tr>\n<td>15<\/td>\n<td>\u041b\u043e\u043a\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f en\/ru\/de + 6 \u0442\u0435\u043c<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>Localization<\/code>, <code>AppTheme<\/code><\/td>\n<\/tr>\n<tr>\n<td>16<\/td>\n<td>\u041c\u043e\u043d\u0438\u0442\u043e\u0440 \u041e\u0417\u0423 + \u043a\u043d\u043e\u043f\u043a\u0430 \u00ab\u041e\u0441\u0432\u043e\u0431\u043e\u0434\u0438\u0442\u044c \u041e\u0417\u0423\u00bb<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>refreshMemory<\/code>, <code>freeRam<\/code><\/td>\n<\/tr>\n<tr>\n<td>17<\/td>\n<td>\u041a\u043d\u043e\u043f\u043a\u0430 \u00ab+\u00bb \u0441 \u043c\u0435\u043d\u044e (\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\/\u0433\u0430\u043b\u0435\u0440\u0435\u044f\/\u043a\u0430\u043c\u0435\u0440\u0430)<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>DropdownMenu<\/code><\/td>\n<\/tr>\n<tr>\n<td>18<\/td>\n<td>\u0413\u0435\u0439\u0442 \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u0438 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>ModelCompatibility<\/code><\/td>\n<\/tr>\n<tr>\n<td>19<\/td>\n<td>\u041f\u0430\u0440\u0441\u0435\u0440 GGUF-\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u0432<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>GgufReader<\/code><\/td>\n<\/tr>\n<tr>\n<td>20<\/td>\n<td>Q8_0 mmproj \u0438\u0437 ggml-org \u043d\u0430 \u043d\u043e\u0432\u043e\u043c mtmd<\/td>\n<td>\u2705 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043e<\/td>\n<td>\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442<\/td>\n<\/tr>\n<tr>\n<td>21<\/td>\n<td>GPU\/NNAPI-\u0443\u0441\u043a\u043e\u0440\u0435\u043d\u0438\u0435 \u0438\u043d\u0444\u0435\u0440\u0435\u043d\u0441\u0430<\/td>\n<td>\u23f3 \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u043d\u043e<\/td>\n<td>\u0441\u0435\u0439\u0447\u0430\u0441 CPU-only, <code>mtmd use_gpu=false<\/code><\/td>\n<\/tr>\n<tr>\n<td>22<\/td>\n<td>UI \u0434\u043b\u044f sampler (top_k, repeat\/presence, seed)<\/td>\n<td>\u23f3 \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u043d\u043e<\/td>\n<td>\u0432 UI \u0442\u043e\u043b\u044c\u043a\u043e temperature\/top_p<\/td>\n<\/tr>\n<tr>\n<td>23<\/td>\n<td>\u042d\u043a\u0441\u043f\u043e\u0440\u0442\/\u0438\u043c\u043f\u043e\u0440\u0442 \u0447\u0430\u0442\u043e\u0432<\/td>\n<td>\u23f3 \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u043d\u043e<\/td>\n<td>\u0441\u0435\u0441\u0441\u0438\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0432 <code>files\/sessions<\/code><\/td>\n<\/tr>\n<tr>\n<td>24<\/td>\n<td>RAG\/\u044d\u043c\u0431\u0435\u0434\u0434\u0438\u043d\u0433\u0438 \u0434\u043b\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432<\/td>\n<td>\u23f3 \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u043d\u043e<\/td>\n<td>\u0441\u0435\u0439\u0447\u0430\u0441 \u043f\u0440\u043e\u0441\u0442\u0430\u044f \u043e\u0431\u0440\u0435\u0437\u043a\u0430 \u043f\u043e \u0431\u044e\u0434\u0436\u0435\u0442\u0443<\/td>\n<\/tr>\n<tr>\n<td>25<\/td>\n<td>LoRA\/adapter<\/td>\n<td>\u23f3 \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u043d\u043e<\/td>\n<td>\u043c\u0430\u0440\u043a\u0435\u0440\u044b \u0435\u0441\u0442\u044c, \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043d\u0435\u0442<\/td>\n<\/tr>\n<tr>\n<td>26<\/td>\n<td>Split-GGUF (<code>-00001-of-0000N<\/code>)<\/td>\n<td>\u23f3 \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u043d\u043e<\/td>\n<td>\u0447\u0430\u0441\u0442\u0438 \u043e\u0442\u043a\u043b\u043e\u043d\u044f\u044e\u0442\u0441\u044f<\/td>\n<\/tr>\n<tr>\n<td>27<\/td>\n<td>\u0422\u043e\u0447\u043d\u044b\u0439 \u043f\u043e\u0434\u0441\u0447\u0451\u0442 \u0442\u043e\u043a\u0435\u043d\u043e\u0432 \u0447\u0435\u0440\u0435\u0437 \u043d\u0430\u0442\u0438\u0432\u043d\u0443\u044e \u043b\u0435\u043a\u0441\u0438\u043a\u0443<\/td>\n<td>\u23f3 \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u043d\u043e<\/td>\n<td>\u044d\u0432\u0440\u0438\u0441\u0442\u0438\u043a\u0430 <code>DocumentReader<\/code><\/td>\n<\/tr>\n<tr>\n<td>28<\/td>\n<td>\u041c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u0432 UI<\/td>\n<td>\u23f3 \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u043d\u043e<\/td>\n<td><code>GgufInfo.metadata<\/code> \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f, \u044d\u043a\u0440\u0430\u043d\u0430 \u043d\u0435\u0442<\/td>\n<\/tr>\n<tr>\n<td>29<\/td>\n<td>Vision \u0434\u043b\u044f gemma3n\/llama4\/minicpm<\/td>\n<td>\u23f3 \u043d\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043e<\/td>\n<td>\u0432 \u0441\u043f\u0438\u0441\u043a\u0435 <code>VISION_ARCHS<\/code>, \u043d\u043e \u043d\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043b\u043e\u0441\u044c<\/td>\n<\/tr>\n<tr>\n<td>30<\/td>\n<td>\u041e\u0444\u043b\u0430\u0439\u043d-STT (whisper) \u0432\u043c\u0435\u0441\u0442\u043e \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u043e\u0433\u043e<\/td>\n<td>\u23f3 \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u043d\u043e<\/td>\n<td>\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f <code>SpeechRecognizer<\/code><\/td>\n<\/tr>\n<tr>\n<td>31<\/td>\n<td>\u041c\u043e\u0434\u0435\u043b\u044c \u043d\u0435 \u0433\u0440\u0443\u0437\u0438\u0442\u0441\u044f \u0434\u0432\u0430\u0436\u0434\u044b \/ \u043f\u0440\u043e\u0433\u0440\u0435\u0432<\/td>\n<td>\u23f3 \u0431\u044d\u043a\u043b\u043e\u0433<\/td>\n<td><code>freeRam<\/code> \u0432\u044b\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043c\u043e\u0434\u0435\u043b\u044c<\/td>\n<\/tr>\n<tr>\n<td>32<\/td>\n<td>Unit\/UI-\u0442\u0435\u0441\u0442\u044b \u0438 CI<\/td>\n<td>\u23f3 \u043d\u0435 \u0441\u0434\u0435\u043b\u0430\u043d\u043e<\/td>\n<td>\u0442\u0435\u0441\u0442\u043e\u0432 \u043d\u0435\u0442<\/td>\n<\/tr>\n<tr>\n<td>33<\/td>\n<td>\u041f\u043e\u0442\u043e\u043a\u043e\u0432\u044b\u0439 TTS (\u043e\u0437\u0432\u0443\u0447\u043a\u0430 \u0444\u0440\u0430\u0433\u043c\u0435\u043d\u0442\u043e\u0432 \u043f\u043e \u043c\u0435\u0440\u0435 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438)<\/td>\n<td>\u2705 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043e<\/td>\n<td>8 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0441\u0438\u043d\u0442\u0435\u0437\u0430 \u0437\u0430 \u043e\u0434\u0438\u043d \u043e\u0442\u0432\u0435\u0442<\/td>\n<\/tr>\n<tr>\n<td>34<\/td>\n<td>\u0423\u043c\u043d\u044b\u0439 \u0430\u0432\u0442\u043e\u0441\u043a\u0440\u043e\u043b\u043b (\u0437\u0430 \u043a\u043e\u043d\u0446\u043e\u043c, \u043a \u043d\u0430\u0447\u0430\u043b\u0443 \u043f\u043e\u0441\u043b\u0435 \u0447\u0438\u043f\u043e\u0432)<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>LaunchedEffect<\/code> \u0432 <code>ChatScreen<\/code><\/td>\n<\/tr>\n<tr>\n<td>35<\/td>\n<td>\u0424\u0443\u0442\u0435\u0440 \u043e\u0442\u0432\u0435\u0442\u0430: copy, \u043f\u043e\u043b\u043d\u044b\u0439 timestamp, \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438, \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0442\u043e\u043a\u0435\u043d\u043e\u0432<\/td>\n<td>\u2705 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043e<\/td>\n<td>\u00ab2026-10-05 06:25:39 \u00b7 0:33 \u00b7 1.3 t\/s\u00bb<\/td>\n<\/tr>\n<tr>\n<td>36<\/td>\n<td>\u041a\u043e\u0434-\u0431\u043b\u043e\u043a\u0438 markdown \u0441 \u043f\u043e\u0434\u0441\u0432\u0435\u0442\u043a\u043e\u0439 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441\u0430 \u0438 copy \u0443 \u0431\u043b\u043e\u043a\u0430<\/td>\n<td>\u2705 \u0433\u043e\u0442\u043e\u0432\u043e<\/td>\n<td><code>MarkdownMessage.kt<\/code><\/td>\n<\/tr>\n<tr>\n<td>37<\/td>\n<td>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 ctx size \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 (512..131072, \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u043f\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c\u0443 \u043c\u043e\u0434\u0435\u043b\u0438)<\/td>\n<td>\u2705 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043e<\/td>\n<td><code>coerceIn(512, modelMaxCtx)<\/code>, \u0432 \u0448\u0430\u043f\u043a\u0435 \u00ab\u00b7 8192 ctx\u00bb<\/td>\n<\/tr>\n<tr>\n<td>38<\/td>\n<td>\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043f\u0440\u0435\u0441\u0435\u0442\u043e\u0432 \u0432 \u043e\u0434\u0438\u043d \u0442\u0430\u043f (Gemma 4 E2B, Qwen2-VL 2B + mmproj Q8)<\/td>\n<td>\u2705 \u0441\u043e\u0431\u0440\u0430\u043d\u043e<\/td>\n<td>\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u043e\u0436\u0438\u0434\u0430\u0435\u0442<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<hr \/>\n<h2>\u0421\u0441\u044b\u043b\u043a\u0438<\/h2>\n<ul>\n<li>GitHub: <a href=\"https:\/\/github.com\/sashok53011\/gguf-chat-android\">https:\/\/github.com\/sashok53011\/gguf-chat-android<\/a><\/li>\n<li>git clone: <a href=\"https:\/\/github.com\/sashok53011\/gguf-chat-android.git\">https:\/\/github.com\/sashok53011\/gguf-chat-android.git<\/a><\/li>\n<\/ul>\n<p align=\"center\">\n  <figure id=\"attachment_mmd_1756\" class=\"wp-block-image \"><img decoding=\"async\" width=\"150\" height=\"150\" src=\"https:\/\/devhorizon.online\/wordpress\/wp-content\/uploads\/2026\/10\/gguf-chat-icon-150x150.png\" class=\"attachment-150x150 size-150x150\" alt=\"GGUF Chat\" loading=\"lazy\" \/><\/figure>\n  <br>\n  <a href=\"https:\/\/github.com\/sashok53011\/gguf-chat-android\/releases\/download\/v1.0-debug\/app-debug.apk\"><strong>\u0421\u043a\u0430\u0447\u0430\u0442\u044c APK (debug)<\/strong><\/a>\n<\/p>","protected":false},"excerpt":{"rendered":"<p>\u041d\u0430\u0442\u0438\u0432\u043d\u043e\u0435 Android-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 GGUF-\u043c\u043e\u0434\u0435\u043b\u0438 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044e \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u0447\u0435\u0440\u0435\u0437 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 llama.cpp; \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f\u043c\u0438 \u0438 \u0441\u043b\u043e\u0432\u0430\u0440\u0438 RU \u00b7 EN \u00b7 DE.<\/p>","protected":false},"author":10,"featured_media":1756,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[62],"tags":[],"class_list":["post-1753","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development-programming"],"_links":{"self":[{"href":"https:\/\/devhorizon.online\/wordpress\/wp-json\/wp\/v2\/posts\/1753","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devhorizon.online\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devhorizon.online\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devhorizon.online\/wordpress\/wp-json\/wp\/v2\/users\/10"}],"replies":[{"embeddable":true,"href":"https:\/\/devhorizon.online\/wordpress\/wp-json\/wp\/v2\/comments?post=1753"}],"version-history":[{"count":8,"href":"https:\/\/devhorizon.online\/wordpress\/wp-json\/wp\/v2\/posts\/1753\/revisions"}],"predecessor-version":[{"id":1806,"href":"https:\/\/devhorizon.online\/wordpress\/wp-json\/wp\/v2\/posts\/1753\/revisions\/1806"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devhorizon.online\/wordpress\/wp-json\/wp\/v2\/media\/1756"}],"wp:attachment":[{"href":"https:\/\/devhorizon.online\/wordpress\/wp-json\/wp\/v2\/media?parent=1753"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devhorizon.online\/wordpress\/wp-json\/wp\/v2\/categories?post=1753"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devhorizon.online\/wordpress\/wp-json\/wp\/v2\/tags?post=1753"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}