Update constants from api (#607)
* fix caption ipa display * fetch gpt/tts providers from API * fetch remote gpt presets * update constants * fix conversavtion save * refactor ipa convert * fetch ipa mapping from api * fix ipa mark * fix constant * validate camdict pron audio src
This commit is contained in:
@@ -36,7 +36,11 @@ import {
|
||||
} from "@renderer/context";
|
||||
import { LoaderIcon } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { GPT_PROVIDERS, TTS_PROVIDERS, GPTShareButton } from "@renderer/components";
|
||||
import {
|
||||
GPT_PROVIDERS,
|
||||
TTS_PROVIDERS,
|
||||
GPTShareButton,
|
||||
} from "@renderer/components";
|
||||
|
||||
const conversationFormSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
@@ -73,12 +77,22 @@ export const ConversationForm = (props: {
|
||||
}) => {
|
||||
const { conversation, onFinish } = props;
|
||||
const [submitting, setSubmitting] = useState<boolean>(false);
|
||||
const [providers, setProviders] = useState<any>(GPT_PROVIDERS);
|
||||
const { EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
const [gptProviders, setGptProviders] = useState<any>(GPT_PROVIDERS);
|
||||
const [ttsProviders, setTtsProviders] = useState<any>(TTS_PROVIDERS);
|
||||
const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext);
|
||||
const { openai } = useContext(AISettingsProviderContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const refreshProviders = async () => {
|
||||
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(
|
||||
@@ -87,7 +101,8 @@ export const ConversationForm = (props: {
|
||||
} catch (e) {
|
||||
console.warn(`No ollama server found: ${e.message}`);
|
||||
}
|
||||
setProviders({ ...providers });
|
||||
|
||||
setGptProviders({ ...providers });
|
||||
};
|
||||
|
||||
const destroyConversation = async () => {
|
||||
@@ -98,8 +113,22 @@ export const ConversationForm = (props: {
|
||||
});
|
||||
};
|
||||
|
||||
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(() => {
|
||||
refreshProviders();
|
||||
refreshGptProviders();
|
||||
refreshTtsProviders();
|
||||
}, []);
|
||||
|
||||
const defaultConfig = JSON.parse(JSON.stringify(conversation || {}));
|
||||
@@ -116,7 +145,7 @@ export const ConversationForm = (props: {
|
||||
}
|
||||
|
||||
if (defaultConfig.configuration.tts?.engine === "openai" && openai) {
|
||||
if (!defaultConfig.configuration.tts.baseUrl) {
|
||||
if (!defaultConfig.configuration.tts?.baseUrl) {
|
||||
defaultConfig.configuration.tts.baseUrl = openai.baseUrl;
|
||||
}
|
||||
}
|
||||
@@ -165,7 +194,8 @@ export const ConversationForm = (props: {
|
||||
}
|
||||
|
||||
// use default base url if not set
|
||||
if (!configuration.tts.baseUrl) {
|
||||
if (!configuration?.tts?.baseUrl) {
|
||||
configuration.tts ||= {};
|
||||
configuration.tts.baseUrl = GPT_PROVIDERS[engine]?.baseUrl;
|
||||
}
|
||||
|
||||
@@ -273,21 +303,15 @@ export const ConversationForm = (props: {
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.keys(providers)
|
||||
.filter((key) =>
|
||||
GPT_PROVIDERS[key].types.includes(
|
||||
form.watch("configuration.type")
|
||||
)
|
||||
)
|
||||
.map((key) => (
|
||||
<SelectItem key={key} value={key}>
|
||||
{providers[key].name}
|
||||
</SelectItem>
|
||||
))}
|
||||
{Object.keys(gptProviders).map((key) => (
|
||||
<SelectItem key={key} value={key}>
|
||||
{gptProviders[key].name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormDescription>
|
||||
{providers[form.watch("engine")]?.description}
|
||||
{gptProviders[form.watch("engine")]?.description}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -309,13 +333,13 @@ export const ConversationForm = (props: {
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{(providers[form.watch("engine")]?.models || []).map(
|
||||
(option: string) => (
|
||||
<SelectItem key={option} value={option}>
|
||||
{option}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
{(
|
||||
gptProviders[form.watch("engine")]?.models || []
|
||||
).map((option: string) => (
|
||||
<SelectItem key={option} value={option}>
|
||||
{option}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
@@ -573,9 +597,9 @@ export const ConversationForm = (props: {
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.keys(TTS_PROVIDERS).map((key) => (
|
||||
{Object.keys(ttsProviders).map((key) => (
|
||||
<SelectItem key={key} value={key}>
|
||||
{TTS_PROVIDERS[key].name}
|
||||
{ttsProviders[key].name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -585,7 +609,7 @@ export const ConversationForm = (props: {
|
||||
)}
|
||||
/>
|
||||
|
||||
{TTS_PROVIDERS[
|
||||
{ttsProviders[
|
||||
form.watch("configuration.tts.engine")
|
||||
]?.configurable.includes("model") && (
|
||||
<FormField
|
||||
@@ -606,7 +630,7 @@ export const ConversationForm = (props: {
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{(
|
||||
TTS_PROVIDERS[form.watch("configuration.tts.engine")]
|
||||
ttsProviders[form.watch("configuration.tts.engine")]
|
||||
?.models || []
|
||||
).map((model: string) => (
|
||||
<SelectItem key={model} value={model}>
|
||||
@@ -621,7 +645,7 @@ export const ConversationForm = (props: {
|
||||
/>
|
||||
)}
|
||||
|
||||
{TTS_PROVIDERS[
|
||||
{ttsProviders[
|
||||
form.watch("configuration.tts.engine")
|
||||
]?.configurable.includes("voice") && (
|
||||
<FormField
|
||||
@@ -642,7 +666,7 @@ export const ConversationForm = (props: {
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{(
|
||||
TTS_PROVIDERS[form.watch("configuration.tts.engine")]
|
||||
ttsProviders[form.watch("configuration.tts.engine")]
|
||||
?.voices || []
|
||||
).map((voice: string) => (
|
||||
<SelectItem key={voice} value={voice}>
|
||||
@@ -657,7 +681,7 @@ export const ConversationForm = (props: {
|
||||
/>
|
||||
)}
|
||||
|
||||
{TTS_PROVIDERS[
|
||||
{ttsProviders[
|
||||
form.watch("configuration.tts.engine")
|
||||
]?.configurable.includes("baseUrl") && (
|
||||
<FormField
|
||||
|
||||
@@ -4,21 +4,15 @@ 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-4o",
|
||||
"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",
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-instruct",
|
||||
],
|
||||
configurable: [
|
||||
"model",
|
||||
@@ -31,27 +25,20 @@ export const GPT_PROVIDERS: { [key: string]: any } = {
|
||||
"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-4o",
|
||||
"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",
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-instruct",
|
||||
],
|
||||
configurable: [
|
||||
"model",
|
||||
@@ -65,7 +52,6 @@ export const GPT_PROVIDERS: { [key: string]: any } = {
|
||||
"historyBufferSize",
|
||||
"tts",
|
||||
],
|
||||
types: ["gpt", "tts"],
|
||||
},
|
||||
googleGenerativeAi: {
|
||||
name: "Google Generative AI",
|
||||
@@ -78,7 +64,6 @@ export const GPT_PROVIDERS: { [key: string]: any } = {
|
||||
"historyBufferSize",
|
||||
"tts",
|
||||
],
|
||||
types: ["gpt"],
|
||||
},
|
||||
ollama: {
|
||||
name: "Ollama",
|
||||
@@ -96,6 +81,5 @@ export const GPT_PROVIDERS: { [key: string]: any } = {
|
||||
"presencePenalty",
|
||||
"tts",
|
||||
],
|
||||
types: ["gpt"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -14,4 +14,4 @@ export const TTS_PROVIDERS: { [key: string]: any } = {
|
||||
voices: ["alloy", "echo", "fable", "onyx", "nova", "shimmer"],
|
||||
configurable: ["model", "voice", "baseUrl"],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
Timeline,
|
||||
TimelineEntry,
|
||||
} from "echogarden/dist/utilities/Timeline.d.js";
|
||||
import { convertIpaToNormal } from "@/utils";
|
||||
import { convertWordIpaToNormal } from "@/utils";
|
||||
import { useCopyToClipboard } from "@uidotdev/usehooks";
|
||||
import { MediaCaptionTabs } from "./media-captions";
|
||||
|
||||
@@ -37,6 +37,7 @@ export const MediaCaption = () => {
|
||||
editingRegion,
|
||||
setEditingRegion,
|
||||
setTranscriptionDraft,
|
||||
ipaMappings,
|
||||
} = useContext(MediaPlayerProviderContext);
|
||||
const { EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
const [activeIndex, setActiveIndex] = useState<number>(0);
|
||||
@@ -411,12 +412,12 @@ export const MediaCaption = () => {
|
||||
if (displayIpa) {
|
||||
const text = caption.timeline
|
||||
.map((word) => {
|
||||
const ipa = word.timeline
|
||||
.map((t) =>
|
||||
t.timeline.map((s) => convertIpaToNormal(s.text)).join("")
|
||||
)
|
||||
.join(" · ");
|
||||
return `${word.text}(${ipa})`;
|
||||
const ipas = word.timeline.map((t) =>
|
||||
t.timeline.map((s) => s.text).join("")
|
||||
);
|
||||
return `${word.text}(${convertWordIpaToNormal(ipas, {
|
||||
mappings: ipaMappings,
|
||||
}).join("")})`;
|
||||
})
|
||||
.join(" ");
|
||||
|
||||
@@ -475,13 +476,18 @@ const Caption = (props: {
|
||||
onClick,
|
||||
} = props;
|
||||
|
||||
const { currentNotes } = useContext(MediaPlayerProviderContext);
|
||||
const { currentNotes, ipaMappings } = useContext(MediaPlayerProviderContext);
|
||||
const notes = currentNotes.filter((note) => note.parameters?.quoteIndices);
|
||||
const [notedquoteIndices, setNotedquoteIndices] = useState<number[]>([]);
|
||||
|
||||
let words = caption.text.split(" ");
|
||||
const ipas = caption.timeline.map((w) =>
|
||||
w.timeline.map((t) => t.timeline.map((s) => s.text))
|
||||
w.timeline.map((t) =>
|
||||
convertWordIpaToNormal(
|
||||
t.timeline.map((s) => s.text),
|
||||
{ mappings: ipaMappings }
|
||||
).join("")
|
||||
)
|
||||
);
|
||||
|
||||
if (words.length !== caption.timeline.length) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { MediaPlayerProviderContext } from "@renderer/context";
|
||||
import { TabsContent, Separator } from "@renderer/components/ui";
|
||||
import { t } from "i18next";
|
||||
import { TimelineEntry } from "echogarden/dist/utilities/Timeline";
|
||||
import { convertIpaToNormal } from "@/utils";
|
||||
import { convertWordIpaToNormal } from "@/utils";
|
||||
import {
|
||||
CamdictLookupResult,
|
||||
AiLookupResult,
|
||||
@@ -37,7 +37,7 @@ const SelectedWords = (props: {
|
||||
}) => {
|
||||
const { selectedIndices, caption } = props;
|
||||
|
||||
const { transcription } = useContext(MediaPlayerProviderContext);
|
||||
const { transcription, ipaMappings } = useContext(MediaPlayerProviderContext);
|
||||
|
||||
const word = selectedIndices
|
||||
.map((index) => caption.timeline[index]?.text || "")
|
||||
@@ -76,11 +76,14 @@ const SelectedWords = (props: {
|
||||
>
|
||||
{word.timeline
|
||||
.map((t) =>
|
||||
t.timeline
|
||||
.map((s) => convertIpaToNormal(s.text))
|
||||
.join("")
|
||||
convertWordIpaToNormal(
|
||||
t.timeline.map((s) => s.text),
|
||||
{
|
||||
mappings: ipaMappings,
|
||||
}
|
||||
).join("")
|
||||
)
|
||||
.join("")}
|
||||
.join(" ")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -238,28 +238,19 @@ export const CamdictLookupResult = (props: { word: string }) => {
|
||||
<span className="text-sm font-code">
|
||||
/{pron.pronunciation}/
|
||||
</span>
|
||||
{pron.audio && (
|
||||
{pron.audio && pron.audio.match(/\.mp3/i) && (
|
||||
<div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="rounded-full p-0 w-6 h-6"
|
||||
onClick={() => {
|
||||
const audio = document.getElementById(
|
||||
`${posItem.type}-${pron.region}`
|
||||
) as HTMLAudioElement;
|
||||
if (audio) {
|
||||
audio.play();
|
||||
}
|
||||
const audio = new Audio(pron.audio);
|
||||
audio.play();
|
||||
}}
|
||||
>
|
||||
<Volume2Icon className="w-4 h-4" />
|
||||
</Button>
|
||||
<audio
|
||||
className="hidden"
|
||||
id={`${posItem.type}-${pron.region}`}
|
||||
src={pron.audio}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user