diff --git a/enjoy/package.json b/enjoy/package.json index 31d486aa1..e5be59929 100644 --- a/enjoy/package.json +++ b/enjoy/package.json @@ -39,35 +39,35 @@ "@types/intl-tel-input": "^18.1.4", "@types/lodash": "^4.14.202", "@types/mark.js": "^8.11.12", - "@types/node": "^20.11.5", + "@types/node": "^20.11.10", "@types/react": "^18.2.48", "@types/react-dom": "^18.2.18", "@types/validator": "^13.11.8", "@types/wavesurfer.js": "^6.0.12", - "@typescript-eslint/eslint-plugin": "^6.19.0", - "@typescript-eslint/parser": "^6.19.0", + "@typescript-eslint/eslint-plugin": "^6.20.0", + "@typescript-eslint/parser": "^6.20.0", "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.17", - "electron": "^28.1.4", + "electron": "^28.2.0", "eslint": "^8.56.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.29.1", "flora-colossus": "^2.0.0", "octokit": "^3.1.2", - "tailwind-merge": "^2.2.0", + "tailwind-merge": "^2.2.1", "tailwindcss": "^3.4.1", "tailwindcss-animate": "^1.0.7", "ts-node": "^10.9.2", "tslib": "^2.6.2", "typescript": "^5.3.3", - "vite-plugin-static-copy": "^1.0.0", + "vite-plugin-static-copy": "^1.0.1", "zx": "^7.2.3" }, "dependencies": { "@ffmpeg/ffmpeg": "^0.12.10", "@ffmpeg/util": "^0.12.1", "@hookform/resolvers": "^3.3.4", - "@langchain/google-genai": "^0.0.7", + "@langchain/google-genai": "^0.0.8", "@mozilla/readability": "^0.5.0", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-alert-dialog": "^1.0.5", @@ -92,46 +92,46 @@ "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-tooltip": "^1.0.7", "@uidotdev/usehooks": "^2.4.1", - "@vidstack/react": "^1.9.8", + "@vidstack/react": "^1.10.2", "adm-zip": "^0.5.10", "autosize": "^6.0.1", - "axios": "^1.6.5", + "axios": "^1.6.7", "camelcase": "^8.0.0", "camelcase-keys": "^9.1.3", "cheerio": "^1.0.0-rc.12", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "command-exists": "^1.2.9", - "compromise": "^14.11.1", + "compromise": "^14.11.2", "compromise-paragraphs": "^0.1.0", "compromise-stats": "^0.1.0", "dayjs": "^1.11.10", "decamelize": "^6.0.0", "decamelize-keys": "^2.0.1", - "electron-log": "^5.0.4", + "electron-log": "^5.1.0", "electron-settings": "^4.0.2", "electron-squirrel-startup": "^1.0.0", "fluent-ffmpeg": "^2.1.2", "fs-extra": "^11.2.0", "html-to-text": "^9.0.5", - "i18next": "^23.7.18", - "intl-tel-input": "^19.2.12", + "i18next": "^23.8.1", + "intl-tel-input": "^19.2.15", "js-md5": "^0.8.3", - "langchain": "^0.1.5", + "langchain": "^0.1.10", "lodash": "^4.17.21", - "lucide-react": "^0.314.0", + "lucide-react": "^0.319.0", "mark.js": "^8.11.1", "microsoft-cognitiveservices-speech-sdk": "^1.34.0", "next-themes": "^0.2.1", - "openai": "^4.24.7", + "openai": "^4.26.0", "pitchfinder": "^2.3.2", "postcss": "^8.4.33", "react": "^18.2.0", - "react-activity-calendar": "^2.2.6", + "react-activity-calendar": "^2.2.7", "react-dom": "^18.2.0", "react-hook-form": "^7.49.3", "react-hotkeys-hook": "^4.4.4", - "react-i18next": "^14.0.0", + "react-i18next": "^14.0.1", "react-markdown": "^9.0.1", "react-router-dom": "^6.21.3", "react-tooltip": "^5.26.0", @@ -139,11 +139,11 @@ "rimraf": "^5.0.5", "sequelize": "^6.35.2", "sequelize-typescript": "^2.1.6", - "sonner": "^1.3.1", + "sonner": "^1.4.0", "sqlite3": "^5.1.7", "tailwind-scrollbar-hide": "^1.1.7", "umzug": "^3.5.1", - "wavesurfer.js": "^7.6.5", + "wavesurfer.js": "^7.7.1", "zod": "^3.22.4" } } diff --git a/enjoy/src/api/client.ts b/enjoy/src/api/client.ts index 92f7025ad..d1fa39f54 100644 --- a/enjoy/src/api/client.ts +++ b/enjoy/src/api/client.ts @@ -13,8 +13,9 @@ export class Client { baseUrl: string; accessToken?: string; logger?: any; + locale?: "en" | "zh-CN"; }) { - const { baseUrl, accessToken, logger } = options; + const { baseUrl, accessToken, logger, locale = "en" } = options; this.baseUrl = baseUrl; this.logger = logger || console; @@ -27,6 +28,7 @@ export class Client { }); this.api.interceptors.request.use((config) => { config.headers.Authorization = `Bearer ${accessToken}`; + config.headers['Accept-Language'] = locale; this.logger.debug( config.method.toUpperCase(), diff --git a/enjoy/src/i18n/en.json b/enjoy/src/i18n/en.json index 07563022d..623a8e5dd 100644 --- a/enjoy/src/i18n/en.json +++ b/enjoy/src/i18n/en.json @@ -66,9 +66,11 @@ "name": "Name", "engine": "AI engine", "baseUrl": "Request endpoint", + "baseUrlDescription": "BaseURL, leave it blank if you don't have one", "configuration": "Configuration", "model": "AI model", "roleDefinition": "Role definition", + "roleDefinitionPlaceholder": "Describe the AI role", "temperature": "Temperature", "temperatureDescription": "The higher the temperature, the more creative the result", "maxTokens": "Max tokens", @@ -85,6 +87,7 @@ "ttsModel": "TTS model", "ttsVoice": "TTS voice", "ttsBaseUrl": "TTS base URL", + "ttsBaseUrlDescription": "BaseURL for TTS, leave it blank if you don't have one", "notFound": "Conversation not found", "contentRequired": "Content required", "failedToGenerateResponse": "Failed to generate response, please retry" @@ -147,8 +150,11 @@ "yesterday": "yesterday", "play": "play", "pause": "pause", - "loop": "loop", - "stopLoop": "stop loop", + "playSingleSegment": "play single segment", + "playAllSegments": "play all segments", + "playInLoop": "play in loop", + "playNextSegment": "play next segment", + "playPreviousSegment": "play previous segment", "playbackSpeed": "playback speed", "zoomIn": "zoom in", "zoomOut": "zoom out", @@ -161,6 +167,7 @@ "detail": "detail", "remove": "remove", "share": "share", + "forward": "forward", "loadMore": "Load more", "databaseError": "Failed to connect to database {{url}}", "somethingWentWrong": "Something went wrong", @@ -218,7 +225,7 @@ "welcomeBack": "Welcome back! {{name}}", "download": "Download", "downloading": "Downloading {{file}}", - "chooseAIModelDependingOnYourHardware": "Choose AI Model depending on your hardware", + "chooseAIModelDependingOnYourHardware": "Choose AI Model depending on your hardware.", "areYouSureToDownload": "Are you sure to download {{name}}?", "yourModelsWillBeDownloadedTo": "Your models will be downloaded to {{path}}", "logout": "Logout", @@ -291,17 +298,23 @@ "noRecordingActivities": "no recording activities", "basicSettingsShort": "Basic", "basicSettings": "Basic settings", + "accountSettingsShort": "Account", + "accountSettings": "Account settings", "advancedSettingsShort": "Advanced", "advancedSettings": "Advanced settings", "advanced": "Advanced", "language": "Language", + "balance": "Balance", + "deposit": "Deposit", + "notAvailableYet": "Not available yet", + "whisperModel": "Whisper Model", "sttAiService": "STT AI service", "local": "Local", - "localSpeechToTextDescription": "Use local whisper model to transcribe.", + "localSpeechToTextDescription": "Use local whisper model to transcribe. It is free.", "azureAi": "Azure AI", - "azureSpeechToTextDescription": "Use Azure AI Speech to transcribe.", + "azureSpeechToTextDescription": "Use Azure AI Speech to transcribe. It is a paid service.", "cloudflareAi": "Cloudflare AI", - "cloudflareSpeechToTextDescription": "Use Cloudflare AI Worker to transcribe.", + "cloudflareSpeechToTextDescription": "Use Cloudflare AI Worker to transcribe. It is in beta and free for now.", "checkingWhisper": "Checking whisper status", "pleaseDownloadWhisperModelFirst": "Please download whisper model first", "whisperIsWorkingGood": "Whisper is working good", @@ -310,6 +323,9 @@ "whisperModelIsWorkingGood": "Whisper model is working good", "whisperModelIsNotWorking": "Whisper model is not working", "relaunchIsNeededAfterChanged": "Relaunch is needed after changed", + "defaultAiEngine": "Default AI engine", + "openAiEngineTips": "Use OpenAI with your own key as default AI engine.", + "enjoyAiEngineTips": "Use EnjoyAI as default AI engine. It is a paid service.", "openaiKeySaved": "OpenAI key saved", "openaiConfigSaved": "OpenAI config saved", "openaiKeyRequired": "OpenAI key required", @@ -318,6 +334,8 @@ "key": "key", "leaveEmptyToUseDefault": "Leave empty to use default", "newConversation": "New conversation", + "selectAiRole": "Select AI role", + "custom": "Custom", "startConversation": "Start conversation", "editConversation": "Edit conversation", "deleteConversation": "Delete conversation", @@ -417,5 +435,11 @@ "removeSharing": "Remove sharing", "areYouSureToRemoveThisSharing": "Are you sure to remove this sharing?", "removeSharingSuccessfully": "Remove sharing successfully", - "removeSharingFailed": "Remove sharing failed" + "removeSharingFailed": "Remove sharing failed", + "generatingIpa": "Generating IPA", + "generatedIpaSuccessfully": "Generated IPA successfully", + "generatingIpaFailed": "Generating IPA failed", + "translating": "Translating", + "translatedSuccessfully": "Translated successfully", + "translationFailed": "Translation failed" } diff --git a/enjoy/src/i18n/zh-CN.json b/enjoy/src/i18n/zh-CN.json index 7bae3d254..d62a73d66 100644 --- a/enjoy/src/i18n/zh-CN.json +++ b/enjoy/src/i18n/zh-CN.json @@ -66,9 +66,11 @@ "name": "对话标题", "engine": "AI 引擎", "baseUrl": "接口地址", + "baseUrlDescription": "接口地址,留空则使用默认值", "configuration": "AI 配置", "model": "AI 模型", "roleDefinition": "角色定义", + "roleDefinitionPlaceholder": "描述 AI 扮演的角色", "temperature": "随机性 (temperature)", "temperatureDescription": "值越高,生成的文本越具创造性,反之则越稳定", "maxTokens": "单次回复限制", @@ -84,7 +86,8 @@ "ttsEngine": "TTS 引擎", "ttsModel": "TTS 模型", "ttsVoice": "TTS 声音", - "ttsBaseUrl": "TTS 请求地址", + "ttsBaseUrl": "TTS 接口地址", + "ttsBaseUrlDescription": "TTS 接口地址,留空则使用默认值", "notFound": "未找到对话", "contentRequired": "对话内容不能为空", "failedToGenerateResponse": "生成失败,请重试" @@ -147,8 +150,11 @@ "yesterday": "昨天", "play": "播放", "pause": "暂停", - "loop": "循环", - "stopLoop": "停止循环", + "playSingleSegment": "播放单句", + "playAllSegments": "播放所有", + "playInLoop": "单句循环", + "playNextSegment": "播放下一句", + "playPreviousSegment": "播放上一句", "playbackSpeed": "播放速度", "zoomIn": "放大", "zoomOut": "缩小", @@ -161,6 +167,7 @@ "detail": "详情", "remove": "删除", "share": "分享", + "forward": "转发", "loadMore": "加载更多", "databaseError": "数据库错误 {{url}}", "somethingWentWrong": "出错了", @@ -218,7 +225,7 @@ "welcomeBack": "欢迎回来, {{name}}", "download": "下载", "downloading": "正在下载 {{file}}", - "chooseAIModelDependingOnYourHardware": "根据您的硬件选择合适的 AI 模型", + "chooseAIModelDependingOnYourHardware": "根据您的硬件选择合适的 AI 模型, 以便语音转文本服务正常工作", "areYouSureToDownload": "您确定要下载 {{name}} 吗?", "yourModelsWillBeDownloadedTo": "您的模型将下载到目录 {{path}}", "logout": "退出登录", @@ -291,16 +298,22 @@ "noRecordingActivities": "没有练习活动", "basicSettingsShort": "基本设置", "basicSettings": "基本设置", + "accountSettingsShort": "账户设置", + "accountSettings": "账户设置", "advancedSettingsShort": "高级设置", "advancedSettings": "高级设置", "language": "语言", + "balance": "余额", + "deposit": "充值", + "notAvailableYet": "暂未开放", + "whisperModel": "Whisper 模型", "sttAiService": "语音转文本服务", "local": "本地", - "localSpeechToTextDescription": "使用本地 whisper 模型进行语音转文本", + "localSpeechToTextDescription": "使用本地 whisper 模型进行语音转文本,不会产生费用", "azureAi": "Azure AI", - "azureSpeechToTextDescription": "使用 Azure AI Speech 进行语音转文本", + "azureSpeechToTextDescription": "使用 Azure AI Speech 进行语音转文本,收费服务", "cloudflareAi": "Cloudflare AI", - "cloudflareSpeechToTextDescription": "使用 Cloudflare AI 进行语音转文本", + "cloudflareSpeechToTextDescription": "使用 Cloudflare AI 进行语音转文本,目前免费", "checkingWhisper": "正在检查 Whisper", "pleaseDownloadWhisperModelFirst": "请先下载 Whisper 模型", "whisperIsWorkingGood": "Whisper 正常工作", @@ -309,14 +322,19 @@ "whisperModelIsWorkingGood": "Whisper 模型正常工作", "whisperModelIsNotWorking": "Whisper 模型无法正常工作,请尝试更换模型后重试,或联系开发者", "relaunchIsNeededAfterChanged": "更改后需要重新启动", + "defaultAiEngine": "默认 AI 引擎", + "openAiEngineTips": "使用 OpenAI 作为默认 AI 引擎,需要配置 API 密钥。", + "enjoyAiEngineTips": "使用 EnjoyAI 作为默认 AI 引擎,收费服务。", "openaiKeySaved": "OpenAI 密钥已保存", "openaiConfigSaved": "OpenAI 配置已保存", - "openaiKeyRequired": "未提供 OpenAI 密钥", + "openaiKeyRequired": "未配置 OpenAI 密钥", "baseUrl": "接口地址", "model": "模型", "key": "密钥", "leaveEmptyToUseDefault": "留空则使用默认值", "newConversation": "新对话", + "selectAiRole": "选择 AI 角色", + "custom": "自定义", "startConversation": "开始对话", "editConversation": "编辑对话", "deleteConversation": "删除对话", @@ -416,5 +434,11 @@ "removeSharing": "取消分享", "areYouSureToRemoveThisSharing": "您确定要取消分享吗?", "removeSharingSuccessfully": "取消分享成功", - "removeSharingFailed": "取消分享失败" + "removeSharingFailed": "取消分享失败", + "generatingIpa": "正在生成音标", + "generatedIpaSuccessfully": "音标生成成功", + "generatingIpaFailed": "音标生成失败", + "translating": "正在翻译", + "translatedSuccessfully": "翻译成功", + "translationFailed": "翻译失败" } diff --git a/enjoy/src/main/db/handlers/audios-handler.ts b/enjoy/src/main/db/handlers/audios-handler.ts index 18b7b9dd2..9cec1909d 100644 --- a/enjoy/src/main/db/handlers/audios-handler.ts +++ b/enjoy/src/main/db/handlers/audios-handler.ts @@ -14,7 +14,7 @@ class AudiosHandler { options: FindOptions> ) { return Audio.findAll({ - order: [["createdAt", "DESC"]], + order: [["updatedAt", "DESC"]], include: [ { association: "transcription", @@ -66,39 +66,6 @@ class AudiosHandler { }); } - private async transcribe(event: IpcMainEvent, id: string) { - const audio = await Audio.findOne({ - where: { - id, - }, - }); - if (!audio) { - event.sender.send("on-notification", { - type: "error", - message: t("models.audio.notFound"), - }); - } - - const timeout = setTimeout(() => { - event.sender.send("on-notification", { - type: "warning", - message: t("stillTranscribing"), - }); - }, 1000 * 10); - - audio - .transcribe() - .catch((err) => { - event.sender.send("on-notification", { - type: "error", - message: err.message, - }); - }) - .finally(() => { - clearTimeout(timeout); - }); - } - private async create( event: IpcMainEvent, uri: string, @@ -148,7 +115,7 @@ class AudiosHandler { id: string, params: Attributes