mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-07-21 02:05:45 +00:00
* feat(frontend): add voice dictation input * fix(frontend): address voice input review feedback * ci: rerun frontend checks
121 lines
4.1 KiB
TypeScript
121 lines
4.1 KiB
TypeScript
import { describe, expect, it } from "@rstest/core";
|
|
|
|
import {
|
|
appendSpeechTranscript,
|
|
getSpeechRecognitionConstructor,
|
|
getSpeechRecognitionLanguage,
|
|
mapSpeechRecognitionError,
|
|
readSpeechRecognitionTranscript,
|
|
shouldRestartSpeechRecognition,
|
|
type SpeechRecognitionConstructor,
|
|
} from "@/core/voice-input/speech-recognition";
|
|
|
|
describe("speech recognition helpers", () => {
|
|
it("prefers the standard constructor and falls back to webkit", () => {
|
|
const standard = makeSpeechRecognitionConstructor();
|
|
const webkit = makeSpeechRecognitionConstructor();
|
|
|
|
expect(
|
|
getSpeechRecognitionConstructor({
|
|
SpeechRecognition: standard as unknown as SpeechRecognitionConstructor,
|
|
webkitSpeechRecognition:
|
|
webkit as unknown as SpeechRecognitionConstructor,
|
|
}),
|
|
).toBe(standard);
|
|
expect(
|
|
getSpeechRecognitionConstructor({
|
|
webkitSpeechRecognition:
|
|
webkit as unknown as SpeechRecognitionConstructor,
|
|
}),
|
|
).toBe(webkit);
|
|
expect(getSpeechRecognitionConstructor({})).toBeNull();
|
|
});
|
|
|
|
it("maps DeerFlow locales to browser recognition locales", () => {
|
|
expect(getSpeechRecognitionLanguage("zh-CN")).toBe("zh-CN");
|
|
expect(getSpeechRecognitionLanguage("zh-Hans")).toBe("zh-CN");
|
|
expect(getSpeechRecognitionLanguage("en-US")).toBe("en-US");
|
|
expect(getSpeechRecognitionLanguage("en-GB")).toBe("en-GB");
|
|
expect(getSpeechRecognitionLanguage("fr-FR")).toBe("fr-FR");
|
|
expect(getSpeechRecognitionLanguage("ja-JP")).toBe("ja-JP");
|
|
expect(getSpeechRecognitionLanguage("xx-YY")).toBe("en-US");
|
|
expect(getSpeechRecognitionLanguage("not a locale")).toBe("en-US");
|
|
});
|
|
|
|
it("combines final and interim transcripts with whitespace cleanup", () => {
|
|
expect(
|
|
readSpeechRecognitionTranscript({
|
|
length: 3,
|
|
0: { isFinal: true, length: 1, 0: { transcript: " hello " } },
|
|
1: { isFinal: true, length: 1, 0: { transcript: " world" } },
|
|
2: { isFinal: false, length: 1, 0: { transcript: " again " } },
|
|
}),
|
|
).toEqual({
|
|
finalText: "hello world",
|
|
interimText: "again",
|
|
text: "hello world again",
|
|
});
|
|
});
|
|
|
|
it("appends transcript to an existing draft without duplicating whitespace", () => {
|
|
expect(appendSpeechTranscript("", " hello world ")).toBe("hello world");
|
|
expect(appendSpeechTranscript("Draft", "voice text")).toBe(
|
|
"Draft voice text",
|
|
);
|
|
expect(appendSpeechTranscript("Draft\n", "voice text")).toBe(
|
|
"Draft voice text",
|
|
);
|
|
expect(appendSpeechTranscript("Draft", " ")).toBe("Draft");
|
|
});
|
|
|
|
it("normalizes browser speech recognition errors", () => {
|
|
expect(mapSpeechRecognitionError("not-allowed")).toBe("permission_denied");
|
|
expect(mapSpeechRecognitionError("service-not-allowed")).toBe(
|
|
"permission_denied",
|
|
);
|
|
expect(mapSpeechRecognitionError("audio-capture")).toBe(
|
|
"microphone_unavailable",
|
|
);
|
|
expect(mapSpeechRecognitionError("language-not-supported")).toBe(
|
|
"unsupported_language",
|
|
);
|
|
expect(mapSpeechRecognitionError("network")).toBe("network");
|
|
expect(mapSpeechRecognitionError("no-speech")).toBe("no_speech");
|
|
expect(mapSpeechRecognitionError("aborted")).toBe("cancelled");
|
|
expect(mapSpeechRecognitionError("bad-grammar")).toBe("unknown");
|
|
});
|
|
|
|
it("restarts only after browser auto-end conditions", () => {
|
|
expect(shouldRestartSpeechRecognition(null)).toBe(true);
|
|
expect(shouldRestartSpeechRecognition("no_speech")).toBe(true);
|
|
expect(shouldRestartSpeechRecognition("cancelled")).toBe(false);
|
|
expect(shouldRestartSpeechRecognition("permission_denied")).toBe(false);
|
|
expect(shouldRestartSpeechRecognition("network")).toBe(false);
|
|
expect(shouldRestartSpeechRecognition("unknown")).toBe(false);
|
|
});
|
|
});
|
|
|
|
function makeSpeechRecognitionConstructor(): SpeechRecognitionConstructor {
|
|
return class {
|
|
continuous = false;
|
|
interimResults = false;
|
|
lang = "";
|
|
maxAlternatives = 1;
|
|
onend = null;
|
|
onerror = null;
|
|
onresult = null;
|
|
|
|
start() {
|
|
return undefined;
|
|
}
|
|
|
|
stop() {
|
|
return undefined;
|
|
}
|
|
|
|
abort() {
|
|
return undefined;
|
|
}
|
|
};
|
|
}
|