diff --git a/1000-hours/enjoy-app/audios.md b/1000-hours/enjoy-app/audios.md index 13c7ab49..c7595faf 100644 --- a/1000-hours/enjoy-app/audios.md +++ b/1000-hours/enjoy-app/audios.md @@ -25,9 +25,26 @@ Enjoy 支持添加本地的音频资源,和在线资源。在音频页面, 如果第二步语音转文本失败,请检查是否在正在使用本地 whisper 组件进行语音转文本,在某些电脑上可能因为兼容性和未知问题导致无法使用。如果出现这种情况,请在 [语音转文本服务设置](./settings#语音转文本服务) 中改用其他语音转文本的云服务。 ::: +## 播放 + +点击播放键(或者快捷键 Space)即可播放或者暂停音频。 + +Enjoy 会将音频按照句子切分,默认播放模式为“播放单句”,以便逐句反复听练。 + +其他可选的播放模式有: + +- 单句循环 +- 播放所有 + +## 智能断句 + +Enjoy 按照原音的停顿和标点符号,将当前句子分成几个断句,以便逐一反复练习。 + +你也可以通过点击当前句子的任意词语(或同时按下 Shift 进行多选)进行选中单词或者短语,进行听练。 + ## 录音 -Enjoy 会将音频按照句子切分,用户以句子为单位进行跟读练习。在激活的音频句子下,点击下方的录音按钮,即可开始录音,用户可以模仿音频朗读当前句子作为练习。 +Enjoy 会将音频按照句子切分,用户以句子为单位进行跟读练习。在激活的音频句子下,点击红色的录音按钮(或者快捷键 r),即可开始录音,用户可以模仿音频朗读当前句子作为练习。  _\* 音频播放页面_ @@ -36,9 +53,16 @@ _\* 音频播放页面_ 在 Mac 电脑上,首次使用录音功能时,会弹窗请求麦克风的使用权限,请务必点允许,否则无法使用录音功能。 ::: +## 录音对比 + +将录音的 Pitch contour 与原音对比,以便自纠发音。对比状态下,按下播放键,会同时播放录音和原音。 + + +_\* 录音于原音对比_ + ## 发音评估 -Enjoy 集成了微软 Azure 的发音评估功能,作为自我的发音检查参考。点击录音右下方的仪表盘图标,即可打开发音评估弹窗。 +Enjoy 集成了微软 Azure 的发音评估功能,作为自我的发音检查参考。 该功能会以**录音时的句子文本作为参考**,评估录音的发音情况,各指标的详细说明可以参考微软的 [官方文档](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/how-to-pronunciation-assessment?pivots=programming-language-javascript#scripted-assessment-results)。 @@ -48,9 +72,7 @@ _\* 发音评估示例_ ::: warning 发音评估的使用建议 该功能是收费功能,每次使用均会在 Enjoy 账户的余额扣费,如果余额不足则需要 [充值](./settings#充值) 后才可继续使用。 -根据实测,该功能对提高学习者的发音作用水平有限,其评估结果仅做参考,不应该过度依赖。 - -另外,我们正在开发一套更有效的发音评估体系,敬请期待。 +值得注意的是,发音评估更侧重于单词的发音是否正确,无法评估音调变化是否正确。 ::: ## 修改音频信息 diff --git a/1000-hours/enjoy-app/faq.md b/1000-hours/enjoy-app/faq.md new file mode 100644 index 00000000..6fe388d9 --- /dev/null +++ b/1000-hours/enjoy-app/faq.md @@ -0,0 +1,23 @@ +# 常见问题 + +## 为什么本地语音转文本服务无法使用? + +Enjoy 集成了 [whipser.cpp](https://github.com/ggerganov/whisper.cpp) 作为本地的语音转文本(STT)服务,但是由于兼容性的问题,某些配置较低或者操作系统版本较低的电脑无法使用。 + +如果您遇到这种情况,Enjoy 提供了其他 STT 的云服务,可以前往 [软件设置](./settings#语音转文本服务) 进行配置。推荐优先使用 Azure AI。 + +## 403 Insufficient balance + +遇到这个报错,说明您正在使用 Enjoy 的付费功能,但是账户余额不足了。 + +Enjoy 内有很多功能都由 AI 驱动,比如[智能助手](./ai-assistant)、智能翻译、句子分析等。如果您在 [软件设置](./settings#默认-ai-引擎) 中的配置了 `OpenAI` 作为默认 AI 引擎,在使用这些功能时候,会使用您配置的 OpenAI 信息进行实现,不会涉及 Enjoy 的扣费。 + +(需要注意的是,[智能助手](./ai-assistant) 的对话一旦创建,AI 引擎无法修改。如果需要切换,比如由 Enjoy AI 换成 Open AI,则需要新建一个对话) + +另外,[发音评估](./audios#发音评估) 是收费功能,并非 OpenAI 提供,所以无论 [默认 AI 引擎](./settings#默认-ai-引擎) 选了什么,使用发音评估时,总是会在 Enjoy 账户中扣费。 + +如果需要充值,请参考[充值](./settings#充值)。 + +## 如何下载音频、录音 + +Enjoy 提供了音频、视频、录音的下载功能,以便可以在其他设备使用。 diff --git a/1000-hours/enjoy-app/install.md b/1000-hours/enjoy-app/install.md index 3fd9e594..13a2b4c6 100644 --- a/1000-hours/enjoy-app/install.md +++ b/1000-hours/enjoy-app/install.md @@ -4,13 +4,13 @@ Enjoy App 是一个跨平台的桌面应用,可以在 Windows、Mac 和 Linux 请根据电脑设备的操作系统,下载相应的版本安装使用。 -当前最新版本:[v0.1.0-alpha.13](https://github.com/xiaolai/everyone-can-use-english/releases/latest) +当前最新版本:[v0.2.0](https://github.com/xiaolai/everyone-can-use-english/releases/latest) ## Windows 支持 Windows 10 以上版本。 -[点击下载](https://github.com/xiaolai/everyone-can-use-english/releases/download/v0.1.0-alpha.13/Enjoy-0.1.0-alpha.13-Setup.exe) +[点击下载](https://github.com/xiaolai/everyone-can-use-english/releases/download/v0.2.0/Enjoy-0.2.0.Setup.exe) 下载后,双击即可安装。 @@ -18,8 +18,8 @@ Enjoy App 是一个跨平台的桌面应用,可以在 Windows、Mac 和 Linux 根据使用 Mac 电脑的芯片不同,需要下载不同的版本。 -- [Silicon 芯片版本(arm64)](https://github.com/xiaolai/everyone-can-use-english/releases/download/v0.1.0-alpha.13/Enjoy-0.1.0-alpha.13-arm64.dmg) -- [Intel 芯片版本(x64)](https://github.com/xiaolai/everyone-can-use-english/releases/download/v0.1.0-alpha.13/Enjoy-0.1.0-alpha.13-x64.dmg) +- [Silicon 芯片版本(arm64)](https://github.com/xiaolai/everyone-can-use-english/releases/download/v0.2.0/Enjoy-0.2.0-arm64.dmg) +- [Intel 芯片版本(x64)](https://github.com/xiaolai/everyone-can-use-english/releases/download/v0.2.0/Enjoy-0.2.0-x64.dmg) ::: info 如何查看本机配置 M1 以后的 Mac 电脑型号(M1、M2、M3),均为 Silicon 芯片。 @@ -37,8 +37,8 @@ M1 以后的 Mac 电脑型号(M1、M2、M3),均为 Silicon 芯片。 请根据不同发行版本选用安装文件。 -- [下载 deb 版本](https://github.com/xiaolai/everyone-can-use-english/releases/download/v0.1.0-alpha.13/enjoy_0.1.0-alpha.13_amd64.deb) -- [下载 rpm 版本](https://github.com/xiaolai/everyone-can-use-english/releases/download/v0.1.0-alpha.13/enjoy-0.1.0.alpha.13-1.x86_64.rpm) +- [下载 deb 版本](https://github.com/xiaolai/everyone-can-use-english/releases/download/v0.2.0/enjoy_0.2.0_amd64.deb) +- [下载 rpm 版本](https://github.com/xiaolai/everyone-can-use-english/releases/download/v0.2.0/Enjoy-linux-x64-0.2.0.zip) ## 历史版本 diff --git a/1000-hours/enjoy-app/use-case-generate-audio-resources.md b/1000-hours/enjoy-app/use-case-generate-audio-resources.md index b1b1cf06..776cf37a 100644 --- a/1000-hours/enjoy-app/use-case-generate-audio-resources.md +++ b/1000-hours/enjoy-app/use-case-generate-audio-resources.md @@ -1,4 +1,4 @@ -# 利用 AI 生成训练材料 +# 使用案例:利用 AI 生成训练材料 用外语说我们自己想说的话是学习外语的其中一个重要目的。市面上的口语书实际上并不实用,因为那些话大都不是我们想要说的,我们需要创建专属自己的口语书。使用 Enjoy 可以很容易做到这一点。 diff --git a/1000-hours/enjoy-app/videos.md b/1000-hours/enjoy-app/videos.md index 0828e9d5..2e2b9d77 100644 --- a/1000-hours/enjoy-app/videos.md +++ b/1000-hours/enjoy-app/videos.md @@ -5,10 +5,6 @@  _\* 视频播放页面_ -## 录音 - -在视频播放页面,需要录音时,可以点播放控制栏上的录音图标,视频将会被缩小至小窗,主界面上会显示录音按钮。 - ## 视频大小的限制 过大的视频文件会导致加载卡死而无法使用,目前 Enjoy 将添加视频的大小限制在 100 Mb,超过则会提示添加失败。 diff --git a/1000-hours/public/images/enjoy/audio-page.png b/1000-hours/public/images/enjoy/audio-page.png index 8c086863..074ddf18 100644 Binary files a/1000-hours/public/images/enjoy/audio-page.png and b/1000-hours/public/images/enjoy/audio-page.png differ diff --git a/1000-hours/public/images/enjoy/conversation-add-speech-to-audio.png b/1000-hours/public/images/enjoy/conversation-add-speech-to-audio.png index 4e520a42..f3b830ff 100644 Binary files a/1000-hours/public/images/enjoy/conversation-add-speech-to-audio.png and b/1000-hours/public/images/enjoy/conversation-add-speech-to-audio.png differ diff --git a/1000-hours/public/images/enjoy/recording-comparing.png b/1000-hours/public/images/enjoy/recording-comparing.png new file mode 100644 index 00000000..f0088668 Binary files /dev/null and b/1000-hours/public/images/enjoy/recording-comparing.png differ diff --git a/1000-hours/public/images/enjoy/select-ai-role.png b/1000-hours/public/images/enjoy/select-ai-role.png index a0d06a57..e7cf5871 100644 Binary files a/1000-hours/public/images/enjoy/select-ai-role.png and b/1000-hours/public/images/enjoy/select-ai-role.png differ diff --git a/1000-hours/public/images/enjoy/video-page.png b/1000-hours/public/images/enjoy/video-page.png index ca1aeb9d..50ec78ab 100644 Binary files a/1000-hours/public/images/enjoy/video-page.png and b/1000-hours/public/images/enjoy/video-page.png differ diff --git a/1000-hours/training-tasks/procedures.md b/1000-hours/training-tasks/procedures.md index 54566fd0..3fac70da 100644 --- a/1000-hours/training-tasks/procedures.md +++ b/1000-hours/training-tasks/procedures.md @@ -1,6 +1,6 @@ # 2. 训练方法 -请后继反复认真阅读《[语音塑造](/sounds-of-english/01-basics)》中的每一个章节。 +请后续反复认真阅读《[语音塑造](/sounds-of-english/01-basics)》中的每一个章节。 ## 2.1 搞清每个单词的读音 @@ -61,7 +61,7 @@ 自然语音中有**可换气停顿**的地方,就相当于是乐谱里的小节分界线。分段练习,实在难的小节,还可以继续拆分…… -在跟读的时候,有必要**夸张**一点。声音也要正常地**大** —— 确实不用喊,嗓子的确需要保护。实际上,我们主要需要练的是嘴唇、舌头、气流震动的配合,主要练的还真不是声带。 +在跟读的时候,有必要**夸张**一点。声音也要正常地**大** —— 确实不用喊,嗓子的确需要保护。实际上,我们主要需要练的是嘴唇、舌头、气流振动的配合,主要练的还真不是声带。 跟读的时候,可以戴着耳机。一个比较好的方法是戴单只耳机 —— 这样,一方面录音听得更清楚,另外一方面也不妨碍听清自己的声音,还可以换着耳朵戴。 @@ -119,4 +119,4 @@ TED 上有一个讲座,Benjamin Zander 讲 [The transformative power of classi 一天至少三个小时,每周最多可以中止一天。 -每天结束之前,要花 5~10 分钟做一下**复盘**,回忆并记录一下自己今天遇到的困难、已经克服的困难、尚未克服的困难、以及面对那些困难时所采用的方法…… 想一想还有什么方法可以试试?这些都要写下来。相信我,记录这个东西 ,总是会以想象不到的方式起想象不到的作用。 \ No newline at end of file +每天结束之前,要花 5~10 分钟做一下**复盘**,回忆并记录一下自己今天遇到的困难、已经克服的困难、尚未克服的困难、以及面对那些困难时所采用的方法…… 想一想还有什么方法可以试试?这些都要写下来。相信我,记录这个东西 ,总是会以想象不到的方式起想象不到的作用。 diff --git a/book/chapter2.md b/book/chapter2.md index dde360d8..8e0d904d 100644 --- a/book/chapter2.md +++ b/book/chapter2.md @@ -42,7 +42,7 @@ * [GRE的Issue题目](files/GRE-Analytical-Writing-Argument-Task-Topics.md) * [GRE的Argument题目](files/GRE-Analytical-Writing-Issue-Task-Topics) -在相当长一段时间里,我以为自己 “发现” 了一条真正的捷径,后来才发现 “我并不孤独”。在国内,卖得最好的口语教材,实际上是北京外国语大学的专业教材,作者是北外教授吴桢福老师:一共三本,《英语初级口语》、《英语中级口语》、《英语高级口语》,多年来多次再版,印数均超过 100 万本。如果读者有机会,不妨去书店翻翻这套教材──你会发现其中的大多数课文话题,实际上也许都是从 TOEFL 作文题脱胎而来的。 +在相当长一段时间里,我以为自己 “发现” 了一条真正的捷径,后来才发现 “我并不孤独”。在国内,卖得最好的口语教材,实际上是北京外国语大学的专业教材,作者是北外教授吴祯福老师:一共三本,《英语初级口语》、《英语中级口语》、《英语高级口语》,多年来多次再版,印数均超过 100 万本。如果读者有机会,不妨去书店翻翻这套教材──你会发现其中的大多数课文话题,实际上也许都是从 TOEFL 作文题脱胎而来的。 请允许我重复一遍:**你的问题也许不在于你不会说,而在于你没什么话可说。** diff --git a/enjoy/package.json b/enjoy/package.json index 9732bb57..92386055 100644 --- a/enjoy/package.json +++ b/enjoy/package.json @@ -3,7 +3,7 @@ "private": true, "name": "enjoy", "productName": "Enjoy", - "version": "0.2.0-preview", + "version": "0.2.0", "description": "Enjoy desktop app", "main": ".vite/build/main.js", "types": "./src/types.d.ts", @@ -110,8 +110,11 @@ "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-tooltip": "^1.0.7", + "@sentry/electron": "^4.21.0", + "@types/ahoy.js": "^0.4.2", "@uidotdev/usehooks": "^2.4.1", "@vidstack/react": "^1.10.9", + "ahoy.js": "^0.4.3", "autosize": "^6.0.1", "axios": "^1.6.8", "camelcase": "^8.0.0", diff --git a/enjoy/src/commands/analyze.command.ts b/enjoy/src/commands/analyze.command.ts index edcf69a1..9e5d7ead 100644 --- a/enjoy/src/commands/analyze.command.ts +++ b/enjoy/src/commands/analyze.command.ts @@ -22,32 +22,26 @@ export const analyzeCommand = async ( }, cache: false, verbose: true, + maxRetries: 2, }); const prompt = ChatPromptTemplate.fromMessages([ ["system", SYSTEM_PROMPT], - ["human", TRANSLATION_PROMPT], + ["human", text], ]); - const response = await prompt.pipe(chatModel).invoke({ - native_language: "Chinese", - text, - }); + const response = await prompt.pipe(chatModel).invoke({}); return response.text; }; -const SYSTEM_PROMPT = `You are a language coach of English, and you are helping a student to learn {native_language}.`; -const TRANSLATION_PROMPT = ` -{text} +const SYSTEM_PROMPT = `你是我的英语教练,我将提供英语文本,你将帮助我分析文本的句子结构、语法和词汇/短语,并对文本进行详细解释。请用中文回答,并按以下格式返回结果: -Please analyze the text above, including sentence structure, grammar, and vocabulary/phrases, and provide a detailed explanation of the text. Please reply in {native_language} and return the result only in the following format: + ### 句子结构 + (解释句子的每个元素) - ### Sentence structure - (explain every element of the sentence) + ### 语法 + (解释句子的语法) - ### Grammar - (explain the grammar of the sentence) - - ### Vocabulary/phrases - (explain the key vocabulary and phrases used)`; + ### 词汇/短语 + (解释使用的关键词汇和短语)`; diff --git a/enjoy/src/commands/extract-story.command.ts b/enjoy/src/commands/extract-story.command.ts index 66500fc7..d5f66070 100644 --- a/enjoy/src/commands/extract-story.command.ts +++ b/enjoy/src/commands/extract-story.command.ts @@ -39,6 +39,7 @@ export const extractStoryCommand = async ( }, cache: true, verbose: true, + maxRetries: 2, }).bind({ tools: [ { diff --git a/enjoy/src/commands/ipa.command.ts b/enjoy/src/commands/ipa.command.ts index 88987b9c..c0ce3df1 100644 --- a/enjoy/src/commands/ipa.command.ts +++ b/enjoy/src/commands/ipa.command.ts @@ -59,6 +59,7 @@ export const ipaCommand = async ( }, cache: true, verbose: true, + maxRetries: 2, }); const prompt = ChatPromptTemplate.fromMessages([ diff --git a/enjoy/src/commands/lookup.command.ts b/enjoy/src/commands/lookup.command.ts index 2e21a864..7ab7b6bd 100644 --- a/enjoy/src/commands/lookup.command.ts +++ b/enjoy/src/commands/lookup.command.ts @@ -71,6 +71,7 @@ export const lookupCommand = async ( }, cache: true, verbose: true, + maxRetries: 2, }); const prompt = ChatPromptTemplate.fromMessages([ diff --git a/enjoy/src/commands/translate.command.ts b/enjoy/src/commands/translate.command.ts index ab248493..32c6eeb9 100644 --- a/enjoy/src/commands/translate.command.ts +++ b/enjoy/src/commands/translate.command.ts @@ -22,6 +22,7 @@ export const translateCommand = async ( }, cache: false, verbose: true, + maxRetries: 2, }); const prompt = ChatPromptTemplate.fromMessages([ diff --git a/enjoy/src/constants.ts b/enjoy/src/constants.ts index df6c2970..38495c15 100644 --- a/enjoy/src/constants.ts +++ b/enjoy/src/constants.ts @@ -7,6 +7,8 @@ export const WEB_API_URL = "https://enjoy-web.fly.dev"; export const REPO_URL = "https://github.com/xiaolai/everyone-can-use-english"; +export const SENTRY_DSN = "https://d51056d7af7d14eae446c0c15b4f3d31@o1168905.ingest.us.sentry.io/4506969353289728" + export const MAGIC_TOKEN_REGEX = /\b(Mrs|Ms|Mr|Dr|Prof|St|[a-zA-Z]{1,2}|\d{1,2})\.\b/g; export const END_OF_SENTENCE_REGEX = /[^\.!,\?][\.!\?]/g; diff --git a/enjoy/src/main.ts b/enjoy/src/main.ts index f2e28acf..d9d7c6b3 100644 --- a/enjoy/src/main.ts +++ b/enjoy/src/main.ts @@ -6,6 +6,12 @@ import mainWindow from "@main/window"; import ElectronSquirrelStartup from "electron-squirrel-startup"; import contextMenu from "electron-context-menu"; import { t } from "i18next"; +import * as Sentry from "@sentry/electron"; +import { SENTRY_DSN } from "@/constants"; + +Sentry.init({ + dsn: SENTRY_DSN, +}); app.commandLine.appendSwitch("enable-features", "SharedArrayBuffer"); diff --git a/enjoy/src/renderer.ts b/enjoy/src/renderer.ts index f3c7ec81..d6e184dd 100644 --- a/enjoy/src/renderer.ts +++ b/enjoy/src/renderer.ts @@ -28,6 +28,12 @@ import "./index.css"; import "./renderer/index"; +import * as Sentry from "@sentry/electron"; +import { SENTRY_DSN } from "@/constants"; + +Sentry.init({ + dsn: SENTRY_DSN, +}); declare global { interface Window { diff --git a/enjoy/src/renderer/components/medias/media-caption-tabs.tsx b/enjoy/src/renderer/components/medias/media-caption-tabs.tsx index 8c638835..f3b6438e 100644 --- a/enjoy/src/renderer/components/medias/media-caption-tabs.tsx +++ b/enjoy/src/renderer/components/medias/media-caption-tabs.tsx @@ -348,7 +348,7 @@ const TranslationTabContent = (props: { text: string }) => { setTranslation(result); } }) - .catch((err) => t("translationFailed", { error: err.message })) + .catch((err) => toast.error(err.message)) .finally(() => { setTranslating(false); }); @@ -422,7 +422,7 @@ const AnalysisTabContent = (props: { text: string }) => { setAnalysisResult(result); } }) - .catch((err) => t("analysisFailed", { error: err.message })) + .catch((err) => toast.error(err.message)) .finally(() => { setAnalyzing(false); }); diff --git a/enjoy/src/renderer/components/medias/media-current-recording.tsx b/enjoy/src/renderer/components/medias/media-current-recording.tsx index 7e92bd92..3a2a7316 100644 --- a/enjoy/src/renderer/components/medias/media-current-recording.tsx +++ b/enjoy/src/renderer/components/medias/media-current-recording.tsx @@ -222,7 +222,7 @@ export const MediaCurrentRecording = (props: { height?: number }) => { const calContainerWidth = () => { const w = document - .querySelector(".media-recording-container") + .querySelector(".media-recording-wrapper") ?.getBoundingClientRect()?.width; if (!w) return; @@ -371,10 +371,15 @@ export const MediaCurrentRecording = (props: { height?: number }) => { ]); useEffect(() => { - if (!ref?.current) return; + if (!width) return; - ref.current.style.width = `${width}px`; - }, [width]); + const container: HTMLDivElement = document.querySelector( + ".media-recording-container" + ); + if (!container) return; + + container.style.width = `${width}px`; + }, [width, currentRecording, isRecording]); useEffect(() => { calContainerWidth(); @@ -387,7 +392,7 @@ export const MediaCurrentRecording = (props: { height?: number }) => { calContainerWidth(); }); }; - }, []); + }, [currentRecording, isRecording]); useHotkeys( ["Ctrl+R", "Meta+R"], @@ -428,9 +433,9 @@ export const MediaCurrentRecording = (props: { height?: number }) => { ); return ( -