Files
local-transcriber/.scratch/transcript-format-prototype.html
T
Dmitriy Dementiev 352296b84a docs(prototype): добавлен макет транскрипта со спикерами
- Зачем:
  - требовалось выбрать компактный Markdown-формат реплик до реализации диаризации.
- Что:
  - добавлены три переключаемых варианта на реальном обезличенном фрагменте.
  - выбранный линейный вариант обновлён до формата `[MM:SS] Speaker N: текст`.
- Проверка:
  - файл открыт локально, варианты A–C переключаются стрелками и параметром `variant`.
2026-08-14 14:38:42 +03:00

327 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>