@linto-ai/transcript-ui-core 0.9.10 → 0.9.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linto-ai/transcript-ui-core",
3
- "version": "0.9.10",
3
+ "version": "0.9.12",
4
4
  "description": "State layer and UI shell for @linto-ai/transcript-ui — core, stores, format adapters, and the TranscriptUI component",
5
5
  "keywords": [
6
6
  "vue",
@@ -39,8 +39,8 @@
39
39
  "vue": "^3.5.0"
40
40
  },
41
41
  "dependencies": {
42
- "@linto-ai/transcript-ui-i18n": "0.9.10",
43
- "@linto-ai/transcript-ui-ui": "0.9.10",
42
+ "@linto-ai/transcript-ui-i18n": "0.9.12",
43
+ "@linto-ai/transcript-ui-ui": "0.9.12",
44
44
  "vue-stick-to-bottom": "^1.0.0"
45
45
  }
46
46
  }
@@ -11,11 +11,15 @@ const props = defineProps<{
11
11
  speakerCount: number
12
12
  isMobile: boolean
13
13
  canAsk?: boolean
14
+ canUndo?: boolean
15
+ canRedo?: boolean
14
16
  }>()
15
17
 
16
18
  defineEmits<{
17
19
  toggleSidebar: []
18
20
  openChat: []
21
+ undo: []
22
+ redo: []
19
23
  }>()
20
24
 
21
25
  const { t, locale } = useI18n()
@@ -58,6 +62,20 @@ const metaParts = computed(() =>
58
62
  @click="$emit('toggleSidebar')">
59
63
  <template #icon><EditorIcon name="users" :size="16" /></template>
60
64
  </Button>
65
+ <Button
66
+ variant="transparent"
67
+ :aria-label="t('header.undo')"
68
+ :disabled="!props.canUndo"
69
+ @click="$emit('undo')">
70
+ <template #icon><EditorIcon name="undo" :size="16" /></template>
71
+ </Button>
72
+ <Button
73
+ variant="transparent"
74
+ :aria-label="t('header.redo')"
75
+ :disabled="!props.canRedo"
76
+ @click="$emit('redo')">
77
+ <template #icon><EditorIcon name="redo" :size="16" /></template>
78
+ </Button>
61
79
  <Button
62
80
  variant="primary"
63
81
  :aria-label="t('header.ask')"
@@ -109,8 +109,12 @@ function onTranslationChange(translationId: string) {
109
109
  :speaker-count="speakers.size"
110
110
  :is-mobile="isMobile"
111
111
  :can-ask="!!core.chat"
112
+ :can-undo="core.transcriptionEditor?.canUndo.value ?? false"
113
+ :can-redo="core.transcriptionEditor?.canRedo.value ?? false"
112
114
  @toggle-sidebar="isSidebarOpen = !isSidebarOpen"
113
- @open-chat="core.chat?.setDrawerOpen(true)" />
115
+ @open-chat="core.chat?.setDrawerOpen(true)"
116
+ @undo="core.transcriptionEditor?.undo()"
117
+ @redo="core.transcriptionEditor?.redo()" />
114
118
  <TabBar v-model="activeTab" />
115
119
  <SelectionActionBar v-if="showTranscription" />
116
120
  <main class="editor-body">
@@ -56,7 +56,7 @@ function exportAsText(): void {
56
56
 
57
57
  function onExport(format?: string): void {
58
58
  if (!format) return
59
- if (format === "txt" && core.verbatimFormatsAreDefault) {
59
+ if (format === "txt" && core.verbatimFormatsAreDefault.value) {
60
60
  exportAsText()
61
61
  return
62
62
  }
@@ -68,7 +68,7 @@ function onExport(format?: string): void {
68
68
  <section class="verbatim-panel">
69
69
  <DocumentArticle>
70
70
  <template #toolbar-right>
71
- <DownloadMenu :formats="core.verbatimFormats" @select="onExport" />
71
+ <DownloadMenu :formats="core.verbatimFormats.value" @select="onExport" />
72
72
  </template>
73
73
 
74
74
  <article class="verbatim-panel__content">
@@ -33,9 +33,17 @@ export function createCore(options: CoreOptions = {}): Core {
33
33
  const capabilities = ref<CoreCapabilities>(
34
34
  options.capabilities ?? { text: "edit", speakers: "edit" },
35
35
  )
36
- const verbatimFormatsAreDefault = options.verbatimFormats == null
37
- const verbatimFormats =
38
- options.verbatimFormats ?? DEFAULT_VERBATIM_FORMATS
36
+ // Ref, not a plain array: unlike `document` (setDocument) or `capabilities`
37
+ // (already a Ref), there is no dedicated setter, and no web component prop
38
+ // either (an array can't cross an HTML attribute cleanly) — a host sets
39
+ // this the same way it sets capabilities, core.verbatimFormats.value = […],
40
+ // once it holds `core` (see TranscriptUI's defineExpose).
41
+ const verbatimFormats = ref<DownloadFormat[]>(
42
+ options.verbatimFormats ?? DEFAULT_VERBATIM_FORMATS,
43
+ )
44
+ const verbatimFormatsAreDefault = computed(
45
+ () => verbatimFormats.value === DEFAULT_VERBATIM_FORMATS,
46
+ )
39
47
 
40
48
  // ── Event bus ──────────────────────────────────────────────────────
41
49
 
package/src/core/index.ts CHANGED
@@ -26,6 +26,7 @@ export type {
26
26
  TurnSpeakerUpdated,
27
27
  SpeakerRenamed,
28
28
  SpeakerReplaced,
29
+ SpeakerRestored,
29
30
  LivePluginApi,
30
31
  LivePartialEventData,
31
32
  LiveFinalEventData,
package/src/core/types.ts CHANGED
@@ -263,6 +263,10 @@ export interface TurnSpeakerUpdated {
263
263
  speaker: { id: string; name: string }
264
264
  removedSpeakerId?: string
265
265
  version?: number
266
+ /** @see SpeakerRenamed.revisionId */
267
+ revisionId?: string | null
268
+ /** @see SpeakerRenamed.redoRevisionId */
269
+ redoRevisionId?: string | null
266
270
  }
267
271
 
268
272
  export interface SpeakerRenamed {
@@ -270,6 +274,18 @@ export interface SpeakerRenamed {
270
274
  speakerId: string
271
275
  name: string
272
276
  version?: number
277
+ /** The undo cursor AFTER this broadcast (see onUndo/onRedo server-side) —
278
+ * a plain rename and an undo/redo replaying one are indistinguishable on
279
+ * the wire, and don't need to be: both just mean "the cursor is here
280
+ * now". Absent from broadcasts predating the undo/redo feature. */
281
+ revisionId?: string | null
282
+ /** The revision a redo would target FROM this new cursor, server-computed
283
+ * — null when there's nothing to redo to. This is server truth, not a
284
+ * client guess: right after a fresh mutation it's always null (nothing
285
+ * could already be chained onto a revision that didn't exist a moment
286
+ * ago), right after an undo it's the revision just undone, right after a
287
+ * redo it's whatever (if anything) comes next in the chain. */
288
+ redoRevisionId?: string | null
273
289
  }
274
290
 
275
291
  /** From-speaker replaced by to-speaker on every turn — its removal is
@@ -279,6 +295,27 @@ export interface SpeakerReplaced {
279
295
  fromSpeakerId: string
280
296
  toSpeakerId: string
281
297
  version?: number
298
+ /** @see SpeakerRenamed.revisionId */
299
+ revisionId?: string | null
300
+ /** @see SpeakerRenamed.redoRevisionId */
301
+ redoRevisionId?: string | null
302
+ }
303
+
304
+ /** Undo of a replace_speaker: fromSpeaker resurrected and turnIds moved back
305
+ * onto it — the mirror of SpeakerReplaced, not its opposite payload shape
306
+ * (fromSpeaker rides the raw stored speaker record, not just its id, since
307
+ * restoring it is literally re-inserting that record). Redo of the same
308
+ * replace_speaker re-broadcasts SpeakerReplaced, not this. */
309
+ export interface SpeakerRestored {
310
+ translationId: string
311
+ fromSpeaker: { speaker_id: string; speaker_name: string }
312
+ toSpeakerId: string
313
+ turnIds: string[]
314
+ version?: number
315
+ /** @see SpeakerRenamed.revisionId */
316
+ revisionId?: string | null
317
+ /** @see SpeakerRenamed.redoRevisionId */
318
+ redoRevisionId?: string | null
282
319
  }
283
320
 
284
321
  export interface TranscriptionEditorPluginApi {
@@ -310,6 +347,16 @@ export interface TranscriptionEditorPluginApi {
310
347
  /** Reassign every turn of a speaker to another (speaker merge). */
311
348
  replaceSpeaker(fromSpeakerId: string, toSpeakerId: string): void
312
349
 
350
+ // ── Undo/redo (speaker mutations only — see server EditorHandler) ──
351
+ /** True when the active translation has a speaker mutation to undo. */
352
+ readonly canUndo: ComputedRef<boolean>
353
+ /** True when the active translation has an undo to redo — server-computed
354
+ * (see SpeakerRenamed.redoRevisionId), not a client guess: stays correct
355
+ * across collaborators, no staleness caveat. */
356
+ readonly canRedo: ComputedRef<boolean>
357
+ undo(): void
358
+ redo(): void
359
+
313
360
  // ── Server sync (host-pushed from broadcasts) ──
314
361
  /** Apply a saved turn broadcast by the server (any track, any author). */
315
362
  applyTurnUpdate(update: TurnUpdate): void
@@ -330,6 +377,9 @@ export interface TranscriptionEditorPluginApi {
330
377
  applyTurnSpeakerUpdated(update: TurnSpeakerUpdated): void
331
378
  applySpeakerRenamed(renamed: SpeakerRenamed): void
332
379
  applySpeakerReplaced(replaced: SpeakerReplaced): void
380
+ /** Apply an undo of replace_speaker (server event editor:speaker_restored
381
+ * — redo re-broadcasts applySpeakerReplaced instead, see SpeakerRestored). */
382
+ applySpeakerRestored(restored: SpeakerRestored): void
333
383
 
334
384
  // ── Locks (host-pushed from server broadcasts; UI pulls per turn) ──
335
385
  /** Lock held on a turn of the ACTIVE translation, if any. */
@@ -570,11 +620,15 @@ export interface Core {
570
620
  readonly date: Ref<string | number | null>
571
621
  readonly activeChannelId: Ref<string>
572
622
  readonly capabilities: Ref<CoreCapabilities>
573
- /** Formats shown in the verbatim panel's download menu (see CoreOptions.verbatimFormats). */
574
- readonly verbatimFormats: DownloadFormat[]
575
- /** True when verbatimFormats is the built-in default — the verbatim panel
576
- * then fulfills "txt" itself instead of emitting "verbatim:export". */
577
- readonly verbatimFormatsAreDefault: boolean
623
+ /** Formats shown in the verbatim panel's download menu (see
624
+ * CoreOptions.verbatimFormats for the initial value). A Ref, not a plain
625
+ * array: there's no dedicated setter and no web component prop (a web
626
+ * component attribute can't carry an array) — set verbatimFormats.value
627
+ * directly once you hold `core`, the same way you set capabilities. */
628
+ readonly verbatimFormats: Ref<DownloadFormat[]>
629
+ /** True when verbatimFormats is still the built-in default — the verbatim
630
+ * panel then fulfills "txt" itself instead of emitting "verbatim:export". */
631
+ readonly verbatimFormatsAreDefault: ComputedRef<boolean>
578
632
 
579
633
  // ── Stores ───────────────────────────────────────────────────────────
580
634
  readonly speakers: SpeakersStore
package/src/index.ts CHANGED
@@ -30,6 +30,7 @@ export type {
30
30
  TurnSpeakerUpdated,
31
31
  SpeakerRenamed,
32
32
  SpeakerReplaced,
33
+ SpeakerRestored,
33
34
  LivePluginApi,
34
35
  LivePartialEventData,
35
36
  LiveFinalEventData,