diff --git a/apps/desktop/src/components/welcome-modal/custom-endpoint-view.tsx b/apps/desktop/src/components/welcome-modal/custom-endpoint-view.tsx new file mode 100644 index 0000000000..4919f38e70 --- /dev/null +++ b/apps/desktop/src/components/welcome-modal/custom-endpoint-view.tsx @@ -0,0 +1,605 @@ +import { Trans } from "@lingui/react/macro"; +import { useEffect, useState } from "react"; + +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@hypr/ui/components/ui/form"; +import { Input } from "@hypr/ui/components/ui/input"; +import PushableButton from "@hypr/ui/components/ui/pushable-button"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@hypr/ui/components/ui/select"; +import { cn } from "@hypr/ui/lib/utils"; +import { useQuery } from "@tanstack/react-query"; +import { fetch as tauriFetch } from "@tauri-apps/plugin-http"; +import useDebouncedCallback from "beautiful-react-hooks/useDebouncedCallback"; +import { UseFormReturn } from "react-hook-form"; +import { ConfigureEndpointConfig } from "../settings/components/ai/shared"; + +const openaiModels = [ + "gpt-4o", + "gpt-4o-mini", + "gpt-4.1-nano", + "gpt-4.1", + "chatgpt-4o-latest", +]; + +const geminiModels = [ + "gemini-2.5-pro", + "gemini-1.5-pro", + "gemini-1.5-flash", +]; + +const openrouterModels = [ + "x-ai/grok-4", + "openai/gpt-4o-mini", + "openai/gpt-4o", + "openai/gpt-4.1-nano", + "openai/chatgpt-4o-latest", + "anthropic/claude-sonnet-4", + "moonshotai/kimi-k2", + "mistralai/mistral-small-3.2-24b-instruct", +]; + +interface CustomEndpointViewProps { + onContinue: () => void; + configureCustomEndpoint: (config: ConfigureEndpointConfig) => void; + openaiForm: UseFormReturn<{ api_key: string; model: string }>; + geminiForm: UseFormReturn<{ api_key: string; model: string }>; + openrouterForm: UseFormReturn<{ api_key: string; model: string }>; + customForm: UseFormReturn<{ api_base: string; api_key?: string; model: string }>; +} + +export function CustomEndpointView({ + onContinue, + configureCustomEndpoint, + openaiForm, + geminiForm, + openrouterForm, + customForm, +}: CustomEndpointViewProps) { + const [selectedProvider, setSelectedProvider] = useState<"openai" | "gemini" | "openrouter" | "others" | null>(null); + const [isConfigured, setIsConfigured] = useState(false); + + // Watch forms and submit when complete and valid + useEffect(() => { + const subscription = openaiForm.watch((values) => { + if (selectedProvider === "openai" && values.api_key && values.api_key.startsWith("sk-") && values.model) { + configureCustomEndpoint({ + provider: "openai", + api_base: "", + api_key: values.api_key, + model: values.model, + }); + setIsConfigured(true); + } + }); + return () => subscription.unsubscribe(); + }, [openaiForm, configureCustomEndpoint, selectedProvider]); + + useEffect(() => { + const subscription = geminiForm.watch((values) => { + if (selectedProvider === "gemini" && values.api_key && values.api_key.startsWith("AIza") && values.model) { + configureCustomEndpoint({ + provider: "gemini", + api_base: "", + api_key: values.api_key, + model: values.model, + }); + setIsConfigured(true); + } + }); + return () => subscription.unsubscribe(); + }, [geminiForm, configureCustomEndpoint, selectedProvider]); + + useEffect(() => { + const subscription = openrouterForm.watch((values) => { + if (selectedProvider === "openrouter" && values.api_key && values.api_key.startsWith("sk-") && values.model) { + configureCustomEndpoint({ + provider: "openrouter", + api_base: "", + api_key: values.api_key, + model: values.model, + }); + setIsConfigured(true); + } + }); + return () => subscription.unsubscribe(); + }, [openrouterForm, configureCustomEndpoint, selectedProvider]); + + useEffect(() => { + const subscription = customForm.watch((values) => { + if (selectedProvider === "others" && values.api_base && values.model) { + try { + new URL(values.api_base); + configureCustomEndpoint({ + provider: "others", + api_base: values.api_base, + api_key: values.api_key, + model: values.model, + }); + setIsConfigured(true); + } catch { + setIsConfigured(false); + } + } + }); + return () => subscription.unsubscribe(); + }, [customForm, configureCustomEndpoint, selectedProvider]); + + // temporary fix for fetching models smoothly + const [debouncedApiBase, setDebouncedApiBase] = useState(""); + const [debouncedApiKey, setDebouncedApiKey] = useState(""); + + const updateDebouncedValues = useDebouncedCallback( + (apiBase: string, apiKey: string) => { + setDebouncedApiBase(apiBase); + setDebouncedApiKey(apiKey); + }, + [], + 2000, + ); + + // Watch for form changes + useEffect(() => { + const apiBase = customForm.watch("api_base"); + const apiKey = customForm.watch("api_key"); + + updateDebouncedValues(apiBase || "", apiKey || ""); + }, [customForm.watch("api_base"), customForm.watch("api_key"), updateDebouncedValues]); + + const isLocalEndpoint = () => { + const apiBase = customForm.watch("api_base"); + return apiBase?.includes("localhost") || apiBase?.includes("127.0.0.1"); + }; + + const othersModels = useQuery({ + queryKey: ["others-direct-models", debouncedApiBase, debouncedApiKey?.slice(0, 8)], + queryFn: async (): Promise => { + const apiBase = debouncedApiBase; + const apiKey = debouncedApiKey; + + const url = new URL(apiBase); + url.pathname += url.pathname.endsWith("/") ? "models" : "/models"; + + const headers: Record = { + "Content-Type": "application/json", + }; + + if (apiKey && apiKey.trim().length > 0) { + headers["Authorization"] = `Bearer ${apiKey}`; + } + + const response = await tauriFetch(url.toString(), { + method: "GET", + headers, + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + + if (!data.data || !Array.isArray(data.data)) { + throw new Error("Invalid response format"); + } + + const models = data.data + .map((model: any) => model.id) + .filter((id: string) => { + const excludeKeywords = ["dall-e", "codex", "whisper"]; + return !excludeKeywords.some(keyword => id.includes(keyword)); + }); + + return models; + }, + enabled: (() => { + const isLocal = debouncedApiBase?.includes("localhost") || debouncedApiBase?.includes("127.0.0.1"); + + try { + return Boolean(debouncedApiBase && new URL(debouncedApiBase) && (isLocal || debouncedApiKey)); + } catch { + return false; + } + })(), + retry: 1, + refetchInterval: false, + }); + + const handleProviderClick = (provider: "openai" | "gemini" | "openrouter" | "others") => { + setSelectedProvider(provider); + setIsConfigured(false); + }; + + return ( +
+

+ Configure Your LLM +

+ +
+ {/* Provider Selection Pills */} +
+ + + + +
+ + {/* Form Container */} +
+ {!selectedProvider && ( +
+

+ Select a provider above to configure +

+
+ )} + + {/* OpenAI Form */} + {selectedProvider === "openai" && ( +
+
+ + + + + OpenAI +
+
+ + ( + + + API Key + + + + + + + )} + /> + + ( + + + Model + + + + + + + )} + /> + + +
+ )} + + {/* Gemini Form */} + {selectedProvider === "gemini" && ( +
+
+ + + + + Google Gemini +
+
+ + ( + + + API Key + + + + + + + )} + /> + + ( + + + Model + + + + + + + )} + /> + + +
+ )} + + {/* OpenRouter Form */} + {selectedProvider === "openrouter" && ( +
+
+ + OpenRouter + + + + OpenRouter +
+
+ + ( + + + API Key + + + + + + + )} + /> + + ( + + + Model + + + + + + + )} + /> + + +
+ )} + + {/* Others Form */} + {selectedProvider === "others" && ( +
+
+ + ( + + + API Base URL + + + + + + + )} + /> + + ( + + + API Key + {customForm.watch("api_base") && isLocalEndpoint() && ( + + (Optional) + + )} + + + + + + + )} + /> + + ( + + + Model Name + + + {othersModels.isLoading && !field.value + ? ( +
+ Loading models... +
+ ) + : othersModels.data && othersModels.data.length > 0 + ? ( + + ) + : ( + + )} +
+ +
+ )} + /> + + +
+ )} +
+
+ + + Continue + + + {!isConfigured && selectedProvider && ( +

+ Complete the configuration to continue +

+ )} +
+ ); +} diff --git a/apps/desktop/src/components/welcome-modal/download-progress-view.tsx b/apps/desktop/src/components/welcome-modal/download-progress-view.tsx index 6859f05233..f56f2e02d1 100644 --- a/apps/desktop/src/components/welcome-modal/download-progress-view.tsx +++ b/apps/desktop/src/components/welcome-modal/download-progress-view.tsx @@ -19,6 +19,7 @@ interface ModelDownloadProgress { interface DownloadProgressViewProps { selectedSttModel: SupportedModel; + llmSelection: "hyprllm" | "byom" | null; onContinue: () => void; } @@ -86,6 +87,7 @@ const ModelProgressCard = ({ export const DownloadProgressView = ({ selectedSttModel, + llmSelection, onContinue, }: DownloadProgressViewProps) => { const [sttDownload, setSttDownload] = useState({ @@ -107,7 +109,11 @@ export const DownloadProgressView = ({ useEffect(() => { localSttCommands.downloadModel(selectedSttModel, sttDownload.channel); - localLlmCommands.downloadModel("HyprLLM", llmDownload.channel); + if (llmSelection === "hyprllm") { + localLlmCommands.downloadModel("HyprLLM", llmDownload.channel); + } else { + setLlmDownload(prev => ({ ...prev, completed: true })); + } sttDownload.channel.onmessage = (progress) => { if (progress < 0) { @@ -122,19 +128,21 @@ export const DownloadProgressView = ({ })); }; - llmDownload.channel.onmessage = (progress) => { - if (progress < 0) { - setLlmDownload(prev => ({ ...prev, error: true })); - return; - } + if (llmSelection === "hyprllm") { + llmDownload.channel.onmessage = (progress) => { + if (progress < 0) { + setLlmDownload(prev => ({ ...prev, error: true })); + return; + } - setLlmDownload(prev => ({ - ...prev, - progress: Math.max(prev.progress, progress), - completed: progress >= 100, - })); - }; - }, [selectedSttModel, sttDownload.channel, llmDownload.channel]); + setLlmDownload(prev => ({ + ...prev, + progress: Math.max(prev.progress, progress), + completed: progress >= 100, + })); + }; + } + }, [selectedSttModel, sttDownload.channel, llmDownload.channel, llmSelection]); const bothCompleted = sttDownload.completed && llmDownload.completed; const hasErrors = sttDownload.error || llmDownload.error; @@ -174,7 +182,7 @@ export const DownloadProgressView = ({ }; const handleLlmCompletion = async () => { - if (llmDownload.completed) { + if (llmDownload.completed && llmSelection === "hyprllm") { try { await localLlmCommands.setCurrentModel("HyprLLM"); await localLlmCommands.startServer(); @@ -186,7 +194,7 @@ export const DownloadProgressView = ({ handleSttCompletion(); handleLlmCompletion(); - }, [sttDownload.completed, llmDownload.completed, selectedSttModel]); + }, [sttDownload.completed, llmDownload.completed, selectedSttModel, llmSelection]); const sttMetadata = sttModelMetadata[selectedSttModel]; @@ -233,12 +241,14 @@ export const DownloadProgressView = ({ size={sttMetadata?.size || "250MB"} /> - + {llmSelection === "hyprllm" && ( + + )} void; } +// Form schemas +const openaiSchema = z.object({ + api_key: z.string().min(1, "API key is required").startsWith("sk-", "OpenAI API key must start with 'sk-'"), + model: z.string().min(1, "Model selection is required"), +}); + +const geminiSchema = z.object({ + api_key: z.string().min(1, "API key is required").startsWith("AIza", "Gemini API key must start with 'AIza'"), + model: z.string().min(1, "Model selection is required"), +}); + +const openrouterSchema = z.object({ + api_key: z.string().min(1, "API key is required").startsWith("sk-", "OpenRouter API key must start with 'sk-'"), + model: z.string().min(1, "Model selection is required"), +}); + +const customSchema = z.object({ + api_base: z.string().url("Must be a valid URL"), + api_key: z.string().optional(), + model: z.string().min(1, "Model is required"), +}); + export function WelcomeModal({ isOpen, onClose }: WelcomeModalProps) { const navigate = useNavigate(); const queryClient = useQueryClient(); @@ -37,15 +68,98 @@ export function WelcomeModal({ isOpen, onClose }: WelcomeModalProps) { | "model-selection" | "download-progress" | "audio-permissions" + | "llm-selection" + | "custom-endpoint" | "language-selection" >("welcome"); const [selectedSttModel, setSelectedSttModel] = useState("QuantizedSmall"); const [wentThroughDownloads, setWentThroughDownloads] = useState(false); + const [llmSelection, setLlmSelection] = useState<"hyprllm" | "byom" | null>(null); + const [cameFromLlmSelection, setCameFromLlmSelection] = useState(false); const selectSTTModel = useMutation({ mutationFn: (model: SupportedModel) => localSttCommands.setCurrentModel(model), }); + const openaiForm = useForm<{ api_key: string; model: string }>({ + resolver: zodResolver(openaiSchema), + mode: "onChange", + defaultValues: { + api_key: "", + model: "", + }, + }); + + const geminiForm = useForm<{ api_key: string; model: string }>({ + resolver: zodResolver(geminiSchema), + mode: "onChange", + defaultValues: { + api_key: "", + model: "", + }, + }); + + const openrouterForm = useForm<{ api_key: string; model: string }>({ + resolver: zodResolver(openrouterSchema), + mode: "onChange", + defaultValues: { + api_key: "", + model: "", + }, + }); + + const customForm = useForm<{ api_base: string; api_key?: string; model: string }>({ + resolver: zodResolver(customSchema), + mode: "onChange", + defaultValues: { + api_base: "", + api_key: "", + model: "", + }, + }); + + const configureCustomEndpoint = async (config: ConfigureEndpointConfig) => { + const finalApiBase = config.provider === "openai" + ? "https://api.openai.com/v1" + : config.provider === "gemini" + ? "https://generativelanguage.googleapis.com/v1beta/openai" + : config.provider === "openrouter" + ? "https://openrouter.ai/api/v1" + : config.api_base; + + try { + await connectorCommands.setCustomLlmEnabled(true); + + await connectorCommands.setProviderSource(config.provider); + + await connectorCommands.setCustomLlmModel(config.model); + + await connectorCommands.setCustomLlmConnection({ + api_base: finalApiBase, + api_key: config.api_key || null, + }); + + if (config.provider === "openai" && config.api_key) { + await connectorCommands.setOpenaiApiKey(config.api_key); + await connectorCommands.setOpenaiModel(config.model); + } else if (config.provider === "gemini" && config.api_key) { + await connectorCommands.setGeminiApiKey(config.api_key); + await connectorCommands.setGeminiModel(config.model); + } else if (config.provider === "openrouter" && config.api_key) { + await connectorCommands.setOpenrouterApiKey(config.api_key); + await connectorCommands.setOpenrouterModel(config.model); + } else if (config.provider === "others") { + await connectorCommands.setOthersApiBase(config.api_base); + if (config.api_key) { + await connectorCommands.setOthersApiKey(config.api_key); + } + await connectorCommands.setOthersModel(config.model); + } + } catch (error) { + console.error("Failed to configure custom endpoint:", error); + } + }; + useEffect(() => { let cleanup: (() => void) | undefined; let unlisten: (() => void) | undefined; @@ -117,6 +231,15 @@ export function WelcomeModal({ isOpen, onClose }: WelcomeModalProps) { } }, [currentStep, userId]); + useEffect(() => { + if (currentStep === "llm-selection" && userId) { + analyticsCommands.event({ + event: "onboarding_reached_llm_selection", + distinct_id: userId, + }); + } + }, [currentStep, userId]); + useEffect(() => { if (currentStep === "language-selection" && userId) { analyticsCommands.event({ @@ -143,6 +266,22 @@ export function WelcomeModal({ isOpen, onClose }: WelcomeModalProps) { }; const handleAudioPermissionsContinue = () => { + setCurrentStep("llm-selection"); + }; + + const handleLLMSelectionContinue = (selection: "hyprllm" | "byom") => { + setLlmSelection(selection); + if (selection === "hyprllm") { + setCameFromLlmSelection(true); + setCurrentStep("model-selection"); + } else { + setCameFromLlmSelection(false); + setCurrentStep("custom-endpoint"); + } + }; + + const handleCustomEndpointContinue = () => { + setCameFromLlmSelection(false); setCurrentStep("model-selection"); }; @@ -167,19 +306,22 @@ export function WelcomeModal({ isOpen, onClose }: WelcomeModalProps) { useEffect(() => { if (!isOpen && wentThroughDownloads) { localSttCommands.startServer(); + localLlmCommands.startServer(); const checkAndShowToasts = async () => { try { const sttModelExists = await localSttCommands.isModelDownloaded(selectedSttModel as SupportedModel); - const llmModelExists = await localLlmCommands.isModelDownloaded("HyprLLM"); if (!sttModelExists) { showSttModelDownloadToast(selectedSttModel, undefined, queryClient); } - if (!llmModelExists) { - showLlmModelDownloadToast("HyprLLM", undefined, queryClient); + if (llmSelection === "hyprllm") { + const llmModelExists = await localLlmCommands.isModelDownloaded("HyprLLM"); + if (!llmModelExists) { + showLlmModelDownloadToast("HyprLLM", undefined, queryClient); + } } } catch (error) { console.error("Error checking model download status:", error); @@ -188,7 +330,7 @@ export function WelcomeModal({ isOpen, onClose }: WelcomeModalProps) { checkAndShowToasts(); } - }, [isOpen, wentThroughDownloads, selectedSttModel, queryClient]); + }, [isOpen, wentThroughDownloads, selectedSttModel, llmSelection, queryClient]); return ( + {/* Back button for custom-endpoint */} + {currentStep === "custom-endpoint" && ( + + )} + + {/* Back button for model-selection (only when coming from llm-selection) */} + {currentStep === "model-selection" && cameFromLlmSelection && ( + + )} +
{currentStep === "welcome" && ( )} @@ -222,6 +387,21 @@ export function WelcomeModal({ isOpen, onClose }: WelcomeModalProps) { onContinue={handleAudioPermissionsContinue} /> )} + {currentStep === "llm-selection" && ( + + )} + {currentStep === "custom-endpoint" && ( + + )} {currentStep === "language-selection" && ( void; +} + +export function LLMSelectionView({ onContinue }: LLMSelectionViewProps) { + const [selected, setSelected] = useState<"hyprllm" | "byom" | null>(null); + + const handleContinue = () => { + if (selected) { + onContinue(selected); + } + }; + + const options = [ + { + id: "hyprllm", + title: "HyprLLM (Local)", + subtitle: "Privacy matters more than anything to me", + icon: ShieldIcon, + }, + { + id: "byom", + title: "Bring Your Own Model", + subtitle: "I want first-in-class meeting summarization", + icon: Network, + }, + ] as const; + + return ( +
+

+ Choose Your LLM +

+ +

+ Select how you want to process your meeting notes +

+ +
+
+ {options.map((option) => { + const isSelected = selected === option.id; + const Icon = option.icon; + + return ( +
+
+ setSelected(option.id)} + > + +
+
+ +
+
{option.title}
+
{option.subtitle}
+
+
+
+
+
+ ); + })} +
+
+ + + Continue + + + {!selected && ( +

+ Select an option to continue +

+ )} +
+ ); +} diff --git a/apps/desktop/src/components/welcome-modal/model-selection-view.tsx b/apps/desktop/src/components/welcome-modal/model-selection-view.tsx index 8e60ced466..a38c69c94e 100644 --- a/apps/desktop/src/components/welcome-modal/model-selection-view.tsx +++ b/apps/desktop/src/components/welcome-modal/model-selection-view.tsx @@ -70,7 +70,7 @@ export const ModelSelectionView = ({ return (

- Select a transcribing model + Select a transcribing model (STT)

diff --git a/apps/desktop/src/locales/en/messages.po b/apps/desktop/src/locales/en/messages.po index bb2cc9ae4d..e081f2465b 100644 --- a/apps/desktop/src/locales/en/messages.po +++ b/apps/desktop/src/locales/en/messages.po @@ -260,6 +260,10 @@ msgstr "(Beta) Upcoming meeting notifications" msgid "(Optional for localhost)" msgstr "(Optional for localhost)" +#: src/components/welcome-modal/custom-endpoint-view.tsx:521 +msgid "(Optional)" +msgstr "(Optional)" + #. placeholder {0}: isViewingTemplate ? "Back" : "Save and close" #. placeholder {0}: lang.language #. placeholder {0}: disabled ? "Wait..." : isHovered ? "Resume" : "Ended" @@ -365,7 +369,7 @@ msgstr "AI" #~ msgid "AI notepad for meetings" #~ msgstr "AI notepad for meetings" -#: src/components/welcome-modal/download-progress-view.tsx:216 +#: src/components/welcome-modal/download-progress-view.tsx:224 msgid "All models ready!" msgstr "All models ready!" @@ -386,10 +390,15 @@ msgstr "and {0} more members" msgid "Anyone with the link can view this page" msgstr "Anyone with the link can view this page" +#: src/components/welcome-modal/custom-endpoint-view.tsx:498 #: src/components/settings/components/ai/llm-custom-view.tsx:576 msgid "API Base URL" msgstr "API Base URL" +#: src/components/welcome-modal/custom-endpoint-view.tsx:294 +#: src/components/welcome-modal/custom-endpoint-view.tsx:361 +#: src/components/welcome-modal/custom-endpoint-view.tsx:438 +#: src/components/welcome-modal/custom-endpoint-view.tsx:518 #: src/components/settings/views/integrations.tsx:197 #: src/components/settings/components/ai/llm-custom-view.tsx:284 #: src/components/settings/components/ai/llm-custom-view.tsx:380 @@ -427,6 +436,11 @@ msgstr "Audio Permissions" msgid "Autonomy Selector" msgstr "Autonomy Selector" +#: src/components/welcome-modal/index.tsx:351 +#: src/components/welcome-modal/index.tsx:362 +msgid "Back" +msgstr "Back" + #: src/components/settings/views/integrations.tsx:246 msgid "Base Folder" msgstr "Base Folder" @@ -504,6 +518,10 @@ msgstr "Choose the languages you speak for better transcription accuracy" msgid "Choose whether to save your recordings locally." msgstr "Choose whether to save your recordings locally." +#: src/components/welcome-modal/llm-selection-view.tsx:40 +msgid "Choose Your LLM" +msgstr "Choose Your LLM" + #: src/components/settings/views/general.tsx:230 #~ msgid "Choose your preferred language of use" #~ msgstr "Choose your preferred language of use" @@ -528,6 +546,14 @@ msgstr "Company description" msgid "Company name" msgstr "Company name" +#: src/components/welcome-modal/custom-endpoint-view.tsx:600 +msgid "Complete the configuration to continue" +msgstr "Complete the configuration to continue" + +#: src/components/welcome-modal/custom-endpoint-view.tsx:214 +msgid "Configure Your LLM" +msgstr "Configure Your LLM" + #: src/components/settings/components/calendar/cloud-calendar-integration-details.tsx:63 #~ msgid "Connect" #~ msgstr "Connect" @@ -569,7 +595,9 @@ msgid "Contacts Access" msgstr "Contacts Access" #: src/components/welcome-modal/model-selection-view.tsx:137 -#: src/components/welcome-modal/download-progress-view.tsx:248 +#: src/components/welcome-modal/llm-selection-view.tsx:94 +#: src/components/welcome-modal/download-progress-view.tsx:258 +#: src/components/welcome-modal/custom-endpoint-view.tsx:595 #: src/components/welcome-modal/calendar-permissions-view.tsx:153 #: src/components/welcome-modal/audio-permissions-view.tsx:189 msgid "Continue" @@ -666,7 +694,7 @@ msgstr "Display language" #~ msgid "Download {0}" #~ msgstr "Download {0}" -#: src/components/welcome-modal/download-progress-view.tsx:196 +#: src/components/welcome-modal/download-progress-view.tsx:204 msgid "Downloading AI Models" msgstr "Downloading AI Models" @@ -860,7 +888,7 @@ msgstr "Invite" msgid "Invite members" msgstr "Invite members" -#: src/components/welcome-modal/download-progress-view.tsx:252 +#: src/components/welcome-modal/download-progress-view.tsx:262 msgid "It's ok to move on, downloads will continue in the background" msgstr "It's ok to move on, downloads will continue in the background" @@ -920,6 +948,10 @@ msgstr "Loading available models..." msgid "Loading events..." msgstr "Loading events..." +#: src/components/welcome-modal/custom-endpoint-view.tsx:550 +msgid "Loading models..." +msgstr "Loading models..." + #: src/components/settings/views/templates.tsx:238 msgid "Loading templates..." msgstr "Loading templates..." @@ -962,12 +994,16 @@ msgstr "Members" msgid "Microphone Access" msgstr "Microphone Access" +#: src/components/welcome-modal/custom-endpoint-view.tsx:315 +#: src/components/welcome-modal/custom-endpoint-view.tsx:382 +#: src/components/welcome-modal/custom-endpoint-view.tsx:459 #: src/components/settings/components/ai/llm-custom-view.tsx:304 #: src/components/settings/components/ai/llm-custom-view.tsx:400 #: src/components/settings/components/ai/llm-custom-view.tsx:506 msgid "Model" msgstr "Model" +#: src/components/welcome-modal/custom-endpoint-view.tsx:544 #: src/components/settings/components/ai/llm-custom-view.tsx:623 msgid "Model Name" msgstr "Model Name" @@ -1153,7 +1189,7 @@ msgstr "Primary language for the interface" msgid "Publish your note" msgstr "Publish your note" -#: src/components/welcome-modal/download-progress-view.tsx:66 +#: src/components/welcome-modal/download-progress-view.tsx:67 msgid "Ready" msgstr "Ready" @@ -1253,13 +1289,25 @@ msgstr "Sections" msgid "Select a model from the dropdown (if available) or manually enter the model name required by your endpoint." msgstr "Select a model from the dropdown (if available) or manually enter the model name required by your endpoint." +#: src/components/welcome-modal/custom-endpoint-view.tsx:271 +msgid "Select a provider above to configure" +msgstr "Select a provider above to configure" + #: src/components/settings/views/templates.tsx:255 msgid "Select a template to enhance your meeting notes" msgstr "Select a template to enhance your meeting notes" #: src/components/welcome-modal/model-selection-view.tsx:73 -msgid "Select a transcribing model" -msgstr "Select a transcribing model" +#~ msgid "Select a transcribing model" +#~ msgstr "Select a transcribing model" + +#: src/components/welcome-modal/model-selection-view.tsx:73 +msgid "Select a transcribing model (STT)" +msgstr "Select a transcribing model (STT)" + +#: src/components/welcome-modal/llm-selection-view.tsx:99 +msgid "Select an option to continue" +msgstr "Select an option to continue" #: src/components/welcome-modal/language-selection-view.tsx:124 msgid "Select at least one language" @@ -1269,6 +1317,10 @@ msgstr "Select at least one language" msgid "Select Calendars" msgstr "Select Calendars" +#: src/components/welcome-modal/llm-selection-view.tsx:44 +msgid "Select how you want to process your meeting notes" +msgstr "Select how you want to process your meeting notes" + #: src/components/settings/views/general.tsx:271 msgid "Select languages you speak for better transcription" msgstr "Select languages you speak for better transcription" @@ -1305,7 +1357,7 @@ msgstr "Show notifications when you join a meeting." #~ msgid "Single sign-on for all users" #~ msgstr "Single sign-on for all users" -#: src/components/welcome-modal/download-progress-view.tsx:222 +#: src/components/welcome-modal/download-progress-view.tsx:230 msgid "Some downloads failed, but you can continue" msgstr "Some downloads failed, but you can continue" diff --git a/apps/desktop/src/locales/ko/messages.po b/apps/desktop/src/locales/ko/messages.po index a8e8443a30..0f46efd382 100644 --- a/apps/desktop/src/locales/ko/messages.po +++ b/apps/desktop/src/locales/ko/messages.po @@ -260,6 +260,10 @@ msgstr "" msgid "(Optional for localhost)" msgstr "" +#: src/components/welcome-modal/custom-endpoint-view.tsx:521 +msgid "(Optional)" +msgstr "" + #. placeholder {0}: isViewingTemplate ? "Back" : "Save and close" #. placeholder {0}: lang.language #. placeholder {0}: disabled ? "Wait..." : isHovered ? "Resume" : "Ended" @@ -365,7 +369,7 @@ msgstr "" #~ msgid "AI notepad for meetings" #~ msgstr "" -#: src/components/welcome-modal/download-progress-view.tsx:216 +#: src/components/welcome-modal/download-progress-view.tsx:224 msgid "All models ready!" msgstr "" @@ -386,10 +390,15 @@ msgstr "" msgid "Anyone with the link can view this page" msgstr "" +#: src/components/welcome-modal/custom-endpoint-view.tsx:498 #: src/components/settings/components/ai/llm-custom-view.tsx:576 msgid "API Base URL" msgstr "" +#: src/components/welcome-modal/custom-endpoint-view.tsx:294 +#: src/components/welcome-modal/custom-endpoint-view.tsx:361 +#: src/components/welcome-modal/custom-endpoint-view.tsx:438 +#: src/components/welcome-modal/custom-endpoint-view.tsx:518 #: src/components/settings/views/integrations.tsx:197 #: src/components/settings/components/ai/llm-custom-view.tsx:284 #: src/components/settings/components/ai/llm-custom-view.tsx:380 @@ -427,6 +436,11 @@ msgstr "" msgid "Autonomy Selector" msgstr "" +#: src/components/welcome-modal/index.tsx:351 +#: src/components/welcome-modal/index.tsx:362 +msgid "Back" +msgstr "" + #: src/components/settings/views/integrations.tsx:246 msgid "Base Folder" msgstr "" @@ -504,6 +518,10 @@ msgstr "" msgid "Choose whether to save your recordings locally." msgstr "" +#: src/components/welcome-modal/llm-selection-view.tsx:40 +msgid "Choose Your LLM" +msgstr "" + #: src/components/settings/views/general.tsx:230 #~ msgid "Choose your preferred language of use" #~ msgstr "" @@ -528,6 +546,14 @@ msgstr "" msgid "Company name" msgstr "" +#: src/components/welcome-modal/custom-endpoint-view.tsx:600 +msgid "Complete the configuration to continue" +msgstr "" + +#: src/components/welcome-modal/custom-endpoint-view.tsx:214 +msgid "Configure Your LLM" +msgstr "" + #: src/components/settings/components/calendar/cloud-calendar-integration-details.tsx:63 #~ msgid "Connect" #~ msgstr "" @@ -569,7 +595,9 @@ msgid "Contacts Access" msgstr "" #: src/components/welcome-modal/model-selection-view.tsx:137 -#: src/components/welcome-modal/download-progress-view.tsx:248 +#: src/components/welcome-modal/llm-selection-view.tsx:94 +#: src/components/welcome-modal/download-progress-view.tsx:258 +#: src/components/welcome-modal/custom-endpoint-view.tsx:595 #: src/components/welcome-modal/calendar-permissions-view.tsx:153 #: src/components/welcome-modal/audio-permissions-view.tsx:189 msgid "Continue" @@ -666,7 +694,7 @@ msgstr "" #~ msgid "Download {0}" #~ msgstr "" -#: src/components/welcome-modal/download-progress-view.tsx:196 +#: src/components/welcome-modal/download-progress-view.tsx:204 msgid "Downloading AI Models" msgstr "" @@ -860,7 +888,7 @@ msgstr "" msgid "Invite members" msgstr "" -#: src/components/welcome-modal/download-progress-view.tsx:252 +#: src/components/welcome-modal/download-progress-view.tsx:262 msgid "It's ok to move on, downloads will continue in the background" msgstr "" @@ -920,6 +948,10 @@ msgstr "" msgid "Loading events..." msgstr "" +#: src/components/welcome-modal/custom-endpoint-view.tsx:550 +msgid "Loading models..." +msgstr "" + #: src/components/settings/views/templates.tsx:238 msgid "Loading templates..." msgstr "" @@ -962,12 +994,16 @@ msgstr "" msgid "Microphone Access" msgstr "" +#: src/components/welcome-modal/custom-endpoint-view.tsx:315 +#: src/components/welcome-modal/custom-endpoint-view.tsx:382 +#: src/components/welcome-modal/custom-endpoint-view.tsx:459 #: src/components/settings/components/ai/llm-custom-view.tsx:304 #: src/components/settings/components/ai/llm-custom-view.tsx:400 #: src/components/settings/components/ai/llm-custom-view.tsx:506 msgid "Model" msgstr "" +#: src/components/welcome-modal/custom-endpoint-view.tsx:544 #: src/components/settings/components/ai/llm-custom-view.tsx:623 msgid "Model Name" msgstr "" @@ -1153,7 +1189,7 @@ msgstr "" msgid "Publish your note" msgstr "" -#: src/components/welcome-modal/download-progress-view.tsx:66 +#: src/components/welcome-modal/download-progress-view.tsx:67 msgid "Ready" msgstr "" @@ -1253,12 +1289,24 @@ msgstr "" msgid "Select a model from the dropdown (if available) or manually enter the model name required by your endpoint." msgstr "" +#: src/components/welcome-modal/custom-endpoint-view.tsx:271 +msgid "Select a provider above to configure" +msgstr "" + #: src/components/settings/views/templates.tsx:255 msgid "Select a template to enhance your meeting notes" msgstr "" #: src/components/welcome-modal/model-selection-view.tsx:73 -msgid "Select a transcribing model" +#~ msgid "Select a transcribing model" +#~ msgstr "" + +#: src/components/welcome-modal/model-selection-view.tsx:73 +msgid "Select a transcribing model (STT)" +msgstr "" + +#: src/components/welcome-modal/llm-selection-view.tsx:99 +msgid "Select an option to continue" msgstr "" #: src/components/welcome-modal/language-selection-view.tsx:124 @@ -1269,6 +1317,10 @@ msgstr "" msgid "Select Calendars" msgstr "" +#: src/components/welcome-modal/llm-selection-view.tsx:44 +msgid "Select how you want to process your meeting notes" +msgstr "" + #: src/components/settings/views/general.tsx:271 msgid "Select languages you speak for better transcription" msgstr "" @@ -1305,7 +1357,7 @@ msgstr "" #~ msgid "Single sign-on for all users" #~ msgstr "" -#: src/components/welcome-modal/download-progress-view.tsx:222 +#: src/components/welcome-modal/download-progress-view.tsx:230 msgid "Some downloads failed, but you can continue" msgstr ""