From 8037273549adb17f36c9fa1bf0f9500c959ece23 Mon Sep 17 00:00:00 2001 From: an-lee Date: Thu, 22 Feb 2024 16:00:17 +0800 Subject: [PATCH] Feat: add tts conversation (#341) * may create tts type conversation * support tts reply * upgrade deps * test: e2e for create TTS conversation * test: e2e for gpt conversation * test: e2e for from create conversation to add speech audio to library * refactor use-conversation * generate speech before create msg in tts conversation * refactor conversation-shorts * revert change in 1000-hours * revert sass dep changed in 1000-hours * fix CI --- enjoy/e2e/main.spec.ts | 79 +- enjoy/e2e/renderer.spec.ts | 201 + enjoy/package.json | 12 +- enjoy/samples/speech.mp3 | Bin 0 -> 17280 bytes enjoy/src/i18n/en.json | 9 +- enjoy/src/i18n/zh-CN.json | 9 +- enjoy/src/main/db/models/conversation.ts | 11 +- .../components/audios/audio-detail.tsx | 2 +- .../conversations/conversation-form.tsx | 592 +- .../conversations/conversation-shortcuts.tsx | 205 + .../conversations/conversations-list.tsx | 8 +- .../conversations/conversations-shortcut.tsx | 121 - .../components/conversations/index.ts | 3 +- .../conversations/speech-player.tsx | 1 + .../components/medias/media-player.tsx | 8 +- .../components/medias/media-transcription.tsx | 11 +- .../components/messages/assistant-message.tsx | 198 +- .../renderer/components/messages/message.tsx | 5 +- .../components/messages/user-message.tsx | 72 +- .../components/posts/post-actions.tsx | 39 +- enjoy/src/renderer/components/sidebar.tsx | 1 + enjoy/src/renderer/hooks/use-conversation.tsx | 59 +- enjoy/src/renderer/pages/conversation.tsx | 32 +- enjoy/src/renderer/pages/conversations.tsx | 41 +- enjoy/src/types/conversation.d.ts | 1 + package.json | 3 +- yarn.lock | 23098 +++++++++------- 27 files changed, 14940 insertions(+), 9881 deletions(-) create mode 100644 enjoy/e2e/renderer.spec.ts create mode 100644 enjoy/samples/speech.mp3 create mode 100644 enjoy/src/renderer/components/conversations/conversation-shortcuts.tsx delete mode 100644 enjoy/src/renderer/components/conversations/conversations-shortcut.tsx diff --git a/enjoy/e2e/main.spec.ts b/enjoy/e2e/main.spec.ts index c440ff27..d3822bb3 100644 --- a/enjoy/e2e/main.spec.ts +++ b/enjoy/e2e/main.spec.ts @@ -18,15 +18,6 @@ declare global { } } -const user = { - id: 24000001, - name: "李安", - avatarUrl: - "https://mixin-images.zeromesh.net/9tMscDkZuXyLKMRChmFi5IiFF2XuQHO8PQpED8zKOCBDGKGSVB9J2eqzyjhgJKPDVunXiT-DPiisImX_bhBDPi4=s256", - accessToken: - "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOm51bGwsInNpZCI6IjkyN2RjNGRhLTI3YTItNDU5MC1hY2ZiLWMxYTJmZjhhMmFjMiIsInVpZCI6MjQwMDAwMDEsImlhdCI6MTcwODMyODk1N30.PCN_SZ7JH-VYLl56XU8kxYN9Cy44sO13mBQNNz6x-pa", -}; - let electronApp: ElectronApplication; const resultDir = path.join(process.cwd(), "test-results"); @@ -65,61 +56,27 @@ test.afterAll(async () => { await electronApp.close(); }); -test.describe("main dependencies", () => { - test("validate whisper command", async () => { - const page = await electronApp.firstWindow(); - const res = await page.evaluate(() => { - return window.__ENJOY_APP__.whisper.check(); - }); - console.info(res.log); - expect(res.success).toBeTruthy(); - - const settings = fs.readJsonSync(path.join(resultDir, "settings.json")); - expect(settings.whisper.service).toBe("local"); +test("validate whisper command", async () => { + const page = await electronApp.firstWindow(); + const res = await page.evaluate(() => { + return window.__ENJOY_APP__.whisper.check(); }); + console.info(res.log); + expect(res.success).toBeTruthy(); - test("valid ffmpeg command", async () => { - const page = await electronApp.firstWindow(); - const res = await page.evaluate(() => { - return window.__ENJOY_APP__.ffmpeg.check(); - }); - expect(res).toBeTruthy(); - }); - - test("should setup default library path", async () => { - const settings = fs.readJsonSync(path.join(resultDir, "settings.json")); - expect(settings.library).not.toBeNull(); - }); + const settings = fs.readJsonSync(path.join(resultDir, "settings.json")); + expect(settings.whisper.service).toBe("local"); }); -test.describe("with login", async () => { - let page: Page; - - test.beforeAll(async () => { - const settings = fs.readJsonSync(path.join(resultDir, "settings.json")); - settings.user = user; - fs.writeJsonSync(path.join(resultDir, "settings.json"), settings); - - page = await electronApp.firstWindow(); - page.route("**/api/me", (route) => { - route.fulfill({ - json: user, - }); - }); - - await page.evaluate(() => { - return window.__ENJOY_APP__.app.reload(); - }); - }); - - test("should enter homepage after login", async () => { - await page.waitForSelector("[data-testid=layout-home]"); - - await page.screenshot({ path: "test-results/homepage.png" }); - - expect(await page.getByTestId("layout-onboarding").isVisible()).toBeFalsy(); - expect(await page.getByTestId("layout-db-error").isVisible()).toBeFalsy(); - expect(await page.getByTestId("layout-home").isVisible()).toBeTruthy(); - expect(await page.getByTestId("sidebar").isVisible()).toBeTruthy(); +test("valid ffmpeg command", async () => { + const page = await electronApp.firstWindow(); + const res = await page.evaluate(() => { + return window.__ENJOY_APP__.ffmpeg.check(); }); + expect(res).toBeTruthy(); +}); + +test("should setup default library path", async () => { + const settings = fs.readJsonSync(path.join(resultDir, "settings.json")); + expect(settings.library).not.toBeNull(); }); diff --git a/enjoy/e2e/renderer.spec.ts b/enjoy/e2e/renderer.spec.ts new file mode 100644 index 00000000..e7aa9b24 --- /dev/null +++ b/enjoy/e2e/renderer.spec.ts @@ -0,0 +1,201 @@ +import { expect, test } from "@playwright/test"; +import { findLatestBuild, parseElectronApp } from "electron-playwright-helpers"; +import { ElectronApplication, Page, _electron as electron } from "playwright"; +import path from "path"; +import fs from "fs-extra"; + +const user = { + id: 24000001, + name: "李安", + avatarUrl: + "https://mixin-images.zeromesh.net/9tMscDkZuXyLKMRChmFi5IiFF2XuQHO8PQpED8zKOCBDGKGSVB9J2eqzyjhgJKPDVunXiT-DPiisImX_bhBDPi4=s256", + accessToken: + "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOm51bGwsInNpZCI6IjkyN2RjNGRhLTI3YTItNDU5MC1hY2ZiLWMxYTJmZjhhMmFjMiIsInVpZCI6MjQwMDAwMDEsImlhdCI6MTcwODMyODk1N30.PCN_SZ7JH-VYLl56XU8kxYN9Cy44sO13mBQNNz6x-pa", +}; + +let electronApp: ElectronApplication; +const resultDir = path.join(process.cwd(), "test-results"); + +test.beforeAll(async () => { + // find the latest build in the out directory + const latestBuild = findLatestBuild(); + // parse the directory and find paths and other info + const appInfo = parseElectronApp(latestBuild); + // set the CI environment variable to true + process.env.CI = "e2e"; + + fs.ensureDirSync(resultDir); + process.env.SETTINGS_PATH = resultDir; + process.env.LIBRARY_PATH = resultDir; + + electronApp = await electron.launch({ + args: [appInfo.main], + executablePath: appInfo.executable, + }); + electronApp.on("window", async (page) => { + const filename = page.url()?.split("/").pop(); + console.info(`Window opened: ${filename}`); + + // capture errors + page.on("pageerror", (error) => { + console.error(error); + }); + // capture console messages + page.on("console", (msg) => { + console.info(msg.text()); + }); + }); +}); + +test.afterAll(async () => { + await electronApp.close(); +}); + +test.describe("with login", async () => { + let page: Page; + + test.beforeAll(async () => { + const settings = fs.readJsonSync(path.join(resultDir, "settings.json")); + settings.user = user; + fs.writeJsonSync(path.join(resultDir, "settings.json"), settings); + + page = await electronApp.firstWindow(); + page.route("**/api/me", (route) => { + route.fulfill({ + json: user, + }); + }); + + await page.evaluate(() => { + return window.__ENJOY_APP__.app.reload(); + }); + }); + + test("should enter homepage after login", async () => { + await page.getByTestId("layout-home").waitFor(); + + await page.screenshot({ path: "test-results/homepage.png" }); + + expect(await page.getByTestId("layout-onboarding").isVisible()).toBeFalsy(); + expect(await page.getByTestId("layout-db-error").isVisible()).toBeFalsy(); + expect(await page.getByTestId("layout-home").isVisible()).toBeTruthy(); + expect(await page.getByTestId("sidebar").isVisible()).toBeTruthy(); + }); + + test.describe("with conversation", async () => { + test.beforeEach(async () => { + const file = fs.readFileSync( + path.join(process.cwd(), "samples", "speech.mp3") + ); + + page = await electronApp.firstWindow(); + page.on("console", (msg) => { + console.info(msg.text()); + }); + + await page.route("**/api/ai/audio/speech", (route) => { + route.fulfill({ + body: file, + }); + }); + await page.route("**/api/ai/chat/completions", (route) => { + route.fulfill({ + json: { + id: "1", + choices: [ + { + index: 1, + message: { + role: "assistant", + content: "I'm fine, thank you.", + }, + finish_reason: "stop", + }, + ], + }, + }); + }); + + // navigate to the conversations page + await page.getByTestId("sidebar-conversations").click(); + }); + + /* + * steps: + * 1. create a gpt conversation + * 2. submit a message to the conversation, AI should reply + * 3. create a speech from the AI message + * 4. add the speech to the library + * 5. audio waveform player should be visible and transcription should be generated + */ + test("gpt conversation", async () => { + // trigger new conversation modal + await page.getByTestId("conversation-new-button").click(); + + // create a gpt conversation + await page.getByTestId("conversation-preset-english-coach").click(); + await page.getByTestId("conversation-form").waitFor(); + await page.click("[data-testid=conversation-form-submit]"); + + // wait for the conversation to be created + await page.getByTestId("conversation-page").waitFor(); + + // submit a message to the conversation + await page.getByTestId("conversation-page-input").fill("How are you?"); + await page.getByTestId("conversation-page-submit").click(); + await page.locator(".ai-message").waitFor(); + const message = page.locator(".ai-message").first(); + expect(await message.isVisible()).toBeTruthy(); + + // create a speech + await page.getByTestId("message-create-speech").click(); + + // wait for the speech player + const player = page + .locator(".ai-message") + .getByTestId("wavesurfer-container"); + await player.waitFor(); + expect(await player.isVisible()).toBeTruthy(); + + // add to library + await page.getByTestId("message-start-shadow").click(); + await page.getByTestId("audio-detail").waitFor(); + await page.getByTestId("media-player-container").waitFor(); + await page.getByTestId("media-transcription").waitFor(); + await page.getByTestId("media-transcription-result").waitFor(); + expect( + await page.getByTestId("media-transcription-result").isVisible() + ).toBeTruthy(); + }); + + /* + * steps: + * 1. create a tts conversation + * 2. submit a message to the conversation + * 3. the speech should auto create + */ + test("tts conversation", async () => { + // trigger new conversation modal + await page.getByTestId("conversation-new-button").click(); + + // create a tts conversation + await page.click("[data-testid=conversation-preset-tts]"); + await page.getByTestId("conversation-form").waitFor(); + await page.click("[data-testid=conversation-form-submit]"); + + // wait for the conversation to be created + await page.getByTestId("conversation-page").waitFor(); + + // submit a message to the conversation + await page.getByTestId("conversation-page-input").fill("How are you?"); + await page.getByTestId("conversation-page-submit").click(); + await page.locator(".ai-message").waitFor(); + const player = page + .locator(".ai-message") + .getByTestId("wavesurfer-container"); + await player.waitFor(); + + expect(await player.isVisible()).toBeTruthy(); + }); + }); +}); diff --git a/enjoy/package.json b/enjoy/package.json index 1270dd80..9deca7a4 100644 --- a/enjoy/package.json +++ b/enjoy/package.json @@ -45,15 +45,15 @@ "@types/lodash": "^4.14.202", "@types/mark.js": "^8.11.12", "@types/node": "^20.11.19", - "@types/react": "^18.2.56", + "@types/react": "^18.2.57", "@types/react-dom": "^18.2.19", "@types/validator": "^13.11.9", "@types/wavesurfer.js": "^6.0.12", - "@typescript-eslint/eslint-plugin": "^7.0.1", - "@typescript-eslint/parser": "^7.0.1", + "@typescript-eslint/eslint-plugin": "^7.0.2", + "@typescript-eslint/parser": "^7.0.2", "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.17", - "electron": "^28.2.0", + "electron": "^28.2.3", "electron-playwright-helpers": "^1.7.1", "eslint": "^8.56.0", "eslint-import-resolver-typescript": "^3.6.1", @@ -101,7 +101,7 @@ "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-tooltip": "^1.0.7", "@uidotdev/usehooks": "^2.4.1", - "@vidstack/react": "^1.10.7", + "@vidstack/react": "^1.10.9", "autosize": "^6.0.1", "axios": "^1.6.7", "camelcase": "^8.0.0", @@ -129,7 +129,7 @@ "js-md5": "^0.8.3", "langchain": "^0.1.20", "lodash": "^4.17.21", - "lucide-react": "^0.334.0", + "lucide-react": "^0.335.0", "mark.js": "^8.11.1", "microsoft-cognitiveservices-speech-sdk": "^1.35.0", "next-themes": "^0.2.1", diff --git a/enjoy/samples/speech.mp3 b/enjoy/samples/speech.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..8501460a4c62819a727df8e588b4ebc4595748d1 GIT binary patch literal 17280 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 literal 0 HcmV?d00001 diff --git a/enjoy/src/i18n/en.json b/enjoy/src/i18n/en.json index 0c0bceb3..78e1e6f1 100644 --- a/enjoy/src/i18n/en.json +++ b/enjoy/src/i18n/en.json @@ -66,9 +66,10 @@ "name": "Name", "engine": "AI engine", "baseUrl": "Request endpoint", - "baseUrlDescription": "BaseURL, leave it blank if you don't have one", + "baseUrlDescription": "leave it blank if you don't have one", "configuration": "Configuration", "model": "AI model", + "type": "AI Type", "roleDefinition": "Role definition", "roleDefinitionPlaceholder": "Describe the AI role", "temperature": "Temperature", @@ -87,7 +88,7 @@ "ttsModel": "TTS model", "ttsVoice": "TTS voice", "ttsBaseUrl": "TTS base URL", - "ttsBaseUrlDescription": "BaseURL for TTS, leave it blank if you don't have one", + "ttsBaseUrlDescription": "leave it blank if you don't have one", "notFound": "Conversation not found", "contentRequired": "Content required", "failedToGenerateResponse": "Failed to generate response, please retry" @@ -461,5 +462,7 @@ "itMayTakeAWhileToPrepareForTheFirstLoad": "It may take a while to prepare for the first load. Please be patient.", "loadingTranscription": "Loading transcription", "cannotFindMicrophone": "Cannot find microphone", - "failedToSaveRecording": "Failed to save recording" + "failedToSaveRecording": "Failed to save recording", + "speechNotCreatedYet": "Speech not created yet", + "goToConversation": "Go to conversation" } diff --git a/enjoy/src/i18n/zh-CN.json b/enjoy/src/i18n/zh-CN.json index d1f6411f..da86749e 100644 --- a/enjoy/src/i18n/zh-CN.json +++ b/enjoy/src/i18n/zh-CN.json @@ -66,9 +66,10 @@ "name": "对话标题", "engine": "AI 引擎", "baseUrl": "接口地址", - "baseUrlDescription": "接口地址,留空则使用默认值", + "baseUrlDescription": "留空则使用默认值", "configuration": "AI 配置", "model": "AI 模型", + "type": "AI 类型", "roleDefinition": "角色定义", "roleDefinitionPlaceholder": "描述 AI 扮演的角色", "temperature": "随机性 (temperature)", @@ -87,7 +88,7 @@ "ttsModel": "TTS 模型", "ttsVoice": "TTS 声音", "ttsBaseUrl": "TTS 接口地址", - "ttsBaseUrlDescription": "TTS 接口地址,留空则使用默认值", + "ttsBaseUrlDescription": "留空则使用默认值", "notFound": "未找到对话", "contentRequired": "对话内容不能为空", "failedToGenerateResponse": "生成失败,请重试" @@ -460,5 +461,7 @@ "itMayTakeAWhileToPrepareForTheFirstLoad": "首次加载可能需要一些时间,请耐心等候", "loadingTranscription": "正在加载语音文本", "cannotFindMicrophone": "无法找到麦克风", - "failedToSaveRecording": "保存录音失败" + "failedToSaveRecording": "保存录音失败", + "speechNotCreatedYet": "尚未生成语音", + "goToConversation": "前往对话" } diff --git a/enjoy/src/main/db/models/conversation.ts b/enjoy/src/main/db/models/conversation.ts index 8ca2ec1e..0d1f2e54 100644 --- a/enjoy/src/main/db/models/conversation.ts +++ b/enjoy/src/main/db/models/conversation.ts @@ -18,7 +18,10 @@ import { ConversationChain } from "langchain/chains"; import { ChatOpenAI } from "@langchain/openai"; import { ChatOllama } from "@langchain/community/chat_models/ollama"; import { ChatGoogleGenerativeAI } from "@langchain/google-genai"; -import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts"; +import { + ChatPromptTemplate, + MessagesPlaceholder, +} from "@langchain/core/prompts"; import { type Generation } from "langchain/dist/schema"; import settings from "@main/settings"; import db from "@main/db"; @@ -58,11 +61,17 @@ export class Conversation extends Model { @Column(DataType.JSON) configuration: { model: string; + type: "gpt" | "tts"; roleDefinition?: string; temperature?: number; maxTokens?: number; } & { [key: string]: any }; + @Column(DataType.VIRTUAL) + get type(): 'gpt' | 'tts' { + return this.getDataValue("configuration").type || "gpt"; + } + @Column(DataType.VIRTUAL) get model(): string { return this.getDataValue("configuration").model; diff --git a/enjoy/src/renderer/components/audios/audio-detail.tsx b/enjoy/src/renderer/components/audios/audio-detail.tsx index 35e634a3..e2566b39 100644 --- a/enjoy/src/renderer/components/audios/audio-detail.tsx +++ b/enjoy/src/renderer/components/audios/audio-detail.tsx @@ -206,7 +206,7 @@ export const AudioDetail = (props: { id?: string; md5?: string }) => { } return ( -
+
m.name ); } catch (e) { - console.error(e); + console.warn(`No ollama server found: ${e.message}`); } setProviders({ ...providers }); }; @@ -112,7 +113,8 @@ export const ConversationForm = (props: { defaultConfig.configuration.baseUrl = openai.baseUrl; } } - if (defaultConfig.configuration.tts.engine === "openai" && openai) { + + if (defaultConfig.configuration.tts?.engine === "openai" && openai) { if (!defaultConfig.configuration.tts.baseUrl) { defaultConfig.configuration.tts.baseUrl = openai.baseUrl; } @@ -126,10 +128,8 @@ export const ConversationForm = (props: { name: conversation.name, engine: conversation.engine, configuration: { + type: conversation.configuration.type || "gpt", ...conversation.configuration, - tts: { - ...conversation.configuration?.tts, - }, }, } : { @@ -146,16 +146,24 @@ export const ConversationForm = (props: { setSubmitting(true); Object.keys(configuration).forEach((key) => { + if (key === "type") return; + if (!LLM_PROVIDERS[engine]?.configurable.includes(key)) { // @ts-ignore delete configuration[key]; } }); + if (configuration.type === "tts") { + conversation.model = configuration.tts.model; + } + + // use default base url if not set if (!configuration.baseUrl) { configuration.baseUrl = LLM_PROVIDERS[engine]?.baseUrl; } + // use default base url if not set if (!configuration.tts.baseUrl) { configuration.tts.baseUrl = LLM_PROVIDERS[engine]?.baseUrl; } @@ -193,6 +201,7 @@ export const ConversationForm = (props: {
{conversation.id ? t("editConversation") : t("startConversation")} @@ -213,10 +222,10 @@ export const ConversationForm = (props: { ( - {t("models.conversation.engine")} + {t("models.conversation.type")} - - {providers[form.watch("engine")]?.description} - - - - )} - /> - - ( - - {t("models.conversation.model")} - @@ -270,227 +250,306 @@ export const ConversationForm = (props: { )} /> - ( - - - {t("models.conversation.roleDefinition")} - -