<?xml version="1.0" encoding="UTF-8"?>        <rss version="2.0"
             xmlns:atom="http://www.w3.org/2005/Atom"
             xmlns:dc="http://purl.org/dc/elements/1.1/"
             xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
             xmlns:admin="http://webns.net/mvcb/"
             xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
             xmlns:content="http://purl.org/rss/1.0/modules/content/">
        <channel>
            <title>
									Devhorizon Форум - Последние темы				            </title>
            <link>https://devhorizon.online/wordpress/community/</link>
            <description>Devhorizon Доска обсуждений</description>
            <language>ru-RU</language>
            <lastBuildDate>Fri, 16 Oct 2026 17:46:32 +0000</lastBuildDate>
            <generator>wpForo</generator>
            <ttl>60</ttl>
							                    <item>
                        <title>online.devhorizon.sourcecodemapper</title>
                        <link>https://devhorizon.online/wordpress/community/built/online-devhorizon-sourcecodemapper/</link>
                        <pubDate>Mon, 05 Oct 2026 22:29:38 +0000</pubDate>
                        <description><![CDATA[Source Code Mapper AI
English | Русский | Deutsch

Android-приложение: открывает локальный репозиторий или клонирует его с GitHub, строит «полную картину» кода и формирует интерактивный H...]]></description>
                        <content:encoded><![CDATA[<h1>Source Code Mapper AI</h1>
<p><a href="https://github.com/sashok53011/SourceCodeMapperAI/blob/main/README.md">English</a> | <a href="https://github.com/sashok53011/SourceCodeMapperAI/blob/main/README.ru.md">Русский</a> | <a href="https://github.com/sashok53011/SourceCodeMapperAI/blob/main/README.de.md">Deutsch</a></p>
<p><a href="https://github.com/sashok53011/SourceCodeMapperAI/actions/workflows/android.yml"><img src="https://github.com/sashok53011/SourceCodeMapperAI/actions/workflows/android.yml/badge.svg" alt="Android CI" /></a></p>
<p>Android-приложение: открывает локальный репозиторий или клонирует его с GitHub, строит «полную картину» кода и формирует интерактивный <strong>HTML-отчёт из 4 столбцов</strong> со сворачиваемыми блоками.</p>
<h2>Что делает</h2>
<ol>
<li><strong>Открыть локальный репозиторий</strong> — системный выбор папки (SAF, доступ только на чтение).</li>
<li><strong>Клонировать с GitHub</strong> — <code>owner/repo</code> или полный URL; по умолчанию git clone (JGit), при ошибке — автоматический откат на скачивание ZIP (<code>codeload.github.com</code>).</li>
<li><strong>Построить отчёт</strong> — гибридный конвейер:
<ul>
<li>на устройстве: обход файлов → статический разбор (файлы, классы, функции, методы, свойства, UI-элементы, триггеры, ресурсы манифеста, маршруты) → эвристики безопасности;</li>
<li>через AI: столбец 2 (описание) и столбец 4 (оценка), плюс обзор архитектуры;</li>
<li>генерация HTML и показ в WebView с экспортом/шерингом.</li>
</ul>
</li>
<li><strong>Express-отчёт</strong> — прогон одного и того же кода по всем настроенным моделям по очереди, затем сравнение результатов в <strong>супер-отчёт</strong> (согласие/расхождения моделей, мнения по каждому элементу, AI-разбор разногласий).</li>
<li><strong>AI-провайдеры</strong> — добавление, редактирование и удаление OpenAI-совместимых провайдеров, возврат к набору по умолчанию.</li>
</ol>
<h3>Правила анализа</h3>
<ul>
<li><strong>README, <code>*.md</code>, <code>*.txt</code>, лицензии, changelog и прочая документация не анализируются</strong> — учитывается только реальный исполняемый код.</li>
<li><strong>Комментарии в коде игнорируются</strong>: элементы строятся только по значимым строкам, комментарии и пустые строки в отчёт не попадают; при этом каждая строка кода находит место.</li>
<li>Если AI не уверен в назначении участка, он обязан пометить строку уровнем уверенности (<code>Подтверждено / Вероятно / Не подтверждено</code>) и указать краткое обоснование или причину, по которой подтвердить не удалось (столбец 4).</li>
</ul>
<h2>Отчёт: 4 столбца</h2>
<table>
<thead>
<tr>
<th>Колонка</th>
<th>Содержимое</th>
</tr>
</thead>
<tbody>
<tr>
<td>1. Наименование / имя / обозначение</td>
<td>Все элементы: файлы, классы, функции, методы, свойства, UI-элементы, триггеры и пр. Имя + краткое пояснение. В свёрнутом виде видна только эта суммаризация.</td>
</tr>
<tr>
<td>2. Способ / технология / метод / тип / класс</td>
<td>Подробное описание (AI + статика).</td>
</tr>
<tr>
<td>3. Конкретные строки</td>
<td>Номера строк и полное содержимое. Каждая строка файла попадает либо в свой элемент, либо в блок «Прочие строки» — покрытие 100%.</td>
</tr>
<tr>
<td>4. Оценка кода</td>
<td><code>Уязвимость / Проблема / Предупреждение / Норма / Лучшая практика</code> + пояснение. База — статические правила, уточняется AI.</td>
</tr>
</tbody>
</table>
<p>Всё свёрнуто по умолчанию: группа файла → строка элемента → 4-колоночная сетка. Любой текст, не вмещающийся в четверть ширины экрана (ячейка ~25%), получает кнопку <code>▸ / ▾</code>. Есть поиск, фильтр по оценке, «Развернуть всё / Свернуть всё».</p>
<h2>Языки интерфейса и отчёта</h2>
<p><strong>Английский — основной язык и язык по умолчанию.</strong> Русский и немецкий поддерживаются полностью: интерфейс, HTML-отчёт (заголовки, столбцы, уровни, супер-отчёт), статические описания элементов, сообщения и названия провайдеров. Выбор языка влияет и на язык ответов модели. Технический журнал анализа остаётся на английском (отладочная информация).</p>
<h2>Провайдеры ИИ (Настройки)</h2>
<table>
<thead>
<tr>
<th>Провайдер</th>
<th>Base URL</th>
<th>Модель по умолчанию</th>
<th>Ключ</th>
</tr>
</thead>
<tbody>
<tr>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td>Ollama Cloud</td>
<td><code>https://ollama.com/v1</code></td>
<td>задаётся пользователем</td>
<td>API-ключ или OAuth device flow</td>
</tr>
<tr>
<td>OpenCode Go</td>
<td><code>https://opencode.ai/zen/go/v1</code></td>
<td><code>space-bunny-free</code></td>
<td><code>sk-...</code></td>
</tr>
<tr>
<td>OpenCode Zen</td>
<td><code>https://opencode.ai/zen/v1</code></td>
<td><code>big-pickle</code></td>
<td><code>sk-...</code></td>
</tr>
</tbody>
</table>
<p>Кнопка «Проверить соединение» дергает <code>/models</code>. Ollama OAuth: приложение открывает <code>ollama.com/connect?...</code> и опрашивает API до появления ключа.</p>
<h2>Сборка</h2>
<pre contenteditable="false"><code class="lang-powershell language-powershell powershell">$env:JAVA_HOME="C:\Program Files\Microsoft\jdk-17.0.15.6-hotspot"
$env:ANDROID_HOME="C:\Android\Sdk"
.\gradlew.bat :app:assembleDebug</code></pre>
<p>APK: <code>app/build/outputs/apk/debug/app-debug.apk</code></p>
<p>Установка на эмулятор:</p>
<pre contenteditable="false"><code class="lang-powershell language-powershell powershell">adb -s emulator-5554 install -r -t app/build/outputs/apk/debug/app-debug.apk</code></pre>
<h2>Стек</h2>
<p>Kotlin (встроенный в AGP 9.1.1), Jetpack Compose (BOM 2024.09.00, Material3), coroutines, kotlinx-serialization-json, OkHttp, JGit, DocumentFile, Gradle 9.3.1, compileSdk 36 / minSdk 26.</p>
<h2>Структура исходников</h2>
<pre contenteditable="false"><code>app/src/main/java/online/devhorizon/sourcecodemapper/
  MainActivity.kt, MainViewModel.kt
  model/Models.kt
  i18n/Strings.kt                 # EN / RU / DE
  data/Settings.kt                # провайдеры, настройки, кэш
  data/RepoAccess.kt              # SAF + JGit + ZIP-фолбэк
  analysis/Language.kt
  analysis/Comments.kt            # фильтр комментариев и пустых строк
  analysis/StaticIndexer.kt       # извлечение элементов + покрытие строк
  analysis/SecurityRules.kt       # правила столбца 4
  analysis/Enricher.kt            # AI для столбцов 2 и 4
  analysis/ExpressMerger.kt       # супер-отчёт по нескольким моделям
  ai/AiClient.kt                  # 4 провайдера + Ollama OAuth
  report/HtmlReport.kt            # HTML: 4 колонки, сворачивание, поиск
  ui/                             # Compose-экраны</code></pre>
<h2>Ограничения</h2>
<ul>
<li>Лимиты по умолчанию: 2000 файлов, 320 КБ на файл, 25 МБ текста (настраиваются).</li>
<li>Скорость AI зависит от выбранной модели; результаты кэшируются по хэшу файла.</li>
<li>Для приватных репозиториев нужен GitHub-токен.</li>
</ul>
<h2>CI</h2>
<p>GitHub Actions собирает debug-APK при каждом пуше в <code>main</code> и в pull request (<code>.github/workflows/android.yml</code>). Пуш тега <code>v*</code> прикрепляет APK к GitHub Release.</p>
<hr />
<h3>Скриншоты</h3>
<ul>
<li><strong>Главный экран</strong> — источник репозитория: локальная папка через SAF, клон с GitHub (owner/repo, ветка, токен) либо встроенный пример; тут же выбор языка отчёта.<br /><a href="https://devhorizon.online/wordpress/wp-content/uploads/2026/10/sourcecodemapper-screen-home.png"><img src="https://devhorizon.online/wordpress/wp-content/uploads/2026/10/sourcecodemapper-screen-home.png" alt="Source Code Mapper AI — Главный экран" width="330" /></a></li>
<li><strong>Клонированный репозиторий</strong> — после клона: 38 файлов, 4756 строк, 203,7 КБ, разбивка по языкам (Kotlin 24 файла / 4200 строк, Text, BAT, XML, YAML, GITIGNORE, Properties, PRO) и кнопки «Build 4-column report» и «Express report (all models)».<br /><a href="https://devhorizon.online/wordpress/wp-content/uploads/2026/10/sourcecodemapper-screen-repo.png"><img src="https://devhorizon.online/wordpress/wp-content/uploads/2026/10/sourcecodemapper-screen-repo.png" alt="Source Code Mapper AI — Клонированный репозиторий" width="330" /></a></li>
<li><strong>Настройки и провайдеры ИИ</strong> — язык отчёта (RU / DE / EN) и список OpenAI-совместимых провайдеров: DevHorizon (primary) без API-ключа, добавление и редактирование своих.</li>
</ul>
<hr />
<h2>Скачать APK</h2>
<p><strong><a href="https://github.com/sashok53011/SourceCodeMapperAI/releases/download/v1.0-debug/app-debug.apk">app-debug.apk</a></strong> — 12,4 МБ, debug-сборка версии 1.0 (Android 8.0+, minSdk 26).</p>
<p>Страница релиза: <a href="https://github.com/sashok53011/SourceCodeMapperAI/releases/tag/v1.0-debug">v1.0-debug</a> · исходники: <a href="https://github.com/sashok53011/SourceCodeMapperAI">github.com/sashok53011/SourceCodeMapperAI</a>.</p>]]></content:encoded>
						                            <category domain="https://devhorizon.online/wordpress/community/"></category>                        <dc:creator>admin</dc:creator>
                        <guid isPermaLink="true">https://devhorizon.online/wordpress/community/built/online-devhorizon-sourcecodemapper/</guid>
                    </item>
				                    <item>
                        <title>online.devhorizon.scm</title>
                        <link>https://devhorizon.online/wordpress/community/built/online-devhorizon-scm/</link>
                        <pubDate>Mon, 05 Oct 2026 22:28:14 +0000</pubDate>
                        <description><![CDATA[SCM Analyzer
Языки: English · Русский · Deutsch
     
Android-приложение, которое анализирует локальные или склонированные с GitHub репозитории, строит глубокое архитектурное понимание ко...]]></description>
                        <content:encoded><![CDATA[<h1>SCM Analyzer</h1>
<p><strong>Языки:</strong> <a href="https://github.com/sashok53011/scm-analyzer/blob/main/README.md">English</a> · <strong>Русский</strong> · <a href="https://github.com/sashok53011/scm-analyzer/blob/main/README.de.md">Deutsch</a></p>
<p><a href="https://github.com/sashok53011/scm-analyzer/actions/workflows/android.yml"><img src="https://github.com/sashok53011/scm-analyzer/actions/workflows/android.yml/badge.svg" alt="Android CI" /></a> <a href="https://github.com/sashok53011/scm-analyzer/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a> <img src="https://img.shields.io/badge/platform-Android-3DDC84" alt="Platform" /> <img src="https://img.shields.io/badge/minSdk-26-blue" alt="minSdk" /> <img src="https://img.shields.io/badge/Kotlin-2.2.10-7F52FF" alt="Kotlin" /> <img src="https://img.shields.io/badge/languages-EN%20%7C%20RU%20%7C%20DE-blueviolet" alt="Languages" /></p>
<p>Android-приложение, которое анализирует локальные или склонированные с GitHub репозитории, строит глубокое архитектурное понимание кодовой базы и формирует интерактивный сворачиваемый HTML-отчёт с мульти-LLM оценкой качества и безопасности.</p>
<hr />
<h2>Возможности</h2>
<ul>
<li><strong>Источник репозитория</strong>
<ul>
<li>Открытие локальной папки через Storage Access Framework (SAF).</li>
<li>Клонирование публичного репозитория GitHub (zipball; история git для анализа не нужна).</li>
<li>Встроенный демонстрационный репозиторий для быстрой проверки всего конвейера.</li>
</ul>
</li>
<li><strong>Рекурсивный анализ</strong> Kotlin, Java, JavaScript/TypeScript, Python, C/C++, Go, Rust, C#, PHP, Ruby, Shell, а также разметки/конфигов (HTML, CSS, XML, JSON, YAML, TOML, SQL, Dockerfile…).</li>
<li><strong>Анализируется только реальный исполняемый код.</strong> README/<code>*.md</code>/<code>*.txt</code>/<code>*.rst</code> и прочая документация пропускаются; чистые комментарии и пустые строки игнорируются.</li>
<li>
<table>
<thead>
<tr>
<th><strong>Мульти-LLM проверка</strong>: четыре встроенных провайдера и любые свои OpenAI-совместимые:</th>
<th>Провайдер</th>
<th>Base URL</th>
<th>Примечание</th>
</tr>
</thead>
<tbody>
<tr>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td>Ollama Cloud</td>
<td><code>https://ollama.com/v1</code></td>
<td>API-ключ / OAuth</td>
</tr>
<tr>
<td>OpenCode Zen</td>
<td><code>https://opencode.ai/zen/v1</code></td>
<td>Бесплатная модель по умолчанию</td>
</tr>
<tr>
<td>OpenCode Go</td>
<td><code>https://opencode.ai/zen/go/v1</code></td>
<td>Своя модель</td>
</tr>
</tbody>
</table>
</li>
<li><strong>Управление провайдерами.</strong> Добавление, дублирование, редактирование и удаление своих OpenAI-совместимых эндпоинтов; ключи хранятся в зашифрованном виде, у каждого есть «Проверить соединение».</li>
<li><strong>Express-отчёт (ансамбль).</strong> Прогоняет один и тот же код по очереди через все настроенные провайдеры, делает отдельный отчёт по каждой модели, затем сравнивает вердикты в <strong>общий супер-отчёт</strong> (голосование большинством, эскалация к самой строгой оценке, пометка «disputed»).</li>
<li><strong>Интерактивный HTML-отчёт</strong>
<ul>
<li>Четыре колонки: Имя/Описание · Категория/Технология/Тип · Точный код и ссылка на строки · Качество и безопасность.</li>
<li>Сворачиваемые строки; любой блок шире ¼ экрана автоматически сворачивается.</li>
<li>Значки-вердикты: &#x2705; оптимально · &#x26a0;&#xfe0f; предупреждение · &#x1f6e1;&#xfe0f; уязвимость · &#x2139;&#xfe0f; инфо.</li>
<li>Мгновенный поиск, фильтры по типу/вердикту, тёмная/светлая тема, «развернуть/свернуть всё».</li>
<li>Экспорт, отправка и открытие в браузере.</li>
</ul>
</li>
<li><strong>Локализованный интерфейс</strong> на английском, русском и немецком с переключателем языка в приложении; HTML-отчёт и вердикты LLM следуют выбранному языку.</li>
</ul>
<h2>Как это работает</h2>
<pre contenteditable="false"><code>Приём ─&#x25b6; Фильтр ─&#x25b6; Определение языка ─&#x25b6; Разбор символов ─&#x25b6; Покрытие каждой строки кода ─&#x25b6; Оценка ─&#x25b6; Отчёт
(SAF/GitHub)                                (regex/эвристики                    (статические правила
                                             по семействам языков)               + LLM-батчи)</code></pre>
<ul>
<li><strong>Воспроизводимая метрика покрытия:</strong> каждая строка <em>реального кода</em> привязана к строке отчёта, а отчёт показывает <code>покрыто / всего строк кода</code>. Найденным символам (классы, функции, методы, переменные, UI-компоненты, триггеры) соответствует отдельная строка; остальной код группируется в строки-«code block».</li>
<li><strong>Оценка:</strong> локальный набор статических правил (захардкоженные секреты, SQL/command-инъекции, <code>eval</code>, HTTP без шифрования, слабая криптография, отключённая проверка TLS, XSS-стоки, пустой <code>catch</code>, TODO…) даёт базовую оценку; LLM её уточняет. Результаты кэшируются по хэшу содержимого — повторные прогоны и смена провайдера не тратят токены заново.</li>
</ul>
<h2>Требования</h2>
<ul>
<li>Android 8.0+ (minSdk 26), проверено на Android 13 (API 33).</li>
<li>Android SDK Platform 37 и Build-Tools 37 для сборки.</li>
<li>JDK 17.</li>
<li>AGP 9.1.1 со встроенным Kotlin (Kotlin 2.2.10), Gradle 9.3.1, Jetpack Compose Material 3.</li>
<li>androidx.appcompat 1.8.0 (переключение языка приложения).</li>
</ul>
<h2>Загрузка (CI)</h2>
<p>Каждый пуш в <code>main</code> собирает debug APK через GitHub Actions. Откройте последний <a href="https://github.com/sashok53011/scm-analyzer/actions/workflows/android.yml">запуск workflow</a> и скачайте артефакт <code>scm-analyzer-debug-apk</code>.</p>
<h2>Сборка и установка</h2>
<pre contenteditable="false"><code class="lang-bash language-bash bash"># укажите путь к SDK в local.properties, например:
# sdk.dir=C\:\\Android\\Sdk

./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk</code></pre>
<h2>Структура проекта</h2>
<pre contenteditable="false"><code>app/src/main/java/online/devhorizon/scm/
  domain/model/     CodeElement, Badge, AnalysisResult, AnalysisOptions
  domain/ingest/    LanguageDetector, RepoFilter, RepoSource (File/SAF/Cached), GitHubRepo
  domain/parse/     BraceAnalyzer, PythonAnalyzer, MarkupAnalyzer, CodeIndexer (покрытие строк)
  domain/assess/    StaticRules, LlmAssessor (батчи + кэш), AssessmentCache
  domain/express/   ExpressPipeline, Consensus
  domain/           AnalysisPipeline, ResultAssembler, Progress
  data/llm/         ProviderConfig, ProviderStore (EncryptedSharedPreferences), OpenAiCompatClient
  report/           HtmlReportBuilder, SuperReportBuilder, ReportStorage
  ui/               MainViewModel + экраны (Home, Analyze, Providers, Report)
app/src/main/assets/  report_template.html, super_report_template.html, sample_repo/</code></pre>
<h2>Приватность и безопасность</h2>
<ul>
<li>API-ключи хранятся в зашифрованных настройках (Android Keystore) и никогда не попадают в отчёты или workflow.</li>
<li>Текст репозитория, встроенный в отчёт, экранируется по HTML, поэтому анализируемый код не может внедрить скрипты.</li>
<li>Сгенерированный HTML полностью автономен; JavaScript используется только для UI отчёта.</li>
</ul>
<h2>Ограничения</h2>
<ul>
<li>Эвристические парсеры приблизительно извлекают символы; это не полноценные AST-парсеры.</li>
<li>LLM-проверка ограничена бюджетом элементов за прогон и требует доступа в сеть.</li>
<li>Приватные репозитории GitHub не поддерживаются (только публичный zipball).</li>
</ul>
<h2>История изменений</h2>
<p>См. <a href="https://github.com/sashok53011/scm-analyzer/blob/main/change-history.txt">change-history.txt</a>.</p>
<h2>Лицензия</h2>
<p>Проект распространяется под <a href="https://github.com/sashok53011/scm-analyzer/blob/main/LICENSE">лицензией MIT</a>.</p>
<hr />
<h3>Скриншоты</h3>
<ul>
<li><strong>Главный экран</strong> — выбор источника: локальная папка (SAF), клон с GitHub, встроенный пример; ниже активный LLM-провайдер.<br /><br /></li>
<li><strong>Провайдеры ИИ</strong> — Ollama Cloud, OpenCode Zen; добавление и дублирование своих OpenAI-совместимых провайдеров.<br /><br /></li>
<li><strong>Экран анализа</strong> — выбранный репозиторий (sample: 7 файлов), параметры, запуск анализа и экспресс-отчёт по всем провайдерам.<br /><a href="https://devhorizon.online/wordpress/wp-content/uploads/2026/10/scm-analyzer-screen-analyze.png"><img src="https://devhorizon.online/wordpress/wp-content/uploads/2026/10/scm-analyzer-screen-analyze.png" alt="SCM Analyzer — Экран анализа" width="330" /></a></li>
<li><strong>Анализ завершён</strong> — 51 элемент, 6 из 7 файлов, покрытие кода 100,0 процента; уязвимостей 11, предупреждений 4, нормы 36.<br /><a href="https://devhorizon.online/wordpress/wp-content/uploads/2026/10/scm-analyzer-screen-analysis-done.png"><img src="https://devhorizon.online/wordpress/wp-content/uploads/2026/10/scm-analyzer-screen-analysis-done.png" alt="SCM Analyzer — Анализ завершён" width="330" /></a></li>
<li><strong>Отчёт</strong> — интерактивный HTML в WebView: 4 столбца, сворачиваемые блоки, поиск и фильтры, экспорт и шеринг.<br /><a href="https://devhorizon.online/wordpress/wp-content/uploads/2026/10/scm-analyzer-screen-report.png"><img src="https://devhorizon.online/wordpress/wp-content/uploads/2026/10/scm-analyzer-screen-report.png" alt="SCM Analyzer — Отчёт" width="330" /></a></li>
<li><strong>Отчёт в деталях</strong> — прокрученный вид: строки кода со ссылками на файл и оценкой.<br /><a href="https://devhorizon.online/wordpress/wp-content/uploads/2026/10/scm-analyzer-screen-report-scrolled.png"><img src="https://devhorizon.online/wordpress/wp-content/uploads/2026/10/scm-analyzer-screen-report-scrolled.png" alt="SCM Analyzer — Отчёт в деталях" width="330" /></a></li>
</ul>
<hr />
<h3>Скачать APK</h3>
<p><strong><a href="https://github.com/sashok53011/scm-analyzer/releases/download/v0.1.0-debug/app-debug.apk">app-debug.apk</a></strong> — 22,0 МБ, debug-сборка (Android 8.0+, minSdk 26).<br />Страница релиза: <a href="https://github.com/sashok53011/scm-analyzer/releases/tag/v0.1.0-debug">v0.1.0-debug</a> · исходники: <a href="https://github.com/sashok53011/scm-analyzer">github.com/sashok53011/scm-analyzer</a><br />Каждая сборка в GitHub Actions публикует артефакт <code>scm-analyzer-debug-apk</code>.</p>]]></content:encoded>
						                            <category domain="https://devhorizon.online/wordpress/community/"></category>                        <dc:creator>admin</dc:creator>
                        <guid isPermaLink="true">https://devhorizon.online/wordpress/community/built/online-devhorizon-scm/</guid>
                    </item>
				                    <item>
                        <title>AI Source Code Mapper</title>
                        <link>https://devhorizon.online/wordpress/community/planned/ai-source-code-mapper-2/</link>
                        <pubDate>Mon, 05 Oct 2026 20:29:55 +0000</pubDate>
                        <description><![CDATA[Role &amp; Goal
You are an expert mobile developer and system architect. Your task is to build an Android application (APK) that analyzes local or GitHub-cloned repositories, generates a de...]]></description>
                        <content:encoded><![CDATA[<h2>Role &amp; Goal</h2>
<p>You are an expert mobile developer and system architect. Your task is to build an Android application (APK) that analyzes local or GitHub-cloned repositories, generates a deep architectural understanding of the codebase, and renders an interactive, collapsible HTML summary report with integrated multi-LLM support.</p>
<hr />
<h2>Key Application Features</h2>
<h3>1. Repository Input &amp; Processing</h3>
<ul>
<li>Allow the user to either <strong>open a local repository</strong> or <strong>clone a public/private repository from GitHub</strong>.</li>
<li>Perform a complete recursive code analysis to capture all active and passive codebase elements (UI components, files, functions, methods, triggers, etc.).</li>
</ul>
<h3>2. Multi-LLM Endpoint Configuration</h3>
<p>Implement a configurable LLM provider settings screen allowing users to choose and switch between the following endpoints for repository analysis:</p>
<ol>
<li><strong>Primary Custom OpenAI-Compatible API:</strong></li>
<li><strong>Ollama Cloud Endpoint:</strong>
<ul>
<li>Supports user-provided API Key or OAuth authentication.</li>
</ul>
</li>
<li><strong>OpenCode Go Endpoint:</strong>
<ul>
<li>Supports user-provided API Key and custom model selection.</li>
</ul>
</li>
<li><strong>OpenCode Zen Endpoint:</strong>
<ul>
<li>Configured with the default free model name.</li>
</ul>
</li>
</ol>
<hr />
<h2>Report Generation Requirements</h2>
<p>Generate a standalone, interactive HTML report with collapsible details (<code>&lt;details&gt;</code>/<code>&lt;summary&gt;</code> or custom JS collapse).</p>
<h3>Column Structure (4 Columns):</h3>
<ol>
<li><strong>Name / Identifier / Description (1st Column):</strong>
<ul>
<li>Must cover EVERY code element: UI elements, files, functions, methods, triggers, variables, and architectural entities.</li>
<li>Includes: Name + brief high-level explanation of what the element does.</li>
</ul>
</li>
<li><strong>Category / Technology / Type (2nd Column):</strong>
<ul>
<li>Detailed specification of method type, design pattern, class type, technology stack, or execution layer used.</li>
</ul>
</li>
<li><strong>Exact Code &amp; Line Reference (3rd Column):</strong>
<ul>
<li>Direct line number(s) and the exact full source code snippet corresponding to the entry.</li>
<li><strong>Completeness Rule:</strong> Every single line of code in the analyzed repository must eventually map to a corresponding row in this column.</li>
</ul>
</li>
<li><strong>Code Quality &amp; Security Assessment (4th Column):</strong>
<ul>
<li>AI-driven evaluation for that specific snippet.</li>
<li>Categorize and badge each entry:
<ul>
<li>&#x26a0;&#xfe0f; <code>Problem / Warning</code> (potential bug or performance bottleneck)</li>
<li>&#x1f512; <code>Vulnerability</code> (security risk)</li>
<li>&#x2705; <code>Best Practice Passed</code> (clean, optimal code)</li>
</ul>
</li>
</ul>
</li>
</ol>
<hr />
<h2>UI / Layout &amp; Collapse Constraints</h2>
<ul>
<li><strong>Responsive Width Handling:</strong> If any text or content block exceeds <strong>25% (1/4) of the screen width</strong>, it MUST be automatically collapsed.</li>
<li><strong>Accordion / Collapsible UX:</strong>
<ul>
<li>In the <strong>collapsed state</strong>, only show a concise summary (1-2 sentences max).</li>
<li>The full, detailed content (full line code snippets, deep analysis) must <strong>only expand upon user click</strong>.</li>
</ul>
</li>
<li>Provide a clean, modern UI for the generated HTML with dark mode / light mode toggle and instant search/filter capabilities.</li>
</ul>
<p><em>enhanced by gemini</em></p>]]></content:encoded>
						                            <category domain="https://devhorizon.online/wordpress/community/"></category>                        <dc:creator>admin</dc:creator>
                        <guid isPermaLink="true">https://devhorizon.online/wordpress/community/planned/ai-source-code-mapper-2/</guid>
                    </item>
				                    <item>
                        <title>AI Source Code Mapper</title>
                        <link>https://devhorizon.online/wordpress/community/requests/ai-source-code-mapper/</link>
                        <pubDate>Mon, 05 Oct 2026 19:25:17 +0000</pubDate>
                        <description><![CDATA[Нужно приложение (APK), которое исследует клонированный репозиторий, формирует полное понимание его структуры и создаёт таблицу, покрывающую:

с одной стороны — описание каждой строки кода;
...]]></description>
                        <content:encoded><![CDATA[<p>Нужно приложение (APK), которое исследует клонированный репозиторий, формирует полное понимание его структуры и создаёт таблицу, покрывающую:</p>
<ul>
<li>с одной стороны — описание каждой строки кода;</li>
<li>с другой стороны — каждый файл, каждый UI-элемент, каждый URL, каждый хук и вообще любой активный или пассивный элемент кодовой базы.</li>
</ul>]]></content:encoded>
						                            <category domain="https://devhorizon.online/wordpress/community/"></category>                        <dc:creator>admin</dc:creator>
                        <guid isPermaLink="true">https://devhorizon.online/wordpress/community/requests/ai-source-code-mapper/</guid>
                    </item>
				                    <item>
                        <title>Демо-запрос: голосование «за / против»</title>
                        <link>https://devhorizon.online/wordpress/community/requests/%d0%b4%d0%b5%d0%bc%d0%be-%d0%b7%d0%b0%d0%bf%d1%80%d0%be%d1%81-%d0%b3%d0%be%d0%bb%d0%be%d1%81%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5-%d0%b7%d0%b0-%d0%bf%d1%80%d0%be%d1%82%d0%b8%d0%b2/</link>
                        <pubDate>Mon, 05 Oct 2026 19:07:08 +0000</pubDate>
                        <description><![CDATA[Это демонстрация голосования в режиме «Вопрос-Ответ». Под этим сообщением есть кнопки Vote Up / Vote Down (вверх/вниз) — то есть «за» и «против».
Свой собственный пост оценивать нельзя, поэ...]]></description>
                        <content:encoded><![CDATA[<p>Это демонстрация голосования в режиме «Вопрос-Ответ». Под этим сообщением есть кнопки <strong>Vote Up</strong> / <strong>Vote Down</strong> (вверх/вниз) — то есть «за» и «против».</p>
<p>Свой собственный пост оценивать нельзя, поэтому проголосовать должен другой участник (например opecoder).</p>
<p>Тему можно удалить в любой момент — она создана только для примера.</p>]]></content:encoded>
						                            <category domain="https://devhorizon.online/wordpress/community/"></category>                        <dc:creator>admin</dc:creator>
                        <guid isPermaLink="true">https://devhorizon.online/wordpress/community/requests/%d0%b4%d0%b5%d0%bc%d0%be-%d0%b7%d0%b0%d0%bf%d1%80%d0%be%d1%81-%d0%b3%d0%be%d0%bb%d0%be%d1%81%d0%be%d0%b2%d0%b0%d0%bd%d0%b8%d0%b5-%d0%b7%d0%b0-%d0%bf%d1%80%d0%be%d1%82%d0%b8%d0%b2/</guid>
                    </item>
				                    <item>
                        <title>com.devhorizon.online.ggufchat</title>
                        <link>https://devhorizon.online/wordpress/community/built/com-devhorizon-online-ggufchat/</link>
                        <pubDate>Mon, 05 Oct 2026 12:47:23 +0000</pubDate>
                        <description><![CDATA[GGUF Chat — Android LLM client
RU Русский · EN English · DE Deutsch

1. Что это
GGUF Chat — нативное Android-приложение (пакет com.devhorizon.online.ggufchat), которое:

загружает лока...]]></description>
                        <content:encoded><![CDATA[<p><img src="https://devhorizon.online/wordpress/wp-content/uploads/2026/10/gguf-chat-icon.png" alt="com.devhorizon.online.ggufchat" width="512" height="512" /></p>
<h1>GGUF Chat — Android LLM client</h1>
<p><strong>RU</strong> <a href="README.ru.md">Русский</a> · <strong>EN</strong> <a href="README.md">English</a> · <strong>DE</strong> <a href="README.de.md">Deutsch</a></p>
<hr />
<h2>1. Что это</h2>
<p><code>GGUF Chat</code> — нативное Android-приложение (пакет <code>com.devhorizon.online.ggufchat</code>), которое:</p>
<ul>
<li>загружает локальные GGUF-модели и запускает генерацию на CPU через вендоренный <code>llama.cpp</code>;</li>
<li>поддерживает <strong>зрение</strong> (изображения) через <code>mtmd</code> + <code>mmproj</code>-проектор (Qwen2-VL, Gemma 4 E2B и др.);</li>
<li>умеет текстовый чат по документам, голосовой ввод (STT) и озвучку (TTS), подсказки-чипы, поиск/скачивание моделей с Hugging Face, монитор ОЗУ и кнопку «Освободить ОЗУ»;</li>
<li>локализован на <strong>en / ru / de</strong> и имеет 6 тем.</li>
</ul>
<h2>2. Полный стек</h2>
<table>
<thead>
<tr>
<th>Слой</th>
<th>Технологии</th>
</tr>
</thead>
<tbody>
<tr>
<td>Язык</td>
<td>Kotlin <code>2.2.10</code> (JVM target 17)</td>
</tr>
<tr>
<td>Сборка</td>
<td>Gradle + AGP <code>9.1.1</code>, Kotlin Compose plugin</td>
</tr>
<tr>
<td>UI</td>
<td>Jetpack Compose (BOM <code>2024.09.00</code>), Material3, Material Icons (core+extended), Navigation Compose <code>2.8.9</code></td>
</tr>
<tr>
<td>Состояние</td>
<td>AndroidX Lifecycle/ViewModel <code>2.8.7</code>, Kotlin Coroutines/Flow <code>1.10.2</code></td>
</tr>
<tr>
<td>Данные</td>
<td><code>org.json</code> (JSONObject/JSONArray), <code>SharedPreferences</code>, файлы сессий в <code>files/sessions/*.json</code></td>
</tr>
<tr>
<td>Сеть</td>
<td><code>HttpURLConnection</code>, REST API Hugging Face</td>
</tr>
<tr>
<td>Медиа</td>
<td>Android <code>SpeechRecognizer</code> (STT), <code>TextToSpeech</code> (TTS), <code>ContentResolver</code>/document picker, <code>FileProvider</code> (камера), <code>BitmapFactory</code></td>
</tr>
<tr>
<td>Нативный слой</td>
<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>, только <code>arm64-v8a</code></td>
</tr>
<tr>
<td>Платформа</td>
<td><code>minSdk 26</code>, <code>targetSdk 36</code>, <code>compileSdk 36</code>, <code>ANDROID_STL=c++_shared</code>, нативная сборка всегда <code>Release</code> (<code>-DCMAKE_BUILD_TYPE=Release</code>)</td>
</tr>
</tbody>
</table>
<p>Ключевые зависимости (<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>. Манифест: разрешения <code>RECORD_AUDIO</code>, <code>INTERNET</code>; <code>FileProvider</code> с authority <code>${applicationId}.fileprovider</code>.</p>
<h2>3. Архитектура APK</h2>
<pre contenteditable="false"><code>┌───────────────────────────── UI (Compose) ─────────────────────────────┐
│ MainActivity / AppNavigation (NavHost: chat, models, settings)          │
│ ChatScreen · ModelPickerScreen · SettingsScreen · DrawerContent         │
│ MarkdownText · Theme · Localization                                     │
└───────────────▲───────────────────────────────────────────▲────────────┘
                │ collectAsState()                          │ l(key, lang)
┌───────────────┴───────────── ViewModel ────────────────────┴────────────┐
│ ChatViewModel (AndroidViewModel): StateFlow-состояние, sendMessage,      │
│ loadModel, freeRam, suggestions, documents, STT/TTS, настройки          │
└───▲──────────▲───────────────▲───────────────────▲──────────────────────┘
    │          │               │                   │
┌───┴──────┐ ┌─┴───────────┐ ┌─┴──────────────┐ ┌──┴───────────────┐
│ Chat     │ │ LlmEngine   │ │ Suggestion     │ │ SttManager /     │
│ Repo     │ │ (inference, │ │ Engine         │ │ TtsManager       │
│ (sessions│ │  models,    │ │ (dedup, retry) │ │ (voice)          │
│  docs)   │ │  mmproj,    │ └────────────────┘ └──────────────────┘
└──────────┘ │  settings)  │
             └──────┬──────┘
                    │ JNI (LocalLlmNative)
┌───────────────────┴────────────────────────────────────────────────────┐
│ llama_jni.cpp  →  llama.cpp (llama) + mtmd (vision) + ggml (CPU)        │
│ libllama_jni.so (arm64-v8a)                                             │
└────────────────────────────────────────────────────────────────────────┘</code></pre>
<p>Поток данных: UI → <code>ChatViewModel</code> → <code>LlmEngine.generate/generateVision</code> → JNI → <code>llama_decode</code> → токены стримятся обратно через <code>TokenCallback</code> → <code>StateFlow</code> → Compose. Сессии/документы — <code>ChatRepository</code> (JSON на диске); модели/настройки — <code>SharedPreferences</code> (<code>gguf_chat_prefs</code>).</p>
<h2>4. Функции и код</h2>
<h3>4.1 Точка входа и навигация — <code>MainActivity.kt</code></h3>
<table>
<thead>
<tr>
<th>Функция</th>
<th>Что делает</th>
<th>Код</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>MainActivity.onCreate</code></td>
<td>Точка входа, включает edge-to-edge и контент Compose</td>
<td><code>setContent { AppNavigation() }</code></td>
</tr>
<tr>
<td><code>AppNavigation()</code></td>
<td>Создаёт <code>ChatViewModel</code>, <code>NavHost</code>, тему, локализатор <code>l</code></td>
<td><code>val l = { key -&amp;gt; Localization.getString(key, lang) }</code></td>
</tr>
<tr>
<td><code>AppContent(...)</code></td>
<td>Drawer + <code>NavHost</code> с маршрутами <code>chat</code>/<code>models</code>/<code>settings</code></td>
<td><code>navController.navigate("models")</code></td>
</tr>
<tr>
<td>маршрут <code>chat</code></td>
<td>Экран чата</td>
<td><code>composable("chat") { ChatScreen(...) }</code></td>
</tr>
</tbody>
</table>
<h3>4.2 ViewModel — <code>ui/viewmodel/ChatViewModel.kt</code></h3>
<table>
<thead>
<tr>
<th>Функция</th>
<th>Что делает</th>
<th>Код</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>importModel(uri)</code></td>
<td>Импорт модели из файла</td>
<td><code>val result = llmEngine.importModel(uri, displayName)</code></td>
</tr>
<tr>
<td><code>downloadModelFromUrl(url)</code></td>
<td>Скачивание модели; mmproj-URL роутится в проектор</td>
<td><code>if (rawName.contains("mmproj", ignoreCase = true)) { ... }</code></td>
</tr>
<tr>
<td><code>searchModels(q, maxMB, includeProj)</code></td>
<td>Поиск на HF в IO-диспетчере</td>
<td><code>HuggingFaceClient.searchGgufModels(...)</code></td>
</tr>
<tr>
<td><code>loadModel(model)</code></td>
<td>Загрузка модели + её проектора</td>
<td><code>llmEngine.loadModel(modelInfo, modelInfo.mmprojPath)</code></td>
</tr>
<tr>
<td><code>attachImage(uri)</code></td>
<td>Копирует выбранную картинку в <code>files/images</code></td>
<td><code>java.io.File(dir, "img_${UUID.randomUUID()}.jpg")</code></td>
</tr>
<tr>
<td><code>clearPendingImage()</code></td>
<td>Сбрасывает выбранную картинку</td>
<td><code>_pendingImage.value = null</code></td>
</tr>
<tr>
<td><code>unloadModel()</code></td>
<td>Выгрузка текущей модели</td>
<td><code>llmEngine.unloadModel()</code></td>
</tr>
<tr>
<td><code>refreshMemory()</code></td>
<td>Читает total/avail ОЗУ</td>
<td><code>am.getMemoryInfo(mi)</code></td>
</tr>
<tr>
<td><code>freeRam()</code></td>
<td><strong>Выгружает всё</strong> и возвращает память ОС</td>
<td><code>llmEngine.unloadModel(); System.gc(); LocalLlmNative.nativeFreeMemory()</code></td>
</tr>
<tr>
<td><code>deleteModel(modelInfo)</code></td>
<td>Удаляет модель и запись</td>
<td><code>llmEngine.deleteModel(modelInfo)</code></td>
</tr>
<tr>
<td><code>createNewSession()</code></td>
<td>Новая сессия чата</td>
<td><code>chatRepository.createSession()</code></td>
</tr>
<tr>
<td><code>selectSession(id)</code></td>
<td>Переключение сессии</td>
<td><code>chatRepository.selectSession(id)</code></td>
</tr>
<tr>
<td><code>addDocument(uri)</code></td>
<td>Читает документ и режет под бюджет контекста</td>
<td><code>val (text, truncated) = DocumentReader.truncate(raw.text, remaining)</code></td>
</tr>
<tr>
<td><code>removeDocument(id)</code></td>
<td>Удаляет документ из сессии</td>
<td><code>chatRepository.removeDocument(sessionId, id)</code></td>
</tr>
<tr>
<td><code>documentBudgetTokens()</code></td>
<td>Бюджет токенов под документы</td>
<td><code>DocumentReader.budgetTokens(...)</code></td>
</tr>
<tr>
<td><code>sendMessage()</code></td>
<td>Главный сценарий: выбор vision/text, генерация, сохранение, автоподсказки; фрагменты TTS ставятся в очередь по знакам препинания</td>
<td><code>val useVision = imagePath != null &amp;amp;&amp;amp; llmEngine.hasVision</code></td>
</tr>
<tr>
<td><code>lastTtsDelimiter(sb)</code></td>
<td>Индекс последнего знака препинания в буфере TTS</td>
<td><code>'.', '!', '?', ';', ':', ',', '…' -&amp;gt; return i</code></td>
</tr>
<tr>
<td><code>attachMmprojToModel(model, uri)</code></td>
<td>Импорт и привязка проектора</td>
<td><code>llmEngine.importMmproj(uri).onSuccess { llmEngine.setModelMmproj(...) }</code></td>
</tr>
<tr>
<td><code>downloadMmprojForModel(...)</code></td>
<td>Скачивание проектора и привязка</td>
<td><code>llmEngine.downloadMmprojFromUrl(url, name)</code></td>
</tr>
<tr>
<td><code>removeMmprojFromModel(model)</code></td>
<td>Снимает привязку проектора</td>
<td><code>llmEngine.setModelMmproj(model.path, null)</code></td>
</tr>
<tr>
<td><code>stopGeneration()</code></td>
<td>Остановка генерации</td>
<td><code>llmEngine.cancelGeneration()</code></td>
</tr>
<tr>
<td><code>generateSuggestions(onlyNew)</code></td>
<td>Запуск подсказок через <code>SuggestionEngine</code></td>
<td><code>suggestionEngine.generate(...)</code></td>
</tr>
<tr>
<td><code>onSuggestionClicked(s)</code></td>
<td>Подставляет подсказку в поле ввода</td>
<td><code>_inputText.value = suggestion</code></td>
</tr>
<tr>
<td><code>startVoiceInput()/stopVoiceInput()</code></td>
<td>STT</td>
<td><code>sttManager.startListening(lang) { ... }</code></td>
</tr>
<tr>
<td><code>speakText()/stopSpeaking()</code></td>
<td>TTS</td>
<td><code>ttsManager.speak(text)</code></td>
</tr>
<tr>
<td><code>updateSettings(new)</code> / <code>setAppLanguage(lang)</code></td>
<td>Сохранение настроек</td>
<td><code>llmEngine.saveSettings(newSettings)</code></td>
</tr>
<tr>
<td><code>onCleared()</code></td>
<td>Освобождение ресурсов</td>
<td><code>llmEngine.unloadModel()</code></td>
</tr>
</tbody>
</table>
<h3>4.3 Данные чата — <code>data/chat/</code></h3>
<table>
<thead>
<tr>
<th>Функция</th>
<th>Что делает</th>
<th>Код</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ChatRepository.loadSessions()</code></td>
<td>Читает <code>files/sessions/*.json</code></td>
<td><code>sessionsDir.listFiles { f -&amp;gt; f.extension == "json" }</code></td>
</tr>
<tr>
<td><code>parseSession(json)</code></td>
<td>Парсит сессию/сообщения</td>
<td><code>val arr = o.getJSONArray("messages")</code></td>
</tr>
<tr>
<td><code>parseDocuments(o)</code></td>
<td>Парсит документы сессии</td>
<td><code>val arr = o.optJSONArray("documents") ?: return emptyList()</code></td>
</tr>
<tr>
<td><code>saveSession(session)</code></td>
<td>Пишет сессию в JSON</td>
<td><code>File(sessionsDir, "${session.id}.json").writeText(o.toString())</code></td>
</tr>
<tr>
<td><code>createSession()</code></td>
<td>Создаёт сессию и делает текущей</td>
<td><code>_currentSessionId.value = session.id</code></td>
</tr>
<tr>
<td><code>addMessage(id, msg)</code></td>
<td>Добавляет сообщение + авто-заголовок</td>
<td><code>message.content.take(40).replace("\n", " ")</code></td>
</tr>
<tr>
<td><code>addDocument</code> / <code>removeDocument</code></td>
<td>Изменяют документы сессии</td>
<td><code>session.copy(documents = session.documents + document)</code></td>
</tr>
<tr>
<td><code>deleteSession(id)</code> / <code>clearAll()</code></td>
<td>Удаление сессий</td>
<td><code>File(sessionsDir, "$id.json").delete()</code></td>
</tr>
<tr>
<td><code>DocumentReader.estimateTokens(t)</code></td>
<td>Эвристика токенов (кириллица дороже)</td>
<td><code>val charsPerToken = if (cyrRatio &amp;gt; 0.2) 1.8 else 3.5</code></td>
</tr>
<tr>
<td><code>DocumentReader.truncate(t, n)</code></td>
<td>Обрезка под лимит</td>
<td><code>(remainingTokens * charsPerToken(text)).toInt()</code></td>
</tr>
<tr>
<td><code>DocumentReader.read(ctx, uri)</code></td>
<td>Читает txt/md/json/html/csv (≤8 МБ)</td>
<td><code>val ALLOWED_EXTENSIONS = setOf("txt","md","markdown","json","htm","html","csv")</code></td>
</tr>
<tr>
<td><code>stripHtml(html)</code></td>
<td>Превращает HTML в текст</td>
<td><code>s.replace(Regex("]+&amp;gt;"), " ")</code></td>
</tr>
<tr>
<td><code>SuggestionEngine.generate(...)</code></td>
<td>Подсказки с дедупом и ретраями по температуре</td>
<td><code>val temperatures = listOf(0.7f, 0.85f, 1.0f)</code></td>
</tr>
<tr>
<td><code>SuggestionEngine.normalize/markShown/clearHistory</code></td>
<td>Дедуп показанных подсказок</td>
<td><code>shownSuggestions.add(normalize(it))</code></td>
</tr>
</tbody>
</table>
<h3>4.4 Движок инференса — <code>data/llm/LlmEngine.kt</code></h3>
<table>
<thead>
<tr>
<th>Функция</th>
<th>Что делает</th>
<th>Код</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>loadModelsFromPrefs()</code></td>
<td>Загрузка списка моделей; дроп отсутствующих; миграция stale-mmproj</td>
<td><code>if (m.mmprojPath != null &amp;amp;&amp;amp; !ModelCompatibility.isVisionArch(m.architecture)) m = m.copy(mmprojPath = null)</code></td>
</tr>
<tr>
<td><code>saveModelsToPrefs()</code></td>
<td>Сохранение списка</td>
<td><code>prefs.edit().putString(KEY_MODELS, arr.toString()).apply()</code></td>
</tr>
<tr>
<td><code>loadSettingsFromPrefs()</code> / <code>saveSettings()</code></td>
<td>Настройки</td>
<td><code>o.put("temperature", ...)</code></td>
</tr>
<tr>
<td><code>importModel(uri)</code></td>
<td>Копирует GGUF в <code>files/models</code> с прогрессом</td>
<td><code>context.contentResolver.openInputStream(uri)</code></td>
</tr>
<tr>
<td><code>downloadModelFromUrl(url)</code></td>
<td>Скачивает с проверкой размера/валидности</td>
<td><code>if (totalBytes &amp;gt; 0 &amp;amp;&amp;amp; totalRead != totalBytes)</code></td>
</tr>
<tr>
<td><code>loadModel(model, mmproj)</code></td>
<td>Грузит модель, чистит RAM-гейт, привязывает vision-проектор</td>
<td><code>val isVision = ModelCompatibility.isVisionArch(arch)</code></td>
</tr>
<tr>
<td>Эффективный ctx</td>
<td>Запрошенный ctx ограничивается максимумом модели и применяется при загрузке</td>
<td><code>val effectiveCtx = s.contextLength.coerceIn(512, modelMaxCtx)</code></td>
</tr>
<tr>
<td><code>unloadModel()</code></td>
<td><code>nativeFreeModel</code> + сброс состояния</td>
<td><code>LocalLlmNative.nativeFreeModel(handle)</code></td>
</tr>
<tr>
<td><code>loadMmproj(path)</code></td>
<td>Привязка проектора к загруженной модели</td>
<td><code>LocalLlmNative.nativeLoadMmproj(handle, path)</code></td>
</tr>
<tr>
<td><code>setModelMmproj(path, mmproj)</code></td>
<td>Персистентная привязка проектора</td>
<td><code>it.copy(mmprojPath = mmprojPath)</code></td>
</tr>
<tr>
<td><code>autoFindMmproj(modelPath)</code></td>
<td>Ищет sibling <code>mmproj*.gguf</code></td>
<td><code>n.endsWith(".gguf") &amp;amp;&amp;amp; n.startsWith("mmproj")</code></td>
</tr>
<tr>
<td><code>importMmproj(uri)</code> / <code>downloadMmprojFromUrl(url)</code></td>
<td>Импорт/скачивание проектора</td>
<td><code>ModelCompatibility.isProjectorFileName(name)</code></td>
</tr>
<tr>
<td><code>deleteModel(model)</code></td>
<td>Удаление файла+записи</td>
<td><code>File(modelInfo.path).delete()</code></td>
</tr>
<tr>
<td><code>generate(messages, onToken, docs)</code></td>
<td>Текстовый чат: chat-template + язык + стрим</td>
<td><code>val templated = LocalLlmNative.nativeApplyChatTemplate(...)</code></td>
</tr>
<tr>
<td><code>generateVision(messages, imagePath, ...)</code></td>
<td>Vision: RGB → mtmd</td>
<td><code>LocalLlmNative.nativeGenerateStreamingVision(handle, prompt, rgb, w, h, ...)</code></td>
</tr>
<tr>
<td><code>buildVisionPrompt()</code></td>
<td>Выбор шаблона по архитектуре</td>
<td><code>if (arch.startsWith("gemma")) buildGemmaTurnPrompt(...) else buildChatmlPrompt(...)</code></td>
</tr>
<tr>
<td><code>buildChatmlPrompt()</code> / <code>buildGemmaTurnPrompt()</code></td>
<td>Ручные шаблоны Qwen/Gemma с медиа-маркером</td>
<td><code>sb.append(mediaMarker)</code></td>
</tr>
<tr>
<td><code>buildManualPrompt()</code></td>
<td>Fallback, если у модели нет chat-template</td>
<td><code>when { arch.startsWith("qwen") -&amp;gt; ... }</code></td>
</tr>
<tr>
<td><code>cancelGeneration()</code></td>
<td>Отмена</td>
<td><code>LocalLlmNative.nativeCancel(handle)</code></td>
</tr>
<tr>
<td><code>resolveAnswerLanguageCode()/answerLanguageName()</code></td>
<td>Язык ответа (auto → язык приложения)</td>
<td><code>if (code.isBlank() \|\| code == "auto") s.appLanguage else code</code></td>
</tr>
<tr>
<td><code>languageReminder()/languageDirective()</code></td>
<td>Инструкции языка для модели</td>
<td><code>"Отвечай только на русском языке…"</code></td>
</tr>
<tr>
<td><code>generateSuggestions(...)</code></td>
<td>JSON-подсказки + фильтр языка</td>
<td><code>.filter { suggestionMatchesLanguage(it, langCode) }</code></td>
</tr>
<tr>
<td><code>buildSuggestionPrompt(...)</code></td>
<td>Промпт подсказок <strong>на языке-цели</strong> (ru/de/en)</td>
<td><code>private data class SuggestionStrings(...)</code></td>
</tr>
<tr>
<td><code>suggestionMatchesLanguage(t, code)</code></td>
<td>Отсев иноязычных чипов</td>
<td><code>"ru" -&amp;gt; hasCyr</code></td>
</tr>
<tr>
<td><code>parsePartial()</code> / <code>parseSuggestions()</code> / <code>unescape()</code></td>
<td>Парсинг JSON-подсказок (терпимый)</td>
<td><code>Regex("\\", DOT_MATCHES_ALL)</code></td>
</tr>
<tr>
<td><code>formatBytes()</code></td>
<td>Человекочитаемый размер</td>
<td><code>"%.1f GB".format(bytes / 1_073_741_824.0)</code></td>
</tr>
</tbody>
</table>
<h3>4.5 GGUF-парсер и совместимость — <code>data/llm/</code></h3>
<table>
<thead>
<tr>
<th>Функция</th>
<th>Что делает</th>
<th>Код</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>GgufReader.readHeaders(ctx, uri)</code> / <code>readHeadersFromFile(file)</code></td>
<td>Чтение заголовка GGUF</td>
<td><code>context.contentResolver.openInputStream(uri)</code></td>
</tr>
<tr>
<td><code>readHeadersFromStream()</code></td>
<td>Magic/version/tensors/KV</td>
<td><code>if (String(magic) != "GGUF")</code></td>
</tr>
<tr>
<td><code>readGgufValue(buf, type)</code></td>
<td>Разбор типов значений GGUF</td>
<td><code>8 -&amp;gt; { val len = buffer.long ... }</code></td>
</tr>
<tr>
<td><code>deriveArchitecture(meta)</code></td>
<td>Fallback-архитектура по ключам</td>
<td><code>metadata.keys.any { it.startsWith("$arch.") }</code></td>
</tr>
<tr>
<td><code>ModelCompatibility.isArchSupported(arch)</code></td>
<td>Гейт поддерживаемых архитектур</td>
<td><code>arch.lowercase() in SUPPORTED_ARCHS</code></td>
</tr>
<tr>
<td><code>isModelFileName(name)</code></td>
<td>Отсев mmproj/lora/split</td>
<td><code>NON_MODEL_MARKERS.any { n.contains(it) }</code></td>
</tr>
<tr>
<td><code>isProjectorFileName(name)</code></td>
<td>Определяет mmproj</td>
<td><code>n.endsWith(".gguf") &amp;amp;&amp;amp; n.contains("mmproj")</code></td>
</tr>
<tr>
<td><code>isVisionArch(arch)</code></td>
<td>Vision-архитектуры</td>
<td><code>arch.lowercase() in VISION_ARCHS</code></td>
</tr>
</tbody>
</table>
<h3>4.6 Сеть — <code>data/llm/HuggingFaceClient.kt</code></h3>
<table>
<thead>
<tr>
<th>Функция</th>
<th>Что делает</th>
<th>Код</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>searchGgufModels(q, maxBytes, ...)</code></td>
<td>Параллельный поиск репозиториев</td>
<td><code>Semaphore(MAX_CONCURRENT_REQUESTS)</code></td>
</tr>
<tr>
<td><code>collectRepoFiles(repo, ...)</code></td>
<td>Обходит дерево репо, фильтрует .gguf</td>
<td><code>$BASE/api/models/${encodePath(repoId)}/tree/main?recursive=true</code></td>
</tr>
<tr>
<td><code>encodePath(path)</code></td>
<td>URL-кодирование пути</td>
<td><code>URLEncoder.encode(it, "UTF-8").replace("+", "%20")</code></td>
</tr>
<tr>
<td><code>httpGetJsonArray(url)</code></td>
<td>HTTP GET → JSONArray</td>
<td><code>conn.setRequestProperty("User-Agent", USER_AGENT)</code></td>
</tr>
</tbody>
</table>
<h3>4.7 Голос — <code>data/voice/</code></h3>
<table>
<thead>
<tr>
<th>Функция</th>
<th>Что делает</th>
<th>Код</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>SttManager.startListening(lang, onResult)</code></td>
<td>Распознавание речи</td>
<td><code>Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)</code></td>
</tr>
<tr>
<td><code>SttManager.stopListening()/destroy()</code></td>
<td>Остановка/освобождение STT</td>
<td><code>speechRecognizer.destroy()</code></td>
</tr>
<tr>
<td><code>onResults</code> / <code>onPartialResults</code></td>
<td>Результаты распознавания</td>
<td><code>results?.getStringArrayList(RESULTS_RECOGNITION)</code></td>
</tr>
<tr>
<td><code>TtsManager.initialize()</code></td>
<td>Инициализация TTS</td>
<td><code>TextToSpeech(context) { status -&amp;gt; ... }</code></td>
</tr>
<tr>
<td><code>TtsManager.setLanguage(lang)</code></td>
<td>Локаль ru/de/en</td>
<td><code>"ru" -&amp;gt; Locale("ru", "RU")</code></td>
</tr>
<tr>
<td><code>TtsManager.speak(text)</code></td>
<td>Озвучка полного текста (сбрасывает очередь)</td>
<td><code>fun speak(text: String) = speakChunk(text, flush = true)</code></td>
</tr>
<tr>
<td><code>TtsManager.speakChunk(text, flush)</code></td>
<td>Потоковая озвучка: добавить фрагмент или сбросить очередь</td>
<td><code>val mode = if (flush) TextToSpeech.QUEUE_FLUSH else TextToSpeech.QUEUE_ADD</code></td>
</tr>
<tr>
<td><code>TtsManager.stop()/shutdown()</code></td>
<td>Стоп/освобождение</td>
<td><code>tts?.shutdown()</code></td>
</tr>
</tbody>
</table>
<h3>4.8 UI-экраны и компоненты</h3>
<table>
<thead>
<tr>
<th>Функция</th>
<th>Что делает</th>
<th>Код</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ChatScreen(...)</code></td>
<td>Чат: шапка с ОЗУ, список сообщений, ввод, чипы</td>
<td><code>val ramFree by viewModel.ramFree.collectAsState()</code></td>
</tr>
<tr>
<td>Автоскролл чата</td>
<td>Во время стрима держит конец ответа, после готовности чипов прыгает к началу ответа</td>
<td><code>listState.scrollToItem(index, overflow)</code></td>
</tr>
<tr>
<td><code>AnswerFooter(message)</code></td>
<td>Под каждым ответом: copy в буфер, полный timestamp (дата/время/секунды), время генерации (мм:сс), скорость (t/s); статистика из <code>ChatMessage.generationMs</code>/<code>tokenCount</code></td>
<td><code>clipboard.setText(AnnotatedString(message.content))</code></td>
</tr>
<tr>
<td><code>MarkdownMessage(text)</code> / <code>CodeBlock</code></td>
<td>Код-блоки с подписью языка, подсветкой синтаксиса и кнопкой copy у блока</td>
<td><code>FENCE.findAll(text)</code></td>
</tr>
<tr>
<td><code>takePhoto()</code></td>
<td>Съёмка через <code>FileProvider</code></td>
<td><code>FileProvider.getUriForFile(appContext, "${appContext.packageName}.fileprovider", file)</code></td>
</tr>
<tr>
<td><code>sendCurrent()</code></td>
<td>Отправка + скрытие клавиатуры</td>
<td><code>viewModel.sendMessage()</code></td>
</tr>
<tr>
<td><code>MessageBubble(...)</code></td>
<td>Пузырь сообщения (+картинка)</td>
<td><code>val isUser = message.role == "user"</code></td>
</tr>
<tr>
<td><code>chipsContent</code>/<code>suggestionsArea</code></td>
<td>Чипы подсказок (wrap/single-row)</td>
<td><code>AssistChip(onClick = { viewModel.onSuggestionClicked(suggestion) })</code></td>
</tr>
<tr>
<td>меню «+»</td>
<td>Одна кнопка → меню документ/галерея/камера</td>
<td><code>DropdownMenu(expanded = showAttachMenu, ...)</code></td>
</tr>
<tr>
<td><code>ModelPickerScreen(...)</code></td>
<td>Список моделей, импорт/скачивание/поиск/проекторы</td>
<td><code>filePickerLauncher.launch(arrayOf("*/*"))</code></td>
</tr>
<tr>
<td><code>ModelCard(...)</code></td>
<td>Карточка модели + статус проектора</td>
<td><code>val projectorName = model.mmprojPath?.let { File(it).name }</code></td>
</tr>
<tr>
<td><code>DownloadModelDialog</code> / <code>SearchModelsDialog</code> / <code>ProjectorDownloadDialog</code></td>
<td>Диалоги URL/поиска/проектора</td>
<td><code>viewModel.downloadModelFromUrl(url, name)</code></td>
</tr>
<tr>
<td><code>SettingsScreen(...)</code></td>
<td>Настройки (темп., языки, тема, layout)</td>
<td><code>viewModel.updateSettings(currentSettings)</code></td>
</tr>
<tr>
<td>Поле ctx size</td>
<td>Числовой ввод 512..131072; эффективное значение ограничивается моделью</td>
<td><code>currentSettings.copy(contextLength = v.coerceIn(512, 131072))</code></td>
</tr>
<tr>
<td>Диалог быстрых загрузок</td>
<td>Загрузка в один тап: Gemma 4 E2B + mmproj Q8 и Qwen2-VL 2B + mmproj Q8</td>
<td><code>QUICK_DOWNLOADS</code> + <code>viewModel.downloadModelFromUrl(url, fileName)</code></td>
</tr>
<tr>
<td><code>DropdownSelector/SliderSetting/SwitchSetting/SectionTitle</code></td>
<td>Виджеты настроек</td>
<td><code>var expanded by remember { mutableStateOf(false) }</code></td>
</tr>
<tr>
<td><code>DrawerContent(...)</code></td>
<td>Ящик: новый чат, модели, настройки, история</td>
<td><code>Text(l("models"), style = MaterialTheme.typography.bodyLarge)</code></td>
</tr>
<tr>
<td><code>MarkdownText(...)</code> / <code>parseMarkdown</code> / <code>parseInline</code></td>
<td>Лёгкий markdown-рендер</td>
<td><code>val annotated = parseMarkdown(text, textColor)</code></td>
</tr>
<tr>
<td><code>GGUFChatTemplateTheme(...)</code></td>
<td>Темы (light/dark/green/orange/light_blue/yellow)</td>
<td><code>enum class AppTheme { LIGHT, DARK, ... }</code></td>
</tr>
<tr>
<td><code>Localization.getString(key, lang)</code></td>
<td>Перевод строк en/ru/de</td>
<td><code>val selectedLang = if (translations.containsKey(lang)) lang else "en"</code></td>
</tr>
<tr>
<td><code>fmtRam(bytes)</code></td>
<td>Формат ОЗУ в шапке</td>
<td><code>"%.1f GB".format(bytes / 1_073_741_824.0)</code></td>
</tr>
</tbody>
</table>
<h3>4.9 Нативный слой — <code>app/src/main/cpp/llama_jni.cpp</code></h3>
<table>
<thead>
<tr>
<th>JNI-функция</th>
<th>Что делает</th>
<th>Код</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>nativeLoadModel</code></td>
<td>Загрузка модели + контекста</td>
<td><code>session-&amp;gt;model = llama_model_load_from_file(path.c_str(), mparams)</code></td>
</tr>
<tr>
<td><code>nativeApplyChatTemplate</code></td>
<td>Применение chat-template модели</td>
<td><code>llama_chat_apply_template(tmpl, messages.data(), messages.size(), true, ...)</code></td>
</tr>
<tr>
<td><code>nativeGenerateStreaming</code></td>
<td>Текстовая генерация стримом</td>
<td><code>llama_tokenize(...)</code>, <code>llama_decode(s-&amp;gt;ctx, batch)</code>, <code>llama_sampler_sample(...)</code></td>
</tr>
<tr>
<td><code>nativeLoadMmproj</code></td>
<td>Загрузка mmproj (mtmd)</td>
<td><code>s-&amp;gt;mtmd = mtmd_init_from_file(path.c_str(), s-&amp;gt;model, mp)</code></td>
</tr>
<tr>
<td><code>nativeHasMmproj</code></td>
<td>Есть ли проектор</td>
<td><code>s-&amp;gt;mtmd != nullptr</code></td>
</tr>
<tr>
<td><code>nativeMediaMarker</code></td>
<td>Маркер места картинки</td>
<td><code>mtmd_default_marker()</code></td>
</tr>
<tr>
<td><code>nativeGenerateStreamingVision</code></td>
<td>Vision-инференс</td>
<td><code>mtmd_tokenize(...)</code>, <code>mtmd_helper_eval_chunks(...)</code></td>
</tr>
<tr>
<td><code>nativeCancel</code></td>
<td>Флаг отмены</td>
<td><code>s-&amp;gt;cancel_flag.store(true, ...)</code></td>
</tr>
<tr>
<td><code>nativeFreeModel</code></td>
<td>Освобождение модели/проектора</td>
<td><code>mtmd_free(s-&amp;gt;mtmd)</code></td>
</tr>
<tr>
<td><code>nativeFreeMemory</code></td>
<td>Возврат свободной нативной памяти ОС</td>
<td><code>mallopt(M_PURGE, 0)</code></td>
</tr>
<tr>
<td><code>nativeIsModelLoaded</code></td>
<td>Проверка загрузки</td>
<td><code>s-&amp;gt;ready</code></td>
</tr>
</tbody>
</table>
<p>Сэмплер (<code>make_sampler</code>): <code>llama_sampler_init_penalties(...)</code> → <code>llama_sampler_init_temp(temp)</code> → <code>llama_sampler_init_top_p(topP, 1)</code> → <code>llama_sampler_init_dist(LLAMA_DEFAULT_SEED)</code>.</p>
<h3>4.10 Сборка — <code>CMakeLists.txt</code> / Gradle</h3>
<table>
<thead>
<tr>
<th>Файл</th>
<th>Что делает</th>
<th>Код</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>app/src/main/cpp/CMakeLists.txt</code></td>
<td>Подключает llama.cpp-new + mtmd</td>
<td><code>set(LLAMA_CPP_DIR ".../llama.cpp-new")</code></td>
</tr>
<tr>
<td><code>add_library(llama_jni ...)</code></td>
<td>Собирает JNI-библиотеку</td>
<td><code>target_link_libraries(llama_jni mtmd llama ggml android log)</code></td>
</tr>
<tr>
<td><code>app/build.gradle.kts</code></td>
<td>Конфиг Android/NDK/Release</td>
<td><code>arguments += "-DCMAKE_BUILD_TYPE=Release"</code></td>
</tr>
<tr>
<td><code>settings.gradle.kts</code></td>
<td>Модуль приложения</td>
<td><code>include(":app")</code></td>
</tr>
</tbody>
</table>
<h2>5. TODO (обновлено — статус на текущий момент)</h2>
<table>
<thead>
<tr>
<th>#</th>
<th>Фича</th>
<th>Статус</th>
<th>Комментарий</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Чат (Compose, потоковая генерация)</td>
<td>&#x2705; готово</td>
<td><code>ChatScreen</code> + <code>nativeGenerateStreaming</code></td>
</tr>
<tr>
<td>2</td>
<td>Импорт локальной модели</td>
<td>&#x2705; готово</td>
<td><code>importModel</code></td>
</tr>
<tr>
<td>3</td>
<td>Скачивание модели по URL</td>
<td>&#x2705; готово</td>
<td><code>downloadModelFromUrl</code> + проверка целостности</td>
</tr>
<tr>
<td>4</td>
<td>Поиск моделей на Hugging Face</td>
<td>&#x2705; готово</td>
<td><code>HuggingFaceClient.searchGgufModels</code></td>
</tr>
<tr>
<td>5</td>
<td>Vision-проектор (mmproj): привязка/импорт/скачивание</td>
<td>&#x2705; готово</td>
<td><code>attachMmprojToModel</code>, <code>importMmproj</code></td>
</tr>
<tr>
<td>6</td>
<td>Авто-привязка mmproj только для vision-архитектур + чистка stale-привязок</td>
<td>&#x2705; готово</td>
<td><code>isVisionArch</code> в <code>loadModel</code>/<code>loadModelFromPrefs</code></td>
</tr>
<tr>
<td>7</td>
<td>Multimodal-инференс (mtmd) Qwen2-VL / Gemma 4 E2B</td>
<td>&#x2705; проверено</td>
<td>ответ «7391» на тестовой картинке</td>
</tr>
<tr>
<td>8</td>
<td>Chat-templates: native + ручной fallback (Qwen/Gemma/Llama/Phi)</td>
<td>&#x2705; готово</td>
<td><code>buildManualPrompt</code></td>
</tr>
<tr>
<td>9</td>
<td>Язык ответа модели = настройка (text + vision)</td>
<td>&#x2705; готово</td>
<td><code>languageReminder</code> в обоих путях</td>
</tr>
<tr>
<td>10</td>
<td>Подсказки-чипы (стрим, дедуп, язык-цель, фильтр языка)</td>
<td>&#x2705; готово</td>
<td><code>buildSuggestionPrompt</code>, <code>suggestionMatchesLanguage</code></td>
</tr>
<tr>
<td>11</td>
<td>Чат с документами (txt/md/json/html/csv)</td>
<td>&#x2705; готово</td>
<td><code>DocumentReader</code></td>
</tr>
<tr>
<td>12</td>
<td>STT (голосовой ввод)</td>
<td>&#x2705; готово</td>
<td><code>SttManager</code></td>
</tr>
<tr>
<td>13</td>
<td>TTS (озвучка)</td>
<td>&#x2705; готово</td>
<td><code>TtsManager</code> — потоковая, по фрагментам при появлении знаков препинания</td>
</tr>
<tr>
<td>14</td>
<td>Настройки (temp, top_p, max tokens, ctx, threads, языки, темы, layout)</td>
<td>&#x2705; готово</td>
<td><code>SettingsScreen</code></td>
</tr>
<tr>
<td>15</td>
<td>Локализация en/ru/de + 6 тем</td>
<td>&#x2705; готово</td>
<td><code>Localization</code>, <code>AppTheme</code></td>
</tr>
<tr>
<td>16</td>
<td>Монитор ОЗУ + кнопка «Освободить ОЗУ»</td>
<td>&#x2705; готово</td>
<td><code>refreshMemory</code>, <code>freeRam</code></td>
</tr>
<tr>
<td>17</td>
<td>Кнопка «+» с меню (документ/галерея/камера)</td>
<td>&#x2705; готово</td>
<td><code>DropdownMenu</code></td>
</tr>
<tr>
<td>18</td>
<td>Гейт совместимости архитектур</td>
<td>&#x2705; готово</td>
<td><code>ModelCompatibility</code></td>
</tr>
<tr>
<td>19</td>
<td>Парсер GGUF-заголовков</td>
<td>&#x2705; готово</td>
<td><code>GgufReader</code></td>
</tr>
<tr>
<td>20</td>
<td>Q8_0 mmproj из ggml-org на новом mtmd</td>
<td>&#x2705; проверено</td>
<td>работает</td>
</tr>
<tr>
<td>21</td>
<td>GPU/NNAPI-ускорение инференса</td>
<td>&#x23f3; не сделано</td>
<td>сейчас CPU-only, <code>mtmd use_gpu=false</code></td>
</tr>
<tr>
<td>22</td>
<td>UI для sampler (top_k, repeat/presence, seed)</td>
<td>&#x23f3; не сделано</td>
<td>в UI только temperature/top_p</td>
</tr>
<tr>
<td>23</td>
<td>Экспорт/импорт чатов</td>
<td>&#x23f3; не сделано</td>
<td>сессии только в <code>files/sessions</code></td>
</tr>
<tr>
<td>24</td>
<td>RAG/эмбеддинги для документов</td>
<td>&#x23f3; не сделано</td>
<td>сейчас простая обрезка по бюджету</td>
</tr>
<tr>
<td>25</td>
<td>LoRA/adapter</td>
<td>&#x23f3; не сделано</td>
<td>маркеры есть, загрузки нет</td>
</tr>
<tr>
<td>26</td>
<td>Split-GGUF (<code>-00001-of-0000N</code>)</td>
<td>&#x23f3; не сделано</td>
<td>части отклоняются</td>
</tr>
<tr>
<td>27</td>
<td>Точный подсчёт токенов через нативную лексику</td>
<td>&#x23f3; не сделано</td>
<td>эвристика <code>DocumentReader</code></td>
</tr>
<tr>
<td>28</td>
<td>Метаданные модели в UI</td>
<td>&#x23f3; не сделано</td>
<td><code>GgufInfo.metadata</code> собирается, экрана нет</td>
</tr>
<tr>
<td>29</td>
<td>Vision для gemma3n/llama4/minicpm</td>
<td>&#x23f3; не проверено</td>
<td>в списке <code>VISION_ARCHS</code>, но не тестировалось</td>
</tr>
<tr>
<td>30</td>
<td>Офлайн-STT (whisper) вместо системного</td>
<td>&#x23f3; не сделано</td>
<td>используется <code>SpeechRecognizer</code></td>
</tr>
<tr>
<td>31</td>
<td>Модель не грузится дважды / прогрев</td>
<td>&#x23f3; бэклог</td>
<td><code>freeRam</code> выгружает модель</td>
</tr>
<tr>
<td>32</td>
<td>Unit/UI-тесты и CI</td>
<td>&#x23f3; не сделано</td>
<td>тестов нет</td>
</tr>
<tr>
<td>33</td>
<td>Потоковый TTS (озвучка фрагментов по мере генерации)</td>
<td>&#x2705; проверено</td>
<td>8 запросов синтеза за один ответ</td>
</tr>
<tr>
<td>34</td>
<td>Умный автоскролл (за концом, к началу после чипов)</td>
<td>&#x2705; готово</td>
<td><code>LaunchedEffect</code> в <code>ChatScreen</code></td>
</tr>
<tr>
<td>35</td>
<td>Футер ответа: copy, полный timestamp, время генерации, скорость токенов</td>
<td>&#x2705; проверено</td>
<td>«2026-10-05 06:25:39 · 0:33 · 1.3 t/s»</td>
</tr>
<tr>
<td>36</td>
<td>Код-блоки markdown с подсветкой синтаксиса и copy у блока</td>
<td>&#x2705; готово</td>
<td><code>MarkdownMessage.kt</code></td>
</tr>
<tr>
<td>37</td>
<td>Настройка ctx size для всех моделей (512..131072, ограничение по максимуму модели)</td>
<td>&#x2705; проверено</td>
<td><code>coerceIn(512, modelMaxCtx)</code>, в шапке «· 8192 ctx»</td>
</tr>
<tr>
<td>38</td>
<td>Загрузка пресетов в один тап (Gemma 4 E2B, Qwen2-VL 2B + mmproj Q8)</td>
<td>&#x2705; собрано</td>
<td>проверка на устройстве ожидает</td>
</tr>
</tbody>
</table>
<hr />
<h2>Ссылки</h2>
<ul>
<li>GitHub: <a href="https://github.com/sashok53011/gguf-chat-android">https://github.com/sashok53011/gguf-chat-android</a></li>
<li>git clone: <a href="https://github.com/sashok53011/gguf-chat-android.git">https://github.com/sashok53011/gguf-chat-android.git</a></li>
</ul>
<figure class="wp-block-image "><img class="attachment-150x150 size-150x150" src="https://devhorizon.online/wordpress/wp-content/uploads/2026/10/gguf-chat-icon-150x150.png" alt="GGUF Chat" width="150" height="150" /></figure>
<p align="center"><br /><a href="https://github.com/sashok53011/gguf-chat-android/releases/download/v1.0-debug/app-debug.apk"><strong>Скачать APK (debug)</strong></a></p>]]></content:encoded>
						                            <category domain="https://devhorizon.online/wordpress/community/"></category>                        <dc:creator>admin</dc:creator>
                        <guid isPermaLink="true">https://devhorizon.online/wordpress/community/built/com-devhorizon-online-ggufchat/</guid>
                    </item>
							        </channel>
        </rss>
		