lyrics-transcriber 0.46.0__py3-none-any.whl → 0.48.0__py3-none-any.whl

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.
@@ -33018,7 +33018,7 @@ function useWordClick({
33018
33018
  gap2 = matchingGap2;
33019
33019
  }
33020
33020
  }
33021
- if (mode === "highlight" || mode === "edit") {
33021
+ if (mode === "highlight" || mode === "edit" || mode === "delete_word") {
33022
33022
  if (belongsToAnchor && anchor) {
33023
33023
  onWordClick == null ? void 0 : onWordClick({
33024
33024
  word_id: wordId,
@@ -33050,7 +33050,7 @@ function useWordClick({
33050
33050
  gap: void 0
33051
33051
  });
33052
33052
  }
33053
- } else if (mode === "details") {
33053
+ } else {
33054
33054
  if (belongsToAnchor && anchor) {
33055
33055
  onElementClick({
33056
33056
  type: "anchor",
@@ -33650,6 +33650,243 @@ function SegmentDetailsModal({
33650
33650
  const PlayCircleOutlineIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
33651
33651
  d: "m10 16.5 6-4.5-6-4.5zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8"
33652
33652
  }), "PlayCircleOutline");
33653
+ const DeleteOutlineIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
33654
+ d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6zM8 9h8v10H8zm7.5-5-1-1h-5l-1 1H5v2h14V4z"
33655
+ }), "DeleteOutline");
33656
+ const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
33657
+ let nanoid = (size = 21) => {
33658
+ let id = "";
33659
+ let bytes = crypto.getRandomValues(new Uint8Array(size |= 0));
33660
+ while (size--) {
33661
+ id += urlAlphabet[bytes[size] & 63];
33662
+ }
33663
+ return id;
33664
+ };
33665
+ const addSegmentBefore = (data, beforeIndex) => {
33666
+ const newData = { ...data };
33667
+ const beforeSegment = newData.corrected_segments[beforeIndex];
33668
+ const newStartTime = Math.max(0, (beforeSegment.start_time ?? 1) - 1);
33669
+ const newEndTime = newStartTime + 1;
33670
+ const newSegment = {
33671
+ id: nanoid(),
33672
+ text: "REPLACE",
33673
+ start_time: newStartTime,
33674
+ end_time: newEndTime,
33675
+ words: [{
33676
+ id: nanoid(),
33677
+ text: "REPLACE",
33678
+ start_time: newStartTime,
33679
+ end_time: newEndTime,
33680
+ confidence: 1
33681
+ }]
33682
+ };
33683
+ newData.corrected_segments.splice(beforeIndex, 0, newSegment);
33684
+ return newData;
33685
+ };
33686
+ const splitSegment = (data, segmentIndex, afterWordIndex) => {
33687
+ const newData = { ...data };
33688
+ const segment = newData.corrected_segments[segmentIndex];
33689
+ const firstHalfWords = segment.words.slice(0, afterWordIndex + 1);
33690
+ const secondHalfWords = segment.words.slice(afterWordIndex + 1);
33691
+ if (secondHalfWords.length === 0) return null;
33692
+ const lastFirstWord = firstHalfWords[firstHalfWords.length - 1];
33693
+ const firstSecondWord = secondHalfWords[0];
33694
+ const lastSecondWord = secondHalfWords[secondHalfWords.length - 1];
33695
+ const firstSegment = {
33696
+ ...segment,
33697
+ words: firstHalfWords,
33698
+ text: firstHalfWords.map((w) => w.text).join(" "),
33699
+ end_time: lastFirstWord.end_time ?? null
33700
+ };
33701
+ const secondSegment = {
33702
+ id: nanoid(),
33703
+ words: secondHalfWords,
33704
+ text: secondHalfWords.map((w) => w.text).join(" "),
33705
+ start_time: firstSecondWord.start_time ?? null,
33706
+ end_time: lastSecondWord.end_time ?? null
33707
+ };
33708
+ newData.corrected_segments.splice(segmentIndex, 1, firstSegment, secondSegment);
33709
+ return newData;
33710
+ };
33711
+ const deleteSegment = (data, segmentIndex) => {
33712
+ const newData = { ...data };
33713
+ const deletedSegment = newData.corrected_segments[segmentIndex];
33714
+ newData.corrected_segments = newData.corrected_segments.filter((_, index) => index !== segmentIndex);
33715
+ newData.anchor_sequences = newData.anchor_sequences.map((anchor) => ({
33716
+ ...anchor,
33717
+ transcribed_word_ids: anchor.transcribed_word_ids.filter(
33718
+ (wordId) => !deletedSegment.words.some((deletedWord) => deletedWord.id === wordId)
33719
+ )
33720
+ }));
33721
+ newData.gap_sequences = newData.gap_sequences.map((gap2) => ({
33722
+ ...gap2,
33723
+ transcribed_word_ids: gap2.transcribed_word_ids.filter(
33724
+ (wordId) => !deletedSegment.words.some((deletedWord) => deletedWord.id === wordId)
33725
+ )
33726
+ }));
33727
+ return newData;
33728
+ };
33729
+ const updateSegment = (data, segmentIndex, updatedSegment) => {
33730
+ const newData = { ...data };
33731
+ updatedSegment.words = updatedSegment.words.map((word) => ({
33732
+ ...word,
33733
+ id: word.id || nanoid()
33734
+ }));
33735
+ newData.corrected_segments[segmentIndex] = updatedSegment;
33736
+ return newData;
33737
+ };
33738
+ function mergeSegment(data, segmentIndex, mergeWithNext) {
33739
+ const segments = [...data.corrected_segments];
33740
+ const targetIndex = mergeWithNext ? segmentIndex + 1 : segmentIndex - 1;
33741
+ if (targetIndex < 0 || targetIndex >= segments.length) {
33742
+ return data;
33743
+ }
33744
+ const baseSegment = segments[segmentIndex];
33745
+ const targetSegment = segments[targetIndex];
33746
+ const mergedSegment = {
33747
+ id: nanoid(),
33748
+ words: mergeWithNext ? [...baseSegment.words, ...targetSegment.words] : [...targetSegment.words, ...baseSegment.words],
33749
+ text: mergeWithNext ? `${baseSegment.text} ${targetSegment.text}` : `${targetSegment.text} ${baseSegment.text}`,
33750
+ start_time: Math.min(
33751
+ baseSegment.start_time ?? Infinity,
33752
+ targetSegment.start_time ?? Infinity
33753
+ ),
33754
+ end_time: Math.max(
33755
+ baseSegment.end_time ?? -Infinity,
33756
+ targetSegment.end_time ?? -Infinity
33757
+ )
33758
+ };
33759
+ const minIndex = Math.min(segmentIndex, targetIndex);
33760
+ segments.splice(minIndex, 2, mergedSegment);
33761
+ return {
33762
+ ...data,
33763
+ corrected_segments: segments
33764
+ };
33765
+ }
33766
+ function findAndReplace(data, findText, replaceText, options = {
33767
+ caseSensitive: false,
33768
+ useRegex: false,
33769
+ fullTextMode: false
33770
+ }) {
33771
+ const newData = { ...data };
33772
+ if (options.fullTextMode) {
33773
+ newData.corrected_segments = data.corrected_segments.map((segment) => {
33774
+ let pattern;
33775
+ if (options.useRegex) {
33776
+ pattern = new RegExp(findText, options.caseSensitive ? "g" : "gi");
33777
+ } else {
33778
+ const escapedFindText = findText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
33779
+ pattern = new RegExp(escapedFindText, options.caseSensitive ? "g" : "gi");
33780
+ }
33781
+ const segmentText = segment.text;
33782
+ if (!pattern.test(segmentText)) {
33783
+ return segment;
33784
+ }
33785
+ pattern.lastIndex = 0;
33786
+ const newSegmentText = segmentText.replace(pattern, replaceText);
33787
+ const newWordTexts = newSegmentText.trim().split(/\s+/).filter((text) => text.length > 0);
33788
+ const newWords = [];
33789
+ if (newWordTexts.length === segment.words.length) {
33790
+ for (let i = 0; i < newWordTexts.length; i++) {
33791
+ newWords.push({
33792
+ ...segment.words[i],
33793
+ text: newWordTexts[i]
33794
+ });
33795
+ }
33796
+ } else if (newWordTexts.length < segment.words.length) {
33797
+ let oldWordIndex = 0;
33798
+ for (let i = 0; i < newWordTexts.length; i++) {
33799
+ while (oldWordIndex < segment.words.length && segment.words[oldWordIndex].text.trim() === "") {
33800
+ oldWordIndex++;
33801
+ }
33802
+ if (oldWordIndex < segment.words.length) {
33803
+ newWords.push({
33804
+ ...segment.words[oldWordIndex],
33805
+ text: newWordTexts[i]
33806
+ });
33807
+ oldWordIndex++;
33808
+ } else {
33809
+ newWords.push({
33810
+ id: nanoid(),
33811
+ text: newWordTexts[i],
33812
+ start_time: null,
33813
+ end_time: null
33814
+ });
33815
+ }
33816
+ }
33817
+ } else {
33818
+ for (let i = 0; i < newWordTexts.length; i++) {
33819
+ if (i < segment.words.length) {
33820
+ newWords.push({
33821
+ ...segment.words[i],
33822
+ text: newWordTexts[i]
33823
+ });
33824
+ } else {
33825
+ newWords.push({
33826
+ id: nanoid(),
33827
+ text: newWordTexts[i],
33828
+ start_time: null,
33829
+ end_time: null
33830
+ });
33831
+ }
33832
+ }
33833
+ }
33834
+ return {
33835
+ ...segment,
33836
+ words: newWords,
33837
+ text: newSegmentText
33838
+ };
33839
+ });
33840
+ } else {
33841
+ newData.corrected_segments = data.corrected_segments.map((segment) => {
33842
+ let newWords = segment.words.map((word) => {
33843
+ let pattern;
33844
+ if (options.useRegex) {
33845
+ pattern = new RegExp(findText, options.caseSensitive ? "g" : "gi");
33846
+ } else {
33847
+ const escapedFindText = findText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
33848
+ pattern = new RegExp(escapedFindText, options.caseSensitive ? "g" : "gi");
33849
+ }
33850
+ return {
33851
+ ...word,
33852
+ text: word.text.replace(pattern, replaceText)
33853
+ };
33854
+ });
33855
+ newWords = newWords.filter((word) => word.text.trim() !== "");
33856
+ return {
33857
+ ...segment,
33858
+ words: newWords,
33859
+ text: newWords.map((w) => w.text).join(" ")
33860
+ };
33861
+ });
33862
+ }
33863
+ newData.corrected_segments = newData.corrected_segments.filter((segment) => segment.words.length > 0);
33864
+ return newData;
33865
+ }
33866
+ function deleteWord(data, wordId) {
33867
+ const segmentIndex = data.corrected_segments.findIndex(
33868
+ (segment2) => segment2.words.some((word) => word.id === wordId)
33869
+ );
33870
+ if (segmentIndex === -1) {
33871
+ return data;
33872
+ }
33873
+ const segment = data.corrected_segments[segmentIndex];
33874
+ const wordIndex = segment.words.findIndex((word) => word.id === wordId);
33875
+ if (wordIndex === -1) {
33876
+ return data;
33877
+ }
33878
+ const updatedWords = segment.words.filter((_, index) => index !== wordIndex);
33879
+ if (updatedWords.length > 0) {
33880
+ const updatedSegment = {
33881
+ ...segment,
33882
+ words: updatedWords,
33883
+ text: updatedWords.map((w) => w.text).join(" ")
33884
+ };
33885
+ return updateSegment(data, segmentIndex, updatedSegment);
33886
+ } else {
33887
+ return deleteSegment(data, segmentIndex);
33888
+ }
33889
+ }
33653
33890
  const SegmentIndex = styled(Typography)(({ theme: theme2 }) => ({
33654
33891
  color: theme2.palette.text.secondary,
33655
33892
  width: "1.8em",
@@ -33688,9 +33925,16 @@ function TranscriptionView({
33688
33925
  mode,
33689
33926
  onPlaySegment,
33690
33927
  currentTime = 0,
33691
- anchors = []
33928
+ anchors = [],
33929
+ onDataChange
33692
33930
  }) {
33693
33931
  const [selectedSegmentIndex, setSelectedSegmentIndex] = reactExports.useState(null);
33932
+ const handleDeleteSegment = (segmentIndex) => {
33933
+ if (onDataChange) {
33934
+ const updatedData = deleteSegment(data, segmentIndex);
33935
+ onDataChange(updatedData);
33936
+ }
33937
+ };
33694
33938
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Paper, { sx: { p: 0.8 }, children: [
33695
33939
  /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 0.5 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h6", sx: { fontSize: "0.9rem", mb: 0 }, children: "Corrected Transcription" }) }),
33696
33940
  /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { display: "flex", flexDirection: "column", gap: 0.2 }, children: data.corrected_segments.map((segment, segmentIndex) => {
@@ -33745,6 +33989,22 @@ function TranscriptionView({
33745
33989
  children: segmentIndex
33746
33990
  }
33747
33991
  ),
33992
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
33993
+ IconButton,
33994
+ {
33995
+ size: "small",
33996
+ onClick: () => handleDeleteSegment(segmentIndex),
33997
+ sx: {
33998
+ padding: "1px",
33999
+ height: "18px",
34000
+ width: "18px",
34001
+ minHeight: "18px",
34002
+ minWidth: "18px"
34003
+ },
34004
+ title: "Delete segment",
34005
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(DeleteOutlineIcon, { sx: { fontSize: "0.9rem", color: "error.main" } })
34006
+ }
34007
+ ),
33748
34008
  segment.start_time !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(
33749
34009
  IconButton,
33750
34010
  {
@@ -33757,6 +34017,7 @@ function TranscriptionView({
33757
34017
  minHeight: "18px",
33758
34018
  minWidth: "18px"
33759
34019
  },
34020
+ title: "Play segment",
33760
34021
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(PlayCircleOutlineIcon, { sx: { fontSize: "0.9rem" } })
33761
34022
  }
33762
34023
  )
@@ -33794,15 +34055,6 @@ function TranscriptionView({
33794
34055
  const StopIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
33795
34056
  d: "M6 6h12v12H6z"
33796
34057
  }), "Stop");
33797
- const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
33798
- let nanoid = (size = 21) => {
33799
- let id = "";
33800
- let bytes = crypto.getRandomValues(new Uint8Array(size |= 0));
33801
- while (size--) {
33802
- id += urlAlphabet[bytes[size] & 63];
33803
- }
33804
- return id;
33805
- };
33806
34058
  const TAP_THRESHOLD_MS = 200;
33807
34059
  const DEFAULT_WORD_DURATION = 1;
33808
34060
  const OVERLAP_BUFFER = 0.01;
@@ -34766,9 +35018,16 @@ const WordRow = reactExports.memo(function WordRow2({
34766
35018
  onWordUpdate,
34767
35019
  onSplitWord,
34768
35020
  onRemoveWord,
34769
- wordsLength
35021
+ wordsLength,
35022
+ onTabNavigation
34770
35023
  }) {
34771
35024
  var _a, _b;
35025
+ const handleKeyDown = (e) => {
35026
+ if (e.key === "Tab" && !e.shiftKey) {
35027
+ e.preventDefault();
35028
+ onTabNavigation(index);
35029
+ }
35030
+ };
34772
35031
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: {
34773
35032
  display: "flex",
34774
35033
  gap: 2,
@@ -34781,8 +35040,10 @@ const WordRow = reactExports.memo(function WordRow2({
34781
35040
  label: `Word ${index}`,
34782
35041
  value: word.text,
34783
35042
  onChange: (e) => onWordUpdate(index, { text: e.target.value }),
35043
+ onKeyDown: handleKeyDown,
34784
35044
  fullWidth: true,
34785
- size: "small"
35045
+ size: "small",
35046
+ id: `word-text-${index}`
34786
35047
  }
34787
35048
  ),
34788
35049
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -34844,7 +35105,8 @@ const WordItem = reactExports.memo(function WordItem2({
34844
35105
  onAddSegment,
34845
35106
  onMergeSegment,
34846
35107
  wordsLength,
34847
- isGlobal
35108
+ isGlobal,
35109
+ onTabNavigation
34848
35110
  }) {
34849
35111
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { children: [
34850
35112
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -34855,7 +35117,8 @@ const WordItem = reactExports.memo(function WordItem2({
34855
35117
  onWordUpdate,
34856
35118
  onSplitWord,
34857
35119
  onRemoveWord,
34858
- wordsLength
35120
+ wordsLength,
35121
+ onTabNavigation
34859
35122
  }
34860
35123
  ),
34861
35124
  !isGlobal && /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -34915,6 +35178,33 @@ function EditWordList({
34915
35178
  const handlePageChange = (_event, value) => {
34916
35179
  setPage(value);
34917
35180
  };
35181
+ const handleTabNavigation = (currentIndex) => {
35182
+ const nextIndex = (currentIndex + 1) % words.length;
35183
+ if (isGlobal && (nextIndex < startIndex || nextIndex >= endIndex)) {
35184
+ const nextPage = Math.floor(nextIndex / pageSize) + 1;
35185
+ setPage(nextPage);
35186
+ setTimeout(() => {
35187
+ focusWordTextField(nextIndex);
35188
+ }, 50);
35189
+ } else {
35190
+ focusWordTextField(nextIndex);
35191
+ }
35192
+ };
35193
+ const focusWordTextField = (index) => {
35194
+ const element = document.getElementById(`word-text-${index}`);
35195
+ if (element) {
35196
+ let input = element.querySelector("input");
35197
+ if (!input) {
35198
+ input = element.querySelector(".MuiInputBase-input");
35199
+ }
35200
+ if (input) {
35201
+ input.focus();
35202
+ input.select();
35203
+ } else {
35204
+ element.focus();
35205
+ }
35206
+ }
35207
+ };
34918
35208
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 1, flexGrow: 1, minHeight: 0 }, children: [
34919
35209
  !isGlobal && /* @__PURE__ */ jsxRuntimeExports.jsx(
34920
35210
  WordDivider,
@@ -34966,7 +35256,8 @@ function EditWordList({
34966
35256
  onAddSegment,
34967
35257
  onMergeSegment,
34968
35258
  wordsLength: words.length,
34969
- isGlobal
35259
+ isGlobal,
35260
+ onTabNavigation: handleTabNavigation
34970
35261
  },
34971
35262
  word.id
34972
35263
  );
@@ -35179,15 +35470,6 @@ function EditModal({
35179
35470
  isGlobal = false,
35180
35471
  isLoading = false
35181
35472
  }) {
35182
- console.log("EditModal - Render", {
35183
- open,
35184
- isGlobal,
35185
- isLoading,
35186
- hasSegment: !!segment,
35187
- segmentIndex,
35188
- hasOriginalSegment: !!originalSegment,
35189
- hasOriginalTranscribedSegment: !!originalTranscribedSegment
35190
- });
35191
35473
  const [editedSegment, setEditedSegment] = reactExports.useState(segment);
35192
35474
  const [isPlaying, setIsPlaying] = reactExports.useState(false);
35193
35475
  const updateSegment2 = reactExports.useCallback((newWords) => {
@@ -35218,19 +35500,12 @@ function EditModal({
35218
35500
  updateSegment: updateSegment2
35219
35501
  });
35220
35502
  const handleClose = reactExports.useCallback(() => {
35221
- console.log("EditModal - handleClose called");
35222
35503
  cleanupManualSync();
35223
35504
  onClose();
35224
35505
  }, [onClose, cleanupManualSync]);
35225
35506
  reactExports.useEffect(() => {
35226
35507
  const spacebarHandler = handleSpacebar;
35227
35508
  if (open) {
35228
- console.log("EditModal - Setting up modal spacebar handler", {
35229
- hasPlaySegment: !!onPlaySegment,
35230
- editedSegmentId: editedSegment == null ? void 0 : editedSegment.id,
35231
- handlerFunction: spacebarHandler.toString().slice(0, 100),
35232
- isLoading
35233
- });
35234
35509
  const handleKeyEvent = (e) => {
35235
35510
  if (e.code === "Space") {
35236
35511
  spacebarHandler(e);
@@ -35239,7 +35514,6 @@ function EditModal({
35239
35514
  setModalSpacebarHandler(() => handleKeyEvent);
35240
35515
  return () => {
35241
35516
  if (!open) {
35242
- console.log("EditModal - Cleanup: clearing modal spacebar handler");
35243
35517
  setModalSpacebarHandler(void 0);
35244
35518
  }
35245
35519
  };
@@ -35261,11 +35535,6 @@ function EditModal({
35261
35535
  }
35262
35536
  }, [currentTime, editedSegment]);
35263
35537
  reactExports.useEffect(() => {
35264
- console.log("EditModal - segment changed", {
35265
- hasSegment: !!segment,
35266
- segmentId: segment == null ? void 0 : segment.id,
35267
- wordCount: segment == null ? void 0 : segment.words.length
35268
- });
35269
35538
  setEditedSegment(segment);
35270
35539
  }, [segment]);
35271
35540
  reactExports.useEffect(() => {
@@ -35273,7 +35542,6 @@ function EditModal({
35273
35542
  if (!editedSegment) return;
35274
35543
  const endTime = editedSegment.end_time ?? 0;
35275
35544
  if (window.isAudioPlaying && currentTime > endTime) {
35276
- console.log("Stopping playback: current time exceeded end time");
35277
35545
  (_a = window.toggleAudioPlayback) == null ? void 0 : _a.call(window);
35278
35546
  cleanupManualSync();
35279
35547
  }
@@ -35436,7 +35704,6 @@ function EditModal({
35436
35704
  return getSafeTimeRange(editedSegment);
35437
35705
  }, [getSafeTimeRange, editedSegment]);
35438
35706
  const dialogTitle = reactExports.useMemo(() => {
35439
- console.log("EditModal - Rendering dialog title", { isLoading, isGlobal });
35440
35707
  if (isLoading) {
35441
35708
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogTitle, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
35442
35709
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { flex: 1, display: "flex", alignItems: "center", gap: 1 }, children: [
@@ -35466,23 +35733,11 @@ function EditModal({
35466
35733
  ] });
35467
35734
  }, [isGlobal, segmentIndex, segment, onPlaySegment, handlePlayButtonClick, isPlaying, onClose, isLoading]);
35468
35735
  if (!isLoading && (!segment || !editedSegment || !originalSegment)) {
35469
- console.log("EditModal - Early return: missing required data", {
35470
- hasSegment: !!segment,
35471
- hasEditedSegment: !!editedSegment,
35472
- hasOriginalSegment: !!originalSegment,
35473
- isLoading
35474
- });
35475
35736
  return null;
35476
35737
  }
35477
35738
  if (!isLoading && !isGlobal && segmentIndex === null) {
35478
- console.log("EditModal - Early return: non-global mode with null segmentIndex");
35479
35739
  return null;
35480
35740
  }
35481
- console.log("EditModal - Rendering dialog content", {
35482
- isLoading,
35483
- hasEditedSegment: !!editedSegment,
35484
- hasOriginalSegment: !!originalSegment
35485
- });
35486
35741
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
35487
35742
  Dialog,
35488
35743
  {
@@ -35964,207 +36219,6 @@ function ReviewChangesModal({
35964
36219
  }
35965
36220
  );
35966
36221
  }
35967
- const addSegmentBefore = (data, beforeIndex) => {
35968
- const newData = { ...data };
35969
- const beforeSegment = newData.corrected_segments[beforeIndex];
35970
- const newStartTime = Math.max(0, (beforeSegment.start_time ?? 1) - 1);
35971
- const newEndTime = newStartTime + 1;
35972
- const newSegment = {
35973
- id: nanoid(),
35974
- text: "REPLACE",
35975
- start_time: newStartTime,
35976
- end_time: newEndTime,
35977
- words: [{
35978
- id: nanoid(),
35979
- text: "REPLACE",
35980
- start_time: newStartTime,
35981
- end_time: newEndTime,
35982
- confidence: 1
35983
- }]
35984
- };
35985
- newData.corrected_segments.splice(beforeIndex, 0, newSegment);
35986
- return newData;
35987
- };
35988
- const splitSegment = (data, segmentIndex, afterWordIndex) => {
35989
- const newData = { ...data };
35990
- const segment = newData.corrected_segments[segmentIndex];
35991
- const firstHalfWords = segment.words.slice(0, afterWordIndex + 1);
35992
- const secondHalfWords = segment.words.slice(afterWordIndex + 1);
35993
- if (secondHalfWords.length === 0) return null;
35994
- const lastFirstWord = firstHalfWords[firstHalfWords.length - 1];
35995
- const firstSecondWord = secondHalfWords[0];
35996
- const lastSecondWord = secondHalfWords[secondHalfWords.length - 1];
35997
- const firstSegment = {
35998
- ...segment,
35999
- words: firstHalfWords,
36000
- text: firstHalfWords.map((w) => w.text).join(" "),
36001
- end_time: lastFirstWord.end_time ?? null
36002
- };
36003
- const secondSegment = {
36004
- id: nanoid(),
36005
- words: secondHalfWords,
36006
- text: secondHalfWords.map((w) => w.text).join(" "),
36007
- start_time: firstSecondWord.start_time ?? null,
36008
- end_time: lastSecondWord.end_time ?? null
36009
- };
36010
- newData.corrected_segments.splice(segmentIndex, 1, firstSegment, secondSegment);
36011
- return newData;
36012
- };
36013
- const deleteSegment = (data, segmentIndex) => {
36014
- const newData = { ...data };
36015
- const deletedSegment = newData.corrected_segments[segmentIndex];
36016
- newData.corrected_segments = newData.corrected_segments.filter((_, index) => index !== segmentIndex);
36017
- newData.anchor_sequences = newData.anchor_sequences.map((anchor) => ({
36018
- ...anchor,
36019
- transcribed_word_ids: anchor.transcribed_word_ids.filter(
36020
- (wordId) => !deletedSegment.words.some((deletedWord) => deletedWord.id === wordId)
36021
- )
36022
- }));
36023
- newData.gap_sequences = newData.gap_sequences.map((gap2) => ({
36024
- ...gap2,
36025
- transcribed_word_ids: gap2.transcribed_word_ids.filter(
36026
- (wordId) => !deletedSegment.words.some((deletedWord) => deletedWord.id === wordId)
36027
- )
36028
- }));
36029
- return newData;
36030
- };
36031
- const updateSegment = (data, segmentIndex, updatedSegment) => {
36032
- const newData = { ...data };
36033
- updatedSegment.words = updatedSegment.words.map((word) => ({
36034
- ...word,
36035
- id: word.id || nanoid()
36036
- }));
36037
- newData.corrected_segments[segmentIndex] = updatedSegment;
36038
- return newData;
36039
- };
36040
- function mergeSegment(data, segmentIndex, mergeWithNext) {
36041
- const segments = [...data.corrected_segments];
36042
- const targetIndex = mergeWithNext ? segmentIndex + 1 : segmentIndex - 1;
36043
- if (targetIndex < 0 || targetIndex >= segments.length) {
36044
- return data;
36045
- }
36046
- const baseSegment = segments[segmentIndex];
36047
- const targetSegment = segments[targetIndex];
36048
- const mergedSegment = {
36049
- id: nanoid(),
36050
- words: mergeWithNext ? [...baseSegment.words, ...targetSegment.words] : [...targetSegment.words, ...baseSegment.words],
36051
- text: mergeWithNext ? `${baseSegment.text} ${targetSegment.text}` : `${targetSegment.text} ${baseSegment.text}`,
36052
- start_time: Math.min(
36053
- baseSegment.start_time ?? Infinity,
36054
- targetSegment.start_time ?? Infinity
36055
- ),
36056
- end_time: Math.max(
36057
- baseSegment.end_time ?? -Infinity,
36058
- targetSegment.end_time ?? -Infinity
36059
- )
36060
- };
36061
- const minIndex = Math.min(segmentIndex, targetIndex);
36062
- segments.splice(minIndex, 2, mergedSegment);
36063
- return {
36064
- ...data,
36065
- corrected_segments: segments
36066
- };
36067
- }
36068
- function findAndReplace(data, findText, replaceText, options = {
36069
- caseSensitive: false,
36070
- useRegex: false,
36071
- fullTextMode: false
36072
- }) {
36073
- const newData = { ...data };
36074
- if (options.fullTextMode) {
36075
- newData.corrected_segments = data.corrected_segments.map((segment) => {
36076
- let pattern;
36077
- if (options.useRegex) {
36078
- pattern = new RegExp(findText, options.caseSensitive ? "g" : "gi");
36079
- } else {
36080
- const escapedFindText = findText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
36081
- pattern = new RegExp(escapedFindText, options.caseSensitive ? "g" : "gi");
36082
- }
36083
- const segmentText = segment.text;
36084
- if (!pattern.test(segmentText)) {
36085
- return segment;
36086
- }
36087
- pattern.lastIndex = 0;
36088
- const newSegmentText = segmentText.replace(pattern, replaceText);
36089
- const newWordTexts = newSegmentText.trim().split(/\s+/).filter((text) => text.length > 0);
36090
- const newWords = [];
36091
- if (newWordTexts.length === segment.words.length) {
36092
- for (let i = 0; i < newWordTexts.length; i++) {
36093
- newWords.push({
36094
- ...segment.words[i],
36095
- text: newWordTexts[i]
36096
- });
36097
- }
36098
- } else if (newWordTexts.length < segment.words.length) {
36099
- let oldWordIndex = 0;
36100
- for (let i = 0; i < newWordTexts.length; i++) {
36101
- while (oldWordIndex < segment.words.length && segment.words[oldWordIndex].text.trim() === "") {
36102
- oldWordIndex++;
36103
- }
36104
- if (oldWordIndex < segment.words.length) {
36105
- newWords.push({
36106
- ...segment.words[oldWordIndex],
36107
- text: newWordTexts[i]
36108
- });
36109
- oldWordIndex++;
36110
- } else {
36111
- newWords.push({
36112
- id: nanoid(),
36113
- text: newWordTexts[i],
36114
- start_time: null,
36115
- end_time: null
36116
- });
36117
- }
36118
- }
36119
- } else {
36120
- for (let i = 0; i < newWordTexts.length; i++) {
36121
- if (i < segment.words.length) {
36122
- newWords.push({
36123
- ...segment.words[i],
36124
- text: newWordTexts[i]
36125
- });
36126
- } else {
36127
- newWords.push({
36128
- id: nanoid(),
36129
- text: newWordTexts[i],
36130
- start_time: null,
36131
- end_time: null
36132
- });
36133
- }
36134
- }
36135
- }
36136
- return {
36137
- ...segment,
36138
- words: newWords,
36139
- text: newSegmentText
36140
- };
36141
- });
36142
- } else {
36143
- newData.corrected_segments = data.corrected_segments.map((segment) => {
36144
- let newWords = segment.words.map((word) => {
36145
- let pattern;
36146
- if (options.useRegex) {
36147
- pattern = new RegExp(findText, options.caseSensitive ? "g" : "gi");
36148
- } else {
36149
- const escapedFindText = findText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
36150
- pattern = new RegExp(escapedFindText, options.caseSensitive ? "g" : "gi");
36151
- }
36152
- return {
36153
- ...word,
36154
- text: word.text.replace(pattern, replaceText)
36155
- };
36156
- });
36157
- newWords = newWords.filter((word) => word.text.trim() !== "");
36158
- return {
36159
- ...segment,
36160
- words: newWords,
36161
- text: newWords.map((w) => w.text).join(" ")
36162
- };
36163
- });
36164
- }
36165
- newData.corrected_segments = newData.corrected_segments.filter((segment) => segment.words.length > 0);
36166
- return newData;
36167
- }
36168
36222
  const generateStorageKey = (data) => {
36169
36223
  var _a;
36170
36224
  const text = ((_a = data.original_segments[0]) == null ? void 0 : _a.text) || "";
@@ -36238,6 +36292,12 @@ const setModalHandler = (handler, open) => {
36238
36292
  };
36239
36293
  const setupKeyboardHandlers = (state) => {
36240
36294
  Math.random().toString(36).substr(2, 9);
36295
+ const resetModifierStates = () => {
36296
+ var _a;
36297
+ state.setIsShiftPressed(false);
36298
+ (_a = state.setIsCtrlPressed) == null ? void 0 : _a.call(state, false);
36299
+ document.body.style.userSelect = "";
36300
+ };
36241
36301
  const handleKeyDown = (e) => {
36242
36302
  var _a;
36243
36303
  if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
@@ -36246,7 +36306,7 @@ const setupKeyboardHandlers = (state) => {
36246
36306
  if (e.key === "Shift") {
36247
36307
  state.setIsShiftPressed(true);
36248
36308
  document.body.style.userSelect = "none";
36249
- } else if (e.key === "Meta") {
36309
+ } else if (e.key === "Control" || e.key === "Ctrl" || e.key === "Meta") {
36250
36310
  (_a = state.setIsCtrlPressed) == null ? void 0 : _a.call(state, true);
36251
36311
  } else if (e.key === " " || e.code === "Space") {
36252
36312
  e.preventDefault();
@@ -36258,20 +36318,30 @@ const setupKeyboardHandlers = (state) => {
36258
36318
  }
36259
36319
  };
36260
36320
  const handleKeyUp = (e) => {
36261
- var _a;
36262
- if (e.key === "Shift") {
36263
- state.setIsShiftPressed(false);
36264
- document.body.style.userSelect = "";
36265
- } else if (e.key === "Meta") {
36266
- (_a = state.setIsCtrlPressed) == null ? void 0 : _a.call(state, false);
36267
- } else if (e.key === " " || e.code === "Space") {
36321
+ resetModifierStates();
36322
+ if (e.key === " " || e.code === "Space") {
36268
36323
  e.preventDefault();
36269
36324
  if (isModalOpen && currentModalHandler) {
36270
36325
  currentModalHandler(e);
36271
36326
  }
36272
36327
  }
36273
36328
  };
36274
- return { handleKeyDown, handleKeyUp };
36329
+ const handleWindowBlur = () => {
36330
+ resetModifierStates();
36331
+ };
36332
+ const handleWindowFocus = () => {
36333
+ resetModifierStates();
36334
+ };
36335
+ window.addEventListener("blur", handleWindowBlur);
36336
+ window.addEventListener("focus", handleWindowFocus);
36337
+ return {
36338
+ handleKeyDown,
36339
+ handleKeyUp,
36340
+ cleanup: () => {
36341
+ window.removeEventListener("blur", handleWindowBlur);
36342
+ window.removeEventListener("focus", handleWindowFocus);
36343
+ }
36344
+ };
36275
36345
  };
36276
36346
  function ModeSelector({ effectiveMode, onChange }) {
36277
36347
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1.2, height: "32px" }, children: [
@@ -36281,7 +36351,7 @@ function ModeSelector({ effectiveMode, onChange }) {
36281
36351
  {
36282
36352
  value: effectiveMode,
36283
36353
  exclusive: true,
36284
- onChange: (_, newMode) => newMode && onChange(newMode),
36354
+ onChange: (_, newMode) => newMode === "edit" && onChange(newMode),
36285
36355
  size: "small",
36286
36356
  sx: {
36287
36357
  height: "32px",
@@ -36292,28 +36362,38 @@ function ModeSelector({ effectiveMode, onChange }) {
36292
36362
  }
36293
36363
  },
36294
36364
  children: [
36295
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
36365
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Default mode; click words to edit that lyrics segment", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
36296
36366
  ToggleButton,
36297
36367
  {
36298
36368
  value: "edit",
36299
- title: "Click to edit segments and make corrections in the transcription view",
36300
36369
  children: [
36301
36370
  /* @__PURE__ */ jsxRuntimeExports.jsx(EditIcon, { sx: { mr: 0.5, fontSize: "1rem" } }),
36302
36371
  "Edit"
36303
36372
  ]
36304
36373
  }
36305
- ),
36306
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
36374
+ ) }),
36375
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Hold SHIFT and click words to highlight the matching anchor sequence in the reference lyrics", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
36307
36376
  ToggleButton,
36308
36377
  {
36309
36378
  value: "highlight",
36310
- title: "Click words in the transcription view to highlight the matching anchor sequence in the reference lyrics. You can also hold SHIFT to temporarily activate this mode.",
36379
+ disabled: true,
36311
36380
  children: [
36312
36381
  /* @__PURE__ */ jsxRuntimeExports.jsx(HighlightIcon, { sx: { mr: 0.5, fontSize: "1rem" } }),
36313
36382
  "Highlight"
36314
36383
  ]
36315
36384
  }
36316
- )
36385
+ ) }) }),
36386
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Hold CTRL and click words to delete them", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
36387
+ ToggleButton,
36388
+ {
36389
+ value: "delete_word",
36390
+ disabled: true,
36391
+ children: [
36392
+ /* @__PURE__ */ jsxRuntimeExports.jsx(DeleteIcon, { sx: { mr: 0.5, fontSize: "1rem" } }),
36393
+ "Delete"
36394
+ ]
36395
+ }
36396
+ ) }) })
36317
36397
  ]
36318
36398
  }
36319
36399
  )
@@ -37105,7 +37185,8 @@ const MemoizedTranscriptionView = reactExports.memo(function MemoizedTranscripti
37105
37185
  onPlaySegment,
37106
37186
  currentTime,
37107
37187
  anchors,
37108
- disableHighlighting
37188
+ disableHighlighting,
37189
+ onDataChange
37109
37190
  }) {
37110
37191
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
37111
37192
  TranscriptionView,
@@ -37119,7 +37200,8 @@ const MemoizedTranscriptionView = reactExports.memo(function MemoizedTranscripti
37119
37200
  highlightInfo,
37120
37201
  onPlaySegment,
37121
37202
  currentTime: disableHighlighting ? void 0 : currentTime,
37122
- anchors
37203
+ anchors,
37204
+ onDataChange
37123
37205
  }
37124
37206
  );
37125
37207
  });
@@ -37209,6 +37291,7 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37209
37291
  const [originalData] = reactExports.useState(() => JSON.parse(JSON.stringify(initialData)));
37210
37292
  const [interactionMode, setInteractionMode] = reactExports.useState("edit");
37211
37293
  const [isShiftPressed, setIsShiftPressed] = reactExports.useState(false);
37294
+ const [isCtrlPressed, setIsCtrlPressed] = reactExports.useState(false);
37212
37295
  const [editModalSegment, setEditModalSegment] = reactExports.useState(null);
37213
37296
  const [isEditAllModalOpen, setIsEditAllModalOpen] = reactExports.useState(false);
37214
37297
  const [globalEditSegment, setGlobalEditSegment] = reactExports.useState(null);
@@ -37239,27 +37322,30 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37239
37322
  }
37240
37323
  }, [data, isReadOnly, initialData]);
37241
37324
  reactExports.useEffect(() => {
37242
- const { handleKeyDown, handleKeyUp } = setupKeyboardHandlers({
37243
- setIsShiftPressed
37325
+ const { handleKeyDown, handleKeyUp, cleanup } = setupKeyboardHandlers({
37326
+ setIsShiftPressed,
37327
+ setIsCtrlPressed
37244
37328
  });
37245
37329
  window.addEventListener("keydown", handleKeyDown);
37246
37330
  window.addEventListener("keyup", handleKeyUp);
37247
37331
  if (isAnyModalOpen) {
37248
37332
  setIsShiftPressed(false);
37333
+ setIsCtrlPressed(false);
37249
37334
  }
37250
37335
  return () => {
37251
37336
  window.removeEventListener("keydown", handleKeyDown);
37252
37337
  window.removeEventListener("keyup", handleKeyUp);
37253
37338
  document.body.style.userSelect = "";
37339
+ cleanup();
37254
37340
  };
37255
- }, [setIsShiftPressed, isAnyModalOpen]);
37341
+ }, [setIsShiftPressed, setIsCtrlPressed, isAnyModalOpen]);
37256
37342
  reactExports.useEffect(() => {
37257
37343
  const modalOpen = Boolean(
37258
37344
  modalContent || editModalSegment || isReviewModalOpen || isAddLyricsModalOpen || isFindReplaceModalOpen || isEditAllModalOpen
37259
37345
  );
37260
37346
  setIsAnyModalOpen(modalOpen);
37261
37347
  }, [modalContent, editModalSegment, isReviewModalOpen, isAddLyricsModalOpen, isFindReplaceModalOpen, isEditAllModalOpen]);
37262
- const effectiveMode = isShiftPressed ? "highlight" : interactionMode;
37348
+ const effectiveMode = isCtrlPressed ? "delete_word" : isShiftPressed ? "highlight" : interactionMode;
37263
37349
  const handleFlash = reactExports.useCallback((type, info) => {
37264
37350
  setFlashingType(null);
37265
37351
  setHighlightInfo(null);
@@ -37278,6 +37364,12 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37278
37364
  }, []);
37279
37365
  const handleWordClick = reactExports.useCallback((info) => {
37280
37366
  var _a, _b, _c, _d, _e, _f, _g;
37367
+ if (effectiveMode === "delete_word") {
37368
+ const newData = deleteWord(data, info.word_id);
37369
+ setData(newData);
37370
+ handleFlash("word");
37371
+ return;
37372
+ }
37281
37373
  if (effectiveMode === "highlight") {
37282
37374
  const correction = (_a = data.corrections) == null ? void 0 : _a.find(
37283
37375
  (c) => c.corrected_word_id === info.word_id || c.word_id === info.word_id
@@ -37385,7 +37477,7 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37385
37477
  });
37386
37478
  }
37387
37479
  }
37388
- }, [data, effectiveMode, setModalContent]);
37480
+ }, [data, effectiveMode, setModalContent, handleFlash, deleteWord]);
37389
37481
  const handleUpdateSegment = reactExports.useCallback((updatedSegment) => {
37390
37482
  if (!editModalSegment) return;
37391
37483
  const newData = updateSegment(data, editModalSegment.index, updatedSegment);
@@ -37676,7 +37768,10 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37676
37768
  onPlaySegment: handlePlaySegment,
37677
37769
  currentTime: currentAudioTime,
37678
37770
  anchors: data.anchor_sequences,
37679
- disableHighlighting: isAnyModalOpenMemo
37771
+ disableHighlighting: isAnyModalOpenMemo,
37772
+ onDataChange: (updatedData) => {
37773
+ setData(updatedData);
37774
+ }
37680
37775
  }
37681
37776
  ),
37682
37777
  !isReadOnly && apiClient && /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: {
@@ -38158,4 +38253,4 @@ ReactDOM$1.createRoot(document.getElementById("root")).render(
38158
38253
  /* @__PURE__ */ jsxRuntimeExports.jsx(App, {})
38159
38254
  ] })
38160
38255
  );
38161
- //# sourceMappingURL=index-BXOpmKq-.js.map
38256
+ //# sourceMappingURL=index-BvRLUQmZ.js.map