From e4f5bdcfb9f9ef275deeb6842ff0691ca5864dbf Mon Sep 17 00:00:00 2001 From: an-lee Date: Fri, 19 Apr 2024 10:46:04 +0800 Subject: [PATCH] Refactor components codes (#538) * remove deprecated code * refactor code * refactor components codes * fix renderer tests --- .../main/db/handlers/conversations-handler.ts | 33 - enjoy/src/main/db/models/conversation.ts | 282 +------ enjoy/src/preload.ts | 14 - .../audios/audible-books-segment.tsx | 3 +- .../components/choose-library-path-input.tsx | 53 -- .../conversations/conversation-form.tsx | 719 +++++++----------- .../conversations/gpt-providers.tsx | 101 +++ .../conversations/gpt-share-button.tsx | 95 +++ .../components/conversations/index.ts | 5 +- .../components/conversations/speech-form.tsx | 97 --- .../conversations/tts-providers.tsx | 17 + enjoy/src/renderer/components/index.ts | 22 +- .../renderer/components/preferences/index.ts | 3 + .../preferences/openai-settings.tsx | 6 +- .../{ => preferences}/reset-all-button.tsx | 2 +- .../whisper-model-options.tsx | 0 .../src/renderer/components/record-button.tsx | 173 ----- enjoy/src/renderer/components/users/index.ts | 1 + .../components/{ => users}/user-card.tsx | 0 .../components/{ => widgets}/db-state.tsx | 0 .../src/renderer/components/widgets/index.ts | 13 + .../components/{ => widgets}/layout.tsx | 0 .../components/{ => widgets}/loader-spin.tsx | 0 .../components/{ => widgets}/login-form.tsx | 0 .../{ => widgets}/lookup-result.tsx | 0 .../{ => widgets}/no-records-found.tsx | 0 .../{ => widgets}/page-placeholder.tsx | 0 .../{ => widgets}/selection-menu.tsx | 0 .../components/{ => widgets}/sidebar.tsx | 5 +- enjoy/src/types/enjoy-app.d.ts | 12 - 30 files changed, 509 insertions(+), 1147 deletions(-) delete mode 100644 enjoy/src/renderer/components/choose-library-path-input.tsx create mode 100644 enjoy/src/renderer/components/conversations/gpt-providers.tsx create mode 100644 enjoy/src/renderer/components/conversations/gpt-share-button.tsx delete mode 100644 enjoy/src/renderer/components/conversations/speech-form.tsx create mode 100644 enjoy/src/renderer/components/conversations/tts-providers.tsx rename enjoy/src/renderer/components/{ => preferences}/reset-all-button.tsx (97%) rename enjoy/src/renderer/components/{ => preferences}/whisper-model-options.tsx (100%) delete mode 100644 enjoy/src/renderer/components/record-button.tsx rename enjoy/src/renderer/components/{ => users}/user-card.tsx (100%) rename enjoy/src/renderer/components/{ => widgets}/db-state.tsx (100%) create mode 100644 enjoy/src/renderer/components/widgets/index.ts rename enjoy/src/renderer/components/{ => widgets}/layout.tsx (100%) rename enjoy/src/renderer/components/{ => widgets}/loader-spin.tsx (100%) rename enjoy/src/renderer/components/{ => widgets}/login-form.tsx (100%) rename enjoy/src/renderer/components/{ => widgets}/lookup-result.tsx (100%) rename enjoy/src/renderer/components/{ => widgets}/no-records-found.tsx (100%) rename enjoy/src/renderer/components/{ => widgets}/page-placeholder.tsx (100%) rename enjoy/src/renderer/components/{ => widgets}/selection-menu.tsx (100%) rename enjoy/src/renderer/components/{ => widgets}/sidebar.tsx (98%) diff --git a/enjoy/src/main/db/handlers/conversations-handler.ts b/enjoy/src/main/db/handlers/conversations-handler.ts index da78457d..f3a25382 100644 --- a/enjoy/src/main/db/handlers/conversations-handler.ts +++ b/enjoy/src/main/db/handlers/conversations-handler.ts @@ -113,45 +113,12 @@ class ConversationsHandler { }); } - private async ask( - event: IpcMainEvent, - id: string, - params: { - messageId: string; - content?: string; - file?: string; - blob?: { - type: string; - arrayBuffer: ArrayBuffer; - }; - } - ) { - const conversation = await Conversation.findOne({ - where: { id }, - }); - if (!conversation) { - event.sender.send("on-notification", { - type: "error", - message: t("models.conversation.notFound"), - }); - return; - } - - return conversation.ask(params).catch((err) => { - event.sender.send("on-notification", { - type: "error", - message: err.message, - }); - }); - } - register() { ipcMain.handle("conversations-find-all", this.findAll); ipcMain.handle("conversations-find-one", this.findOne); ipcMain.handle("conversations-create", this.create); ipcMain.handle("conversations-update", this.update); ipcMain.handle("conversations-destroy", this.destroy); - ipcMain.handle("conversations-ask", this.ask); } } diff --git a/enjoy/src/main/db/models/conversation.ts b/enjoy/src/main/db/models/conversation.ts index b2d44bb0..6c0456ef 100644 --- a/enjoy/src/main/db/models/conversation.ts +++ b/enjoy/src/main/db/models/conversation.ts @@ -1,4 +1,3 @@ -import { app } from "electron"; import { AfterCreate, AfterDestroy, @@ -13,28 +12,8 @@ import { AllowNull, } from "sequelize-typescript"; import { Message, Speech } from "@main/db/models"; -import { ChatMessageHistory, BufferMemory } from "langchain/memory"; -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 { type Generation } from "langchain/dist/schema"; -import settings from "@main/settings"; -import db from "@main/db"; import mainWindow from "@main/window"; -import { t } from "i18next"; import log from "@main/logger"; -import fs from "fs-extra"; -import path from "path"; -import Ffmpeg from "@main/ffmpeg"; -import whisper from "@main/whisper"; -import { hashFile } from "@main/utils"; -import { WEB_API_URL } from "@/constants"; -import proxyAgent from "@main/proxy-agent"; const logger = log.scope("db/models/conversation"); @Table({ @@ -68,7 +47,7 @@ export class Conversation extends Model { } & { [key: string]: any }; @Column(DataType.VIRTUAL) - get type(): 'gpt' | 'tts' { + get type(): "gpt" | "tts" { return this.getDataValue("configuration").type || "gpt"; } @@ -117,263 +96,4 @@ export class Conversation extends Model { record: conversation.toJSON(), }); } - - // convert messages to chat history - async chatHistory() { - const chatMessageHistory = new ChatMessageHistory(); - let limit = this.configuration.historyBufferSize; - if (!limit || limit < 0) { - limit = 0; - } - const _messages = await Message.findAll({ - where: { conversationId: this.id }, - order: [["createdAt", "DESC"]], - limit, - }); - logger.debug(_messages); - - _messages - .sort((a, b) => a.createdAt - b.createdAt) - .forEach((message) => { - if (message.role === "user") { - chatMessageHistory.addUserMessage(message.content); - } else if (message.role === "assistant") { - chatMessageHistory.addAIChatMessage(message.content); - } - }); - - return chatMessageHistory; - } - - // choose llm based on engine - llm() { - const { httpAgent, fetch } = proxyAgent(); - if (this.engine === "enjoyai") { - return new ChatOpenAI( - { - openAIApiKey: settings.getSync("user.accessToken") as string, - modelName: this.model, - configuration: { - baseURL: `${process.env.WEB_API_URL || WEB_API_URL}/api/ai`, - }, - temperature: this.configuration.temperature, - n: this.configuration.numberOfChoices, - maxTokens: this.configuration.maxTokens, - frequencyPenalty: this.configuration.frequencyPenalty, - presencePenalty: this.configuration.presencePenalty, - }, - { - httpAgent, - // @ts-ignore - fetch, - } - ); - } else if (this.engine === "openai") { - const key = settings.getSync("openai.key") as string; - if (!key) { - throw new Error(t("openaiKeyRequired")); - } - return new ChatOpenAI( - { - openAIApiKey: key, - modelName: this.model, - configuration: { - baseURL: this.configuration.baseUrl, - }, - temperature: this.configuration.temperature, - n: this.configuration.numberOfChoices, - maxTokens: this.configuration.maxTokens, - frequencyPenalty: this.configuration.frequencyPenalty, - presencePenalty: this.configuration.presencePenalty, - }, - { - httpAgent, - // @ts-ignore - fetch, - } - ); - } else if (this.engine === "googleGenerativeAi") { - const key = settings.getSync("googleGenerativeAi.key") as string; - if (!key) { - throw new Error(t("googleGenerativeAiKeyRequired")); - } - return new ChatGoogleGenerativeAI({ - apiKey: key, - modelName: this.model, - temperature: this.configuration.temperature, - maxOutputTokens: this.configuration.maxTokens, - }); - } else if (this.engine == "ollama") { - return new ChatOllama({ - baseUrl: this.configuration.baseUrl, - model: this.model, - temperature: this.configuration.temperature, - frequencyPenalty: this.configuration.frequencyPenalty, - presencePenalty: this.configuration.presencePenalty, - }); - } - } - - // choose memory based on conversation scenario - async memory() { - const chatHistory = await this.chatHistory(); - return new BufferMemory({ - chatHistory, - memoryKey: "history", - returnMessages: true, - }); - } - - chatPrompt() { - return ChatPromptTemplate.fromMessages([ - ["system", this.roleDefinition], - new MessagesPlaceholder("history"), - ["human", "{input}"], - ]); - } - - async chain() { - return new ConversationChain({ - llm: this.llm(), - memory: await this.memory(), - prompt: this.chatPrompt(), - verbose: app.isPackaged ? false : true, - }); - } - - async ask(params: { - messageId?: string; - content?: string; - file?: string; - blob?: { - type: string; - arrayBuffer: ArrayBuffer; - }; - }) { - let { content, file, blob, messageId } = params; - - if (!content && !blob) { - throw new Error(t("models.conversation.contentRequired")); - } - - let md5 = ""; - let extname = ".wav"; - if (file) { - extname = path.extname(file); - md5 = await hashFile(file, { algo: "md5" }); - fs.copySync( - file, - path.join(settings.userDataPath(), "speeches", `${md5}${extname}`) - ); - } else if (blob) { - const filename = `${Date.now()}${extname}`; - const format = blob.type.split("/")[1]; - const tempfile = path.join( - settings.cachePath(), - `${Date.now()}.${format}` - ); - await fs.outputFile(tempfile, Buffer.from(blob.arrayBuffer)); - const wavFile = path.join(settings.userDataPath(), "speeches", filename); - - const ffmpeg = new Ffmpeg(); - await ffmpeg.convertToWav(tempfile, wavFile); - - md5 = await hashFile(wavFile, { algo: "md5" }); - fs.renameSync( - wavFile, - path.join(path.dirname(wavFile), `${md5}${extname}`) - ); - - const previousMessage = await Message.findOne({ - where: { conversationId: this.id }, - order: [["createdAt", "DESC"]], - }); - let prompt = ""; - if (previousMessage?.content) { - prompt = previousMessage.content.replace(/"/g, '\\"'); - } - const { transcription } = await whisper.transcribe(wavFile, { - force: true, - extra: [`--prompt "${prompt}"`], - }); - content = transcription - .map((t: TranscriptionResultSegmentType) => t.text) - .join(" ") - .trim(); - - logger.debug("transcription", transcription); - } - - const chain = await this.chain(); - let response: Generation[] = []; - const result = await chain.call({ input: content }, [ - { - handleLLMEnd: async (output) => { - response = output.generations[0]; - }, - }, - ]); - logger.debug("LLM result:", result); - - if (!response) { - throw new Error(t("models.conversation.failedToGenerateResponse")); - } - - const transaction = await db.connection.transaction(); - - await Message.create( - { - id: messageId, - conversationId: this.id, - role: "user", - content, - }, - { - include: [Conversation], - transaction, - } - ); - - const replies = await Promise.all( - response.map(async (generation) => { - if (!generation?.text) { - throw new Error(t("models.conversation.failedToGenerateResponse")); - } - return await Message.create( - { - conversationId: this.id, - role: "assistant", - content: generation.text, - }, - { - include: [Conversation], - transaction, - } - ); - }) - ); - - if (md5) { - await Speech.create( - { - sourceId: messageId, - sourceType: "message", - text: content, - md5, - extname, - configuration: { - engine: "Human", - }, - }, - { - include: [Message], - transaction, - } - ); - } - - await transaction.commit(); - - return replies.map((reply) => reply.toJSON()); - } } diff --git a/enjoy/src/preload.ts b/enjoy/src/preload.ts index 72df13bc..851c1319 100644 --- a/enjoy/src/preload.ts +++ b/enjoy/src/preload.ts @@ -317,20 +317,6 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", { destroy: (id: string) => { return ipcRenderer.invoke("conversations-destroy", id); }, - ask: ( - id: string, - params: { - messageId?: string; - content?: string; - file?: string; - blob?: { - type: string; - arrayBuffer: ArrayBuffer; - }; - } - ) => { - return ipcRenderer.invoke("conversations-ask", id, params); - }, }, messages: { findAll: (params: { where?: any; offset?: number; limit?: number }) => { diff --git a/enjoy/src/renderer/components/audios/audible-books-segment.tsx b/enjoy/src/renderer/components/audios/audible-books-segment.tsx index 0e86eeb4..c0a27e94 100644 --- a/enjoy/src/renderer/components/audios/audible-books-segment.tsx +++ b/enjoy/src/renderer/components/audios/audible-books-segment.tsx @@ -58,7 +58,8 @@ export const AudibleBooksSegment = () => { EnjoyApp.providers.audible .bestsellers() - .then(({ books }) => { + .then((res) => { + const { books = [] } = res || {}; const filteredBooks = books?.filter((book) => book.language === "English") || []; diff --git a/enjoy/src/renderer/components/choose-library-path-input.tsx b/enjoy/src/renderer/components/choose-library-path-input.tsx deleted file mode 100644 index f0e73656..00000000 --- a/enjoy/src/renderer/components/choose-library-path-input.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { t } from "i18next"; -import { useContext } from "react"; -import { Button, Input, Label } from "@renderer/components/ui"; -import { AppSettingsProviderContext } from "@renderer/context"; - -export const ChooseLibraryPathInput = () => { - const { libraryPath, setLibraryPath, EnjoyApp } = useContext( - AppSettingsProviderContext - ); - - const handleChooseLibraryPath = async () => { - const filePaths = await EnjoyApp.dialog.showOpenDialog({ - properties: ["openDirectory"], - }); - - if (filePaths) { - EnjoyApp.settings.setLibrary(filePaths[0]); - setLibraryPath(await EnjoyApp.settings.getLibrary()); - } - }; - - const openLibraryPath = async () => { - if (libraryPath) { - await EnjoyApp.shell.openPath(libraryPath); - } - }; - - return ( -
- -
- -
- - -
-
-
- ); -}; diff --git a/enjoy/src/renderer/components/conversations/conversation-form.tsx b/enjoy/src/renderer/components/conversations/conversation-form.tsx index 2bdcc115..6c8909d1 100644 --- a/enjoy/src/renderer/components/conversations/conversation-form.tsx +++ b/enjoy/src/renderer/components/conversations/conversation-form.tsx @@ -28,15 +28,15 @@ import { SelectContent, SelectItem, Textarea, - toast, } from "@renderer/components/ui"; import { useState, useEffect, useContext } from "react"; import { AppSettingsProviderContext, AISettingsProviderContext, } from "@renderer/context"; -import { LoaderIcon, Share2Icon } from "lucide-react"; +import { LoaderIcon } from "lucide-react"; import { useNavigate } from "react-router-dom"; +import { GPT_PROVIDERS, TTS_PROVIDERS, GPTShareButton } from "@renderer/components"; const conversationFormSchema = z.object({ name: z.string().optional(), @@ -73,7 +73,7 @@ export const ConversationForm = (props: { }) => { const { conversation, onFinish } = props; const [submitting, setSubmitting] = useState(false); - const [providers, setProviders] = useState(LLM_PROVIDERS); + const [providers, setProviders] = useState(GPT_PROVIDERS); const { EnjoyApp } = useContext(AppSettingsProviderContext); const { openai } = useContext(AISettingsProviderContext); const navigate = useNavigate(); @@ -126,20 +126,20 @@ export const ConversationForm = (props: { // @ts-ignore values: conversation?.id ? { - name: conversation.name, - engine: conversation.engine, - configuration: { - type: conversation.configuration.type || "gpt", - ...conversation.configuration, - }, - } + name: conversation.name, + engine: conversation.engine, + configuration: { + type: conversation.configuration.type || "gpt", + ...conversation.configuration, + }, + } : { - name: defaultConfig.name, - engine: defaultConfig.engine, - configuration: { - ...defaultConfig.configuration, + name: defaultConfig.name, + engine: defaultConfig.engine, + configuration: { + ...defaultConfig.configuration, + }, }, - }, }); const onSubmit = async (data: z.infer) => { @@ -149,7 +149,7 @@ export const ConversationForm = (props: { Object.keys(configuration).forEach((key) => { if (key === "type") return; - if (!LLM_PROVIDERS[engine]?.configurable.includes(key)) { + if (!GPT_PROVIDERS[engine]?.configurable.includes(key)) { // @ts-ignore delete configuration[key]; } @@ -161,12 +161,12 @@ export const ConversationForm = (props: { // use default base url if not set if (!configuration.baseUrl) { - configuration.baseUrl = LLM_PROVIDERS[engine]?.baseUrl; + configuration.baseUrl = GPT_PROVIDERS[engine]?.baseUrl; } // use default base url if not set if (!configuration.tts.baseUrl) { - configuration.tts.baseUrl = LLM_PROVIDERS[engine]?.baseUrl; + configuration.tts.baseUrl = GPT_PROVIDERS[engine]?.baseUrl; } if (conversation?.id) { @@ -275,7 +275,7 @@ export const ConversationForm = (props: { {Object.keys(providers) .filter((key) => - LLM_PROVIDERS[key].types.includes( + GPT_PROVIDERS[key].types.includes( form.watch("configuration.type") ) ) @@ -343,163 +343,163 @@ export const ConversationForm = (props: { )} /> - {LLM_PROVIDERS[form.watch("engine")]?.configurable.includes( + {GPT_PROVIDERS[form.watch("engine")]?.configurable.includes( "temperature" ) && ( - ( - - - {t("models.conversation.temperature")} - - { - field.onChange( - event.target.value - ? parseFloat(event.target.value) - : 0.0 - ); - }} - /> - - {t("models.conversation.temperatureDescription")} - - - - )} - /> - )} + ( + + + {t("models.conversation.temperature")} + + { + field.onChange( + event.target.value + ? parseFloat(event.target.value) + : 0.0 + ); + }} + /> + + {t("models.conversation.temperatureDescription")} + + + + )} + /> + )} - {LLM_PROVIDERS[form.watch("engine")]?.configurable.includes( + {GPT_PROVIDERS[form.watch("engine")]?.configurable.includes( "maxTokens" ) && ( - ( - - - {t("models.conversation.maxTokens")} - - { - if (!event.target.value) return; - field.onChange(parseInt(event.target.value)); - }} - /> - - {t("models.conversation.maxTokensDescription")} - - - - )} - /> - )} + ( + + + {t("models.conversation.maxTokens")} + + { + if (!event.target.value) return; + field.onChange(parseInt(event.target.value)); + }} + /> + + {t("models.conversation.maxTokensDescription")} + + + + )} + /> + )} - {LLM_PROVIDERS[form.watch("engine")]?.configurable.includes( + {GPT_PROVIDERS[form.watch("engine")]?.configurable.includes( "presencePenalty" ) && ( - ( - - - {t("models.conversation.presencePenalty")} - - { - if (!event.target.value) return; - field.onChange(parseInt(event.target.value)); - }} - /> - - {t("models.conversation.presencePenaltyDescription")} - - - - )} - /> - )} + ( + + + {t("models.conversation.presencePenalty")} + + { + if (!event.target.value) return; + field.onChange(parseInt(event.target.value)); + }} + /> + + {t("models.conversation.presencePenaltyDescription")} + + + + )} + /> + )} - {LLM_PROVIDERS[form.watch("engine")]?.configurable.includes( + {GPT_PROVIDERS[form.watch("engine")]?.configurable.includes( "frequencyPenalty" ) && ( - ( - - - {t("models.conversation.frequencyPenalty")} - - { - if (!event.target.value) return; - field.onChange(parseInt(event.target.value)); - }} - /> - - {t("models.conversation.frequencyPenaltyDescription")} - - - - )} - /> - )} + ( + + + {t("models.conversation.frequencyPenalty")} + + { + if (!event.target.value) return; + field.onChange(parseInt(event.target.value)); + }} + /> + + {t("models.conversation.frequencyPenaltyDescription")} + + + + )} + /> + )} - {LLM_PROVIDERS[form.watch("engine")]?.configurable.includes( + {GPT_PROVIDERS[form.watch("engine")]?.configurable.includes( "numberOfChoices" ) && ( - ( - - - {t("models.conversation.numberOfChoices")} - - { - field.onChange( - event.target.value - ? parseInt(event.target.value) - : 1.0 - ); - }} - /> - - {t("models.conversation.numberOfChoicesDescription")} - - - - )} - /> - )} + ( + + + {t("models.conversation.numberOfChoices")} + + { + field.onChange( + event.target.value + ? parseInt(event.target.value) + : 1.0 + ); + }} + /> + + {t("models.conversation.numberOfChoicesDescription")} + + + + )} + /> + )} - {LLM_PROVIDERS[form.watch("engine")]?.configurable.includes( + {GPT_PROVIDERS[form.watch("engine")]?.configurable.includes( "baseUrl" ) && ( - ( - - - {t("models.conversation.baseUrl")} - - - - - )} - /> - )} + ( + + + {t("models.conversation.baseUrl")} + + + + + )} + /> + )} )} @@ -588,95 +588,95 @@ export const ConversationForm = (props: { {TTS_PROVIDERS[ form.watch("configuration.tts.engine") ]?.configurable.includes("model") && ( - ( - - {t("models.conversation.ttsModel")} - - - - )} - /> - )} + ( + + {t("models.conversation.ttsModel")} + + + + )} + /> + )} {TTS_PROVIDERS[ form.watch("configuration.tts.engine") ]?.configurable.includes("voice") && ( - ( - - {t("models.conversation.ttsVoice")} - - - - )} - /> - )} + ( + + {t("models.conversation.ttsVoice")} + + + + )} + /> + )} {TTS_PROVIDERS[ form.watch("configuration.tts.engine") ]?.configurable.includes("baseUrl") && ( - ( - - {t("models.conversation.ttsBaseUrl")} - - - - )} - /> - )} + ( + + {t("models.conversation.ttsBaseUrl")} + + + + )} + /> + )} @@ -730,196 +730,3 @@ export const ConversationForm = (props: { ); }; - -export const LLM_PROVIDERS: { [key: string]: any } = { - enjoyai: { - name: "EnjoyAI", - models: [ - "gpt-3.5-turbo-0125", - "gpt-3.5-turbo", - "gpt-3.5-turbo-1106", - "gpt-3.5-turbo-16k", - "gpt-3.5-turbo-instruct", - "gpt-4-turbo", - "gpt-4-turbo-2024-04-09", - "gpt-4-0125-preview", - "gpt-4-turbo-preview", - "gpt-4-1106-preview", - "gpt-4-vision-preview", - "gpt-4", - "gpt-4-32k", - "gpt-4-0613", - "gpt-4-32k-0613", - ], - configurable: [ - "model", - "roleDefinition", - "temperature", - "numberOfChoices", - "maxTokens", - "frequencyPenalty", - "presencePenalty", - "historyBufferSize", - "tts", - ], - types: ["gpt", "tts"], - }, - openai: { - name: "OpenAI", - description: t("youNeedToSetupApiKeyBeforeUsingOpenAI"), - models: [ - "gpt-3.5-turbo-0125", - "gpt-3.5-turbo", - "gpt-3.5-turbo-1106", - "gpt-3.5-turbo-16k", - "gpt-3.5-turbo-instruct", - "gpt-4-turbo", - "gpt-4-turbo-2024-04-09", - "gpt-4-0125-preview", - "gpt-4-turbo-preview", - "gpt-4-1106-preview", - "gpt-4-vision-preview", - "gpt-4", - "gpt-4-32k", - "gpt-4-0613", - "gpt-4-32k-0613", - ], - configurable: [ - "model", - "baseUrl", - "roleDefinition", - "temperature", - "numberOfChoices", - "maxTokens", - "frequencyPenalty", - "presencePenalty", - "historyBufferSize", - "tts", - ], - types: ["gpt", "tts"], - }, - googleGenerativeAi: { - name: "Google Generative AI", - models: ["gemini-pro"], - configurable: [ - "model", - "roleDefinition", - "temperature", - "maxTokens", - "historyBufferSize", - "tts", - ], - types: ["gpt"], - }, - ollama: { - name: "Ollama", - description: t("ensureYouHaveOllamaRunningLocallyAndHasAtLeastOneModel"), - baseUrl: "http://localhost:11434", - models: [], - configurable: [ - "model", - "baseUrl", - "roleDefinition", - "temperature", - "maxTokens", - "historyBufferSize", - "frequencyPenalty", - "presencePenalty", - "tts", - ], - types: ["gpt"], - }, -}; - -export const TTS_PROVIDERS: { [key: string]: any } = { - enjoyai: { - name: "EnjoyAI", - models: ["tts-1", "tts-1-hd"], - voices: ["alloy", "echo", "fable", "onyx", "nova", "shimmer"], - configurable: ["voice"], - }, - openai: { - name: "OpenAI", - description: t("youNeedToSetupApiKeyBeforeUsingOpenAI"), - models: ["tts-1", "tts-1-hd"], - voices: ["alloy", "echo", "fable", "onyx", "nova", "shimmer"], - configurable: ["model", "voice", "baseUrl"], - }, -}; - -const GPTShareButton = (props: { - conversation: Partial; -}) => { - const { conversation } = props; - const { webApi } = useContext(AppSettingsProviderContext); - const navigate = useNavigate(); - - const handleShare = () => { - const { configuration } = conversation; - delete configuration.baseUrl - delete configuration?.tts?.baseUrl - - if (!configuration.roleDefinition) { - toast.error('shareFailed'); - return; - } - - webApi - .createPost({ - metadata: { - type: "gpt", - content: { - name: conversation.name, - engine: conversation.engine, - configuration, - }, - }, - }) - .then(() => { - toast.success(t("sharedSuccessfully"), { - description: t("sharedGpt"), - action: { - label: t("view"), - onClick: () => { - navigate("/community"); - }, - }, - actionButtonStyle: { - backgroundColor: "var(--primary)", - }, - }); - }) - .catch((err) => { - toast.error(t("shareFailed"), { description: err.message }); - }); - } - - if (!conversation.id) return null; - if (conversation.type !== "gpt") return null; - - return ( - - - - - - - {t("shareGpt")} - - {t("areYouSureToShareThisGptToCommunity")} - - - - {t("cancel")} - - - - - - - ); -} \ No newline at end of file diff --git a/enjoy/src/renderer/components/conversations/gpt-providers.tsx b/enjoy/src/renderer/components/conversations/gpt-providers.tsx new file mode 100644 index 00000000..be9fccd3 --- /dev/null +++ b/enjoy/src/renderer/components/conversations/gpt-providers.tsx @@ -0,0 +1,101 @@ +import { t } from "i18next"; + +export const GPT_PROVIDERS: { [key: string]: any } = { + enjoyai: { + name: "EnjoyAI", + models: [ + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo", + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-16k", + "gpt-3.5-turbo-instruct", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-0125-preview", + "gpt-4-turbo-preview", + "gpt-4-1106-preview", + "gpt-4-vision-preview", + "gpt-4", + "gpt-4-32k", + "gpt-4-0613", + "gpt-4-32k-0613", + ], + configurable: [ + "model", + "roleDefinition", + "temperature", + "numberOfChoices", + "maxTokens", + "frequencyPenalty", + "presencePenalty", + "historyBufferSize", + "tts", + ], + types: ["gpt", "tts"], + }, + openai: { + name: "OpenAI", + description: t("youNeedToSetupApiKeyBeforeUsingOpenAI"), + models: [ + "gpt-3.5-turbo-0125", + "gpt-3.5-turbo", + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-16k", + "gpt-3.5-turbo-instruct", + "gpt-4-turbo", + "gpt-4-turbo-2024-04-09", + "gpt-4-0125-preview", + "gpt-4-turbo-preview", + "gpt-4-1106-preview", + "gpt-4-vision-preview", + "gpt-4", + "gpt-4-32k", + "gpt-4-0613", + "gpt-4-32k-0613", + ], + configurable: [ + "model", + "baseUrl", + "roleDefinition", + "temperature", + "numberOfChoices", + "maxTokens", + "frequencyPenalty", + "presencePenalty", + "historyBufferSize", + "tts", + ], + types: ["gpt", "tts"], + }, + googleGenerativeAi: { + name: "Google Generative AI", + models: ["gemini-pro"], + configurable: [ + "model", + "roleDefinition", + "temperature", + "maxTokens", + "historyBufferSize", + "tts", + ], + types: ["gpt"], + }, + ollama: { + name: "Ollama", + description: t("ensureYouHaveOllamaRunningLocallyAndHasAtLeastOneModel"), + baseUrl: "http://localhost:11434", + models: [], + configurable: [ + "model", + "baseUrl", + "roleDefinition", + "temperature", + "maxTokens", + "historyBufferSize", + "frequencyPenalty", + "presencePenalty", + "tts", + ], + types: ["gpt"], + }, +}; diff --git a/enjoy/src/renderer/components/conversations/gpt-share-button.tsx b/enjoy/src/renderer/components/conversations/gpt-share-button.tsx new file mode 100644 index 00000000..fcabf428 --- /dev/null +++ b/enjoy/src/renderer/components/conversations/gpt-share-button.tsx @@ -0,0 +1,95 @@ +import { AppSettingsProviderContext } from "@renderer/context"; +import { t } from "i18next"; +import { useContext } from "react"; +import { useNavigate } from "react-router-dom"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, + Button, + toast +} from "@renderer/components/ui"; +import { Share2Icon } from "lucide-react"; + +export const GPTShareButton = (props: { + conversation: Partial; +}) => { + const { conversation } = props; + const { webApi } = useContext(AppSettingsProviderContext); + const navigate = useNavigate(); + + const handleShare = () => { + const { configuration } = conversation; + delete configuration.baseUrl; + delete configuration?.tts?.baseUrl; + + if (!configuration.roleDefinition) { + toast.error("shareFailed"); + return; + } + + webApi + .createPost({ + metadata: { + type: "gpt", + content: { + name: conversation.name, + engine: conversation.engine, + configuration, + }, + }, + }) + .then(() => { + toast.success(t("sharedSuccessfully"), { + description: t("sharedGpt"), + action: { + label: t("view"), + onClick: () => { + navigate("/community"); + }, + }, + actionButtonStyle: { + backgroundColor: "var(--primary)", + }, + }); + }) + .catch((err) => { + toast.error(t("shareFailed"), { description: err.message }); + }); + }; + + if (!conversation.id) return null; + if (conversation.type !== "gpt") return null; + + return ( + + + + + + + {t("shareGpt")} + + {t("areYouSureToShareThisGptToCommunity")} + + + + {t("cancel")} + + + + + + + ); +}; diff --git a/enjoy/src/renderer/components/conversations/index.ts b/enjoy/src/renderer/components/conversations/index.ts index 553bb366..45eb79b6 100644 --- a/enjoy/src/renderer/components/conversations/index.ts +++ b/enjoy/src/renderer/components/conversations/index.ts @@ -1,5 +1,8 @@ export * from "./conversation-form"; export * from "./conversation-shortcuts"; -export * from "./speech-form"; export * from "./speech-player"; + +export * from "./gpt-providers"; +export * from "./gpt-share-button"; +export * from "./tts-providers"; diff --git a/enjoy/src/renderer/components/conversations/speech-form.tsx b/enjoy/src/renderer/components/conversations/speech-form.tsx deleted file mode 100644 index 5ddc8dfa..00000000 --- a/enjoy/src/renderer/components/conversations/speech-form.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { useState, useContext } from "react"; -import { RecordButton, SpeechPlayer } from "@renderer/components"; -import { - Button, - Textarea, - Dialog, - DialogContent, -} from "@renderer/components/ui"; -import { AppSettingsProviderContext } from "@renderer/context"; -import { LoaderIcon } from "lucide-react"; -import { t } from "i18next"; - -export const SpeechForm = (props: { - lastMessage?: MessageType; - onSubmit: (content: string, file: string) => void; -}) => { - const { lastMessage, onSubmit } = props; - const { EnjoyApp } = useContext(AppSettingsProviderContext); - const [transcribing, setTranscribing] = useState(false); - const [editting, setEditting] = useState(false); - const [content, setContent] = useState(""); - const [file, setFile] = useState(""); - - const handleCancel = () => { - setEditting(false); - setContent(""); - setFile(""); - }; - - const handleSubmit = () => { - if (!content) return; - onSubmit(content, file); - handleCancel(); - }; - - return ( - <> - { - setTranscribing(true); - setEditting(true); - EnjoyApp.whisper - .transcribe( - { - type: blob.type.split(";")[0], - arrayBuffer: await blob.arrayBuffer(), - }, - lastMessage?.content - ) - .then(({ content, file }) => { - setContent(content); - setFile(file); - }) - .finally(() => { - setTranscribing(false); - }); - }} - /> - { - setEditting(value); - }} - > - - {transcribing ? ( -
- -
- ) : ( -
-
-