Макеты — один 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:
2026-09-28 10:07:57 +03:00
parent 960cd60120
commit 27f349550c
25 changed files with 2416 additions and 4448 deletions
+98 -112
View File
@@ -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`.