Макеты — один 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 приведены к фактическому состоянию
This commit is contained in:
+98
-112
@@ -1,150 +1,136 @@
|
||||
# Откуда брать отрисовку Markdown
|
||||
# Отрисовка Markdown: откуда взято и где лежит
|
||||
|
||||
Готовый рендер Markdown для Compose уже написан и лежит в репозитории **`ai/assistent`**.
|
||||
Писать своё не надо — надо взять это.
|
||||
**Статус: УЖЕ СКОПИРОВАНО В ПРОЕКТ (28.09.2026).** Код вендорен в `src/`,
|
||||
собирается, тесты парсера (11 шт.) зелёные. Своё писать не надо.
|
||||
|
||||
**Репозиторий:** https://git.binom.pw/ai/assistent (ветка `main`)
|
||||
**Проверено на коммите:** `2c5b3f9` (файлы добавлены в `1acced4`, 2026-08-13)
|
||||
**Важно:** репозиторий приватный. Браузерная ссылка откроется только у залогиненного;
|
||||
для копирования файлов из скрипта — RAW-адреса с логином/паролем (ниже).
|
||||
Источник — приватный репозиторий **`ai/assistent`** (https://git.binom.pw/ai/assistent,
|
||||
ветка `main`, проверено на коммите `2c5b3f9`, файлы добавлены в `1acced4` 2026-08-13).
|
||||
Пакеты переименованы под наш проект; внешняя зависимость — одна
|
||||
(`ru.otpbank.ai:markdown`).
|
||||
|
||||
---
|
||||
|
||||
## Что именно копировать: 4 файла
|
||||
## Куда что легло
|
||||
|
||||
Всё лежит в двух модулях. Модуль `client-shared` — общий, собирается под **JVM + Compose**
|
||||
(то есть ровно наш случай, десктоп), не под Android.
|
||||
| В 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` |
|
||||
|
||||
### 1. Модель данных — `MarkdownSegment.kt` (59 строк)
|
||||
Переименование пакетов:
|
||||
|
||||
```
|
||||
client-shared/src/main/kotlin/pw/binom/client/shared/markdown/MarkdownSegment.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`
|
||||
|
||||
Что внутри: типы кусков разметки — текст, блок кода, таблица, картинка, разделитель.
|
||||
Плюс описание стилевого участка: жирный, курсив, код, зачёркнутый, ссылка.
|
||||
Зависимостей от Compose нет вообще — чистые данные.
|
||||
### Почему файлов шесть, а не четыре
|
||||
|
||||
### 2. Парсер — `MarkdownParserImpl.kt` (353 строки)
|
||||
Изначально в доке значились 4 файла. При сборке выяснилось, что
|
||||
`MarkdownRendering.kt` для сегментов-картинок вызывает `ChatImage(...)`, а тот —
|
||||
`ClientImageSupport`. Поэтому взяты ещё `ChatImage.kt` и `ClientImageSupport.kt`.
|
||||
Оба независимы от `chat-*` и `MessageBubble`.
|
||||
|
||||
```
|
||||
client-shared/src/main/kotlin/pw/binom/client/shared/markdown/MarkdownParserImpl.kt
|
||||
```
|
||||
|
||||
Точка входа: `parseMarkdown(markdown: String): List<MarkdownSegment>`.
|
||||
Единственная внешняя зависимость — библиотека парсера `ru.otpbank.ai:markdown` (см. ниже).
|
||||
От Композа не зависит.
|
||||
|
||||
### 3. Отрисовка — `MarkdownRendering.kt` (377 строк)
|
||||
|
||||
```
|
||||
client-shared/src/main/kotlin/pw/binom/client/shared/ui/MarkdownRendering.kt
|
||||
```
|
||||
|
||||
Точка входа:
|
||||
`renderMarkdownSegments(content, textColor, fontSize, isBubbleScrolled, onUrlClick)`.
|
||||
Внутри: текст с inline-стилями, кликабельные ссылки, блоки кода с копированием,
|
||||
таблицы, картинки, разделители. Самый жирный файл и самая нужная часть.
|
||||
|
||||
### 4. Десктопная обёртка — `MarkdownRenderer.kt` (25 строк)
|
||||
|
||||
```
|
||||
client/src/main/kotlin/pw/binom/client/ui/MarkdownRenderer.kt
|
||||
```
|
||||
|
||||
Открытие ссылок в системном браузере (`Desktop.getDesktop().browse`) и
|
||||
`parseMultiline`. Модуль `client` в assistent — это **десктопный** клиент на
|
||||
Compose Desktop, так что пример использования ровно наш.
|
||||
|
||||
---
|
||||
|
||||
## Что копировать НЕ надо
|
||||
|
||||
- `client-shared/src/main/kotlin/pw/binom/client/shared/ui/MessageBubble.kt` (789 строк) —
|
||||
пузырь сообщения целиком. Тянет за собой модуль `chat-common` (модели сообщений),
|
||||
из плюшек — выделение текста и контекстное меню. Для отрисовки разметки не нужен,
|
||||
но полезен, если понадобится пример.
|
||||
|
||||
**Границы заимствования — подробнее в `BORROW-FROM-ASSISTENT.md`.** Коротко:
|
||||
из assistent берём приёмы отрисовки, плагины и версии; архитектуру, слои и готовые
|
||||
экраны (`ChatScreen`, `SettingsDialog`, модули `chat-*`) — не берём, у нас своё.
|
||||
|
||||
---
|
||||
|
||||
## Зависимости
|
||||
|
||||
Одна, и она есть в нашем Nexus:
|
||||
`ClientImageSupport` — это реестр: платформенный клиент при старте регистрирует
|
||||
`decoder` (байты → `ImageBitmap`) и, при желании, `fetcher` и `serverBaseUrl`.
|
||||
Пока `decoder` не зарегистрирован, картинки деградируют в текст (alt/ссылку) —
|
||||
ничего не падает. На десктопе декодер регистрируется так:
|
||||
|
||||
```kotlin
|
||||
implementation("ru.otpbank.ai:markdown:0.51.0")
|
||||
```
|
||||
|
||||
Репозиторий в `build.gradle.kts` (без него не соберётся):
|
||||
|
||||
```kotlin
|
||||
maven {
|
||||
url = uri("http://192.168.76.117/repository/developerspace-prod-mvn-hosted/")
|
||||
isAllowInsecureProtocol = true
|
||||
ClientImageSupport.decoder = { bytes ->
|
||||
org.jetbrains.skia.Image.makeFromEncoded(bytes).toComposeImageBitmap()
|
||||
}
|
||||
```
|
||||
|
||||
Проверено: в Nexus лежит, версии с `0.1.0` по `0.51.0`, файл `markdown-0.51.0.pom`
|
||||
отдаёт `HTTP 200`.
|
||||
## Точки входа
|
||||
|
||||
Из Compose нужны: `compose.material3`, `compose.foundation`, `compose.ui`,
|
||||
`compose.materialIconsExtended` (иконка копирования в блоках кода).
|
||||
- Парсинг: `parseMarkdown(markdown: String): List<MarkdownSegment>` (в `...desktop.markdown`).
|
||||
- Отрисовка: `renderMarkdownSegments(content, textColor, fontSize, isBubbleScrolled, onUrlClick)` (в `...desktop.ui`).
|
||||
- Открытие ссылок/`parseMultiline`: `...desktop.ui.MarkdownRenderer`.
|
||||
|
||||
## Опубликован ли `client-shared` как библиотека
|
||||
## Зависимости и сборка (уже настроено)
|
||||
|
||||
**Нет.** Проверено поиском в Nexus (`pw/binom/client-shared`, `pw/binom/chat-common`) —
|
||||
`HTTP 404`. Поэтому вариантов два: либо копируем исходники к себе (4 файла выше),
|
||||
либо сперва публикуем модуль. Копирование проще.
|
||||
`gradle/libs.versions.toml`:
|
||||
|
||||
---
|
||||
```toml
|
||||
compose-multiplatform = "1.7.3" # версия из assistent; собирается на нашем Kotlin 2.4.20
|
||||
markdown = "0.51.0"
|
||||
|
||||
## Как копировать (RAW-адреса, работают с логином)
|
||||
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" }
|
||||
```
|
||||
|
||||
Адрес вида `/raw/branch/main/<путь>` отдаёт файл. Проверено: `HTTP 200`, 1805 байт —
|
||||
совпадает с локальным размером.
|
||||
`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:ПАРОЛЬ' # подставить перед запуском
|
||||
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 "$(basename "$f")"
|
||||
curl -sS -u "$AUTH" "$BASE/$f" -o "/tmp/$(basename "$f")"
|
||||
done
|
||||
```
|
||||
|
||||
Браузерные ссылки для чтения глазами (нужен залогиненный git.binom.pw):
|
||||
После копирования — тем же `sed`-ом переименовать пакеты (см. таблицу выше),
|
||||
`MarkdownRenderer` дополнительно сослать `renderMarkdownSegments` на
|
||||
`pw.binom.agentik.desktop.ui`.
|
||||
|
||||
```
|
||||
https://git.binom.pw/ai/assistent/src/branch/main/client-shared/src/main/kotlin/pw/binom/client/shared/markdown/MarkdownSegment.kt
|
||||
https://git.binom.pw/ai/assistent/src/branch/main/client-shared/src/main/kotlin/pw/binom/client/shared/markdown/MarkdownParserImpl.kt
|
||||
https://git.binom.pw/ai/assistent/src/branch/main/client-shared/src/main/kotlin/pw/binom/client/shared/ui/MarkdownRendering.kt
|
||||
https://git.binom.pw/ai/assistent/src/branch/main/client/src/main/kotlin/pw/binom/client/ui/MarkdownRenderer.kt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Что придётся поправить после копирования
|
||||
|
||||
1. **Пакеты.** Файлы лежат в `pw.binom.client.shared.*` — переименовать под наш проект
|
||||
(или оставить как есть, если не смущает).
|
||||
2. **`openUrl`.** В `MarkdownRenderer` открытие ссылок идёт через `java.awt.Desktop` —
|
||||
на десктопе это работает, ничего менять не надо.
|
||||
3. **Стиль.** Цвета текста и блоков кода в `MarkdownRendering` берутся из аргументов и
|
||||
`MaterialTheme` — под нашу тёмную тему подставить свои значения.
|
||||
|
||||
## Что этот рендер умеет (по коду)
|
||||
## Что рендер умеет (по коду)
|
||||
|
||||
- **Текст** с участками: жирный, курсив, код, зачёркнутый, ссылки.
|
||||
- **Блоки кода** — с названием языка и кнопкой копирования.
|
||||
- **Таблицы** — заголовки и строки, с горизонтальной прокруткой.
|
||||
- **Картинки** — `data:`-ссылки и обычные `http(s)`. Первые рисуются сразу из памяти.
|
||||
- **Блоки кода** — с названием языка и кнопкой копирования (`LocalClipboardManager`).
|
||||
- **Таблицы** — заголовки/строки, переключаются на горизонтальный скролл по
|
||||
`isBubbleScrolled` (иначе Compose падает на бесконечной maxWidth).
|
||||
- **Картинки** — `data:` и `http(s)`; рисуются через `ChatImage`/`ClientImageSupport`.
|
||||
- **Разделители** (`---`).
|
||||
- **Кликабельные ссылки** — нажатие уходит в `onUrlClick`, оттуда открываем браузер.
|
||||
- **Кликабельные ссылки** — нажатие уходит в `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`.
|
||||
|
||||
Reference in New Issue
Block a user