- sketches/index.html: все экраны одним документом в порядке 1·… (14–19 — бывшие approved) - approved/ (4 файла групп + 3 макета + README) удалён как дубль - стили вынесены в sketches/style.css - README, THEMES, BORROW-FROM-ASSISTENT, MARKDOWN-SOURCE, MIC-ASR-SEARCH приведены к фактическому состоянию
7.5 KiB
Отрисовка 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.markdownpw.binom.client.shared.ui→pw.binom.agentik.desktop.uipw.binom.client.shared(ClientImageSupport) →pw.binom.agentik.desktop.uipw.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.