Files
agentik-desktop/MARKDOWN-SOURCE.md
subochev 27f349550c Макеты — один sketches/index.html; approved/ удалён
- sketches/index.html: все экраны одним документом в порядке 1·… (14–19 — бывшие approved)
- approved/ (4 файла групп + 3 макета + README) удалён как дубль
- стили вынесены в sketches/style.css
- README, THEMES, BORROW-FROM-ASSISTENT, MARKDOWN-SOURCE, MIC-ASR-SEARCH
  приведены к фактическому состоянию
2026-09-28 10:07:57 +03:00

7.5 KiB
Raw Permalink Blame History

Отрисовка Markdown: откуда взято и где лежит

Статус: УЖЕ СКОПИРОВАНО В ПРОЕКТ (28.09.2026). Код вендорен в src/, собирается, тесты парсера (11 шт.) зелёные. Своё писать не надо.

Источник — приватный репозиторий ai/assistent (https://git.binom.pw/ai/assistent, ветка main, проверено на коммите 2c5b3f9, файлы добавлены в 1acced4 2026-08-13). Пакеты переименованы под наш проект; внешняя зависимость — одна (ru.otpbank.ai:markdown).


Куда что легло

В assistent У нас
client-shared/.../shared/markdown/MarkdownSegment.kt src/jvmMain/kotlin/pw/binom/agentik/desktop/markdown/MarkdownSegment.kt
client-shared/.../shared/markdown/MarkdownParserImpl.kt src/jvmMain/kotlin/pw/binom/agentik/desktop/markdown/MarkdownParserImpl.kt
client-shared/.../shared/ui/MarkdownRendering.kt src/jvmMain/kotlin/pw/binom/agentik/desktop/ui/MarkdownRendering.kt
client-shared/.../shared/ui/ChatImage.kt src/jvmMain/kotlin/pw/binom/agentik/desktop/ui/ChatImage.kt
client-shared/.../shared/ClientImageSupport.kt src/jvmMain/kotlin/pw/binom/agentik/desktop/ui/ClientImageSupport.kt
client/.../client/ui/MarkdownRenderer.kt src/jvmMain/kotlin/pw/binom/agentik/desktop/ui/MarkdownRenderer.kt
client-shared/src/test/.../markdown/MarkdownParserTest.kt src/jvmTest/kotlin/pw/binom/agentik/desktop/markdown/MarkdownParserTest.kt

Переименование пакетов:

  • pw.binom.client.shared.markdown → pw.binom.agentik.desktop.markdown
  • pw.binom.client.shared.ui → pw.binom.agentik.desktop.ui
  • pw.binom.client.shared (ClientImageSupport) → pw.binom.agentik.desktop.ui
  • pw.binom.client.ui (MarkdownRenderer) → pw.binom.agentik.desktop.ui

Почему файлов шесть, а не четыре

Изначально в доке значились 4 файла. При сборке выяснилось, что MarkdownRendering.kt для сегментов-картинок вызывает ChatImage(...), а тот — ClientImageSupport. Поэтому взяты ещё ChatImage.kt и ClientImageSupport.kt. Оба независимы от chat-* и MessageBubble.

ClientImageSupport — это реестр: платформенный клиент при старте регистрирует decoder (байты → ImageBitmap) и, при желании, fetcher и serverBaseUrl. Пока decoder не зарегистрирован, картинки деградируют в текст (alt/ссылку) — ничего не падает. На десктопе декодер регистрируется так:

ClientImageSupport.decoder = { bytes ->
    org.jetbrains.skia.Image.makeFromEncoded(bytes).toComposeImageBitmap()
}

Точки входа

  • Парсинг: parseMarkdown(markdown: String): List<MarkdownSegment> (в ...desktop.markdown).
  • Отрисовка: renderMarkdownSegments(content, textColor, fontSize, isBubbleScrolled, onUrlClick) (в ...desktop.ui).
  • Открытие ссылок/parseMultiline: ...desktop.ui.MarkdownRenderer.

Зависимости и сборка (уже настроено)

gradle/libs.versions.toml:

compose-multiplatform = "1.7.3"   # версия из assistent; собирается на нашем Kotlin 2.4.20
markdown = "0.51.0"

markdown = { module = "ru.otpbank.ai:markdown", version.ref = "markdown" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
compose = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }

build.gradle.kts — плагины kotlin-compose + compose, зависимости compose.foundation / compose.ui / compose.material3 / compose.materialIconsExtended (иконка копирования) + libs.markdown. compose.desktop.currentOs пока НЕ подключён — он понадобится, когда появится настоящее окно (compose.desktop { application { mainClass = ... } }), для компиляции рендера он не нужен.

settings.gradle.kts — репозитории (без них ru.otpbank.ai:markdown не найдётся):

maven {
    name = "developerspace"
    url = uri("http://192.168.76.117/repository/developerspace-prod-mvn-hosted/")
    setAllowInsecureProtocol(true)
}
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
maven("https://maven.google.com")

Проверено: ./gradlew compileKotlinJvm — OK; ./gradlew jvmTest — MarkdownParserTest 11/11, ConversationMetaRepositoryIT 14/14.

Как пересинхронизировать, если в assistent что-то поправят

RAW-адрес: /raw/branch/main/<путь> (нужен логин, репозиторий приватный).

BASE=https://git.binom.pw/ai/assistent/raw/branch/main
AUTH='subochev:ПАРОЛЬ'

for f in \
  client-shared/src/main/kotlin/pw/binom/client/shared/markdown/MarkdownSegment.kt \
  client-shared/src/main/kotlin/pw/binom/client/shared/markdown/MarkdownParserImpl.kt \
  client-shared/src/main/kotlin/pw/binom/client/shared/ui/MarkdownRendering.kt \
  client-shared/src/main/kotlin/pw/binom/client/shared/ui/ChatImage.kt \
  client-shared/src/main/kotlin/pw/binom/client/shared/ClientImageSupport.kt \
  client/src/main/kotlin/pw/binom/client/ui/MarkdownRenderer.kt ; do
    curl -sS -u "$AUTH" "$BASE/$f" -o "/tmp/$(basename "$f")"
done

После копирования — тем же sed-ом переименовать пакеты (см. таблицу выше), MarkdownRenderer дополнительно сослать renderMarkdownSegments на pw.binom.agentik.desktop.ui.

Что рендер умеет (по коду)

  • Текст с участками: жирный, курсив, код, зачёркнутый, ссылки.
  • Блоки кода — с названием языка и кнопкой копирования (LocalClipboardManager).
  • Таблицы — заголовки/строки, переключаются на горизонтальный скролл по isBubbleScrolled (иначе Compose падает на бесконечной maxWidth).
  • Картинки — data: и http(s); рисуются через ChatImage/ClientImageSupport.
  • Разделители (---).
  • Кликабельные ссылки — нажатие уходит в onUrlClick.

Известный баг на будущее

В assistent в MessageBubble.kt:153 (НЕ скопирован) hasTable вызывает parseMarkdown вторым проходом до основной отрисовки — двойной парс на пузырь. У нас MessageBubble свой, при его написании в 0-й версии просто звать renderMarkdownSegments один раз; кэшировать разбор через remember(record.id, record.text).

Что НЕ брали

  • MessageBubble.kt — тянет chat-common и чужие модели сообщений.
  • Модули chat-*, ChatScreen, ChatViewModel, SettingsDialog и т. п.
  • Границы заимствования — в BORROW-FROM-ASSISTENT.md.