Merge branch 'main' into sounds-of-american-english-2

This commit is contained in:
xiaolai
2024-03-28 08:16:32 +08:00
committed by GitHub
29 changed files with 278 additions and 62 deletions

View File

@@ -25,9 +25,26 @@ Enjoy 支持添加本地的音频资源,和在线资源。在音频页面,
如果第二步语音转文本失败,请检查是否在正在使用本地 whisper 组件进行语音转文本,在某些电脑上可能因为兼容性和未知问题导致无法使用。如果出现这种情况,请在 [语音转文本服务设置](./settings#语音转文本服务) 中改用其他语音转文本的云服务。
:::
## 播放
点击播放键(或者快捷键 <kbd>Space</kbd>)即可播放或者暂停音频。
Enjoy 会将音频按照句子切分,默认播放模式为“播放单句”,以便逐句反复听练。
其他可选的播放模式有:
- 单句循环
- 播放所有
## 智能断句
Enjoy 按照原音的停顿和标点符号,将当前句子分成几个断句,以便逐一反复练习。
你也可以通过点击当前句子的任意词语(或同时按下 <kbd>Shift</kbd> 进行多选)进行选中单词或者短语,进行听练。
## 录音
Enjoy 会将音频按照句子切分,用户以句子为单位进行跟读练习。在激活的音频句子下,点击下方的录音按钮,即可开始录音,用户可以模仿音频朗读当前句子作为练习。
Enjoy 会将音频按照句子切分,用户以句子为单位进行跟读练习。在激活的音频句子下,点击红色的录音按钮(或者快捷键 <kbd>r</kbd>),即可开始录音,用户可以模仿音频朗读当前句子作为练习。
![音频播放页面](/images/enjoy/audio-page.png)
_\* 音频播放页面_
@@ -36,9 +53,16 @@ _\* 音频播放页面_
在 Mac 电脑上,首次使用录音功能时,会弹窗请求麦克风的使用权限,请务必点允许,否则无法使用录音功能。
:::
## 录音对比
将录音的 Pitch contour 与原音对比,以便自纠发音。对比状态下,按下播放键,会同时播放录音和原音。
![录音对比](/images/enjoy/recording-comparing.png)
_\* 录音于原音对比_
## 发音评估
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#充值) 后才可继续使用。
根据实测,该功能对提高学习者的发音作用水平有限,其评估结果仅做参考,不应该过度依赖
另外,我们正在开发一套更有效的发音评估体系,敬请期待。
值得注意的是,发音评估更侧重于单词的发音是否正确,无法评估音调变化是否正确
:::
## 修改音频信息

View File

@@ -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 提供了音频、视频、录音的下载功能,以便可以在其他设备使用。

View File

@@ -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)
## 历史版本

View File

@@ -1,4 +1,4 @@
# 利用 AI 生成训练材料
# 使用案例:利用 AI 生成训练材料
用外语说我们自己想说的话是学习外语的其中一个重要目的。市面上的口语书实际上并不实用,因为那些话大都不是我们想要说的,我们需要创建专属自己的口语书。使用 Enjoy 可以很容易做到这一点。

View File

@@ -5,10 +5,6 @@
![视频资源播放页面](/images/enjoy/video-page.png)
_\* 视频播放页面_
## 录音
在视频播放页面,需要录音时,可以点播放控制栏上的录音图标,视频将会被缩小至小窗,主界面上会显示录音按钮。
## 视频大小的限制
过大的视频文件会导致加载卡死而无法使用,目前 Enjoy 将添加视频的大小限制在 100 Mb超过则会提示添加失败。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 425 KiB

After

Width:  |  Height:  |  Size: 443 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 669 KiB

View File

@@ -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
一天至少三个小时,每周最多可以中止一天。
每天结束之前,要花 510 分钟做一下**复盘**,回忆并记录一下自己今天遇到的困难、已经克服的困难、尚未克服的困难、以及面对那些困难时所采用的方法…… 想一想还有什么方法可以试试?这些都要写下来。相信我,记录这个东西 ,总是会以想象不到的方式起想象不到的作用。
每天结束之前,要花 510 分钟做一下**复盘**,回忆并记录一下自己今天遇到的困难、已经克服的困难、尚未克服的困难、以及面对那些困难时所采用的方法…… 想一想还有什么方法可以试试?这些都要写下来。相信我,记录这个东西 ,总是会以想象不到的方式起想象不到的作用。

View File

@@ -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 作文题脱胎而来的。
请允许我重复一遍:**你的问题也许不在于你不会说,而在于你没什么话可说。**

View File

@@ -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",

View File

@@ -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)`;
### 词汇/短语
(解释使用的关键词汇和短语)`;

View File

@@ -39,6 +39,7 @@ export const extractStoryCommand = async (
},
cache: true,
verbose: true,
maxRetries: 2,
}).bind({
tools: [
{

View File

@@ -59,6 +59,7 @@ export const ipaCommand = async (
},
cache: true,
verbose: true,
maxRetries: 2,
});
const prompt = ChatPromptTemplate.fromMessages([

View File

@@ -71,6 +71,7 @@ export const lookupCommand = async (
},
cache: true,
verbose: true,
maxRetries: 2,
});
const prompt = ChatPromptTemplate.fromMessages([

View File

@@ -22,6 +22,7 @@ export const translateCommand = async (
},
cache: false,
verbose: true,
maxRetries: 2,
});
const prompt = ChatPromptTemplate.fromMessages([

View File

@@ -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;

View File

@@ -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");

View File

@@ -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 {

View File

@@ -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);
});

View File

@@ -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 (
<div className="flex space-x-4 media-recording-container">
<div className="border rounded-xl shadow-lg flex-1 relative">
<div ref={ref}></div>
<div className="flex space-x-4 media-recording-wrapper">
<div className="border rounded-xl shadow-lg flex-1 relative media-recording-container">
<div className="w-full" ref={ref}></div>
<div className="absolute right-2 top-1">
<span className="text-sm">{formatDuration(currentTime || 0)}</span>

View File

@@ -87,7 +87,7 @@ export const MediaPlayer = () => {
const calContainerWidth = () => {
const w = document
.querySelector(".media-player-container")
.querySelector(".media-player-wrapper")
?.getBoundingClientRect()?.width;
if (!w) return;
@@ -122,7 +122,10 @@ export const MediaPlayer = () => {
}, [ref]);
useEffect(() => {
if (!ref?.current) return;
const container: HTMLDivElement = document.querySelector(
".media-player-container"
);
if (!container) return;
ref.current.style.width = `${width}px`;
}, [width]);
@@ -141,11 +144,11 @@ export const MediaPlayer = () => {
}, []);
return (
<div
data-testid="media-player-container"
className="flex space-x-4 media-player-container"
>
<div className="flex-1 border rounded-xl shadow-lg relative">
<div className="flex space-x-4 media-player-wrapper">
<div
data-testid="media-player-container"
className="flex-1 border rounded-xl shadow-lg relative media-player-container"
>
<div ref={ref} />
<div className="absolute right-2 top-1">
<span className="text-sm">{formatDuration(currentTime || 0)}</span>

View File

@@ -109,10 +109,10 @@ export const MediaRecorder = (props: { height?: number }) => {
setDuration(0);
interval = setInterval(() => {
setDuration((duration) => {
if (duration >= 300) {
if (duration >= 3000) {
setIsRecording(false);
}
return duration + 1;
return duration + 0.1;
});
}, 100);
} else {
@@ -135,7 +135,7 @@ export const MediaRecorder = (props: { height?: number }) => {
<div className="h-full w-full flex items-center space-x-4">
<div className="flex-1 h-full border rounded-xl shadow-lg relative">
<span className="absolute bottom-2 right-2 serif">
{duration / 10}
{duration.toFixed(1)}
<span className="text-xs"> / 300</span>
</span>
<div className="h-full" ref={ref}></div>

View File

@@ -5,6 +5,7 @@ import { Client } from "@/api";
import i18n from "@renderer/i18n";
import { FFmpeg } from "@ffmpeg/ffmpeg";
import { toBlobURL } from "@ffmpeg/util";
import ahoy from "ahoy.js";
type AppSettingsProviderState = {
webApi: Client;
@@ -23,6 +24,7 @@ type AppSettingsProviderState = {
switchLanguage?: (language: "en" | "zh-CN") => void;
proxy?: ProxyConfigType;
setProxy?: (config: ProxyConfigType) => Promise<void>;
ahoy?: typeof ahoy;
};
const initialState: AppSettingsProviderState = {
@@ -71,6 +73,13 @@ export const AppSettingsProvider = ({
locale: language,
})
);
if (user) {
ahoy.configure({
urlPrefix: apiUrl,
});
ahoy.track("logged in", { user: user.id });
}
}, [user, apiUrl, language]);
const prepareFfmpeg = async () => {
@@ -206,6 +215,7 @@ export const AppSettingsProvider = ({
proxy,
setProxy: setProxyConfigHandler,
initialized: Boolean(user && libraryPath),
ahoy,
}}
>
{children}

View File

@@ -39,7 +39,7 @@ export const useConversation = () => {
configuration: {
baseURL: `${apiUrl}/api/ai`,
},
maxRetries: 3,
maxRetries: 2,
modelName: model,
temperature,
maxTokens,
@@ -55,6 +55,7 @@ export const useConversation = () => {
configuration: {
baseURL: baseUrl || openai.baseUrl,
},
maxRetries: 2,
modelName: model,
temperature,
maxTokens,
@@ -69,6 +70,7 @@ export const useConversation = () => {
temperature,
frequencyPenalty,
presencePenalty,
maxRetries: 2,
});
} else if (conversation.engine === "googleGenerativeAi") {
if (!googleGenerativeAi) throw new Error("Google Generative AI API key is required");
@@ -78,6 +80,7 @@ export const useConversation = () => {
modelName: model,
temperature: temperature,
maxOutputTokens: maxTokens,
maxRetries: 2,
});
}
};

View File

@@ -6,6 +6,7 @@
],
"scripts": {
"start:enjoy": "echo 'Please use `yarn enjoy:start` instead'",
"enjoy:add": "yarn workspace enjoy add",
"enjoy:dev": "yarn workspace enjoy dev",
"enjoy:start": "yarn workspace enjoy start",
"enjoy:test": "yarn workspace enjoy test",

146
yarn.lock
View File

@@ -4678,10 +4678,124 @@ __metadata:
languageName: node
linkType: hard
"@shikijs/core@npm:1.2.1, @shikijs/core@npm:^1.2.0":
version: 1.2.1
resolution: "@shikijs/core@npm:1.2.1"
checksum: 10c0/60b5a1cdaf4389c1377b1527657377dac67173d5fd79fe4d9775392c037d6b0fe46c42bb3e3fbe42a88f4f62295f20f5cf3e43cdf1f070f4ccfedf9077379ecd
"@sentry-internal/feedback@npm:7.107.0":
version: 7.107.0
resolution: "@sentry-internal/feedback@npm:7.107.0"
dependencies:
"@sentry/core": "npm:7.107.0"
"@sentry/types": "npm:7.107.0"
"@sentry/utils": "npm:7.107.0"
checksum: 10c0/b79a68eaefef8fb83f0d423a7aa1e80cf83d82be95396e79170452ed7fe82b0c3b75f6ffa2cef525127c5fe0caf64bdffdab5e374d8912b2496e9aa379e6d500
languageName: node
linkType: hard
"@sentry-internal/replay-canvas@npm:7.107.0":
version: 7.107.0
resolution: "@sentry-internal/replay-canvas@npm:7.107.0"
dependencies:
"@sentry/core": "npm:7.107.0"
"@sentry/replay": "npm:7.107.0"
"@sentry/types": "npm:7.107.0"
"@sentry/utils": "npm:7.107.0"
checksum: 10c0/5223d66bb69cde2ba73049cacd55e5712dda6187c87df8514e8642a26ff13b8c3e34a8ec8f6c59437cb3f52a8785c3dc960eb9e046ca3bb2a74573ca99f8b77f
languageName: node
linkType: hard
"@sentry-internal/tracing@npm:7.107.0":
version: 7.107.0
resolution: "@sentry-internal/tracing@npm:7.107.0"
dependencies:
"@sentry/core": "npm:7.107.0"
"@sentry/types": "npm:7.107.0"
"@sentry/utils": "npm:7.107.0"
checksum: 10c0/d87143c548659ce018ba320406b098cc04429904b6194ae03e4626ccc71f2838256cee3390a1c8cd9b0ca945e8349456cf6da460772164545199682962218564
languageName: node
linkType: hard
"@sentry/browser@npm:7.107.0":
version: 7.107.0
resolution: "@sentry/browser@npm:7.107.0"
dependencies:
"@sentry-internal/feedback": "npm:7.107.0"
"@sentry-internal/replay-canvas": "npm:7.107.0"
"@sentry-internal/tracing": "npm:7.107.0"
"@sentry/core": "npm:7.107.0"
"@sentry/replay": "npm:7.107.0"
"@sentry/types": "npm:7.107.0"
"@sentry/utils": "npm:7.107.0"
checksum: 10c0/6cefdb519815f14419135274da22d1748e28d01f9d85d4a77ae8d999dcde08d998b4a7bc417a55c46aa45cd07d5a1abba82701f4f662be15679e6f97b0a26343
languageName: node
linkType: hard
"@sentry/core@npm:7.107.0":
version: 7.107.0
resolution: "@sentry/core@npm:7.107.0"
dependencies:
"@sentry/types": "npm:7.107.0"
"@sentry/utils": "npm:7.107.0"
checksum: 10c0/4d28df638aec9f8b88700a09d6a5d71e05b627a4bd6e75e062a523a51d0aed3dc080909859a2a72fc49e1d7637b732ae616468a3e14482900df7c4b55739f537
languageName: node
linkType: hard
"@sentry/electron@npm:^4.21.0":
version: 4.21.0
resolution: "@sentry/electron@npm:4.21.0"
dependencies:
"@sentry/browser": "npm:7.107.0"
"@sentry/core": "npm:7.107.0"
"@sentry/node": "npm:7.107.0"
"@sentry/types": "npm:7.107.0"
"@sentry/utils": "npm:7.107.0"
deepmerge: "npm:4.3.0"
tslib: "npm:^2.5.0"
checksum: 10c0/596fd402d96c26ae4af7b016010231f9748b3c801022529472e25cfc7caa334fce65f5b5cf44055c10232e35676644e53f96afd0d54b162ac171605919ee26b2
languageName: node
linkType: hard
"@sentry/node@npm:7.107.0":
version: 7.107.0
resolution: "@sentry/node@npm:7.107.0"
dependencies:
"@sentry-internal/tracing": "npm:7.107.0"
"@sentry/core": "npm:7.107.0"
"@sentry/types": "npm:7.107.0"
"@sentry/utils": "npm:7.107.0"
checksum: 10c0/a2992e2c54a17cd323c7f7362c7742417b4fdff800f104f8de0506ca7c0c225b6709b4580bb82cebfbb28e2c53642f663d9e2ad7a68f5ef71f7b4bc8e1564501
languageName: node
linkType: hard
"@sentry/replay@npm:7.107.0":
version: 7.107.0
resolution: "@sentry/replay@npm:7.107.0"
dependencies:
"@sentry-internal/tracing": "npm:7.107.0"
"@sentry/core": "npm:7.107.0"
"@sentry/types": "npm:7.107.0"
"@sentry/utils": "npm:7.107.0"
checksum: 10c0/2bb4b0c05bed77a4ad4f7b6b5b653714d02cb4892d403115bddadb1c88ccbe7d2433a7a097973204b89da1587bc9d74e2b4b402dd2e2cff27c558fc762d9b0b9
languageName: node
linkType: hard
"@sentry/types@npm:7.107.0":
version: 7.107.0
resolution: "@sentry/types@npm:7.107.0"
checksum: 10c0/08a2b95a27106a974a42a1e2462c979b8d61d9ae63cef0d7bd36eb37603009e1e8e9bb181586d1a449b7fe52a4b696b01a4228ac13083a7c4088774f16c89421
languageName: node
linkType: hard
"@sentry/utils@npm:7.107.0":
version: 7.107.0
resolution: "@sentry/utils@npm:7.107.0"
dependencies:
"@sentry/types": "npm:7.107.0"
checksum: 10c0/c0a0cd612f4db3d12853311ba8437e0315dee2f842d8ad741afeef75ab030965fc4288d8751c42f0b263d409792388d51257c3d665fba7efdf175527ef8891c4
languageName: node
linkType: hard
"@shikijs/core@npm:1.1.7, @shikijs/core@npm:^1.1.5":
version: 1.1.7
resolution: "@shikijs/core@npm:1.1.7"
checksum: 10c0/ee59b88d4c81422792651c0ca52ff378c3035f9d1e4907b58c1d6da06fad02d530775a2e6f43bb033832cd3d2a5f69c9aa4eb5b6b05311396acc503f15442f37
languageName: node
linkType: hard
@@ -5274,6 +5388,13 @@ __metadata:
languageName: node
linkType: hard
"@types/ahoy.js@npm:^0.4.2":
version: 0.4.2
resolution: "@types/ahoy.js@npm:0.4.2"
checksum: 10c0/2be990ab96b57e749e35bf3f80c37ce375e0f7b58bd0cc75848a70e22adb0f63c5b657c82a1915d40f4272b8e973103e993ac1b125e28e24e208002621010a15
languageName: node
linkType: hard
"@types/argparse@npm:1.0.38":
version: 1.0.38
resolution: "@types/argparse@npm:1.0.38"
@@ -6297,6 +6418,13 @@ __metadata:
languageName: node
linkType: hard
"ahoy.js@npm:^0.4.3":
version: 0.4.3
resolution: "ahoy.js@npm:0.4.3"
checksum: 10c0/eaacbf267d2094cb80651485af0d58034ec89d48f3755159ec850fb75d62b776222e6e9aa333aeb54d6ba1f0bac58f03653294244f1b01e69458a76fae3c2855
languageName: node
linkType: hard
"ajv@npm:^6.12.4":
version: 6.12.6
resolution: "ajv@npm:6.12.6"
@@ -8435,6 +8563,13 @@ __metadata:
languageName: node
linkType: hard
"deepmerge@npm:4.3.0":
version: 4.3.0
resolution: "deepmerge@npm:4.3.0"
checksum: 10c0/7ff5c6294b3316c1bc6bca9d3ef2193c1d7beec4e62252db8bcb8a6366d85b924850492eb1a746a5f33d609862e03dfb907ce9fa8769583300f65f20a337cec5
languageName: node
linkType: hard
"deepmerge@npm:^4.3.1":
version: 4.3.1
resolution: "deepmerge@npm:4.3.1"
@@ -9151,7 +9286,9 @@ __metadata:
"@radix-ui/react-toast": "npm:^1.1.5"
"@radix-ui/react-toggle": "npm:^1.0.3"
"@radix-ui/react-tooltip": "npm:^1.0.7"
"@sentry/electron": "npm:^4.21.0"
"@tailwindcss/typography": "npm:^0.5.10"
"@types/ahoy.js": "npm:^0.4.2"
"@types/autosize": "npm:^4.0.3"
"@types/command-exists": "npm:^1.2.3"
"@types/electron-squirrel-startup": "npm:^1.0.2"
@@ -9170,6 +9307,7 @@ __metadata:
"@uidotdev/usehooks": "npm:^2.4.1"
"@vidstack/react": "npm:^1.10.9"
"@vitejs/plugin-react": "npm:^4.2.1"
ahoy.js: "npm:^0.4.3"
autoprefixer: "npm:^10.4.19"
autosize: "npm:^6.0.1"
axios: "npm:^1.6.8"