Add local other models translation slot
Some checks failed
CI / cargo check (linux) (push) Has been cancelled
CI / cargo check (macos) (push) Has been cancelled
CI / cargo check (windows) (push) Has been cancelled
CI / tsc + hotkey smoke (push) Has been cancelled

This commit is contained in:
David Perov 2026-07-06 10:08:32 +03:00
parent 741b7488b6
commit 918c414525
2 changed files with 104 additions and 1 deletions

View file

@ -198,6 +198,7 @@ export const settingsModels = {
// ── Local models section ──
"models.local.tabTranscription": { ru: "Транскрибация", en: "Transcription" },
"models.local.tabText": { ru: "Текстовая", en: "Text" },
"models.local.tabOther": { ru: "Другие", en: "Other" },
"models.local.sectionTitle": { ru: "Локальные модели", en: "Local models" },
"models.local.sectionDesc": {
ru: "Выберите модель, скачайте ее через локальный STT runtime и используйте без облака.",
@ -264,6 +265,18 @@ export const settingsModels = {
en: "A compact Qwen ASR model for local recognition via the transcribe.cpp GGUF runtime.",
},
// ── Local auxiliary components ──
"models.localOther.sectionTitle": { ru: "Другие", en: "Other" },
"models.localOther.sectionDesc": {
ru: "Локальные библиотеки и компоненты, которые используются не для распознавания или текстовой модели.",
en: "Local libraries and components used outside recognition or text models.",
},
"models.localOther.status.planned": { ru: "Готовится", en: "Planned" },
"models.localOther.trad.description": {
ru: "Локальная библиотека перевода. Будет использоваться для перевода выделенного текста без подмены текста в активном приложении.",
en: "A local translation library. It will be used for selected-text translation without replacing text in the active app.",
},
// ── Speed / accuracy value labels ──
"models.speedValue.veryFast": { ru: "очень быстро", en: "very fast" },
"models.speedValue.fast": { ru: "быстро", en: "fast" },

View file

@ -16,6 +16,7 @@ import {
IconGlobe,
IconLogout,
Icon,
IconLanguage,
IconMessage,
IconMicrophone,
IconPencil,
@ -78,6 +79,7 @@ import xAiAvatar from "../../../assets/adapters/xai.png";
const IS_DEV = import.meta.env.DEV;
type LocalRuntimeKind = "whisper" | "diarization";
type LocalModelKind = "transcription" | "text" | "other";
type DesktopPlatform = "macos" | "windows" | "linux" | "unknown";
function detectDesktopPlatform(): DesktopPlatform {
@ -296,6 +298,15 @@ interface LocalModelOption {
streamingDefaultEnabled?: boolean;
}
interface LocalOtherComponent {
id: string;
name: string;
descriptionKey: MsgKey;
initials: string;
accent: string;
statusKey: MsgKey;
}
const LOCAL_MODEL_OPTIONS: LocalModelOption[] = [
{
id: "nemotron-35-asr-streaming-06b",
@ -536,6 +547,17 @@ const LOCAL_MODEL_OPTIONS: LocalModelOption[] = [
},
];
const LOCAL_OTHER_COMPONENTS: LocalOtherComponent[] = [
{
id: "trad",
name: "trad",
descriptionKey: "models.localOther.trad.description",
initials: "TR",
accent: "#0f766e",
statusKey: "models.localOther.status.planned",
},
];
// Translation keys for the per-adapter / per-model descriptions. The original
// Russian text lives in the i18n dictionary fragment (settingsModels); these maps
// keep the keys statically typed so they satisfy the MsgKey union.
@ -1291,7 +1313,7 @@ export function SettingsTabs({ type }: SettingsTabsProps) {
const [waitingForSubscriptionRefresh, setWaitingForSubscriptionRefresh] = useState(false);
const [modelModeView, setModelModeView] = useState<ModelMode | null>(null);
const [styleTabView, setStyleTabView] = useState<"style" | "prompts">("style");
const [localModelKind, setLocalModelKind] = useState<"transcription" | "text">(
const [localModelKind, setLocalModelKind] = useState<LocalModelKind>(
"transcription",
);
const [apiModelKind, setApiModelKind] = useState<"transcription" | "text">(
@ -2943,6 +2965,7 @@ export function SettingsTabs({ type }: SettingsTabsProps) {
{([
{ id: "transcription", label: t("models.local.tabTranscription"), Icon: IconMicrophone },
{ id: "text", label: t("models.local.tabText"), Icon: IconMessage },
{ id: "other", label: t("models.local.tabOther"), Icon: IconLanguage },
] as const).map(({ id, label, Icon }) => {
const active = localModelKind === id;
@ -3347,6 +3370,73 @@ export function SettingsTabs({ type }: SettingsTabsProps) {
{localModelKind === "text" && (
<LocalLlmModels settings={settings} update={update} />
)}
{localModelKind === "other" && (
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
<div>
<div style={{ fontSize: 15, fontWeight: 700, color: "var(--text-hi)", marginBottom: 4 }}>
{t("models.localOther.sectionTitle")}
</div>
<div style={{ fontSize: 13, color: "var(--text-mid)", lineHeight: 1.6 }}>
{t("models.localOther.sectionDesc")}
</div>
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{LOCAL_OTHER_COMPONENTS.map((component) => (
<div
key={component.id}
className="card"
style={{ padding: 0, overflow: "hidden", background: "var(--surface)" }}
>
<div
style={{
width: "100%",
padding: "12px 14px",
display: "flex",
alignItems: "center",
gap: 12,
textAlign: "left",
fontFamily: "var(--font-main)",
}}
>
<div
style={{
width: 36,
height: 36,
borderRadius: 999,
background: component.accent,
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
color: "#fff",
fontSize: 12,
fontWeight: 800,
}}
>
{component.initials}
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, marginBottom: 3 }}>
<div style={{ fontSize: 15, fontWeight: 700, color: "var(--text-hi)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
{component.name}
</div>
<div style={{ fontSize: 11, fontWeight: 700, color: "var(--text-low)", padding: "5px 9px", borderRadius: 999, background: "var(--control-muted)", whiteSpace: "nowrap" }}>
{t(component.statusKey)}
</div>
</div>
<div style={{ fontSize: 12, lineHeight: 1.45, color: "var(--text-mid)" }}>
{t(component.descriptionKey)}
</div>
</div>
</div>
</div>
))}
</div>
</div>
)}
</div>
)}
</>