Author SHA1 Message Date
Dmitriy Dementiev 352296b84a docs(prototype): добавлен макет транскрипта со спикерами
- Зачем:
  - требовалось выбрать компактный Markdown-формат реплик до реализации диаризации.
- Что:
  - добавлены три переключаемых варианта на реальном обезличенном фрагменте.
  - выбранный линейный вариант обновлён до формата `[MM:SS] Speaker N: текст`.
- Проверка:
  - файл открыт локально, варианты A–C переключаются стрелками и параметром `variant`.
2026-08-14 14:38:42 +03:00
+326
View File
@@ -0,0 +1,326 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PROTOTYPE — формат транскрипта со спикерами</title>
<style>
:root {
color-scheme: light;
--paper: #fbfaf7;
--ink: #25221f;
--muted: #746e67;
--line: #ddd7ce;
--accent: #9b3f2f;
--code: #f0ece5;
}
* { box-sizing: border-box; }
body {
margin: 0;
background: #e9e4dc;
color: var(--ink);
font: 16px/1.58 system-ui, -apple-system, "Segoe UI", sans-serif;
}
main {
width: min(1180px, calc(100% - 32px));
margin: 28px auto 100px;
}
.prototype-note {
margin-bottom: 18px;
padding: 12px 16px;
border: 1px dashed #a59d92;
background: #fffdf8;
color: #5f5850;
}
.prototype-note strong { color: var(--accent); }
.layout {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(340px, .72fr);
gap: 18px;
align-items: start;
}
.paper, .source-panel {
border: 1px solid var(--line);
border-radius: 10px;
background: var(--paper);
box-shadow: 0 8px 28px rgb(49 40 31 / 9%);
}
.paper { padding: clamp(22px, 4vw, 54px); }
.source-panel { position: sticky; top: 18px; overflow: hidden; }
.source-panel h2 {
margin: 0;
padding: 13px 16px;
border-bottom: 1px solid var(--line);
color: var(--muted);
font-size: 14px;
letter-spacing: .06em;
text-transform: uppercase;
}
pre {
max-height: calc(100vh - 120px);
margin: 0;
padding: 18px;
overflow: auto;
background: var(--code);
white-space: pre-wrap;
word-break: break-word;
font: 13px/1.55 ui-monospace, "Cascadia Code", Consolas, monospace;
}
h1 { margin: 0 0 22px; font: 700 clamp(26px, 3vw, 38px)/1.15 Georgia, serif; }
h2 { margin: 28px 0 12px; font-size: 20px; }
h3 { margin: 25px 0 7px; font-size: 17px; }
ul { padding-left: 21px; }
hr { margin: 28px 0; border: 0; border-top: 1px solid var(--line); }
.turn { margin: 18px 0; }
.time { color: var(--muted); font: 13px ui-monospace, "Cascadia Code", monospace; }
.speaker { color: #783427; }
blockquote {
margin: 8px 0 22px;
padding: 10px 16px;
border-left: 4px solid #b88772;
background: #f5f0e9;
}
blockquote p { margin: 0; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { padding: 9px 8px; border: 1px solid var(--line); vertical-align: top; text-align: left; }
th { background: #eee8de; }
.warning {
margin: 17px 0;
padding: 10px 13px;
border-left: 4px solid #c47b23;
background: #fff2dc;
}
.overlap { background: #f7e9e4; }
.switcher {
position: fixed;
left: 50%;
bottom: 22px;
z-index: 10;
display: flex;
align-items: center;
gap: 6px;
transform: translateX(-50%);
padding: 7px;
border: 1px solid rgb(255 255 255 / 30%);
border-radius: 999px;
background: #201d1a;
box-shadow: 0 8px 32px rgb(0 0 0 / 25%);
color: white;
}
.switcher button {
width: 38px;
height: 34px;
border: 0;
border-radius: 999px;
background: #39332e;
color: white;
cursor: pointer;
font-size: 20px;
}
.switcher button:hover { background: #554b43; }
#variant-label { min-width: 230px; text-align: center; font-size: 14px; }
@media (max-width: 820px) {
.layout { grid-template-columns: 1fr; }
.source-panel { position: static; }
pre { max-height: none; }
#variant-label { min-width: 190px; }
}
</style>
</head>
<body>
<!-- Три варианта markdown-транскрипта, переключаемые через ?variant=, в отдельном throwaway-прототипе. -->
<main>
<div class="prototype-note">
<strong>PROTOTYPE — не часть продукта.</strong>
Реальный фрагмент рабочей встречи слегка сокращён и обезличен; интервалы
и статистика взяты из уже выполненного замера. Слева — вид документа,
справа — буквальный Markdown для оценки последующей обработки ИИ.
</div>
<div class="layout">
<article class="paper" id="preview"></article>
<section class="source-panel">
<h2>Markdown-источник</h2>
<pre id="source"></pre>
</section>
</div>
</main>
<nav class="switcher" aria-label="Переключение вариантов">
<button id="previous" aria-label="Предыдущий вариант"></button>
<span id="variant-label"></span>
<button id="next" aria-label="Следующий вариант"></button>
</nav>
<script>
const variants = {
A: {
name: "Линейные реплики",
source: `# Транскрипт: пример-встречи.mp4
- **Дата транскрипции**: 2026-08-14 12:00:00
- **Модель**: gigaam-v3-e2e-rnnt
- **Язык**: ru (задан явно)
- **Длительность**: 25:59
- **Устройство**: ONNX (CPU)
- **Диаризация**: 4 голосовых кластера
- **Внимание**: Speaker 4 — малый кластер (00:19; 1,3% речи), возможна ошибка разделения
---
[08:50] Speaker 3: А показываем, получается, в их контуре, не в нашем?
[08:54] Speaker 2: В нашем, по-моему.
[08:55] Speaker 3: В нашем. А, отлично. У нас просто есть тестовый контур, который на самом деле…
[09:07] Speaker 1: Мы же у них не разворачиваемся. Мне гораздо проще накатывать обновления на наш контур.
[11:34] Speaker 2: Результаты проверок пишутся туда же.
[11:39] Speaker 1: По сути, нам нужно переписать только ту часть, которая обрабатывает файл.
[11:43] Speaker 3: Да, а дальше переиспользовать существующую запись результатов.`,
preview: `
<h1>Транскрипт: пример-встречи.mp4</h1>
<ul>
<li><strong>Дата транскрипции</strong>: 2026-08-14 12:00:00</li>
<li><strong>Модель</strong>: gigaam-v3-e2e-rnnt</li>
<li><strong>Язык</strong>: ru (задан явно)</li>
<li><strong>Длительность</strong>: 25:59</li>
<li><strong>Устройство</strong>: ONNX (CPU)</li>
<li><strong>Диаризация</strong>: 4 голосовых кластера</li>
</ul>
<div class="warning"><strong>Внимание:</strong> Speaker 4 — малый кластер (00:19; 1,3% речи), возможна ошибка разделения.</div>
<hr>
<p class="turn"><span class="time">[08:50]</span> Speaker 3: А показываем, получается, в их контуре, не в нашем?</p>
<p class="turn"><span class="time">[08:54]</span> Speaker 2: В нашем, по-моему.</p>
<p class="turn"><span class="time">[08:55]</span> Speaker 3: В нашем. А, отлично. У нас просто есть тестовый контур, который на самом деле…</p>
<p class="turn"><span class="time">[09:07]</span> Speaker 1: Мы же у них не разворачиваемся. Мне гораздо проще накатывать обновления на наш контур.</p>
<p class="turn"><span class="time">[11:34]</span> Speaker 2: Результаты проверок пишутся туда же.</p>
<p class="turn"><span class="time">[11:39]</span> Speaker 1: По сути, нам нужно переписать только ту часть, которая обрабатывает файл.</p>
<p class="turn overlap"><span class="time">[11:43]</span> Speaker 3: Да, а дальше переиспользовать существующую запись результатов.</p>`
},
B: {
name: "Сценарий",
source: `# Транскрипт: пример-встречи.mp4
> Диаризация нашла четыре голосовых кластера. Speaker 4 занимает 19 секунд и может быть ошибкой разделения.
---
### Speaker 3 · [08:50.75 - 08:54.73]
> А показываем, получается, в их контуре, не в нашем?
### Speaker 2 · [08:54.73 - 08:55.91]
> В нашем, по-моему.
### Speaker 3 · [08:55.81 - 09:07.96]
> В нашем. А, отлично. У нас просто есть тестовый контур, который на самом деле…
### Speaker 1 · [09:07.96 - 09:18.80]
> Мы же у них не разворачиваемся. Мне гораздо проще накатывать обновления на наш контур.
## Перекрывающаяся речь · [11:43.57 - 11:44.82]
> **Speaker 1:** По сути, нам нужно переписать только ту часть, которая обрабатывает файл.
>
> **Speaker 3:** Да, а дальше переиспользовать существующую запись результатов.`,
preview: `
<h1>Транскрипт: пример-встречи.mp4</h1>
<blockquote><p>Диаризация нашла четыре голосовых кластера. Speaker 4 занимает 19 секунд и может быть ошибкой разделения.</p></blockquote>
<hr>
<h3>Speaker 3 · <span class="time">[08:50.75 - 08:54.73]</span></h3>
<blockquote><p>А показываем, получается, в их контуре, не в нашем?</p></blockquote>
<h3>Speaker 2 · <span class="time">[08:54.73 - 08:55.91]</span></h3>
<blockquote><p>В нашем, по-моему.</p></blockquote>
<h3>Speaker 3 · <span class="time">[08:55.81 - 09:07.96]</span></h3>
<blockquote><p>В нашем. А, отлично. У нас просто есть тестовый контур, который на самом деле…</p></blockquote>
<h3>Speaker 1 · <span class="time">[09:07.96 - 09:18.80]</span></h3>
<blockquote><p>Мы же у них не разворачиваемся. Мне гораздо проще накатывать обновления на наш контур.</p></blockquote>
<h2>Перекрывающаяся речь · <span class="time">[11:43.57 - 11:44.82]</span></h2>
<blockquote class="overlap"><p><strong>Speaker 1:</strong> По сути, нам нужно переписать только ту часть, которая обрабатывает файл.<br><br><strong>Speaker 3:</strong> Да, а дальше переиспользовать существующую запись результатов.</p></blockquote>`
},
C: {
name: "Таблица событий",
source: `# Транскрипт: пример-встречи.mp4
| Speaker | Речь | Доля | Примечание |
|---|---:|---:|---|
| Speaker 1 | 09:33 | 40,3% | основной кластер |
| Speaker 2 | 08:07 | 34,3% | основной кластер |
| Speaker 3 | 05:42 | 24,1% | основной кластер |
| Speaker 4 | 00:19 | 1,3% | возможная ошибка разделения |
| Начало | Конец | Кто | Текст | Событие |
|---:|---:|---|---|---|
| 08:50.75 | 08:54.73 | S3 | А показываем, получается, в их контуре, не в нашем? | — |
| 08:54.73 | 08:55.91 | S2 | В нашем, по-моему. | — |
| 08:55.81 | 09:07.96 | S3 | В нашем. А, отлично. У нас просто есть тестовый контур… | — |
| 09:07.96 | 09:18.80 | S1 | Мы же у них не разворачиваемся. Мне проще накатывать обновления на наш контур. | — |
| 11:39.91 | 11:44.82 | S1 | Нам нужно переписать только ту часть, которая обрабатывает файл. | overlap:S3 |
| 11:43.57 | 11:46.47 | S3 | Да, а дальше переиспользовать существующую запись результатов. | overlap:S1 |`,
preview: `
<h1>Транскрипт: пример-встречи.mp4</h1>
<table>
<thead><tr><th>Speaker</th><th>Речь</th><th>Доля</th><th>Примечание</th></tr></thead>
<tbody>
<tr><td>Speaker 1</td><td>09:33</td><td>40,3%</td><td>основной кластер</td></tr>
<tr><td>Speaker 2</td><td>08:07</td><td>34,3%</td><td>основной кластер</td></tr>
<tr><td>Speaker 3</td><td>05:42</td><td>24,1%</td><td>основной кластер</td></tr>
<tr class="warning"><td>Speaker 4</td><td>00:19</td><td>1,3%</td><td>возможная ошибка разделения</td></tr>
</tbody>
</table>
<h2>События</h2>
<table>
<thead><tr><th>Начало</th><th>Конец</th><th>Кто</th><th>Текст</th><th>Событие</th></tr></thead>
<tbody>
<tr><td>08:50.75</td><td>08:54.73</td><td>S3</td><td>А показываем, получается, в их контуре, не в нашем?</td><td>—</td></tr>
<tr><td>08:54.73</td><td>08:55.91</td><td>S2</td><td>В нашем, по-моему.</td><td>—</td></tr>
<tr><td>08:55.81</td><td>09:07.96</td><td>S3</td><td>В нашем. А, отлично. У нас просто есть тестовый контур…</td><td>—</td></tr>
<tr><td>09:07.96</td><td>09:18.80</td><td>S1</td><td>Мы же у них не разворачиваемся. Мне проще накатывать обновления на наш контур.</td><td>—</td></tr>
<tr class="overlap"><td>11:39.91</td><td>11:44.82</td><td>S1</td><td>Нам нужно переписать только ту часть, которая обрабатывает файл.</td><td>overlap:S3</td></tr>
<tr class="overlap"><td>11:43.57</td><td>11:46.47</td><td>S3</td><td>Да, а дальше переиспользовать существующую запись результатов.</td><td>overlap:S1</td></tr>
</tbody>
</table>`
}
};
const keys = Object.keys(variants);
const params = new URLSearchParams(window.location.search);
let current = (params.get("variant") || "A").toUpperCase();
if (!variants[current]) current = "A";
function render() {
const variant = variants[current];
document.getElementById("preview").innerHTML = variant.preview;
document.getElementById("source").textContent = variant.source;
document.getElementById("variant-label").textContent = `${current}${variant.name}`;
document.title = `${current}${variant.name} · PROTOTYPE`;
}
function move(offset) {
const index = keys.indexOf(current);
current = keys[(index + offset + keys.length) % keys.length];
const url = new URL(window.location.href);
url.searchParams.set("variant", current);
window.history.replaceState({}, "", url);
render();
}
document.getElementById("previous").addEventListener("click", () => move(-1));
document.getElementById("next").addEventListener("click", () => move(1));
window.addEventListener("keydown", (event) => {
const target = event.target;
if (target.matches("input, textarea, [contenteditable]")) return;
if (event.key === "ArrowLeft") move(-1);
if (event.key === "ArrowRight") move(1);
});
render();
</script>
</body>
</html>