echogarden 1.1.0 → 1.2.0

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.
Files changed (56) hide show
  1. package/data/schemas/options.json +56 -1
  2. package/dist/alignment/DTWSequenceAlignmentWindowed.js +6 -1
  3. package/dist/alignment/DTWSequenceAlignmentWindowed.js.map +1 -1
  4. package/dist/alignment/SpeechAlignment.js +1 -1
  5. package/dist/alignment/SpeechAlignment.js.map +1 -1
  6. package/dist/api/APIOptions.d.ts +3 -0
  7. package/dist/api/Alignment.js +2 -1
  8. package/dist/api/Alignment.js.map +1 -1
  9. package/dist/api/GlobalOptions.d.ts +10 -0
  10. package/dist/api/GlobalOptions.js +21 -3
  11. package/dist/api/GlobalOptions.js.map +1 -1
  12. package/dist/cli/CLI.js +224 -191
  13. package/dist/cli/CLI.js.map +1 -1
  14. package/dist/cli/CLIConfigFile.js +7 -7
  15. package/dist/cli/CLIConfigFile.js.map +1 -1
  16. package/dist/cli/CLIOptions.d.ts +7 -0
  17. package/dist/cli/CLIOptions.js +2 -0
  18. package/dist/cli/CLIOptions.js.map +1 -0
  19. package/dist/cli/CLIParser.d.ts +5 -6
  20. package/dist/cli/CLIParser.js +16 -12
  21. package/dist/cli/CLIParser.js.map +1 -1
  22. package/dist/recognition/WhisperSTT.js +34 -17
  23. package/dist/recognition/WhisperSTT.js.map +1 -1
  24. package/dist/utilities/FileDownloader.js +3 -1
  25. package/dist/utilities/FileDownloader.js.map +1 -1
  26. package/dist/utilities/Logger.d.ts +5 -4
  27. package/dist/utilities/Logger.js +19 -8
  28. package/dist/utilities/Logger.js.map +1 -1
  29. package/dist/utilities/PackageManager.js +3 -2
  30. package/dist/utilities/PackageManager.js.map +1 -1
  31. package/dist/utilities/Utilities.d.ts +1 -1
  32. package/dist/utilities/Utilities.js +9 -9
  33. package/dist/utilities/Utilities.js.map +1 -1
  34. package/docs/API.md +3 -10
  35. package/docs/CLI.md +88 -77
  36. package/docs/Contributing.md +4 -4
  37. package/docs/Engines.md +1 -1
  38. package/docs/Options.md +25 -2
  39. package/docs/Releases.md +8 -8
  40. package/docs/Tasklist.md +17 -19
  41. package/docs/Technical.md +2 -2
  42. package/package.json +3 -3
  43. package/src/alignment/DTWSequenceAlignmentWindowed.ts +7 -1
  44. package/src/alignment/SpeechAlignment.ts +1 -1
  45. package/src/api/APIOptions.ts +3 -0
  46. package/src/api/Alignment.ts +4 -2
  47. package/src/api/GlobalOptions.ts +33 -5
  48. package/src/cli/CLI.ts +257 -205
  49. package/src/cli/CLIConfigFile.ts +7 -7
  50. package/src/cli/CLIOptions.ts +8 -0
  51. package/src/cli/CLIParser.ts +20 -17
  52. package/src/recognition/WhisperSTT.ts +38 -17
  53. package/src/utilities/FileDownloader.ts +5 -1
  54. package/src/utilities/Logger.ts +22 -8
  55. package/src/utilities/PackageManager.ts +4 -3
  56. package/src/utilities/Utilities.ts +9 -9
@@ -56,10 +56,10 @@ export async function parseJSONConfigFile(path: string): Promise<ParsedConfigFil
56
56
 
57
57
  const result: ParsedConfigFile = new Map()
58
58
 
59
- for (const command in parsedJSON) {
60
- const commandObj = parsedJSON[command]
59
+ for (const operation in parsedJSON) {
60
+ const operationObj = parsedJSON[operation]
61
61
 
62
- const commandMap = new Map<string, string>()
62
+ const operationMap = new Map<string, string>()
63
63
 
64
64
  function addFromObject(obj: any, pathPrefix: string): void {
65
65
  for (const propertyName in obj) {
@@ -70,19 +70,19 @@ export async function parseJSONConfigFile(path: string): Promise<ParsedConfigFil
70
70
 
71
71
  if (typeof propertyValue == 'object') {
72
72
  if (Array.isArray(propertyValue)) {
73
- commandMap.set(propertyPath, JSON.stringify(propertyValue))
73
+ operationMap.set(propertyPath, JSON.stringify(propertyValue))
74
74
  } else {
75
75
  addFromObject(propertyValue, propertyPath)
76
76
  }
77
77
  } else {
78
- commandMap.set(propertyPath, propertyValue)
78
+ operationMap.set(propertyPath, propertyValue)
79
79
  }
80
80
  }
81
81
  }
82
82
 
83
- addFromObject(commandObj, '')
83
+ addFromObject(operationObj, '')
84
84
 
85
- result.set(command, commandMap)
85
+ result.set(operation, operationMap)
86
86
  }
87
87
 
88
88
  return result
@@ -0,0 +1,8 @@
1
+ export interface CLIOptions {
2
+ play?: boolean
3
+ overwrite?: boolean
4
+ debug?: boolean
5
+ config?: string
6
+ }
7
+
8
+ export const CLIOptionsKeys: (keyof CLIOptions)[] = ['play', 'overwrite', 'debug', 'config']
@@ -1,8 +1,7 @@
1
- export function parseCLIArguments(command: string, args: string[]): CLIArguments {
2
- const parsedArgs: CLIArguments = {
3
- command,
4
- commandArgs: [],
5
- options: new Map(),
1
+ export function parseCLIArguments(args: string[]): ParsedCLIArguments {
2
+ const parsedArgs: ParsedCLIArguments = {
3
+ operationArgs: [],
4
+ parsedArgumentsLookup: new Map(),
6
5
  }
7
6
 
8
7
  for (let i = 0; i < args.length; i++) {
@@ -16,26 +15,30 @@ export function parseCLIArguments(command: string, args: string[]): CLIArguments
16
15
 
17
16
  const optionText = currentArg.substring(2)
18
17
 
19
- const indexOfEquals = optionText.indexOf('=')
20
- if (indexOfEquals == 0) {
18
+ const indexOfEqualSign = optionText.indexOf('=')
19
+ if (indexOfEqualSign == 0) {
21
20
  throw new Error('An option cannot have an empty name.')
22
- } else if (indexOfEquals == -1) {
23
- parsedArgs.options.set(optionText, '')
21
+ } else if (indexOfEqualSign == -1) {
22
+ if (optionText.startsWith('no-')) {
23
+ parsedArgs.parsedArgumentsLookup.set(optionText.substring(3), 'false')
24
+ } else {
25
+ parsedArgs.parsedArgumentsLookup.set(optionText, '')
26
+ }
24
27
  } else {
25
- const key = optionText.substring(0, indexOfEquals)
26
- const value = optionText.substring(indexOfEquals + 1)
27
- parsedArgs.options.set(key, value)
28
+ const key = optionText.substring(0, indexOfEqualSign)
29
+ const value = optionText.substring(indexOfEqualSign + 1)
30
+
31
+ parsedArgs.parsedArgumentsLookup.set(key, value)
28
32
  }
29
33
  } else {
30
- parsedArgs.commandArgs.push(currentArg)
34
+ parsedArgs.operationArgs.push(currentArg)
31
35
  }
32
36
  }
33
37
 
34
38
  return parsedArgs
35
39
  }
36
40
 
37
- export type CLIArguments = {
38
- command: string
39
- commandArgs: string[]
40
- options: Map<string, string>
41
+ export interface ParsedCLIArguments {
42
+ operationArgs: string[]
43
+ parsedArgumentsLookup: Map<string, string>
41
44
  }
@@ -442,7 +442,7 @@ export class Whisper {
442
442
  partTokensConfidence = partTokensConfidence.slice(initialTokens.length)
443
443
  partCrossAttentionQKs = partCrossAttentionQKs.slice(initialTokens.length)
444
444
 
445
- // Compute compression ratio for part
445
+ // Compute compression ratio for part (disabled for now)
446
446
  if (false) {
447
447
  const compressionRatioForPart = (await getDeflateCompressionMetricsForString(this.tokensToText(partTokens))).ratio
448
448
  }
@@ -453,9 +453,11 @@ export class Whisper {
453
453
  // Generate timeline from alignment path
454
454
  const partTimeline = await this.getTokenTimelineFromAlignmentPath(alignmentPath, partTokens, segmentStartTime, segmentEndTime, partTokensConfidence)
455
455
 
456
+ // Add tokens to output
456
457
  allDecodedTokens.push(...partTokens)
457
458
  timeline.push(...partTimeline)
458
459
 
460
+ // Update previous text tokens
459
461
  previousPartTextTokens = partTokens.filter(token => this.isTextToken(token))
460
462
 
461
463
  audioOffset = audioEndOffset
@@ -479,22 +481,24 @@ export class Whisper {
479
481
 
480
482
  whisperAlignmentOptions = extendDeep(defaultWhisperAlignmentOptions, whisperAlignmentOptions)
481
483
 
484
+ const targetLanguage = task === 'transcribe' ? sourceLanguage : 'en'
485
+
482
486
  const shouldSplitToSentences = false
483
487
 
484
488
  let simplifiedTranscript = ''
485
489
 
486
490
  if (shouldSplitToSentences) {
487
- const sentences = splitToSentences(transcript, 'en')
491
+ const sentences = splitToSentences(transcript, targetLanguage)
488
492
 
489
493
  for (const sentence of sentences) {
490
- let sentenceWords = await splitToWords(sentence, 'en')
494
+ let sentenceWords = await splitToWords(sentence, targetLanguage)
491
495
  sentenceWords = sentenceWords.filter(word => isWord(word))
492
496
 
493
497
  simplifiedTranscript += sentenceWords.join(' ')
494
498
  simplifiedTranscript += ' '
495
499
  }
496
500
  } else {
497
- let words = await splitToWords(transcript, 'en')
501
+ let words = await splitToWords(transcript, targetLanguage)
498
502
  words = words.map(word => word.trim())
499
503
  words = words.filter(word => isWord(word))
500
504
  simplifiedTranscript = words.join(' ')
@@ -712,7 +716,8 @@ export class Whisper {
712
716
 
713
717
  // Start decoding loop
714
718
  for (let decodedTokenCount = 0; decodedTokenCount < options.maxTokensPerPart!; decodedTokenCount++) {
715
- const isInitialState = decodedTokens.length == initialTokens.length
719
+ const isInitialState = decodedTokens.length === initialTokens.length
720
+ const atLeastOneTextTokenDecoded = decodedTokens.slice(initialTokens.length).some(token => this.isTextToken(token))
716
721
 
717
722
  // If not in initial state, reshape KV Cache tensor to accomodate a new output token
718
723
  if (!isInitialState) {
@@ -744,10 +749,10 @@ export class Whisper {
744
749
  offset: offsetTensor
745
750
  }
746
751
 
747
- // Run decoder
752
+ // Run decoder model
748
753
  const decoderOutputs = await this.textDecoder!.run(decoderInputs)
749
754
 
750
- // Store results
755
+ // Extract decoder model results
751
756
  const logitsBuffer = decoderOutputs['logits'].data as Float32Array
752
757
  kvCacheTensor = decoderOutputs['output_kv_cache'] as any
753
758
 
@@ -764,7 +769,7 @@ export class Whisper {
764
769
  allTokenLogits[suppressedTokenIndex] = -Infinity
765
770
  }
766
771
 
767
- if (isInitialState) {
772
+ if (!atLeastOneTextTokenDecoded) {
768
773
  // If in initial state, suppress end-of-text token
769
774
  allTokenLogits[endOfTextToken] = -Infinity
770
775
  }
@@ -779,13 +784,19 @@ export class Whisper {
779
784
  return false
780
785
  }
781
786
 
787
+ if (isInitialState) {
788
+ addToken(timestampTokensStart, timestampTokenLogits, 1.0, crossAttentionQKsForToken)
789
+
790
+ return true
791
+ }
792
+
782
793
  const previousTokenWasTimestamp = this.isTimestampToken(decodedTokens[decodedTokens.length - 1])
783
794
  const secondPreviousTokenWasTimestamp = this.isTimestampToken(decodedTokens[decodedTokens.length - 2])
784
795
 
785
796
  // If there are two successive timestamp tokens decoded, or the previous timestamp was the first token,
786
797
  // don't decode a timestamp
787
798
  if (previousTokenWasTimestamp &&
788
- (decodedTokens.length === initialTokens.length + 1) || secondPreviousTokenWasTimestamp) {
799
+ ((decodedTokens.length === initialTokens.length + 1) || secondPreviousTokenWasTimestamp)) {
789
800
  return false
790
801
  }
791
802
 
@@ -817,6 +828,7 @@ export class Whisper {
817
828
  timestampTokenSeenCount += 1
818
829
 
819
830
  if (previousTokenWasTimestamp) {
831
+ // If previously decoded token was a timestamp token, repeat it
820
832
  const previousToken = decodedTokens[decodedTokens.length - 1]
821
833
  const previousTokenTimestampLogits = decodedTokensTimestampLogits[decodedTokensTimestampLogits.length - 1]
822
834
  const previousTokenConfidence = decodedTokensConfidence[decodedTokensConfidence.length - 1]
@@ -825,6 +837,7 @@ export class Whisper {
825
837
 
826
838
  lastTimestampTokenIndex = decodedTokens.length
827
839
  } else {
840
+ // Otherwise decode the highest probability timestamp
828
841
  const timestampToken = timestampTokensStart + indexOfMaxTimestampLogProb
829
842
  const confidence = probabilities[timestampToken]
830
843
 
@@ -834,6 +847,7 @@ export class Whisper {
834
847
  return true
835
848
  }
836
849
 
850
+ // Call the method to decode timestamp token if needed
837
851
  const timestampTokenDecoded = decodeTimestampTokenIfNeeded()
838
852
 
839
853
  if (timestampTokenDecoded) {
@@ -847,9 +861,9 @@ export class Whisper {
847
861
 
848
862
  let shouldDecodeEndfOfTextToken = false
849
863
 
850
- // If not in initial state, and the end-of-text token's probability is sufficiently higher than
851
- // the second highest ranked token, then set to accept it
852
- if (!isInitialState) {
864
+ // If at least one text token was decoded, and the end-of-text token's probability is
865
+ // sufficiently higher than the second highest ranked token, then accept end-of-text
866
+ if (atLeastOneTextTokenDecoded) {
853
867
  const endOfTextTokenLogit = nonTimestampTokenLogits[endOfTextToken]
854
868
 
855
869
  const otherTokensLogits = nonTimestampTokenLogits.slice()
@@ -888,7 +902,7 @@ export class Whisper {
888
902
  nonTimestampTokenLogits[suppressedTokenIndex] = -Infinity
889
903
  }
890
904
 
891
- // Suppress space token if at initial state
905
+ // Suppress the space token if at initial state
892
906
  if (isInitialState) {
893
907
  nonTimestampTokenLogits[spaceToken] = -Infinity
894
908
  }
@@ -902,7 +916,7 @@ export class Whisper {
902
916
  break
903
917
  }
904
918
 
905
- // Suppress end-of-text if it shouldn't be included in candidates
919
+ // Suppress end-of-text token if it shouldn't be included in candidates
906
920
  if (!options.includeEndTokenInCandidates) {
907
921
  nonTimestampTokenLogits[endOfTextToken] = -Infinity
908
922
  }
@@ -924,8 +938,8 @@ export class Whisper {
924
938
  if (options.suppressRepetition) {
925
939
  // Using some hardcoded constants, for now
926
940
  const tokenWindowSize = 30
927
- const thresholdMatchLength = 6
928
- const thresholdCycleRepetition = 2.0
941
+ const thresholdMatchLength = 4
942
+ const thresholdCycleRepetition = 3
929
943
 
930
944
  const filteredCandidates: typeof topCandidates = []
931
945
 
@@ -1072,8 +1086,10 @@ export class Whisper {
1072
1086
  throw new Error(`Audio part is longer than 30 seconds`)
1073
1087
  }
1074
1088
 
1089
+ // Compute a mel spectogram
1075
1090
  await logger.startAsync('Extract mel spectogram from audio part')
1076
1091
 
1092
+ // Pad audio samples to ensure that have a duration of 30 seconds
1077
1093
  const paddedAudioSamples = new Float32Array(maxAudioSamples)
1078
1094
  paddedAudioSamples.set(audioSamples, 0)
1079
1095
 
@@ -1084,6 +1100,8 @@ export class Whisper {
1084
1100
  await logger.startAsync('Normalize mel spectogram')
1085
1101
 
1086
1102
  const logMelSpectogram = melSpectogram.map(spectrum => spectrum.map(mel => Math.log10(Math.max(mel, 1e-10))))
1103
+
1104
+ // Find maximum log mel value in the spectrum
1087
1105
  let maxLogMel = -Infinity
1088
1106
 
1089
1107
  for (const spectrum of logMelSpectogram) {
@@ -1094,9 +1112,11 @@ export class Whisper {
1094
1112
  }
1095
1113
  }
1096
1114
 
1115
+ // Normalize log mel spectogram (based on Python reference code)
1097
1116
  const normalizedLogMelSpectogram = logMelSpectogram.map(spectrum => spectrum.map(
1098
1117
  logMel => (Math.max(logMel, maxLogMel - 8) + 4) / 4))
1099
1118
 
1119
+ // Flatten the normalized log mel spectogram
1100
1120
  const flattenedNormalizedLogMelSpectogram = new Float32Array(maxAudioFrames * filterbankCount)
1101
1121
 
1102
1122
  for (let i = 0; i < filterbankCount; i++) {
@@ -1105,6 +1125,7 @@ export class Whisper {
1105
1125
  }
1106
1126
  }
1107
1127
 
1128
+ // Run the encoder model
1108
1129
  await logger.startAsync('Encode mel spectogram with Whisper encoder model')
1109
1130
 
1110
1131
  const inputTensor = new Onnx.Tensor('float32', flattenedNormalizedLogMelSpectogram, [1, filterbankCount, maxAudioFrames])
@@ -1758,7 +1779,7 @@ export function normalizeWhisperModelName(modelName: WhisperModelName, languageC
1758
1779
  modelName = modelName.slice(0, modelName.length - 3) as WhisperModelName
1759
1780
 
1760
1781
  const logger = new Logger()
1761
- logger.logTitledMessage(`Warning`, `The model '${originalModelName}' is English only and cannot be used to transcribe language '${languageCode}'. using '${modelName}' instead.`, chalk.yellowBright)
1782
+ logger.logTitledMessage(`Warning`, `The model '${originalModelName}' is English only and cannot be used to transcribe language '${languageCode}'. Using '${modelName}' instead.`, chalk.yellowBright, 'warning')
1762
1783
  }
1763
1784
 
1764
1785
  return modelName
@@ -9,6 +9,7 @@ import path from 'node:path'
9
9
  import { extractTarball } from './Compression.js'
10
10
  import { createWriteStream, move, remove, readdir, ensureDir } from './FileSystem.js'
11
11
  import chalk from 'chalk'
12
+ import { logLevelGreaterOrEqualTo } from '../api/GlobalOptions.js'
12
13
 
13
14
  export async function downloadAndExtractTarball(options: GaxiosOptions, targetDir: string, baseTempPath: string, displayName = 'archive') {
14
15
  const logger = new Logger()
@@ -18,7 +19,10 @@ export async function downloadAndExtractTarball(options: GaxiosOptions, targetDi
18
19
  const tempDirPath = path.join(baseTempPath, `/${randomID}`)
19
20
  await ensureDir(tempDirPath)
20
21
 
22
+ logger.end()
23
+
21
24
  await downloadFile(options, tempTarballPath, `${chalk.cyanBright('Downloading')} ${chalk.greenBright(displayName)}`)
25
+
22
26
  logger.end()
23
27
 
24
28
  logger.start(`Extracting ${displayName}`)
@@ -40,7 +44,7 @@ export async function downloadAndExtractTarball(options: GaxiosOptions, targetDi
40
44
  }
41
45
 
42
46
  export async function downloadFile(options: GaxiosOptions, targetFilePath: string, prompt = 'Downloading') {
43
- const write = writeToStderr
47
+ const write = logLevelGreaterOrEqualTo('info') ? writeToStderr : () => {}
44
48
 
45
49
  const downloadPromise = new OpenPromise<void>()
46
50
 
@@ -1,6 +1,7 @@
1
1
  import chalk from 'chalk'
2
2
  import { Timer } from './Timer.js'
3
3
  import { logToStderr, writeToStderr, yieldToEventLoop } from './Utilities.js'
4
+ import { LogLevel, logLevelGreaterOrEqualTo, logLevelSmallerThan } from '../api/GlobalOptions.js'
4
5
 
5
6
  let currentActiveLogger: Logger | null = null
6
7
 
@@ -23,7 +24,9 @@ export class Logger {
23
24
  await yieldToEventLoop()
24
25
  }
25
26
 
26
- writeToStderr(`${titleColor(title)}.. `)
27
+ if (logLevelGreaterOrEqualTo('info')) {
28
+ writeToStderr(`${titleColor(title)}.. `)
29
+ }
27
30
 
28
31
  this.setAsActiveLogger()
29
32
 
@@ -44,30 +47,41 @@ export class Logger {
44
47
  if (this.active && currentActiveLogger == this) {
45
48
  const elapsedTime = this.timer.elapsedTime
46
49
 
47
- writeToStderr(`${elapsedTime.toFixed(1)}ms\n`)
50
+ if (logLevelGreaterOrEqualTo('info')) {
51
+ writeToStderr(`${elapsedTime.toFixed(1)}ms\n`)
52
+ }
53
+
48
54
  currentActiveLogger = null
49
55
  }
50
56
 
51
57
  this.active = false
52
58
  }
53
59
 
54
- logDuration(message: any, startTime: number, titleColor = chalk.cyanBright) {
60
+ logDuration(message: any, startTime: number, titleColor = chalk.cyanBright, logLevel: LogLevel = 'info') {
55
61
  const duration = Timer.currentTime - startTime
56
62
 
57
- this.log(`${titleColor(message)}: ${duration.toFixed(1)}ms`)
63
+ this.log(`${titleColor(message)}: ${duration.toFixed(1)}ms`, logLevel)
58
64
  }
59
65
 
60
- logTitledMessage(title: string, content: string, titleColor = chalk.cyanBright) {
61
- this.log(`${titleColor(title)}: ${content}`)
66
+ logTitledMessage(title: string, content: any, titleColor = chalk.cyanBright, logLevel: LogLevel = 'info') {
67
+ this.log(`${titleColor(title)}: ${content}`, logLevel)
62
68
  }
63
69
 
64
- log(message: any) {
70
+ log(message: any, logLevel: LogLevel = 'info') {
71
+ if (logLevelSmallerThan(logLevel)) {
72
+ return
73
+ }
74
+
65
75
  if (currentActiveLogger == this || currentActiveLogger == null) {
66
76
  logToStderr(message)
67
77
  }
68
78
  }
69
79
 
70
- write(message: any) {
80
+ write(message: any, logLevel: LogLevel = 'info') {
81
+ if (logLevelSmallerThan(logLevel)) {
82
+ return
83
+ }
84
+
71
85
  if (currentActiveLogger == this || currentActiveLogger == null) {
72
86
  writeToStderr(message)
73
87
  }
@@ -4,6 +4,7 @@ import { getAppDataDir, ensureDir, existsSync, remove } from './FileSystem.js'
4
4
  import { appName } from '../api/Common.js'
5
5
  import { GaxiosOptions } from 'gaxios'
6
6
  import { getAppTempDir } from './PathUtilities.js'
7
+ import { getGlobalOption } from '../api/GlobalOptions.js'
7
8
 
8
9
  export async function loadPackage(packageName: string) {
9
10
  packageName = resolveToVersionedPackageNameIfNeeded(packageName)
@@ -16,13 +17,15 @@ export async function loadPackage(packageName: string) {
16
17
  return packagePath
17
18
  }
18
19
 
20
+ const packageBaseURL = getGlobalOption('packageBaseURL')
21
+
19
22
  const tempPath = getAppTempDir(appName)
20
23
 
21
24
  const headers = {
22
25
  }
23
26
 
24
27
  const options: GaxiosOptions = {
25
- url: `${basePackageUrl}${packageName}.tar.gz`,
28
+ url: `${packageBaseURL}${packageName}.tar.gz`,
26
29
  headers
27
30
  }
28
31
 
@@ -76,8 +79,6 @@ export function resolveVersionTagForUnversionedPackageName(unversionedPackageNam
76
79
  return packageVersionTagResolutionLookup[unversionedPackageName] || defaultVersionTag
77
80
  }
78
81
 
79
- const basePackageUrl = 'https://huggingface.co/echogarden/echogarden-packages/resolve/main/'
80
-
81
82
  const defaultVersionTag = '20230718'
82
83
 
83
84
  const packageVersionTagResolutionLookup: { [packageName: string]: string } = {
@@ -517,7 +517,7 @@ export function getTokenRepetitionScore(tokens: string[] | number[]) {
517
517
  longestMatch = matchLength
518
518
  }
519
519
 
520
- const cycleCount = matchLength / i
520
+ const cycleCount = (matchLength / i) + 1
521
521
 
522
522
  if (cycleCount > longestCycleRepetition) {
523
523
  longestCycleRepetition = cycleCount
@@ -540,7 +540,7 @@ export async function resolveModuleScriptPath(moduleName: string) {
540
540
  export async function runOperationWithRetries<R>(
541
541
  operationFunc: () => Promise<R>,
542
542
  logger: Logger,
543
- opreationName = 'Operation',
543
+ operationName = 'Operation',
544
544
  delayBetweenRetries = 2000,
545
545
  maxRetries = 200) {
546
546
 
@@ -560,21 +560,21 @@ export async function runOperationWithRetries<R>(
560
560
 
561
561
  logger.setAsActiveLogger()
562
562
 
563
- logger.logTitledMessage(`Error`, e.message, chalk.redBright)
564
- logger.log(``)
565
- logger.logTitledMessage(`${opreationName} failed`, `Trying again in ${delayBetweenRetries}ms..`, chalk.redBright)
563
+ logger.logTitledMessage(`Error`, e.message, chalk.redBright, 'error')
564
+ logger.log('', 'error')
565
+ logger.logTitledMessage(`${operationName} failed`, `Trying again in ${delayBetweenRetries}ms..`, chalk.redBright, 'error')
566
566
 
567
567
  await delay(delayBetweenRetries)
568
568
 
569
- logger.log(``)
570
- logger.logTitledMessage(`Starting retry attempt`, `${retryIndex} / ${maxRetries}`, chalk.yellowBright)
571
- logger.log(``)
569
+ logger.log(``, 'warning')
570
+ logger.logTitledMessage(`Starting retry attempt`, `${retryIndex} / ${maxRetries}`, chalk.yellowBright, 'warning')
571
+ logger.log(``, 'warning')
572
572
 
573
573
  logger.unsetAsActiveLogger()
574
574
  }
575
575
  }
576
576
 
577
- throw new Error(`${opreationName} failed after ${maxRetries} retry attempts`)
577
+ throw new Error(`${operationName} failed after ${maxRetries} retry attempts`)
578
578
  }
579
579
 
580
580
  export function writeToStdinInChunks(process: ChildProcessWithoutNullStreams, buffer: Buffer, chunkSize: number) {