diff --git a/1000-hours/package.json b/1000-hours/package.json index 00b8424f..b87c9c22 100644 --- a/1000-hours/package.json +++ b/1000-hours/package.json @@ -8,7 +8,7 @@ "markdown-it-sub": "^2.0.0", "markdown-it-sup": "^2.0.0", "mermaid": "^10.9.1", - "sass": "^1.77.2", + "sass": "^1.77.4", "vitepress": "^1.2.2", "vitepress-plugin-mermaid": "^2.0.16", "vue": "^3.4.27" diff --git a/enjoy/package.json b/enjoy/package.json index 43026a83..fdaf92e5 100644 --- a/enjoy/package.json +++ b/enjoy/package.json @@ -15,7 +15,7 @@ "make": "rimraf .vite && yarn run download && electron-forge make", "publish": "rimraf .vite && yarn run download && electron-forge publish", "lint": "eslint --ext .ts,.tsx .", - "test": "yarn run package && playwright test", + "test": "yarn run package && yarn run playwright test", "test:main": "yarn run playwright test e2e/main.spec.ts", "test:renderer": "yarn run playwright test e2e/renderer.spec.ts", "create-migration": "zx ./src/main/db/create-migration.mjs", @@ -51,18 +51,18 @@ "@types/intl-tel-input": "^18.1.4", "@types/lodash": "^4.17.4", "@types/mark.js": "^8.11.12", - "@types/node": "^20.12.12", + "@types/node": "^20.14.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@types/validator": "^13.11.10", "@types/wavesurfer.js": "^6.0.12", - "@typescript-eslint/eslint-plugin": "^7.10.0", - "@typescript-eslint/parser": "^7.10.0", + "@typescript-eslint/eslint-plugin": "^7.12.0", + "@typescript-eslint/parser": "^7.12.0", "@vitejs/plugin-react": "^4.3.0", "autoprefixer": "^10.4.19", - "electron": "^30.0.8", + "electron": "^30.0.9", "electron-playwright-helpers": "^1.7.1", - "eslint": "^9.3.0", + "eslint": "^9.4.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.29.1", "flora-colossus": "^2.0.0", @@ -75,15 +75,15 @@ "ts-node": "^10.9.2", "tslib": "^2.6.2", "typescript": "^5.4.5", - "vite": "^5.2.11", + "vite": "^5.2.12", "vite-plugin-static-copy": "^1.0.5", - "zx": "^8.1.1" + "zx": "^8.1.2" }, "dependencies": { "@andrkrn/ffprobe-static": "^5.2.0", "@electron-forge/publisher-s3": "^7.4.0", "@hookform/resolvers": "^3.4.2", - "@langchain/community": "^0.2.3", + "@langchain/community": "^0.2.5", "@langchain/google-genai": "^0.0.16", "@mozilla/readability": "^0.5.0", "@radix-ui/react-accordion": "^1.1.2", @@ -142,13 +142,13 @@ "i18next": "^23.11.5", "intl-tel-input": "^23.0.10", "js-md5": "^0.8.3", - "langchain": "^0.2.2", + "langchain": "^0.2.4", "lodash": "^4.17.21", - "lucide-react": "^0.379.0", + "lucide-react": "^0.383.0", "mark.js": "^8.11.1", "microsoft-cognitiveservices-speech-sdk": "^1.36.0", "next-themes": "^0.3.0", - "openai": "^4.47.1", + "openai": "^4.47.3", "pitchfinder": "^2.3.2", "postcss": "^8.4.38", "proxy-agent": "^6.4.0", diff --git a/enjoy/src/api/client.ts b/enjoy/src/api/client.ts index 96a3ee10..58664be2 100644 --- a/enjoy/src/api/client.ts +++ b/enjoy/src/api/client.ts @@ -283,12 +283,26 @@ export class Client { } generateSpeechToken(params?: { + purpose?: string; targetType?: string; targetId?: string; - }): Promise<{ token: string; region: string }> { + input?: string; + }): Promise<{ id: number; token: string; region: string }> { return this.api.post("/api/speech/tokens", decamelizeKeys(params || {})); } + consumeSpeechToken(id: number) { + return this.api.put(`/api/speech/tokens/${id}`, { + state: "consumed", + }); + } + + revokeSpeechToken(id: number) { + return this.api.put(`/api/speech/tokens/${id}`, { + state: "revoked", + }); + } + syncPronunciationAssessment( pronunciationAssessment: Partial ) { diff --git a/enjoy/src/renderer/components/conversations/conversation-form.tsx b/enjoy/src/renderer/components/conversations/conversation-form.tsx deleted file mode 100644 index 733d9309..00000000 --- a/enjoy/src/renderer/components/conversations/conversation-form.tsx +++ /dev/null @@ -1,777 +0,0 @@ -import * as z from "zod"; -import { t } from "i18next"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, - Button, - FormField, - Form, - FormItem, - FormLabel, - FormControl, - FormDescription, - FormMessage, - Input, - ScrollArea, - Select, - SelectTrigger, - SelectValue, - SelectContent, - SelectItem, - Textarea, - toast, -} from "@renderer/components/ui"; -import { useState, useEffect, useContext } from "react"; -import { - AppSettingsProviderContext, - AISettingsProviderContext, -} from "@renderer/context"; -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(), - engine: z - .enum(["enjoyai", "openai", "ollama", "googleGenerativeAi"]) - .default("openai"), - configuration: z.object({ - type: z.enum(["gpt", "tts"]), - model: z.string().optional(), - baseUrl: z.string().optional(), - roleDefinition: z.string().optional(), - temperature: z.number().min(0).max(1).default(0.2), - numberOfChoices: z.number().min(1).default(1), - maxTokens: z.number().min(-1).default(2000), - presencePenalty: z.number().min(-2).max(2).default(0), - frequencyPenalty: z.number().min(-2).max(2).default(0), - historyBufferSize: z.number().min(0).default(10), - tts: z.object({ - engine: z.enum(["openai", "enjoyai"]).default("enjoyai"), - model: z.string().default("tts-1"), - voice: z.string(), - baseUrl: z.string().optional(), - }), - }), -}); - -export const ConversationForm = (props: { - conversation: Partial; - onFinish?: () => void; -}) => { - const { conversation, onFinish } = props; - const [submitting, setSubmitting] = useState(false); - const [gptProviders, setGptProviders] = useState(GPT_PROVIDERS); - const [ttsProviders, setTtsProviders] = useState(TTS_PROVIDERS); - const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext); - const { openai } = useContext(AISettingsProviderContext); - const navigate = useNavigate(); - - const refreshGptProviders = async () => { - let providers = GPT_PROVIDERS; - - try { - const config = await webApi.config("gpt_providers"); - providers = Object.assign(providers, config); - } catch (e) { - console.warn(`Failed to fetch remote GPT config: ${e.message}`); - } - - try { - const response = await fetch(providers["ollama"]?.baseUrl + "/api/tags"); - providers["ollama"].models = (await response.json()).models.map( - (m: any) => m.name - ); - } catch (e) { - console.warn(`No ollama server found: ${e.message}`); - } - - setGptProviders({ ...providers }); - }; - - const destroyConversation = async () => { - if (!conversation.id) return; - - EnjoyApp.conversations.destroy(conversation.id).then(() => { - navigate(`/conversations`); - }); - }; - - const refreshTtsProviders = async () => { - let providers = TTS_PROVIDERS; - - try { - const config = await webApi.config("tts_providers"); - providers = Object.assign(providers, config); - } catch (e) { - console.warn(`Failed to fetch remote TTS config: ${e.message}`); - } - - setTtsProviders({ ...providers }); - }; - - useEffect(() => { - refreshGptProviders(); - refreshTtsProviders(); - }, []); - - const defaultConfig = JSON.parse(JSON.stringify(conversation || {})); - - if (defaultConfig.engine === "openai" && openai) { - if (!defaultConfig.configuration) { - defaultConfig.configuration = {}; - } - if (!defaultConfig.configuration.model) { - defaultConfig.configuration.model = openai.model; - } - if (!defaultConfig.configuration.baseUrl) { - defaultConfig.configuration.baseUrl = openai.baseUrl; - } - } - - if (defaultConfig.configuration.tts?.engine === "openai" && openai) { - if (!defaultConfig.configuration.tts?.baseUrl) { - defaultConfig.configuration.tts.baseUrl = openai.baseUrl; - } - } - - const form = useForm>({ - resolver: zodResolver(conversationFormSchema), - // @ts-ignore - values: conversation?.id - ? { - name: conversation.name, - engine: conversation.engine, - configuration: { - type: conversation.configuration.type || "gpt", - ...conversation.configuration, - }, - } - : { - name: defaultConfig.name, - engine: defaultConfig.engine, - configuration: { - ...defaultConfig.configuration, - }, - }, - }); - - const onSubmit = async (data: z.infer) => { - let { name, engine, configuration } = data; - setSubmitting(true); - - try { - configuration = validateConfiguration(data); - } catch (e) { - toast.error(e.message); - setSubmitting(false); - return; - } - - if (conversation?.id) { - EnjoyApp.conversations - .update(conversation.id, { - name, - configuration, - }) - .then(() => { - onFinish && onFinish(); - }) - .finally(() => { - setSubmitting(false); - }); - } else { - EnjoyApp.conversations - .create({ - name, - engine, - configuration, - }) - .then(() => { - onFinish && onFinish(); - }) - .finally(() => { - setSubmitting(false); - }); - } - }; - - const validateConfiguration = ( - data: z.infer - ) => { - const { engine, configuration } = data; - - Object.keys(configuration).forEach((key) => { - if (key === "type") return; - - if ( - configuration.type === "gpt" && - !gptProviders[engine]?.configurable.includes(key) - ) { - // @ts-ignore - delete configuration[key]; - } - - if ( - configuration.type === "tts" && - !ttsProviders[engine]?.configurable.includes(key) - ) { - // @ts-ignore - delete configuration.tts[key]; - } - }); - - // use default base url if not set - if (!configuration.baseUrl) { - configuration.baseUrl = gptProviders[engine]?.baseUrl; - } - - // use default base url if not set - if (!configuration?.tts?.baseUrl) { - configuration.tts ||= {}; - configuration.tts.baseUrl = gptProviders[engine]?.baseUrl; - } - - return configuration; - }; - - return ( -
- -
-
- {conversation.id ? t("editConversation") : t("startConversation")} -
- -
- -
- ( - - {t("models.conversation.name")} - - - - )} - /> - - ( - - {t("models.conversation.type")} - - - - )} - /> - - {form.watch("configuration.type") === "gpt" && ( - <> - ( - - {t("models.conversation.engine")} - - - {gptProviders[form.watch("engine")]?.description} - - - - )} - /> - ( - - {t("models.conversation.model")} - - - - )} - /> - - ( - - - {t("models.conversation.roleDefinition")} - -