1
0

sttDiagnosis.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import { STTBackend } from "@/types/backend";
  2. import { WaveFile } from "wavefile";
  3. import { EvaluationResult } from "./diagnosisScript";
  4. const additionalUrls = {
  5. openai_whisper: "/v1/audio/transcriptions",
  6. whispercpp: "/inference",
  7. };
  8. const TIME_OUT = 8000;
  9. const MIN_DURATION = 2000;
  10. export async function loadAudioAsFloat32Array(
  11. url: string,
  12. ): Promise<Float32Array> {
  13. const response = await fetch(url);
  14. const arrayBuffer = await response.arrayBuffer();
  15. const audioCtx = new (window.AudioContext ||
  16. (window as any).webkitAudioContext)();
  17. const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
  18. return audioBuffer.getChannelData(0);
  19. }
  20. // Utility to safely call fetch
  21. async function safeFetch(
  22. fullUrl: string,
  23. options?: RequestInit,
  24. timeoutMs = TIME_OUT
  25. ): Promise<EvaluationResult> {
  26. const controller = new AbortController();
  27. const id = setTimeout(() => controller.abort(), timeoutMs);
  28. const start = performance.now();
  29. try {
  30. if (!options) {
  31. const res = await fetch(fullUrl, {
  32. signal: controller.signal,
  33. });
  34. const end = performance.now();
  35. clearTimeout(id);
  36. const duration = end - start;
  37. const status = res.ok ? "pass" : "fail";
  38. const score = calculateScore({ status, duration });
  39. return { status, score };
  40. } else {
  41. const res = await fetch(fullUrl, {
  42. ...options,
  43. signal: controller.signal,
  44. });
  45. const end = performance.now();
  46. clearTimeout(id);
  47. const duration = end - start;
  48. const status = res.ok ? "pass" : "fail";
  49. const score = calculateScore({ status, duration });
  50. return { status, score };
  51. }
  52. } catch (err: any) {
  53. const end = performance.now();
  54. clearTimeout(id);
  55. const duration = end - start;
  56. const isAbort = err.name === "AbortError";
  57. return { status: "fail", score: calculateScore({ status: "fail", duration, timeout: isAbort }) };
  58. }
  59. }
  60. // Score calculation logic
  61. function calculateScore({
  62. status,
  63. duration,
  64. timeout = false,
  65. }: {
  66. status: "pass" | "fail";
  67. duration: number;
  68. timeout?: boolean;
  69. }): number {
  70. if (timeout) return 0;
  71. let score = 0;
  72. if (status === "pass") score += 50;
  73. if (duration < MIN_DURATION) score += 50 * ((MIN_DURATION - duration) / MIN_DURATION);
  74. return Math.round(score);
  75. }
  76. // Individual backend handlers
  77. const backendHandlers: Record<
  78. string,
  79. (params: STTBackend) => Promise<EvaluationResult>
  80. > = {
  81. whisper_openai: async (params) => {
  82. const { openai_whisper_apikey, openai_whisper_model, openai_whisper_url } =
  83. params.whisper_openai || {};
  84. if (!openai_whisper_apikey || !openai_whisper_model || !openai_whisper_url)
  85. return {status: "fail", score: 0};
  86. let audio = await loadAudioAsFloat32Array("/sample-voice.wav");
  87. const wav = new WaveFile();
  88. wav.fromScratch(1, 16000, "32f", audio);
  89. const file = new File([new Uint8Array(wav.toBuffer())], "input.wav", { type: "audio/wav" });
  90. const formData = new FormData();
  91. formData.append("file", file);
  92. formData.append("model", openai_whisper_model);
  93. formData.append("language", "en");
  94. return await safeFetch(
  95. `${openai_whisper_url}${additionalUrls.openai_whisper}`,
  96. {
  97. method: "POST",
  98. headers: {
  99. Authorization: `Bearer ${openai_whisper_apikey}`,
  100. },
  101. body: formData,
  102. },
  103. );
  104. },
  105. whispercpp: async (params) => {
  106. const { whispercpp_url } = params.whispercpp || {};
  107. if (!whispercpp_url) return {status: "fail", score: 0};
  108. let audio = await loadAudioAsFloat32Array("/sample-voice.wav");
  109. const wav = new WaveFile();
  110. wav.fromScratch(1, 16000, "32f", audio);
  111. wav.toBitDepth("16");
  112. const file = new File([new Uint8Array(wav.toBuffer())], "input.wav", { type: "audio/wav" });
  113. const formData = new FormData();
  114. formData.append("file", file);
  115. return await safeFetch(`${whispercpp_url}${additionalUrls.whispercpp}`, {
  116. method: "POST",
  117. headers: {
  118. "Content-Type": "application/json",
  119. },
  120. body: formData,
  121. });
  122. },
  123. whisper_browser: async () => { return {status: "pass", score: 100}; }
  124. };
  125. // Dispatcher function
  126. export async function sttDiagnosis(
  127. backend: string,
  128. params: STTBackend,
  129. ): Promise<EvaluationResult> {
  130. const handler = backendHandlers[backend];
  131. if (!handler) return {status: "fail", score: 0};
  132. return await handler(params);
  133. }