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:
an-lee
2024-05-14 20:37:51 +08:00
committed by GitHub
parent e5f682c6c5
commit 49dabc89a3
13 changed files with 379 additions and 198 deletions

View File

@@ -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

View File

@@ -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"],
},
};

View File

@@ -14,4 +14,4 @@ export const TTS_PROVIDERS: { [key: string]: any } = {
voices: ["alloy", "echo", "fable", "onyx", "nova", "shimmer"],
configurable: ["model", "voice", "baseUrl"],
},
};
};

View File

@@ -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) {

View File

@@ -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>
)}

View File

@@ -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>