27f349550c
- sketches/index.html: все экраны одним документом в порядке 1·… (14–19 — бывшие approved) - approved/ (4 файла групп + 3 макета + README) удалён как дубль - стили вынесены в sketches/style.css - README, THEMES, BORROW-FROM-ASSISTENT, MARKDOWN-SOURCE, MIC-ASR-SEARCH приведены к фактическому состоянию
137 lines
7.5 KiB
Markdown
137 lines
7.5 KiB
Markdown
# Отрисовка 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/ссылку) —
|
||
ничего не падает. На десктопе декодер регистрируется так:
|
||
|
||
```kotlin
|
||
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`:
|
||
|
||
```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` не найдётся):
|
||
|
||
```kotlin
|
||
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/<путь>` (нужен логин, репозиторий приватный).
|
||
|
||
```bash
|
||
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`.
|