Refactor components codes (#538)
* remove deprecated code * refactor code * refactor components codes * fix renderer tests
This commit is contained in:
@@ -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") || [];
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<div className="grid gap-1.5 w-full max-w-sm">
|
||||
<Label htmlFor="library-path">{t("libraryPath")}</Label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Input
|
||||
id="library-path"
|
||||
value={libraryPath}
|
||||
disabled
|
||||
className="cursor-pointer!"
|
||||
/>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={openLibraryPath}
|
||||
className="min-w-max"
|
||||
>
|
||||
{t("open")}
|
||||
</Button>
|
||||
<Button onClick={handleChooseLibraryPath} className="min-w-max">
|
||||
{t("select")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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<boolean>(false);
|
||||
const [providers, setProviders] = useState<any>(LLM_PROVIDERS);
|
||||
const [providers, setProviders] = useState<any>(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<typeof conversationFormSchema>) => {
|
||||
@@ -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: {
|
||||
<SelectContent>
|
||||
{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"
|
||||
) && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.temperature"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("models.conversation.temperature")}
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
max="1.0"
|
||||
step="0.1"
|
||||
value={field.value}
|
||||
onChange={(event) => {
|
||||
field.onChange(
|
||||
event.target.value
|
||||
? parseFloat(event.target.value)
|
||||
: 0.0
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FormDescription>
|
||||
{t("models.conversation.temperatureDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.temperature"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("models.conversation.temperature")}
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
max="1.0"
|
||||
step="0.1"
|
||||
value={field.value}
|
||||
onChange={(event) => {
|
||||
field.onChange(
|
||||
event.target.value
|
||||
? parseFloat(event.target.value)
|
||||
: 0.0
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FormDescription>
|
||||
{t("models.conversation.temperatureDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{LLM_PROVIDERS[form.watch("engine")]?.configurable.includes(
|
||||
{GPT_PROVIDERS[form.watch("engine")]?.configurable.includes(
|
||||
"maxTokens"
|
||||
) && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.maxTokens"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("models.conversation.maxTokens")}
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
value={field.value}
|
||||
onChange={(event) => {
|
||||
if (!event.target.value) return;
|
||||
field.onChange(parseInt(event.target.value));
|
||||
}}
|
||||
/>
|
||||
<FormDescription>
|
||||
{t("models.conversation.maxTokensDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.maxTokens"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("models.conversation.maxTokens")}
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="number"
|
||||
min="0"
|
||||
value={field.value}
|
||||
onChange={(event) => {
|
||||
if (!event.target.value) return;
|
||||
field.onChange(parseInt(event.target.value));
|
||||
}}
|
||||
/>
|
||||
<FormDescription>
|
||||
{t("models.conversation.maxTokensDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{LLM_PROVIDERS[form.watch("engine")]?.configurable.includes(
|
||||
{GPT_PROVIDERS[form.watch("engine")]?.configurable.includes(
|
||||
"presencePenalty"
|
||||
) && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.presencePenalty"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("models.conversation.presencePenalty")}
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="number"
|
||||
min="-2"
|
||||
step="0.1"
|
||||
max="2"
|
||||
value={field.value}
|
||||
onChange={(event) => {
|
||||
if (!event.target.value) return;
|
||||
field.onChange(parseInt(event.target.value));
|
||||
}}
|
||||
/>
|
||||
<FormDescription>
|
||||
{t("models.conversation.presencePenaltyDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.presencePenalty"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("models.conversation.presencePenalty")}
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="number"
|
||||
min="-2"
|
||||
step="0.1"
|
||||
max="2"
|
||||
value={field.value}
|
||||
onChange={(event) => {
|
||||
if (!event.target.value) return;
|
||||
field.onChange(parseInt(event.target.value));
|
||||
}}
|
||||
/>
|
||||
<FormDescription>
|
||||
{t("models.conversation.presencePenaltyDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{LLM_PROVIDERS[form.watch("engine")]?.configurable.includes(
|
||||
{GPT_PROVIDERS[form.watch("engine")]?.configurable.includes(
|
||||
"frequencyPenalty"
|
||||
) && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.frequencyPenalty"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("models.conversation.frequencyPenalty")}
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="number"
|
||||
min="-2"
|
||||
step="0.1"
|
||||
max="2"
|
||||
value={field.value}
|
||||
onChange={(event) => {
|
||||
if (!event.target.value) return;
|
||||
field.onChange(parseInt(event.target.value));
|
||||
}}
|
||||
/>
|
||||
<FormDescription>
|
||||
{t("models.conversation.frequencyPenaltyDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.frequencyPenalty"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("models.conversation.frequencyPenalty")}
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="number"
|
||||
min="-2"
|
||||
step="0.1"
|
||||
max="2"
|
||||
value={field.value}
|
||||
onChange={(event) => {
|
||||
if (!event.target.value) return;
|
||||
field.onChange(parseInt(event.target.value));
|
||||
}}
|
||||
/>
|
||||
<FormDescription>
|
||||
{t("models.conversation.frequencyPenaltyDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{LLM_PROVIDERS[form.watch("engine")]?.configurable.includes(
|
||||
{GPT_PROVIDERS[form.watch("engine")]?.configurable.includes(
|
||||
"numberOfChoices"
|
||||
) && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.numberOfChoices"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("models.conversation.numberOfChoices")}
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
step="1.0"
|
||||
value={field.value}
|
||||
onChange={(event) => {
|
||||
field.onChange(
|
||||
event.target.value
|
||||
? parseInt(event.target.value)
|
||||
: 1.0
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FormDescription>
|
||||
{t("models.conversation.numberOfChoicesDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.numberOfChoices"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("models.conversation.numberOfChoices")}
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
step="1.0"
|
||||
value={field.value}
|
||||
onChange={(event) => {
|
||||
field.onChange(
|
||||
event.target.value
|
||||
? parseInt(event.target.value)
|
||||
: 1.0
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FormDescription>
|
||||
{t("models.conversation.numberOfChoicesDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -531,28 +531,28 @@ export const ConversationForm = (props: {
|
||||
)}
|
||||
/>
|
||||
|
||||
{LLM_PROVIDERS[form.watch("engine")]?.configurable.includes(
|
||||
{GPT_PROVIDERS[form.watch("engine")]?.configurable.includes(
|
||||
"baseUrl"
|
||||
) && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.baseUrl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("models.conversation.baseUrl")}
|
||||
</FormLabel>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder={t(
|
||||
"models.conversation.baseUrlDescription"
|
||||
)}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.baseUrl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("models.conversation.baseUrl")}
|
||||
</FormLabel>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder={t(
|
||||
"models.conversation.baseUrlDescription"
|
||||
)}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -588,95 +588,95 @@ export const ConversationForm = (props: {
|
||||
{TTS_PROVIDERS[
|
||||
form.watch("configuration.tts.engine")
|
||||
]?.configurable.includes("model") && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.tts.model"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("models.conversation.ttsModel")}</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t("selectTtsModel")} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{(
|
||||
TTS_PROVIDERS[form.watch("configuration.tts.engine")]
|
||||
?.models || []
|
||||
).map((model: string) => (
|
||||
<SelectItem key={model} value={model}>
|
||||
{model}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.tts.model"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("models.conversation.ttsModel")}</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t("selectTtsModel")} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{(
|
||||
TTS_PROVIDERS[form.watch("configuration.tts.engine")]
|
||||
?.models || []
|
||||
).map((model: string) => (
|
||||
<SelectItem key={model} value={model}>
|
||||
{model}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{TTS_PROVIDERS[
|
||||
form.watch("configuration.tts.engine")
|
||||
]?.configurable.includes("voice") && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.tts.voice"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("models.conversation.ttsVoice")}</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t("selectTtsVoice")} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{(
|
||||
TTS_PROVIDERS[form.watch("configuration.tts.engine")]
|
||||
?.voices || []
|
||||
).map((voice: string) => (
|
||||
<SelectItem key={voice} value={voice}>
|
||||
<span className="capitalize">{voice}</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.tts.voice"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("models.conversation.ttsVoice")}</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t("selectTtsVoice")} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{(
|
||||
TTS_PROVIDERS[form.watch("configuration.tts.engine")]
|
||||
?.voices || []
|
||||
).map((voice: string) => (
|
||||
<SelectItem key={voice} value={voice}>
|
||||
<span className="capitalize">{voice}</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{TTS_PROVIDERS[
|
||||
form.watch("configuration.tts.engine")
|
||||
]?.configurable.includes("baseUrl") && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.tts.baseUrl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("models.conversation.ttsBaseUrl")}</FormLabel>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder={t(
|
||||
"models.conversation.ttsBaseUrlDescription"
|
||||
)}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="configuration.tts.baseUrl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("models.conversation.ttsBaseUrl")}</FormLabel>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder={t(
|
||||
"models.conversation.ttsBaseUrlDescription"
|
||||
)}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
@@ -730,196 +730,3 @@ export const ConversationForm = (props: {
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
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<ConversationType>;
|
||||
}) => {
|
||||
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 (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="link" size="icon" className="rounded-full p-0 w-6 h-6">
|
||||
<Share2Icon className="w-4 h-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t("shareGpt")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t("areYouSureToShareThisGptToCommunity")}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
|
||||
<AlertDialogAction asChild>
|
||||
<Button variant="default" onClick={handleShare}>
|
||||
{t("share")}
|
||||
</Button>
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
101
enjoy/src/renderer/components/conversations/gpt-providers.tsx
Normal file
101
enjoy/src/renderer/components/conversations/gpt-providers.tsx
Normal file
@@ -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"],
|
||||
},
|
||||
};
|
||||
@@ -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<ConversationType>;
|
||||
}) => {
|
||||
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 (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="link" size="icon" className="rounded-full p-0 w-6 h-6">
|
||||
<Share2Icon className="w-4 h-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t("shareGpt")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t("areYouSureToShareThisGptToCommunity")}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
|
||||
<AlertDialogAction asChild>
|
||||
<Button variant="default" onClick={handleShare}>
|
||||
{t("share")}
|
||||
</Button>
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
};
|
||||
@@ -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";
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<RecordButton
|
||||
disabled={false}
|
||||
onRecordEnd={async (blob, _duration) => {
|
||||
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);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Dialog
|
||||
open={editting}
|
||||
onOpenChange={(value) => {
|
||||
setEditting(value);
|
||||
}}
|
||||
>
|
||||
<DialogContent>
|
||||
{transcribing ? (
|
||||
<div className="flex items-center justify-center p-6">
|
||||
<LoaderIcon className="w-6 h-6 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="">
|
||||
<div className="my-4">
|
||||
<Textarea
|
||||
className="w-full h-36"
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{file && (
|
||||
<div className="mb-4">
|
||||
<SpeechPlayer speech={{ playSource: "enjoy://" + file }} />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<Button variant="secondary" onClick={handleCancel}>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit}>{t("send")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { t } from "i18next";
|
||||
|
||||
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"],
|
||||
},
|
||||
};
|
||||
@@ -13,24 +13,4 @@ export * from "./medias";
|
||||
export * from "./posts";
|
||||
export * from "./users";
|
||||
|
||||
export * from "./db-state";
|
||||
|
||||
export * from "./layout";
|
||||
export * from "./sidebar";
|
||||
export * from "./page-placeholder";
|
||||
|
||||
export * from "./user-card";
|
||||
export * from "./login-form";
|
||||
|
||||
export * from "./choose-library-path-input";
|
||||
export * from "./whisper-model-options";
|
||||
|
||||
export * from "./reset-all-button";
|
||||
|
||||
export * from "./loader-spin";
|
||||
export * from "./no-records-found";
|
||||
|
||||
export * from "./selection-menu";
|
||||
export * from "./lookup-result";
|
||||
|
||||
export * from "./record-button";
|
||||
export * from "./widgets";
|
||||
|
||||
@@ -18,7 +18,10 @@ export * from "./balance-settings";
|
||||
|
||||
export * from "./reset-settings";
|
||||
export * from "./reset-all-settings";
|
||||
export * from "./reset-all-button";
|
||||
|
||||
export * from "./theme-settings";
|
||||
|
||||
export * from "./proxy-settings";
|
||||
|
||||
export * from "./whisper-model-options";
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
SelectValue,
|
||||
SelectContent,
|
||||
} from "@renderer/components/ui";
|
||||
import { LLM_PROVIDERS } from "@renderer/components";
|
||||
import { GPT_PROVIDERS } from "@renderer/components";
|
||||
import { AISettingsProviderContext } from "@renderer/context";
|
||||
import { useContext, useState } from "react";
|
||||
|
||||
@@ -28,7 +28,7 @@ export const OpenaiSettings = () => {
|
||||
|
||||
const openAiConfigSchema = z.object({
|
||||
key: z.string().optional(),
|
||||
model: z.enum(LLM_PROVIDERS.openai.models),
|
||||
model: z.enum(GPT_PROVIDERS.openai.models),
|
||||
baseUrl: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -93,7 +93,7 @@ export const OpenaiSettings = () => {
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{(LLM_PROVIDERS.openai.models || []).map(
|
||||
{(GPT_PROVIDERS.openai.models || []).map(
|
||||
(option: string) => (
|
||||
<SelectItem key={option} value={option}>
|
||||
{option}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
AlertDialogTrigger,
|
||||
} from "@renderer/components/ui";
|
||||
import { useContext } from "react";
|
||||
import { AppSettingsProviderContext } from "../context";
|
||||
import { AppSettingsProviderContext } from "../../context";
|
||||
import { t } from "i18next";
|
||||
|
||||
export const ResetAllButton = (props: { children: React.ReactNode }) => {
|
||||
@@ -1,173 +0,0 @@
|
||||
import { t } from "i18next";
|
||||
import { MicIcon, LockIcon } from "lucide-react";
|
||||
import { useState, useEffect, useRef, useContext } from "react";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import RecordPlugin from "wavesurfer.js/dist/plugins/record";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
import { cn } from "@renderer/lib/utils";
|
||||
import { RadialProgress, toast } from "@renderer/components/ui";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import { useTranscribe } from "@renderer/hooks";
|
||||
|
||||
export const RecordButton = (props: {
|
||||
className?: string;
|
||||
disabled: boolean;
|
||||
onRecordBegin?: () => void;
|
||||
onRecordEnd: (blob: Blob, duration: number) => void;
|
||||
}) => {
|
||||
const { className, disabled, onRecordBegin, onRecordEnd } = props;
|
||||
const [isRecording, setIsRecording] = useState<boolean>(false);
|
||||
const [duration, setDuration] = useState<number>(0);
|
||||
const { EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
const [access, setAccess] = useState<boolean>(false);
|
||||
|
||||
const askForMediaAccess = () => {
|
||||
EnjoyApp.system.preferences.mediaAccess("microphone").then((access) => {
|
||||
if (access) {
|
||||
setAccess(true);
|
||||
} else {
|
||||
setAccess(false);
|
||||
toast.warning(t("noMicrophoneAccess"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useHotkeys(["command+alt+r", "control+alt+r"], () => {
|
||||
if (disabled) return;
|
||||
setIsRecording((isRecording) => !isRecording);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isRecording) return;
|
||||
|
||||
onRecordBegin?.();
|
||||
setDuration(0);
|
||||
const interval = setInterval(() => {
|
||||
setDuration((duration) => {
|
||||
if (duration >= 300) {
|
||||
setIsRecording(false);
|
||||
}
|
||||
return duration + 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [isRecording]);
|
||||
|
||||
useEffect(() => {
|
||||
askForMediaAccess();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
`shadow-lg w-full aspect-square rounded-full text-primary-foreground flex items-center justify-center relative ${
|
||||
isRecording
|
||||
? "bg-red-500 w-24 h-24"
|
||||
: " bg-primary opacity-80 w-20 h-20"
|
||||
}
|
||||
${disabled ? "cursor-not-allowed" : "hover:opacity-100 cursor-pointer"}
|
||||
`,
|
||||
className
|
||||
)}
|
||||
onClick={() => {
|
||||
if (disabled) return;
|
||||
if (access) {
|
||||
setIsRecording((isRecording) => !isRecording);
|
||||
} else {
|
||||
askForMediaAccess();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isRecording ? (
|
||||
<div className="w-full my-auto text-center">
|
||||
<span className="font-mono text-xl font-bold">{duration}</span>
|
||||
<RecordButtonPopover
|
||||
onRecordEnd={(blob, duration) => {
|
||||
if (duration > 1000) {
|
||||
onRecordEnd(blob, duration);
|
||||
} else {
|
||||
toast.warning(t("recordTooShort"));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<RadialProgress
|
||||
className="w-24 h-24 absolute top-0 left-0"
|
||||
progress={100 - Math.floor((duration / 300) * 100)}
|
||||
ringClassName="text-white"
|
||||
circleClassName="text-red-500"
|
||||
thickness={6}
|
||||
label=" "
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center space-x-4 h-10">
|
||||
<MicIcon className="w-10 h-10" />
|
||||
{!access && (
|
||||
<LockIcon className="w-4 h-4 absolute right-3 bottom-4" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const RecordButtonPopover = (props: {
|
||||
onRecordEnd: (blob: Blob, duration: number) => void;
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>();
|
||||
const { transcode } = useTranscribe();
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const ws = WaveSurfer.create({
|
||||
container: containerRef.current,
|
||||
waveColor: "#fff",
|
||||
height: 16,
|
||||
autoCenter: false,
|
||||
normalize: true,
|
||||
});
|
||||
|
||||
const record = ws.registerPlugin(RecordPlugin.create());
|
||||
let startAt = 0;
|
||||
|
||||
record.on("record-start", () => {
|
||||
startAt = Date.now();
|
||||
});
|
||||
|
||||
record.on("record-end", async (blob: Blob) => {
|
||||
const duration = Date.now() - startAt;
|
||||
try {
|
||||
const output = await transcode(blob);
|
||||
props.onRecordEnd(output, duration);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast.error(t("failedToSaveRecording"));
|
||||
}
|
||||
});
|
||||
|
||||
RecordPlugin.getAvailableAudioDevices()
|
||||
.then((devices) => devices.find((d) => d.kind === "audioinput"))
|
||||
.then((device) => {
|
||||
if (device) {
|
||||
record.startRecording({ deviceId: device.deviceId });
|
||||
} else {
|
||||
toast.error(t("cannotFindMicrophone"));
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
record.stopRecording();
|
||||
ws.destroy();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="hidden">
|
||||
<div ref={containerRef}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +1,2 @@
|
||||
export * from './users-rankings';
|
||||
export * from './user-card';
|
||||
13
enjoy/src/renderer/components/widgets/index.ts
Normal file
13
enjoy/src/renderer/components/widgets/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export * from "./db-state";
|
||||
|
||||
export * from "./layout";
|
||||
export * from "./sidebar";
|
||||
export * from "./page-placeholder";
|
||||
|
||||
export * from "./login-form";
|
||||
|
||||
export * from "./loader-spin";
|
||||
export * from "./no-records-found";
|
||||
|
||||
export * from "./selection-menu";
|
||||
export * from "./lookup-result";
|
||||
@@ -96,6 +96,7 @@ export const Sidebar = () => {
|
||||
tooltip={t("sidebar.aiAssistant")}
|
||||
active={activeTab.startsWith("/conversations")}
|
||||
Icon={BotIcon}
|
||||
testid="sidebar-conversations"
|
||||
/>
|
||||
|
||||
<SidebarItem
|
||||
@@ -224,8 +225,9 @@ const SidebarItem = (props: {
|
||||
tooltip: string;
|
||||
active: boolean;
|
||||
Icon: LucideIcon;
|
||||
testid?: string;
|
||||
}) => {
|
||||
const { href, label, tooltip, active, Icon } = props;
|
||||
const { href, label, tooltip, active, Icon, testid } = props;
|
||||
|
||||
return (
|
||||
<Link
|
||||
@@ -233,6 +235,7 @@ const SidebarItem = (props: {
|
||||
data-tooltip-id="global-tooltip"
|
||||
data-tooltip-content={tooltip}
|
||||
data-tooltip-place="right"
|
||||
data-testid={testid}
|
||||
className="block px-1 xl:px-2"
|
||||
>
|
||||
<Button
|
||||
Reference in New Issue
Block a user