* update yarn

* fix dep

* upgrade deps

* fix document config save

* fix #1293

* support long recording assessment

* fix bugsnag

* fix style

* fix #1288

* fix bugsnag config
This commit is contained in:
an-lee
2025-02-25 14:44:10 +08:00
committed by GitHub
parent 4218ea0ba4
commit f3b3902f2d
27 changed files with 3145 additions and 2351 deletions

File diff suppressed because one or more lines are too long

View File

@@ -4,4 +4,4 @@ nmHoistingLimits: workspaces
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.5.3.cjs
yarnPath: .yarn/releases/yarn-4.6.0.cjs

View File

@@ -9,8 +9,8 @@
"markdown-it-sub": "^2.0.0",
"markdown-it-sup": "^2.0.0",
"mermaid": "^11.4.1",
"sass": "^1.83.4",
"vitepress": "^1.5.0",
"sass": "^1.85.0",
"vitepress": "^1.6.3",
"vitepress-plugin-mermaid": "^2.0.17",
"vue": "^3.5.13"
},
@@ -21,6 +21,6 @@
},
"dependencies": {
"cheerio": "^1.0.0",
"swiper": "^11.2.1"
"swiper": "^11.2.4"
}
}

View File

@@ -10,16 +10,16 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
"@nuxtjs/seo": "^2.0.3",
"nuxt": "^3.15.1",
"nuxt-og-image": "^4.0.3",
"@nuxtjs/seo": "^2.2.0",
"nuxt": "^3.15.4",
"nuxt-og-image": "^4.1.4",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
"devDependencies": {
"autoprefixer": "^10.4.20",
"postcss": "^8.5.1",
"sass": "^1.83.4",
"postcss": "^8.5.3",
"sass": "^1.85.0",
"tailwindcss": "^3.4.17"
}
}

View File

@@ -2,8 +2,7 @@ import { VitePlugin } from "@electron-forge/plugin-vite";
import os from "os";
import { FusesPlugin } from "@electron-forge/plugin-fuses";
import { FuseV1Options, FuseVersion } from "@electron/fuses";
import { DependenciesPlugin } from "electron-forge-plugin-dependencies";
import pkg from "./package.json" assert { type: "json" };
import pkg from "./package.json" with { type: "json" };
const config = {
packagerConfig: {

View File

@@ -32,62 +32,63 @@
"devDependencies": {
"@bugsnag/source-maps": "^2.3.3",
"@divisey/js-mdict": "^5.0.0",
"@electron-forge/cli": "^7.6.0",
"@electron-forge/maker-deb": "^7.6.0",
"@electron-forge/maker-dmg": "^7.6.0",
"@electron-forge/maker-rpm": "^7.6.0",
"@electron-forge/maker-squirrel": "^7.6.0",
"@electron-forge/maker-zip": "^7.6.0",
"@electron-forge/plugin-auto-unpack-natives": "^7.6.0",
"@electron-forge/plugin-fuses": "^7.6.0",
"@electron-forge/plugin-vite": "7.6.0",
"@electron-forge/publisher-github": "^7.6.0",
"@electron-forge/publisher-s3": "^7.6.0",
"@electron-forge/cli": "^7.7.0",
"@electron-forge/maker-deb": "^7.7.0",
"@electron-forge/maker-dmg": "^7.7.0",
"@electron-forge/maker-rpm": "^7.7.0",
"@electron-forge/maker-squirrel": "^7.7.0",
"@electron-forge/maker-zip": "^7.7.0",
"@electron-forge/plugin-auto-unpack-natives": "^7.7.0",
"@electron-forge/plugin-fuses": "^7.7.0",
"@electron-forge/plugin-vite": "7.7.0",
"@electron-forge/publisher-github": "^7.7.0",
"@electron-forge/publisher-s3": "^7.7.0",
"@electron/fuses": "^1.8.0",
"@hookform/resolvers": "^3.10.0",
"@langchain/community": "^0.3.24",
"@langchain/core": "^0.3.30",
"@langchain/ollama": "^0.1.4",
"@langchain/community": "^0.3.32",
"@langchain/core": "^0.3.40",
"@langchain/ollama": "^0.2.0",
"@mozilla/readability": "^0.5.0",
"@playwright/test": "^1.49.1",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-alert-dialog": "^1.1.4",
"@radix-ui/react-aspect-ratio": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-collapsible": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-hover-card": "^1.1.4",
"@playwright/test": "^1.50.1",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6",
"@radix-ui/react-aspect-ratio": "^1.1.2",
"@radix-ui/react-avatar": "^1.1.3",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-collapsible": "^1.1.3",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-hover-card": "^1.1.6",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-menubar": "^1.1.4",
"@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-progress": "^1.1.1",
"@radix-ui/react-radio-group": "^1.2.2",
"@radix-ui/react-scroll-area": "^1.2.2",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slider": "^1.2.2",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-toast": "^1.2.4",
"@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.6",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-menubar": "^1.1.6",
"@radix-ui/react-popover": "^1.1.6",
"@radix-ui/react-progress": "^1.1.2",
"@radix-ui/react-radio-group": "^1.2.3",
"@radix-ui/react-scroll-area": "^1.2.3",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slider": "^1.2.3",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-switch": "^1.1.3",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toast": "^1.2.6",
"@radix-ui/react-toggle": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.8",
"@rails/actioncable": "8.0.100",
"@tailwindcss/typography": "^0.5.16",
"@types/ahoy.js": "^0.4.2",
"@types/autosize": "^4.0.3",
"@types/command-exists": "^1.2.3",
"@types/diff": "^7",
"@types/electron-squirrel-startup": "^1.0.2",
"@types/fluent-ffmpeg": "^2.1.27",
"@types/html-to-text": "^9.0.4",
"@types/intl-tel-input": "^18.1.4",
"@types/lodash": "^4.17.14",
"@types/lodash": "^4.17.15",
"@types/mark.js": "^8.11.12",
"@types/mime-types": "^2.1.4",
"@types/mustache": "^4.2.5",
"@types/node": "^22.10.6",
"@types/node": "^22.13.5",
"@types/prop-types": "^15.7.14",
"@types/rails__actioncable": "^6.1.11",
"@types/react": "^18.3.18",
@@ -97,8 +98,8 @@
"@types/unzipper": "^0.10.10",
"@types/validator": "^13.12.2",
"@types/wavesurfer.js": "^6.0.12",
"@typescript-eslint/eslint-plugin": "^8.20.0",
"@typescript-eslint/parser": "^8.20.0",
"@typescript-eslint/eslint-plugin": "^8.24.1",
"@typescript-eslint/parser": "^8.24.1",
"@uidotdev/usehooks": "^2.4.1",
"@vidstack/react": "^1.12.12",
"@vitejs/plugin-react": "^4.3.4",
@@ -108,72 +109,73 @@
"axios": "^1.7.9",
"camelcase": "^8.0.0",
"camelcase-keys": "^9.1.3",
"chart.js": "^4.4.7",
"chart.js": "^4.4.8",
"cheerio": "^1.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.4",
"command-exists": "^1.2.9",
"compromise": "^14.14.3",
"compromise": "^14.14.4",
"compromise-paragraphs": "^0.1.0",
"compromise-stats": "^0.1.0",
"dayjs": "^1.11.13",
"decamelize": "^6.0.0",
"decamelize-keys": "^2.0.1",
"dependencies-tree": "^2.0.0",
"electron": "^34.0.0",
"electron-context-menu": "^4.0.4",
"diff": "^7.0.0",
"electron": "^34.2.0",
"electron-context-menu": "^4.0.5",
"electron-devtools-installer": "^4.0.0",
"electron-forge-plugin-dependencies": "^1.0.0",
"electron-log": "^5.2.4",
"electron-log": "^5.3.0",
"electron-playwright-helpers": "^1.7.1",
"electron-squirrel-startup": "^1.0.1",
"eslint": "^9.18.0",
"eslint-import-resolver-typescript": "^3.7.0",
"eslint": "^9.21.0",
"eslint-import-resolver-typescript": "^3.8.3",
"eslint-plugin-import": "^2.31.0",
"flora-colossus": "^2.0.0",
"foliate-js": "https://github.com/johnfactotum/foliate-js.git#commit=b5ae4c22c197ef70e12ead88277dde2856447634",
"foliate-js": "https://github.com/johnfactotum/foliate-js.git",
"html-to-text": "^9.0.5",
"https-proxy-agent": "^7.0.6",
"i18next": "^24.2.1",
"i18next": "^24.2.2",
"input-otp": "^1.4.2",
"intl-tel-input": "^25.2.1",
"intl-tel-input": "^25.3.0",
"js-md5": "^0.8.3",
"langchain": "^0.3.11",
"langchain": "^0.3.19",
"lodash": "^4.17.21",
"lru-cache": "^11.0.2",
"lucide-react": "^0.471.1",
"lucide-react": "^0.475.0",
"mark.js": "^8.11.1",
"media-captions": "^0.0.18",
"microsoft-cognitiveservices-speech-sdk": "^1.42.0",
"mime-types": "^2.1.35",
"mustache": "^4.2.0",
"next-themes": "^0.4.4",
"octokit": "^4.1.0",
"openai": "^4.78.1",
"octokit": "^4.1.2",
"openai": "^4.85.4",
"pitchfinder": "^2.3.2",
"postcss": "^8.5.1",
"postcss": "^8.5.3",
"progress": "^2.0.3",
"prop-types": "^15.8.1",
"proxy-agent": "^6.5.0",
"react": "^18.3.1",
"react-activity-calendar": "^2.7.7",
"react-activity-calendar": "^2.7.8",
"react-audio-visualize": "^1.2.0",
"react-audio-voice-recorder": "^2.2.0",
"react-dom": "^18.3.1",
"react-frame-component": "^5.2.7",
"react-hook-form": "^7.54.2",
"react-hotkeys-hook": "^4.6.1",
"react-i18next": "^15.4.0",
"react-markdown": "^9.0.3",
"react-i18next": "^15.4.1",
"react-markdown": "^9.1.0",
"react-resizable-panels": "^2.1.7",
"react-router-dom": "^7.1.1",
"react-router-dom": "^7.2.0",
"react-shadow-root": "^6.2.0",
"react-tooltip": "^5.28.0",
"remark-gfm": "^4.0.0",
"remark-gfm": "^4.0.1",
"rimraf": "^6.0.1",
"semver": "^7.6.3",
"sonner": "^1.7.1",
"semver": "^7.7.1",
"sonner": "^1.7.4",
"tailwind-merge": "^2.6.0",
"tailwind-scrollbar": "^3.1.0",
"tailwind-scrollbar-hide": "^2.0.0",
@@ -183,23 +185,23 @@
"tslib": "^2.8.1",
"turndown": "^7.2.0",
"typescript": "^5.7.3",
"vite": "^6.0.7",
"vite": "^6.1.1",
"vite-plugin-static-copy": "^2.2.0",
"wavesurfer.js": "^7.8.16",
"zod": "^3.24.1",
"zod-to-json-schema": "^3.24.1",
"zx": "^8.3.0"
"wavesurfer.js": "^7.9.1",
"zod": "^3.24.2",
"zod-to-json-schema": "^3.24.3",
"zx": "^8.3.2"
},
"dependencies": {
"@andrkrn/ffprobe-static": "^5.2.0",
"@bugsnag/electron": "^8.1.2",
"@bugsnag/plugin-react": "^8.1.1",
"@bugsnag/electron": "^8.2.0",
"@bugsnag/plugin-react": "^8.2.0",
"echogarden": "^1.8.7",
"electron-settings": "^4.0.4",
"ffmpeg-static": "^5.2.0",
"file-type": "^19.6.0",
"fluent-ffmpeg": "^2.1.3",
"fs-extra": "^11.2.0",
"fs-extra": "^11.3.0",
"reflect-metadata": "^0.2.2",
"sequelize": "^6.37.5",
"sequelize-typescript": "^2.1.6",

View File

@@ -119,8 +119,6 @@ export const AGENT_FIXTURE_ANDREW = {
},
};
export const BUGSNAG_API_KEY = "828ee1de10c079a250be7fd05177662f";
export const MIME_TYPES: Record<string, string> = {
".mp3": "audio/mpeg",
".wav": "audio/wav",

View File

@@ -940,5 +940,6 @@
"copyFullText": "Copy full text",
"checkingForUpdate": "Checking for update",
"updateAvailable": "Update available",
"quitAndInstall": "Quit and install"
"quitAndInstall": "Quit and install",
"pleaseSelect": "Please select"
}

View File

@@ -940,5 +940,6 @@
"copyFullText": "复制全文",
"checkingForUpdate": "正在检查更新",
"updateAvailable": "有新版本",
"quitAndInstall": "退出并安装新版本"
"quitAndInstall": "退出并安装新版本",
"pleaseSelect": "请选择"
}

View File

@@ -8,19 +8,35 @@ import ElectronSquirrelStartup from "electron-squirrel-startup";
import contextMenu from "electron-context-menu";
import Bugsnag from "@bugsnag/electron";
import { t } from "i18next";
import { BUGSNAG_API_KEY } from "./constants";
import { Client } from "./api";
const logger = log.scope("main");
const initBugsnag = async () => {
if (!app.isPackaged) return;
const webApi = new Client({
baseUrl: settings.apiUrl(),
logger,
});
try {
const apiKey = await webApi.config("bugsnag_api_key");
if (!apiKey) return;
Bugsnag.start({ apiKey: apiKey.bugsnagApiKey });
} catch (err) {
logger.error(err);
}
};
app.commandLine.appendSwitch("enable-features", "SharedArrayBuffer");
if (app.isPackaged) {
Bugsnag.start({ apiKey: BUGSNAG_API_KEY });
} else {
if (!app.isPackaged) {
app.disableHardwareAcceleration();
app.commandLine.appendSwitch("disable-software-rasterizer");
}
initBugsnag();
// Add context menu
contextMenu({
showSearchWithGoogle: false,

View File

@@ -21,12 +21,7 @@ import {
UserSetting,
} from "@main/db/models";
import settings from "@main/settings";
import {
AudioFormats,
MIME_TYPES,
VideoFormats,
WEB_API_URL,
} from "@/constants";
import { AudioFormats, MIME_TYPES, VideoFormats } from "@/constants";
import { hashFile } from "@main/utils";
import path from "path";
import fs from "fs-extra";

View File

@@ -28,7 +28,7 @@ import decompresser from "./decompresser";
import { UserSetting } from "@main/db/models";
import { t } from "i18next";
import { format } from "util";
import pkg from "../../package.json" assert { type: "json" };
import pkg from "../../package.json" with { type: "json" };
const __dirname = import.meta.dirname;

View File

@@ -14,18 +14,27 @@ import { Tooltip } from "react-tooltip";
import { LookupWidget, TranslateWidget } from "./components";
import Bugsnag from "@bugsnag/electron";
import BugsnagPluginReact from "@bugsnag/plugin-react";
import { BUGSNAG_API_KEY } from "@/constants";
import { Client } from "@/api";
import { WEB_API_URL } from "@/constants";
function App() {
window.__ENJOY_APP__.app.isPackaged().then((isPackaged) => {
if (isPackaged) {
window.__ENJOY_APP__.app.isPackaged().then(async (isPackaged) => {
if (!isPackaged) return;
const client = new Client({
baseUrl: WEB_API_URL,
});
try {
const config = await client.config("bugsnag_api_key");
if (!config?.bugsnagApiKey) return;
Bugsnag.start({
apiKey: BUGSNAG_API_KEY,
apiKey: config.bugsnagApiKey,
plugins: [new BugsnagPluginReact()],
});
} catch (err) {
console.error(err);
}
});
window.__ENJOY_APP__.onNotification((_event, notification) => {
switch (notification.type) {
case "success":

View File

@@ -107,7 +107,7 @@ export const AudibleBooksSegment = () => {
</div>
<ScrollArea>
<div className="flex items-center space-x-4 pb-4">
<div className="flex w-max items-center space-x-4 pb-4">
{books.map((book) => {
return (
<AudioBookCard

View File

@@ -65,8 +65,8 @@ export const AudiosSegment = (props: { limit?: number }) => {
<MediaAddButton type="Audio" />
</div>
) : (
<ScrollArea>
<div className="flex items-center space-x-4 pb-4">
<ScrollArea className="w-full">
<div className="flex w-max items-center space-x-4 pb-4">
{audios.map((audio) => {
return (
<AudioCard className="w-36" key={audio.id} audio={audio} />

View File

@@ -43,7 +43,7 @@ export const EnrollmentSegment = () => {
</div>
<ScrollArea>
<div className="flex items-center space-x-4 pb-4">
<div className="flex w-max items-center space-x-4 pb-4">
{enrollments.map((enrollment) => {
return (
<CourseCard

View File

@@ -20,7 +20,7 @@ import { Readability } from "@mozilla/readability";
import { Buffer } from "buffer";
export const DocumentAddButton = () => {
const { EnjoyApp } = useContext(AppSettingsProviderContext);
const { EnjoyApp, learningLanguage } = useContext(AppSettingsProviderContext);
const navigate = useNavigate();
const [uri, setUri] = useState("");
const [open, setOpen] = useState(false);
@@ -52,9 +52,11 @@ export const DocumentAddButton = () => {
config: {
autoTranslate: false,
autoNextSpeech: true,
layout: "horizontal",
tts: {
engine: "enjoyai",
model: "openai/tts-1",
language: learningLanguage,
voice: "alloy",
},
},

View File

@@ -39,6 +39,12 @@ export const DocumentConfigForm = (props: {
onSubmit: (data: z.infer<typeof documentConfigSchema>) => Promise<void>;
}) => {
const { config, onSubmit } = props;
if (!config?.layout) {
config.layout = "horizontal";
}
if (config?.tts && !config?.tts?.language) {
config.tts.language = "en-US";
}
const [submitting, setSubmitting] = useState<boolean>(false);
const { ttsConfig } = useContext(AISettingsProviderContext);
@@ -106,7 +112,7 @@ export const DocumentConfigForm = (props: {
<FormLabel>{t("layout")}</FormLabel>
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger>
<SelectValue placeholder={t("horizontal")} />
<SelectValue placeholder={t("pleaseSelect")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="horizontal">

View File

@@ -39,7 +39,7 @@ export const DocumentsSegment = () => {
</div>
<ScrollArea>
<div className="flex items-center space-x-4 pb-4">
<div className="flex w-max items-center space-x-4 pb-4">
{documents.map((document) => (
<DocumentCard
key={document.id}

View File

@@ -66,7 +66,7 @@ export const VideosSegment = (props: { limit?: number }) => {
</div>
) : (
<ScrollArea>
<div className="flex items-center space-x-4 pb-4">
<div className="flex w-max items-center space-x-4 pb-4">
{videos.map((video) => {
return (
<VideoCard className="w-56" key={video.id} video={video} />

View File

@@ -99,7 +99,7 @@ export const YoutubeVideosSegment = (props: { channel: string }) => {
<div className="ml-auto mr-4"></div>
</div>
<ScrollArea>
<div className="flex items-center space-x-4 pb-4">
<div className="flex w-max items-center space-x-4 pb-4">
{videos.map((video) => {
return (
<YoutubeVideoCard

View File

@@ -27,6 +27,8 @@ import {
import { t } from "i18next";
import { redirect } from "react-router-dom";
import { Deposit } from "@renderer/components";
import Bugsnag from "@bugsnag/electron";
import BugsnagPluginReact from "@bugsnag/plugin-react";
type AppSettingsProviderState = {
webApi: Client;

View File

@@ -2,7 +2,10 @@ import * as sdk from "microsoft-cognitiveservices-speech-sdk";
import { useContext } from "react";
import { AppSettingsProviderContext } from "@renderer/context";
import camelcaseKeys from "camelcase-keys";
import { map, forEach, sum, filter, cloneDeep } from "lodash";
import * as Diff from "diff";
const THIRTY_SECONDS = 30 * 1000;
export const usePronunciationAssessments = () => {
const { webApi, EnjoyApp } = useContext(AppSettingsProviderContext);
@@ -36,15 +39,29 @@ export const usePronunciationAssessments = () => {
targetType,
});
const result = await assess(
{
blob,
language,
reference,
},
{ token, region }
);
let result = null;
if (recording.duration < THIRTY_SECONDS) {
result = await assess(
{
blob,
language,
reference,
},
{ token, region }
);
} else {
result = await continousAssess(
{
blob,
language,
reference,
},
{ token, region }
);
}
console.log("assess result: ", result);
const resultJson = camelcaseKeys(
JSON.parse(JSON.stringify(result.detailResult)),
{
@@ -138,8 +155,211 @@ export const usePronunciationAssessments = () => {
});
};
const continousAssess = async (
params: {
blob: Blob;
language: string;
reference?: string;
},
options: {
token: string;
region: string;
}
): Promise<sdk.PronunciationAssessmentResult> => {
const { blob, language, reference } = params;
const { token, region } = options;
const config = sdk.SpeechConfig.fromAuthorizationToken(token, region);
const audioConfig = sdk.AudioConfig.fromWavFileInput(
new File([blob], "audio.wav")
);
const pronunciationAssessmentConfig = new sdk.PronunciationAssessmentConfig(
reference,
sdk.PronunciationAssessmentGradingSystem.HundredMark,
sdk.PronunciationAssessmentGranularity.Phoneme,
true
);
pronunciationAssessmentConfig.phonemeAlphabet = "IPA";
// setting the recognition language
config.speechRecognitionLanguage = language;
// create the speech recognizer.
const reco = new sdk.SpeechRecognizer(config, audioConfig);
pronunciationAssessmentConfig.applyTo(reco);
return new Promise((resolve, reject) => {
const pronunciationResults: sdk.PronunciationAssessmentResult[] = [];
// The event recognizing signals that an intermediate recognition result is received.
// You will receive one or more recognizing events as a speech phrase is recognized, with each containing
// more recognized speech. The event will contain the text for the recognition since the last phrase was recognized.
reco.recognizing = function (s, e) {
const str =
"(recognizing) Reason: " +
sdk.ResultReason[e.result.reason] +
" Text: " +
e.result.text;
console.log(str);
};
// The event recognized signals that a final recognition result is received.
// This is the final event that a phrase has been recognized.
// For continuous recognition, you will get one recognized event for each phrase recognized.
reco.recognized = function (s, e) {
console.log("pronunciation assessment for: ", e.result.text);
const pronunciation_result =
sdk.PronunciationAssessmentResult.fromResult(e.result);
pronunciationResults.push(pronunciation_result);
console.log("pronunciation result: ", pronunciation_result);
};
// The event signals that the service has stopped processing speech.
// https://docs.microsoft.com/javascript/api/microsoft-cognitiveservices-speech-sdk/speechrecognitioncanceledeventargs?view=azure-node-latest
// This can happen for two broad classes of reasons.
// 1. An error is encountered.
// In this case the .errorDetails property will contain a textual representation of the error.
// 2. Speech was detected to have ended.
// This can be caused by the end of the specified file being reached, or ~20 seconds of silence from a microphone input.
reco.canceled = function (s, e) {
if (e.reason === sdk.CancellationReason.Error) {
const str =
"(cancel) Reason: " +
sdk.CancellationReason[e.reason] +
": " +
e.errorDetails;
console.error(str);
reject(new Error(e.errorDetails));
}
reco.stopContinuousRecognitionAsync();
};
// Signals that a new session has started with the speech service
reco.sessionStarted = function (s, e) {};
// Signals the end of a session with the speech service.
reco.sessionStopped = function (s, e) {
reco.stopContinuousRecognitionAsync();
reco.close();
const mergedDetailResult = mergePronunciationResults();
console.log("Merged detail result:", mergedDetailResult);
const result = {
pronunciationScore:
mergedDetailResult.PronunciationAssessment.PronScore,
accuracyScore:
mergedDetailResult.PronunciationAssessment.AccuracyScore,
completenessScore:
mergedDetailResult.PronunciationAssessment.CompletenessScore,
fluencyScore: mergedDetailResult.PronunciationAssessment.FluencyScore,
prosodyScore: mergedDetailResult.PronunciationAssessment.ProsodyScore,
detailResult: mergedDetailResult,
contentAssessmentResult: mergedDetailResult.ContentAssessmentResult,
};
resolve(result as sdk.PronunciationAssessmentResult);
};
const mergePronunciationResults = () => {
const detailResults = pronunciationResults.map((result) =>
JSON.parse(JSON.stringify(result.detailResult))
);
const mergedDetailResult = detailResults.reduce(
(acc, curr) => {
acc.Confidence += curr.Confidence;
acc.Display += " " + curr.Display;
acc.ITN += " " + curr.ITN;
acc.Lexical += " " + curr.Lexical;
acc.MaskedITN += " " + curr.MaskedITN;
acc.Words.push(...curr.Words);
acc.PronunciationAssessment.AccuracyScore +=
curr.PronunciationAssessment.AccuracyScore;
acc.PronunciationAssessment.CompletenessScore +=
curr.PronunciationAssessment.CompletenessScore;
acc.PronunciationAssessment.FluencyScore +=
curr.PronunciationAssessment.FluencyScore;
acc.PronunciationAssessment.ProsodyScore +=
curr.PronunciationAssessment?.ProsodyScore ?? 0;
acc.PronunciationAssessment.PronScore +=
curr.PronunciationAssessment?.PronScore ?? 0;
acc.ContentAssessmentResult.GrammarScore +=
curr.ContentAssessmentResult?.GrammarScore ?? 0;
acc.ContentAssessmentResult.VocabularyScore +=
curr.ContentAssessmentResult?.VocabularyScore ?? 0;
acc.ContentAssessmentResult.TopicScore +=
curr.ContentAssessmentResult?.TopicScore ?? 0;
return acc;
},
{
Confidence: 0,
Display: "",
ITN: "",
Lexical: "",
MaskedITN: "",
Words: [],
PronunciationAssessment: {
AccuracyScore: 0,
CompletenessScore: 0,
FluencyScore: 0,
ProsodyScore: 0,
PronScore: 0,
},
ContentAssessmentResult: {
GrammarScore: 0,
VocabularyScore: 0,
TopicScore: 0,
},
}
);
mergedDetailResult.PronunciationAssessment.AccuracyScore = (
mergedDetailResult.PronunciationAssessment.AccuracyScore /
pronunciationResults.length
).toFixed(2);
mergedDetailResult.PronunciationAssessment.CompletenessScore = (
mergedDetailResult.PronunciationAssessment.CompletenessScore /
pronunciationResults.length
).toFixed(2);
mergedDetailResult.PronunciationAssessment.FluencyScore = (
mergedDetailResult.PronunciationAssessment.FluencyScore /
pronunciationResults.length
).toFixed(2);
mergedDetailResult.PronunciationAssessment.ProsodyScore = (
mergedDetailResult.PronunciationAssessment.ProsodyScore /
pronunciationResults.length
).toFixed(2);
mergedDetailResult.PronunciationAssessment.PronScore = (
mergedDetailResult.PronunciationAssessment.PronScore /
pronunciationResults.length
).toFixed(2);
mergedDetailResult.Confidence =
mergedDetailResult.Confidence / pronunciationResults.length;
mergedDetailResult.ContentAssessmentResult.GrammarScore = (
mergedDetailResult.ContentAssessmentResult.GrammarScore /
pronunciationResults.length
).toFixed(2);
mergedDetailResult.ContentAssessmentResult.VocabularyScore = (
mergedDetailResult.ContentAssessmentResult.VocabularyScore /
pronunciationResults.length
).toFixed(2);
mergedDetailResult.ContentAssessmentResult.TopicScore = (
mergedDetailResult.ContentAssessmentResult.TopicScore /
pronunciationResults.length
).toFixed(2);
return mergedDetailResult;
};
reco.startContinuousRecognitionAsync();
});
};
return {
createAssessment,
assess,
continousAssess,
};
};

View File

@@ -357,7 +357,7 @@ type EnjoyAppType = {
set: (key: string, value: any, ttl?: number) => Promise<void>;
delete: (key: string) => Promise<void>;
clear: () => Promise<void>;
writeFile: (filename: string, data: ArrayBuffer) => Promise<string>;
writeFile: (filename: string, data: Buffer<ArrayBuffer>) => Promise<string>;
};
transcriptions: {
findOrCreate: (params: any) => Promise<TranscriptionType>;

View File

@@ -7,6 +7,18 @@ declare module "foliate-js/view.js";
declare module "foliate-js/epub.js";
declare module "compromise-paragraphs";
declare module "segment" {
class Segment {
useDefault(): void;
loadDict(path: string): void;
doSegment(
text: string,
options: { stripPunctuation: boolean }
): Array<{ w: string }>;
}
export = Segment;
}
type SupportedLlmProviderType = "enjoyai" | "openai";
type LlmProviderType = {

View File

@@ -25,8 +25,8 @@
"docs:preview": "yarn workspace 1000-hours preview",
"portal:generate": "yarn workspace 1000h-portal generate"
},
"packageManager": "yarn@4.5.3",
"packageManager": "yarn@4.6.0",
"engines": {
"node": ">=18.0.0"
"node": ">=20.0.0"
}
}

4415
yarn.lock

File diff suppressed because it is too large Load Diff