Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
"HistoryCommands": "History commands",
"Playlist": "Playlist",
"SearchCommands": "Search commands...",
"TextIndex": "Text index",
"SearchIndex": "Search recognized text...",
"OCRTextEvidence": "Screen text (OCR)",
"OCREvidenceHint": "OCR observations from video frames, not verified user actions.",
"IndexBaseline": "Initial screen (OCR)",
"IndexNoText": "No text available",
"IndexEmpty": "No indexed events",
"IndexEmptyHint": "This recording contains no OCR text events.",
"IndexNoMatch": "No matching events",
"IndexNoMatchHint": "Try another word or phrase.",
"IndexFailed": "Unable to load text index",
"IndexPartUnavailable": "The recording segment for this event is not available yet.",
"NoCommands": "No command records",
"NoCommandsHint": "This session has no recorded commands",
"CommandsFailed": "Unable to load commands",
Expand All @@ -25,6 +37,8 @@
"HideRail": "Hide panel",
"ShowRail": "Show panel",
"Seeking": "Seeking...",
"WaitingForSeekable": "Waiting for the recording to become seekable...",
"SeekUnavailable": "This position is not seekable yet. Try again after the recording loads.",
"Preparing": "Preparing recording",
"PreparingHint": "The recording is still being processed. Please wait.",
"NotFound": "Recording not found",
Expand Down
14 changes: 14 additions & 0 deletions i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
"HistoryCommands": "Historial de comandos",
"Playlist": "Lista de reproducción",
"SearchCommands": "Buscar comandos...",
"TextIndex": "Índice de texto",
"SearchIndex": "Buscar texto reconocido...",
"OCRTextEvidence": "Texto en pantalla (OCR)",
"OCREvidenceHint": "Observaciones OCR de los fotogramas, no acciones verificadas del usuario.",
"IndexBaseline": "Pantalla inicial (OCR)",
"IndexNoText": "No hay texto disponible",
"IndexEmpty": "No hay eventos indexados",
"IndexEmptyHint": "Esta grabación no contiene eventos de texto OCR.",
"IndexNoMatch": "No hay eventos coincidentes",
"IndexNoMatchHint": "Pruebe con otra palabra o frase.",
"IndexFailed": "No se pudo cargar el índice de texto",
"IndexPartUnavailable": "El segmento de grabación de este evento aún no está disponible.",
"NoCommands": "No hay registros de comandos",
"NoCommandsHint": "Esta sesión no tiene comandos grabados",
"CommandsFailed": "No se pudieron cargar los comandos",
Expand All @@ -25,6 +37,8 @@
"HideRail": "Ocultar panel",
"ShowRail": "Mostrar panel",
"Seeking": "Buscando...",
"WaitingForSeekable": "Esperando a que la grabación permita saltar...",
"SeekUnavailable": "Aún no se puede saltar a esta posición. Inténtelo tras cargar la grabación.",
"Preparing": "Preparando la grabación",
"PreparingHint": "La grabación aún se está procesando. Espere.",
"NotFound": "Grabación no encontrada",
Expand Down
14 changes: 14 additions & 0 deletions i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
"HistoryCommands": "コマンド履歴",
"Playlist": "プレイリスト",
"SearchCommands": "コマンドを検索...",
"TextIndex": "文字インデックス",
"SearchIndex": "認識された文字を検索...",
"OCRTextEvidence": "画面の文字(OCR)",
"OCREvidenceHint": "動画フレームの OCR 観測結果であり、確認済みのユーザー操作ではありません。",
"IndexBaseline": "初期画面(OCR)",
"IndexNoText": "文字情報がありません",
"IndexEmpty": "インデックスイベントがありません",
"IndexEmptyHint": "この録画には OCR 文字イベントがありません。",
"IndexNoMatch": "一致するイベントがありません",
"IndexNoMatchHint": "別の単語や語句を試してください。",
"IndexFailed": "文字インデックスを読み込めません",
"IndexPartUnavailable": "このイベントに対応する録画セグメントはまだ利用できません。",
"NoCommands": "コマンド記録はありません",
"NoCommandsHint": "このセッションにはコマンドが記録されていません",
"CommandsFailed": "コマンドを読み込めません",
Expand All @@ -25,6 +37,8 @@
"HideRail": "パネルを非表示",
"ShowRail": "パネルを表示",
"Seeking": "移動中...",
"WaitingForSeekable": "録画をシークできる状態になるまで待機しています...",
"SeekUnavailable": "この位置にはまだシークできません。録画の読み込み後に再試行してください。",
"Preparing": "録画を準備中",
"PreparingHint": "録画はまだ処理中です。しばらくお待ちください。",
"NotFound": "録画が見つかりません",
Expand Down
14 changes: 14 additions & 0 deletions i18n/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
"HistoryCommands": "명령 기록",
"Playlist": "재생 목록",
"SearchCommands": "명령 검색...",
"TextIndex": "텍스트 색인",
"SearchIndex": "인식된 텍스트 검색...",
"OCRTextEvidence": "화면 텍스트 (OCR)",
"OCREvidenceHint": "영상 프레임의 OCR 관찰 결과이며, 확인된 사용자 작업이 아닙니다.",
"IndexBaseline": "초기 화면 (OCR)",
"IndexNoText": "텍스트 없음",
"IndexEmpty": "색인된 이벤트 없음",
"IndexEmptyHint": "이 녹화에는 OCR 텍스트 이벤트가 없습니다.",
"IndexNoMatch": "일치하는 이벤트 없음",
"IndexNoMatchHint": "다른 단어나 문구를 사용해 보세요.",
"IndexFailed": "텍스트 색인을 불러올 수 없습니다",
"IndexPartUnavailable": "이 이벤트의 녹화 구간을 아직 사용할 수 없습니다.",
"NoCommands": "명령 기록 없음",
"NoCommandsHint": "이 세션에는 기록된 명령이 없습니다",
"CommandsFailed": "명령을 불러올 수 없습니다",
Expand All @@ -25,6 +37,8 @@
"HideRail": "패널 숨기기",
"ShowRail": "패널 표시",
"Seeking": "이동 중...",
"WaitingForSeekable": "녹화를 탐색할 수 있을 때까지 기다리는 중...",
"SeekUnavailable": "아직 이 위치로 이동할 수 없습니다. 녹화가 로드된 후 다시 시도하세요.",
"Preparing": "녹화 준비 중",
"PreparingHint": "녹화를 처리 중입니다. 잠시 기다려 주세요.",
"NotFound": "녹화를 찾을 수 없음",
Expand Down
14 changes: 14 additions & 0 deletions i18n/locales/pt_br.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
"HistoryCommands": "Histórico de comandos",
"Playlist": "Lista de reprodução",
"SearchCommands": "Pesquisar comandos...",
"TextIndex": "Índice de texto",
"SearchIndex": "Pesquisar texto reconhecido...",
"OCRTextEvidence": "Texto da tela (OCR)",
"OCREvidenceHint": "Observações de OCR dos quadros do vídeo, não ações verificadas do usuário.",
"IndexBaseline": "Tela inicial (OCR)",
"IndexNoText": "Nenhum texto disponível",
"IndexEmpty": "Nenhum evento indexado",
"IndexEmptyHint": "Esta gravação não contém eventos de texto OCR.",
"IndexNoMatch": "Nenhum evento correspondente",
"IndexNoMatchHint": "Tente outra palavra ou expressão.",
"IndexFailed": "Não foi possível carregar o índice de texto",
"IndexPartUnavailable": "O segmento da gravação deste evento ainda não está disponível.",
"NoCommands": "Nenhum registro de comando",
"NoCommandsHint": "Esta sessão não possui comandos gravados",
"CommandsFailed": "Não foi possível carregar os comandos",
Expand All @@ -25,6 +37,8 @@
"HideRail": "Ocultar painel",
"ShowRail": "Mostrar painel",
"Seeking": "Buscando...",
"WaitingForSeekable": "Aguardando a gravação ficar disponível para busca...",
"SeekUnavailable": "Ainda não é possível acessar essa posição. Tente novamente após o carregamento.",
"Preparing": "Preparando a gravação",
"PreparingHint": "A gravação ainda está sendo processada. Aguarde.",
"NotFound": "Gravação não encontrada",
Expand Down
14 changes: 14 additions & 0 deletions i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
"HistoryCommands": "История команд",
"Playlist": "Список воспроизведения",
"SearchCommands": "Поиск команд...",
"TextIndex": "Текстовый индекс",
"SearchIndex": "Поиск распознанного текста...",
"OCRTextEvidence": "Текст на экране (OCR)",
"OCREvidenceHint": "Результаты OCR кадров видео, а не подтверждённые действия пользователя.",
"IndexBaseline": "Начальный экран (OCR)",
"IndexNoText": "Текст недоступен",
"IndexEmpty": "Нет индексированных событий",
"IndexEmptyHint": "В этой записи нет текстовых событий OCR.",
"IndexNoMatch": "Совпадений не найдено",
"IndexNoMatchHint": "Попробуйте другое слово или фразу.",
"IndexFailed": "Не удалось загрузить текстовый индекс",
"IndexPartUnavailable": "Фрагмент записи для этого события пока недоступен.",
"NoCommands": "Нет записанных команд",
"NoCommandsHint": "Для этого сеанса команды не записывались",
"CommandsFailed": "Не удалось загрузить команды",
Expand All @@ -25,6 +37,8 @@
"HideRail": "Скрыть панель",
"ShowRail": "Показать панель",
"Seeking": "Переход...",
"WaitingForSeekable": "Ожидание готовности записи к перемотке...",
"SeekUnavailable": "Переход к этой позиции пока недоступен. Повторите после загрузки записи.",
"Preparing": "Подготовка записи",
"PreparingHint": "Запись еще обрабатывается. Подождите.",
"NotFound": "Запись не найдена",
Expand Down
14 changes: 14 additions & 0 deletions i18n/locales/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
"HistoryCommands": "Lịch sử lệnh",
"Playlist": "Danh sách phát",
"SearchCommands": "Tìm kiếm lệnh...",
"TextIndex": "Chỉ mục văn bản",
"SearchIndex": "Tìm văn bản đã nhận dạng...",
"OCRTextEvidence": "Văn bản trên màn hình (OCR)",
"OCREvidenceHint": "Kết quả OCR từ khung hình, không phải hành động người dùng đã được xác minh.",
"IndexBaseline": "Màn hình ban đầu (OCR)",
"IndexNoText": "Không có văn bản",
"IndexEmpty": "Không có sự kiện được lập chỉ mục",
"IndexEmptyHint": "Bản ghi này không có sự kiện văn bản OCR.",
"IndexNoMatch": "Không có sự kiện phù hợp",
"IndexNoMatchHint": "Hãy thử từ hoặc cụm từ khác.",
"IndexFailed": "Không thể tải chỉ mục văn bản",
"IndexPartUnavailable": "Đoạn ghi hình của sự kiện này chưa khả dụng.",
"NoCommands": "Không có bản ghi lệnh",
"NoCommandsHint": "Phiên này không có lệnh nào được ghi lại",
"CommandsFailed": "Không thể tải các lệnh",
Expand All @@ -25,6 +37,8 @@
"HideRail": "Ẩn bảng điều khiển",
"ShowRail": "Hiện bảng điều khiển",
"Seeking": "Đang tìm...",
"WaitingForSeekable": "Đang chờ bản ghi có thể tua đến vị trí này...",
"SeekUnavailable": "Chưa thể tua đến vị trí này. Hãy thử lại sau khi bản ghi tải xong.",
"Preparing": "Đang chuẩn bị bản ghi",
"PreparingHint": "Bản ghi vẫn đang được xử lý. Vui lòng chờ.",
"NotFound": "Không tìm thấy bản ghi",
Expand Down
14 changes: 14 additions & 0 deletions i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
"HistoryCommands": "历史命令",
"Playlist": "播放列表",
"SearchCommands": "搜索命令...",
"TextIndex": "文字索引",
"SearchIndex": "搜索识别文字...",
"OCRTextEvidence": "画面文字(OCR)",
"OCREvidenceHint": "这是录像画面的 OCR 观察结果,并非已核实的用户操作。",
"IndexBaseline": "初始画面(OCR)",
"IndexNoText": "暂无文字内容",
"IndexEmpty": "没有索引事件",
"IndexEmptyHint": "这段录像没有产生 OCR 文字事件。",
"IndexNoMatch": "没有匹配的事件",
"IndexNoMatchHint": "请换一个关键词或短语。",
"IndexFailed": "无法加载文字索引",
"IndexPartUnavailable": "此事件对应的录像分段尚不可用。",
"NoCommands": "无命令记录",
"NoCommandsHint": "此会话未记录命令操作",
"CommandsFailed": "无法加载命令",
Expand All @@ -25,6 +37,8 @@
"HideRail": "隐藏侧栏",
"ShowRail": "显示侧栏",
"Seeking": "正在跳转...",
"WaitingForSeekable": "正在等待录像可跳转,请稍候...",
"SeekUnavailable": "录像尚无法跳转到此时间,请等待加载完成后重试。",
"Preparing": "正在准备录像",
"PreparingHint": "录像文件正在转换处理中,请稍候",
"NotFound": "录像不存在",
Expand Down
14 changes: 14 additions & 0 deletions i18n/locales/zh_hant.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
"HistoryCommands": "歷史命令",
"Playlist": "播放列表",
"SearchCommands": "搜尋命令...",
"TextIndex": "文字索引",
"SearchIndex": "搜尋辨識文字...",
"OCRTextEvidence": "畫面文字(OCR)",
"OCREvidenceHint": "這是錄影畫面的 OCR 觀察結果,並非已核實的使用者操作。",
"IndexBaseline": "初始畫面(OCR)",
"IndexNoText": "暫無文字內容",
"IndexEmpty": "沒有索引事件",
"IndexEmptyHint": "這段錄影沒有產生 OCR 文字事件。",
"IndexNoMatch": "沒有符合的事件",
"IndexNoMatchHint": "請換一個關鍵字或詞句。",
"IndexFailed": "無法載入文字索引",
"IndexPartUnavailable": "此事件對應的錄影片段尚不可用。",
"NoCommands": "無命令記錄",
"NoCommandsHint": "此會話未記錄命令操作",
"CommandsFailed": "無法載入命令",
Expand All @@ -25,6 +37,8 @@
"HideRail": "隱藏側欄",
"ShowRail": "顯示側欄",
"Seeking": "正在跳轉...",
"WaitingForSeekable": "正在等待錄影可跳轉,請稍候...",
"SeekUnavailable": "錄影尚無法跳轉到此時間,請等待載入完成後重試。",
"Preparing": "正在準備錄影",
"PreparingHint": "錄影檔案正在轉換處理中,請稍候",
"NotFound": "錄影不存在",
Expand Down
10 changes: 9 additions & 1 deletion packages/online-player/app/api/replay.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Replay, ReplayCommand, ReplayPartManifest, ReplaySession } from "#online-player/types";
import type { Replay, ReplayCommand, ReplayIndex, ReplayPartManifest, ReplaySession } from "#online-player/types";

import { apiRequest } from "#imports";
import { isReplayApiPath, replayRequestOrgId } from "#online-player/utils/replay";
Expand All @@ -20,6 +20,14 @@ export function fetchReplay(sessionId: string) {
});
}

export function fetchReplayIndex(sessionId: string) {
return apiRequest<ReplayIndex>({
method: "GET",
path: `/api/v1/terminal/sessions/${encodeURIComponent(sessionId)}/replay-index/`,
orgId: requestOrgId()
});
}

export function fetchReplaySession(sessionId: string) {
return apiRequest<ReplaySession>({
method: "GET",
Expand Down
4 changes: 4 additions & 0 deletions packages/online-player/app/assets/replay.css
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@
background: var(--ui-color-error-500);
}

.replay-index-marker .replay-command-marker-tick {
background: var(--ui-color-info-500);
}

.replay-command-marker.is-active .replay-command-marker-tick {
width: 4px;
height: 14px;
Expand Down
36 changes: 35 additions & 1 deletion packages/online-player/app/components/ReplayControls.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script setup lang="ts">
import type { DropdownMenuItem } from "@nuxt/ui";
import type { ReplayCommand, ReplayPartItem } from "#online-player/types";
import type { ReplayCommand, ReplayIndexEvent, ReplayPartItem } from "#online-player/types";
import { REPLAY_SPEEDS } from "#online-player/types";
import { eventExcerpt, eventPositionMs, visibleIndexMarkers } from "#online-player/utils/replayIndex";
import { formatClock } from "#online-player/utils/time";

const props = defineProps<{
Expand All @@ -12,6 +13,10 @@ const props = defineProps<{
durationReady?: boolean;
speed: number;
commands: ReplayCommand[];
indexEvents?: ReplayIndexEvent[];
isParts?: boolean;
activePartIndex?: number;
activeIndexOrdinal?: number;
parts: ReplayPartItem[];
showParts?: boolean;
activePartSrc?: string;
Expand All @@ -27,6 +32,7 @@ const emit = defineEmits<{
restart: [];
seek: [number];
selectCommand: [ReplayCommand];
selectIndexEvent: [ReplayIndexEvent];
selectPart: [ReplayPartItem];
toggleCommandRail: [];
"update:speed": [number];
Expand Down Expand Up @@ -63,6 +69,21 @@ const ticks = computed(() =>
}))
);

const indexTicks = computed(() =>
visibleIndexMarkers(
(props.indexEvents || []).filter(
(event) => eventPositionMs(event, Boolean(props.isParts), props.activePartIndex) !== null
)
)
.map((event) => ({ event, offsetMs: eventPositionMs(event, Boolean(props.isParts), props.activePartIndex) }))
.filter((item) => item.offsetMs !== null && props.durationMs > 0 && item.offsetMs <= props.durationMs)
.map((item) => ({
event: item.event,
left: `${(item.offsetMs! / props.durationMs) * 100}%`,
active: item.event.ordinal === props.activeIndexOrdinal
}))
);

const speedItems = computed<DropdownMenuItem[][]>(() => [
REPLAY_SPEEDS.map((item) => ({
label: `${item.toFixed(1)}×`,
Expand Down Expand Up @@ -109,6 +130,19 @@ const sliderReady = computed(() => Boolean(props.durationReady) && props.duratio
<span class="replay-command-marker-tick" />
<span class="replay-command-marker-label">{{ tick.command.input }}</span>
</button>
<button
v-for="tick in indexTicks"
:key="`index-${tick.event.ordinal}`"
type="button"
class="replay-command-marker replay-index-marker"
:class="{ 'is-active': tick.active }"
:style="{ left: tick.left }"
:aria-label="`${formatClock(tick.event.replay_ms)} ${t('Replay.OCRTextEvidence')}: ${eventExcerpt(tick.event)}`"
@click.stop="emit('selectIndexEvent', tick.event)"
>
<span class="replay-command-marker-tick" />
<span class="replay-command-marker-label">{{ eventExcerpt(tick.event) }}</span>
</button>
</div>

<div class="replay-controls-row flex h-16 items-center gap-4 px-5">
Expand Down
Loading
Loading