+6
![dependabot[bot]](/assets/img/avatar_default.png)







8d7d79d54b
* feat: improve ollama model management experience
This commit introduces several improvements to the Ollama model management modal:
- Adds a cancel button to the model pulling operation, using the existing 'x' button pattern.
- Adds a cancel button to the "Update All" models operation, allowing the user to cancel the update for the currently processing model.
- Cleans up toast notifications when updating all models. A single toast is now shown at the beginning and a summary toast at the end, preventing notification spam.
- Refactors the `ManageOllama.svelte` component to support these new cancellation features.
- Adds tooltips to all buttons in the modal to improve clarity.
- Disables buttons when their corresponding input fields are empty to prevent accidental clicks.
* fix
* i18n: improve Chinese translation
* fix: handle non‑UTF8 chars in third‑party responses without error
* German translation of new strings in i18n
* log web search queries only with level 'debug' instead of 'info'
* Tool calls now only include text and dont inlcude other content like image b64
* fix onedrive
* fix: discovery url
* fix: default permissions not being loaded
* fix: ai hallucination
* fix: non rich text input copy
* refac: rm print statements
* refac: disable direct models from model editors
* refac/fix: do not process xlsx files with azure doc intelligence
* Update pull_request_template.md
* Update generated image translation in DE-de
* added missing danish translations
* feat(onedrive): Enable search and "My Organization" pivot
* style(onedrive): Formatting fix
* feat: Implement toggling for vertical and horizontal flow layouts
This commit introduces the necessary logic and UI controls to allow users to switch the Flow component layout between vertical and horizontal orientations.
* **`Flow.svelte` Refactoring:**
* Updates logic for calculating level offsets and node positions to consistently respect the current flow orientation.
* Adds a control panel using `<Controls>` and `<SwitchButton>` components.
* Provides user interface elements to easily switch the flow layout between horizontal and vertical orientations.
* build(deps): bump pydantic from 2.11.7 to 2.11.9 in /backend
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.11.7 to 2.11.9.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/v2.11.9/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.11.7...v2.11.9)
---
updated-dependencies:
- dependency-name: pydantic
dependency-version: 2.11.9
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot] <support@github.com>
* build(deps): bump black from 25.1.0 to 25.9.0 in /backend
Bumps [black](https://github.com/psf/black) from 25.1.0 to 25.9.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/25.1.0...25.9.0)
---
updated-dependencies:
- dependency-name: black
dependency-version: 25.9.0
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot] <support@github.com>
* build(deps): bump markdown from 3.8.2 to 3.9 in /backend
Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.8.2 to 3.9.
- [Release notes](https://github.com/Python-Markdown/markdown/releases)
- [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md)
- [Commits](https://github.com/Python-Markdown/markdown/compare/3.8.2...3.9.0)
---
updated-dependencies:
- dependency-name: markdown
dependency-version: '3.9'
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot] <support@github.com>
* build(deps): bump chromadb from 1.0.20 to 1.1.0 in /backend
Bumps [chromadb](https://github.com/chroma-core/chroma) from 1.0.20 to 1.1.0.
- [Release notes](https://github.com/chroma-core/chroma/releases)
- [Changelog](https://github.com/chroma-core/chroma/blob/main/RELEASE_PROCESS.md)
- [Commits](https://github.com/chroma-core/chroma/compare/1.0.20...1.1.0)
---
updated-dependencies:
- dependency-name: chromadb
dependency-version: 1.1.0
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot] <support@github.com>
* build(deps): bump opentelemetry-api from 1.36.0 to 1.37.0
Bumps [opentelemetry-api](https://github.com/open-telemetry/opentelemetry-python) from 1.36.0 to 1.37.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-python/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-python/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-python/compare/v1.36.0...v1.37.0)
---
updated-dependencies:
- dependency-name: opentelemetry-api
dependency-version: 1.37.0
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot] <support@github.com>
* refac: ollama embed form data
* fix: non rich text handling
* fix: oauth client registration
* refac
* chore: dep bump
* chore: fastapi bump
* chore/refac: bump bcrypt and remove passlib
* Improving Korean Translation
* refac
* Improving Korean Translation
* feat: PWA share_target implementation
Co-Authored-By: gjveld <19951982+gjveld@users.noreply.github.com>
* refac: message input mobile detection behaviour
* feat: model_ids per folder
* Update translation.json (pt-BR)
inclusion of new translations of items that have been added
* refac
* refac
* refac
* refac
* refac/fix: temp chat
* refac
* refac: stop task
* refac/fix: azure audio escape
* refac: external tool validation
* refac/enh: start.sh additional args support
* refac
* refac: styling
* refac/fix: direct connection floating action buttons
* refac/fix: system prompt duplication
* refac/enh: openai tts additional params support
* refac
* feat: load data in parallel to accelerate page loading speed
* i18n: improve Chinese translation
* refac
* refac: model selector
* UPD: i18n es-ES Translation v0.6.33
UPD: i18n es-ES Translation v0.6.33
Updated new strings.
* refac
* improved query pref by querying only relevant columns
* refac/enh: docling params
* refac
* refac: openai additional headers support
* refac
* FEAT: Add Vega Char Visualizer Renderer
### FEAT: Add Vega Char Visualizer Renderer
Feature required in https://github.com/open-webui/open-webui/discussions/18022
Added npm vega lib to package.json
Added function for visualization renderer to src/libs/utils/index.ts
Added logic to src/lib/components/chat/Messages/CodeBlock.svelte
The treatment is similar as for mermaid diagrams.
Reference: https://vega.github.io/vega/
* refac
* chore
* refac
* FEAT: Add Vega-Lite Char Visualizer Renderer
### FEAT: Add Vega Char Visualizer Renderer
Add suport for Vega-Lite Specifications.
Vega-Lite is a "compiled" version of Vega Char Visualizer.
For be rendered with Vega it have to be compiled.
This PR add the check and compile if necessary, is a complement of recent Vega Renderer Feature added.
* refac
* refac/fix: switch
* enh/refac: url input handling
* refac
* refac: styling
* UPD: Add Validators & Error Toast for Mermaid & Vega diagrams
### UPD: Feat: Add Validators & Error Toast for Mermaid & Vega diagrams
Description:
As many time the diagrams generated or entered have syntax errors the diagrams are not rendered due to that errors, but as there isn't any notification is difficult to know what happend.
This PR add validator and toast notification when error on Mermaid and Vega/Vega-Lite diagrams, helping the user to fix its.
* removed redundant knowledge API call
* Fix Code Format
* refac: model workspace view
* refac
* refac: knowledge
* refac: prompts
* refac: tools
* refac
* feat: attach folder
* refac: make tencentcloud-sdk-python optional
* refac/fix: oauth
* enh: ENABLE_OAUTH_EMAIL_FALLBACK
* refac/fix: folders
* Update requirements.txt
* Update pyproject.toml
* UPD: Add Validators & Error Toast for Mermaid & Vega diagrams
### UPD: Feat: Add Validators & Error Toast for Mermaid & Vega diagrams
Description:
As many time the diagrams generated or entered have syntax errors the diagrams are not rendered due to that errors, but as there isn't any notification is difficult to know what happend.
This PR add validator and toast notification when error on Mermaid and Vega/Vega-Lite diagrams, helping the user to fix its.
Note:
Another possibility of integrating this Graph Visualizer is through its svelte component: https://github.com/vega/svelte-vega/tree/main/packages/svelte-vega
* Removed unused toast import & Code Format
* refac
* refac: external tool server view
* refac
* refac: overview
* refac: styling
* refac
* Update bug_report.yaml
* refac
* refac
* refac
* refac
* refac: oauth client fallback
* Fixed: Cannot handle batch sizes > 1 if no padding token is defined
Fixes Cannot handle batch sizes > 1 if no padding token is defined
For reranker models that do not have this defined in their config by using the eos_token_id if present as pad_token_id.
* refac: fallback to reasoning content
* fix(i18n): corrected typo in Spanish translation for "Reasoning Tags"
Typo fixed in Spanish translation file at line 1240 of `open-webui/src/lib/i18n/locales/es-ES/translation.json`:
- Incorrect: "Eriquetas de Razonamiento"
- Correct: "Etiquetas de Razonamiento"
This improves clarity and consistency in the UI.
* refac/fix: ENABLE_STAR_SESSIONS_MIDDLEWARE
* refac/fix: redirect
* refac
* refac
* refac
* refac: web search error handling
* refac: source parsing
* refac: functions
* refac
* refac/enh: note pdf export
* refac/fix: mcp oauth2.1
* chore: format
* chore: Changelog (#17995)
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* Update CHANGELOG.md
* refac
* chore: dep bump
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: silentoplayz <jacwoo21@outlook.com>
Co-authored-by: Shirasawa <764798966@qq.com>
Co-authored-by: Jan Kessler <jakessle@uni-mainz.de>
Co-authored-by: Jacob Leksan <jacob.leksan@expedient.com>
Co-authored-by: Classic298 <27028174+Classic298@users.noreply.github.com>
Co-authored-by: sinejespersen <sinejespersen@protonmail.com>
Co-authored-by: Selene Blok <selene.blok@rws.nl>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Cyp <cypher9715@naver.com>
Co-authored-by: gjveld <19951982+gjveld@users.noreply.github.com>
Co-authored-by: joaoback <156559121+joaoback@users.noreply.github.com>
Co-authored-by: _00_ <131402327+rgaricano@users.noreply.github.com>
Co-authored-by: expruc <eygabi01@gmail.com>
Co-authored-by: YetheSamartaka <55753928+YetheSamartaka@users.noreply.github.com>
Co-authored-by: Akutangulo <akutangulo@gmail.com>
764 lines
25 KiB
Svelte
764 lines
25 KiB
Svelte
<script lang="ts">
|
|
import { toast } from 'svelte-sonner';
|
|
import { createEventDispatcher, onMount, getContext } from 'svelte';
|
|
const dispatch = createEventDispatcher();
|
|
|
|
import { getBackendConfig } from '$lib/apis';
|
|
import {
|
|
getAudioConfig,
|
|
updateAudioConfig,
|
|
getModels as _getModels,
|
|
getVoices as _getVoices
|
|
} from '$lib/apis/audio';
|
|
import { config, settings } from '$lib/stores';
|
|
|
|
import Spinner from '$lib/components/common/Spinner.svelte';
|
|
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
|
|
|
|
import { TTS_RESPONSE_SPLIT } from '$lib/types';
|
|
|
|
import type { Writable } from 'svelte/store';
|
|
import type { i18n as i18nType } from 'i18next';
|
|
import Textarea from '$lib/components/common/Textarea.svelte';
|
|
|
|
const i18n = getContext<Writable<i18nType>>('i18n');
|
|
|
|
export let saveHandler: () => void;
|
|
|
|
// Audio
|
|
let TTS_OPENAI_API_BASE_URL = '';
|
|
let TTS_OPENAI_API_KEY = '';
|
|
let TTS_API_KEY = '';
|
|
let TTS_ENGINE = '';
|
|
let TTS_MODEL = '';
|
|
let TTS_VOICE = '';
|
|
let TTS_OPENAI_PARAMS = '';
|
|
let TTS_SPLIT_ON: TTS_RESPONSE_SPLIT = TTS_RESPONSE_SPLIT.PUNCTUATION;
|
|
let TTS_AZURE_SPEECH_REGION = '';
|
|
let TTS_AZURE_SPEECH_BASE_URL = '';
|
|
let TTS_AZURE_SPEECH_OUTPUT_FORMAT = '';
|
|
|
|
let STT_OPENAI_API_BASE_URL = '';
|
|
let STT_OPENAI_API_KEY = '';
|
|
let STT_ENGINE = '';
|
|
let STT_MODEL = '';
|
|
let STT_SUPPORTED_CONTENT_TYPES = '';
|
|
let STT_WHISPER_MODEL = '';
|
|
let STT_AZURE_API_KEY = '';
|
|
let STT_AZURE_REGION = '';
|
|
let STT_AZURE_LOCALES = '';
|
|
let STT_AZURE_BASE_URL = '';
|
|
let STT_AZURE_MAX_SPEAKERS = '';
|
|
let STT_DEEPGRAM_API_KEY = '';
|
|
|
|
let STT_WHISPER_MODEL_LOADING = false;
|
|
|
|
// eslint-disable-next-line no-undef
|
|
let voices: SpeechSynthesisVoice[] = [];
|
|
let models: Awaited<ReturnType<typeof _getModels>>['models'] = [];
|
|
|
|
const getModels = async () => {
|
|
if (TTS_ENGINE === '') {
|
|
models = [];
|
|
} else {
|
|
const res = await _getModels(
|
|
localStorage.token,
|
|
$config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
|
|
).catch((e) => {
|
|
toast.error(`${e}`);
|
|
});
|
|
|
|
if (res) {
|
|
console.log(res);
|
|
models = res.models;
|
|
}
|
|
}
|
|
};
|
|
|
|
const getVoices = async () => {
|
|
if (TTS_ENGINE === '') {
|
|
const getVoicesLoop = setInterval(() => {
|
|
voices = speechSynthesis.getVoices();
|
|
|
|
// do your loop
|
|
if (voices.length > 0) {
|
|
clearInterval(getVoicesLoop);
|
|
voices.sort((a, b) => a.name.localeCompare(b.name, $i18n.resolvedLanguage));
|
|
}
|
|
}, 100);
|
|
} else {
|
|
const res = await _getVoices(localStorage.token).catch((e) => {
|
|
toast.error(`${e}`);
|
|
});
|
|
|
|
if (res) {
|
|
console.log(res);
|
|
voices = res.voices;
|
|
voices.sort((a, b) => a.name.localeCompare(b.name, $i18n.resolvedLanguage));
|
|
}
|
|
}
|
|
};
|
|
|
|
const updateConfigHandler = async () => {
|
|
let openaiParams = {};
|
|
try {
|
|
openaiParams = TTS_OPENAI_PARAMS ? JSON.parse(TTS_OPENAI_PARAMS) : {};
|
|
TTS_OPENAI_PARAMS = JSON.stringify(openaiParams, null, 2);
|
|
} catch (e) {
|
|
toast.error($i18n.t('Invalid JSON format for Parameters'));
|
|
return;
|
|
}
|
|
|
|
const res = await updateAudioConfig(localStorage.token, {
|
|
tts: {
|
|
OPENAI_API_BASE_URL: TTS_OPENAI_API_BASE_URL,
|
|
OPENAI_API_KEY: TTS_OPENAI_API_KEY,
|
|
OPENAI_PARAMS: openaiParams,
|
|
API_KEY: TTS_API_KEY,
|
|
ENGINE: TTS_ENGINE,
|
|
MODEL: TTS_MODEL,
|
|
VOICE: TTS_VOICE,
|
|
AZURE_SPEECH_REGION: TTS_AZURE_SPEECH_REGION,
|
|
AZURE_SPEECH_BASE_URL: TTS_AZURE_SPEECH_BASE_URL,
|
|
AZURE_SPEECH_OUTPUT_FORMAT: TTS_AZURE_SPEECH_OUTPUT_FORMAT,
|
|
SPLIT_ON: TTS_SPLIT_ON
|
|
},
|
|
stt: {
|
|
OPENAI_API_BASE_URL: STT_OPENAI_API_BASE_URL,
|
|
OPENAI_API_KEY: STT_OPENAI_API_KEY,
|
|
ENGINE: STT_ENGINE,
|
|
MODEL: STT_MODEL,
|
|
SUPPORTED_CONTENT_TYPES: STT_SUPPORTED_CONTENT_TYPES.split(','),
|
|
WHISPER_MODEL: STT_WHISPER_MODEL,
|
|
DEEPGRAM_API_KEY: STT_DEEPGRAM_API_KEY,
|
|
AZURE_API_KEY: STT_AZURE_API_KEY,
|
|
AZURE_REGION: STT_AZURE_REGION,
|
|
AZURE_LOCALES: STT_AZURE_LOCALES,
|
|
AZURE_BASE_URL: STT_AZURE_BASE_URL,
|
|
AZURE_MAX_SPEAKERS: STT_AZURE_MAX_SPEAKERS
|
|
}
|
|
});
|
|
|
|
if (res) {
|
|
saveHandler();
|
|
config.set(await getBackendConfig());
|
|
}
|
|
};
|
|
|
|
const sttModelUpdateHandler = async () => {
|
|
STT_WHISPER_MODEL_LOADING = true;
|
|
await updateConfigHandler();
|
|
STT_WHISPER_MODEL_LOADING = false;
|
|
};
|
|
|
|
onMount(async () => {
|
|
const res = await getAudioConfig(localStorage.token);
|
|
|
|
if (res) {
|
|
console.log(res);
|
|
TTS_OPENAI_API_BASE_URL = res.tts.OPENAI_API_BASE_URL;
|
|
TTS_OPENAI_API_KEY = res.tts.OPENAI_API_KEY;
|
|
TTS_OPENAI_PARAMS = JSON.stringify(res?.tts?.OPENAI_PARAMS ?? '', null, 2);
|
|
TTS_API_KEY = res.tts.API_KEY;
|
|
|
|
TTS_ENGINE = res.tts.ENGINE;
|
|
TTS_MODEL = res.tts.MODEL;
|
|
TTS_VOICE = res.tts.VOICE;
|
|
|
|
TTS_SPLIT_ON = res.tts.SPLIT_ON || TTS_RESPONSE_SPLIT.PUNCTUATION;
|
|
|
|
TTS_AZURE_SPEECH_REGION = res.tts.AZURE_SPEECH_REGION;
|
|
TTS_AZURE_SPEECH_BASE_URL = res.tts.AZURE_SPEECH_BASE_URL;
|
|
TTS_AZURE_SPEECH_OUTPUT_FORMAT = res.tts.AZURE_SPEECH_OUTPUT_FORMAT;
|
|
|
|
STT_OPENAI_API_BASE_URL = res.stt.OPENAI_API_BASE_URL;
|
|
STT_OPENAI_API_KEY = res.stt.OPENAI_API_KEY;
|
|
|
|
STT_ENGINE = res.stt.ENGINE;
|
|
STT_MODEL = res.stt.MODEL;
|
|
STT_SUPPORTED_CONTENT_TYPES = (res?.stt?.SUPPORTED_CONTENT_TYPES ?? []).join(',');
|
|
STT_WHISPER_MODEL = res.stt.WHISPER_MODEL;
|
|
STT_AZURE_API_KEY = res.stt.AZURE_API_KEY;
|
|
STT_AZURE_REGION = res.stt.AZURE_REGION;
|
|
STT_AZURE_LOCALES = res.stt.AZURE_LOCALES;
|
|
STT_AZURE_BASE_URL = res.stt.AZURE_BASE_URL;
|
|
STT_AZURE_MAX_SPEAKERS = res.stt.AZURE_MAX_SPEAKERS;
|
|
STT_DEEPGRAM_API_KEY = res.stt.DEEPGRAM_API_KEY;
|
|
}
|
|
|
|
await getVoices();
|
|
await getModels();
|
|
});
|
|
</script>
|
|
|
|
<form
|
|
class="flex flex-col h-full justify-between space-y-3 text-sm"
|
|
on:submit|preventDefault={async () => {
|
|
await updateConfigHandler();
|
|
dispatch('save');
|
|
}}
|
|
>
|
|
<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
|
|
<div class="flex flex-col gap-3">
|
|
<div>
|
|
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Speech-to-Text')}</div>
|
|
|
|
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
|
|
|
{#if STT_ENGINE !== 'web'}
|
|
<div class="mb-2">
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Supported MIME Types')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={STT_SUPPORTED_CONTENT_TYPES}
|
|
placeholder={$i18n.t(
|
|
'e.g., audio/wav,audio/mpeg,video/* (leave blank for defaults)'
|
|
)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="mb-2 py-0.5 flex w-full justify-between">
|
|
<div class=" self-center text-xs font-medium">{$i18n.t('Speech-to-Text Engine')}</div>
|
|
<div class="flex items-center relative">
|
|
<select
|
|
class="dark:bg-gray-900 cursor-pointer w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
|
bind:value={STT_ENGINE}
|
|
placeholder={$i18n.t('Select an engine')}
|
|
>
|
|
<option value="">{$i18n.t('Whisper (Local)')}</option>
|
|
<option value="openai">{$i18n.t('OpenAI')}</option>
|
|
<option value="web">{$i18n.t('Web API')}</option>
|
|
<option value="deepgram">{$i18n.t('Deepgram')}</option>
|
|
<option value="azure">{$i18n.t('Azure AI Speech')}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
{#if STT_ENGINE === 'openai'}
|
|
<div>
|
|
<div class="mt-1 flex gap-2 mb-1">
|
|
<input
|
|
class="flex-1 w-full bg-transparent outline-hidden"
|
|
placeholder={$i18n.t('API Base URL')}
|
|
bind:value={STT_OPENAI_API_BASE_URL}
|
|
required
|
|
/>
|
|
|
|
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={STT_OPENAI_API_KEY} />
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="border-gray-100 dark:border-gray-850 my-2" />
|
|
|
|
<div>
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('STT Model')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
list="model-list"
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={STT_MODEL}
|
|
placeholder={$i18n.t('Select a model')}
|
|
/>
|
|
|
|
<datalist id="model-list">
|
|
<option value="whisper-1" />
|
|
</datalist>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{:else if STT_ENGINE === 'deepgram'}
|
|
<div>
|
|
<div class="mt-1 flex gap-2 mb-1">
|
|
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={STT_DEEPGRAM_API_KEY} />
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="border-gray-100 dark:border-gray-850 my-2" />
|
|
|
|
<div>
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('STT Model')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={STT_MODEL}
|
|
placeholder={$i18n.t('Select a model (optional)')}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
|
|
{$i18n.t('Leave model field empty to use the default model.')}
|
|
<a
|
|
class=" hover:underline dark:text-gray-200 text-gray-800"
|
|
href="https://developers.deepgram.com/docs/models"
|
|
target="_blank"
|
|
>
|
|
{$i18n.t('Click here to see available models.')}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{:else if STT_ENGINE === 'azure'}
|
|
<div>
|
|
<div class="mt-1 flex gap-2 mb-1">
|
|
<SensitiveInput
|
|
placeholder={$i18n.t('API Key')}
|
|
bind:value={STT_AZURE_API_KEY}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<hr class="border-gray-100 dark:border-gray-850 my-2" />
|
|
|
|
<div>
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Azure Region')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={STT_AZURE_REGION}
|
|
placeholder={$i18n.t('e.g., westus (leave blank for eastus)')}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Language Locales')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={STT_AZURE_LOCALES}
|
|
placeholder={$i18n.t('e.g., en-US,ja-JP (leave blank for auto-detect)')}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Endpoint URL')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={STT_AZURE_BASE_URL}
|
|
placeholder={$i18n.t('(leave blank for to use commercial endpoint)')}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Max Speakers')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={STT_AZURE_MAX_SPEAKERS}
|
|
placeholder={$i18n.t('e.g., 3, 4, 5 (leave blank for default)')}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{:else if STT_ENGINE === ''}
|
|
<div>
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('STT Model')}</div>
|
|
|
|
<div class="flex w-full">
|
|
<div class="flex-1 mr-2">
|
|
<input
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
placeholder={$i18n.t('Set whisper model')}
|
|
bind:value={STT_WHISPER_MODEL}
|
|
/>
|
|
</div>
|
|
|
|
<button
|
|
class="px-2.5 bg-gray-50 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
|
|
on:click={() => {
|
|
sttModelUpdateHandler();
|
|
}}
|
|
disabled={STT_WHISPER_MODEL_LOADING}
|
|
>
|
|
{#if STT_WHISPER_MODEL_LOADING}
|
|
<div class="self-center">
|
|
<Spinner />
|
|
</div>
|
|
{:else}
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 16 16"
|
|
fill="currentColor"
|
|
class="w-4 h-4"
|
|
>
|
|
<path
|
|
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
|
|
/>
|
|
<path
|
|
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
|
|
/>
|
|
</svg>
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
|
|
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
|
|
{$i18n.t(`Open WebUI uses faster-whisper internally.`)}
|
|
|
|
<a
|
|
class=" hover:underline dark:text-gray-200 text-gray-800"
|
|
href="https://github.com/SYSTRAN/faster-whisper"
|
|
target="_blank"
|
|
>
|
|
{$i18n.t(
|
|
`Click here to learn more about faster-whisper and see the available models.`
|
|
)}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<div>
|
|
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Text-to-Speech')}</div>
|
|
|
|
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
|
|
|
|
<div class="mb-2 py-0.5 flex w-full justify-between">
|
|
<div class=" self-center text-xs font-medium">{$i18n.t('Text-to-Speech Engine')}</div>
|
|
<div class="flex items-center relative">
|
|
<select
|
|
class=" dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
|
bind:value={TTS_ENGINE}
|
|
placeholder={$i18n.t('Select a mode')}
|
|
on:change={async (e) => {
|
|
await updateConfigHandler();
|
|
await getVoices();
|
|
await getModels();
|
|
|
|
if (e.target?.value === 'openai') {
|
|
TTS_VOICE = 'alloy';
|
|
TTS_MODEL = 'tts-1';
|
|
} else {
|
|
TTS_VOICE = '';
|
|
TTS_MODEL = '';
|
|
}
|
|
}}
|
|
>
|
|
<option value="">{$i18n.t('Web API')}</option>
|
|
<option value="transformers">{$i18n.t('Transformers')} ({$i18n.t('Local')})</option>
|
|
<option value="openai">{$i18n.t('OpenAI')}</option>
|
|
<option value="elevenlabs">{$i18n.t('ElevenLabs')}</option>
|
|
<option value="azure">{$i18n.t('Azure AI Speech')}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
{#if TTS_ENGINE === 'openai'}
|
|
<div>
|
|
<div class="mt-1 flex gap-2 mb-1">
|
|
<input
|
|
class="flex-1 w-full bg-transparent outline-hidden"
|
|
placeholder={$i18n.t('API Base URL')}
|
|
bind:value={TTS_OPENAI_API_BASE_URL}
|
|
required
|
|
/>
|
|
|
|
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={TTS_OPENAI_API_KEY} />
|
|
</div>
|
|
</div>
|
|
{:else if TTS_ENGINE === 'elevenlabs'}
|
|
<div>
|
|
<div class="mt-1 flex gap-2 mb-1">
|
|
<input
|
|
class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
placeholder={$i18n.t('API Key')}
|
|
bind:value={TTS_API_KEY}
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
{:else if TTS_ENGINE === 'azure'}
|
|
<div>
|
|
<div class="mt-1 flex gap-2 mb-1">
|
|
<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={TTS_API_KEY} required />
|
|
</div>
|
|
|
|
<hr class="border-gray-100 dark:border-gray-850 my-2" />
|
|
|
|
<div>
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Azure Region')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={TTS_AZURE_SPEECH_REGION}
|
|
placeholder={$i18n.t('e.g., westus (leave blank for eastus)')}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Endpoint URL')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={TTS_AZURE_SPEECH_BASE_URL}
|
|
placeholder={$i18n.t('(leave blank for to use commercial endpoint)')}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="mb-2">
|
|
{#if TTS_ENGINE === ''}
|
|
<div>
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('TTS Voice')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<select
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={TTS_VOICE}
|
|
>
|
|
<option value="" selected={TTS_VOICE !== ''}>{$i18n.t('Default')}</option>
|
|
{#each voices as voice}
|
|
<option
|
|
value={voice.voiceURI}
|
|
class="bg-gray-100 dark:bg-gray-700"
|
|
selected={TTS_VOICE === voice.voiceURI}>{voice.name}</option
|
|
>
|
|
{/each}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{:else if TTS_ENGINE === 'transformers'}
|
|
<div>
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('TTS Model')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
list="model-list"
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={TTS_MODEL}
|
|
placeholder={$i18n.t('CMU ARCTIC speaker embedding name')}
|
|
/>
|
|
|
|
<datalist id="model-list">
|
|
<option value="tts-1" />
|
|
</datalist>
|
|
</div>
|
|
</div>
|
|
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
|
|
{$i18n.t(`Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.`)}
|
|
|
|
To learn more about SpeechT5,
|
|
|
|
<a
|
|
class=" hover:underline dark:text-gray-200 text-gray-800"
|
|
href="https://github.com/microsoft/SpeechT5"
|
|
target="_blank"
|
|
>
|
|
{$i18n.t(`click here`, {
|
|
name: 'SpeechT5'
|
|
})}.
|
|
</a>
|
|
To see the available CMU Arctic speaker embeddings,
|
|
<a
|
|
class=" hover:underline dark:text-gray-200 text-gray-800"
|
|
href="https://huggingface.co/datasets/Matthijs/cmu-arctic-xvectors"
|
|
target="_blank"
|
|
>
|
|
{$i18n.t(`click here`)}.
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{:else if TTS_ENGINE === 'openai'}
|
|
<div class=" flex gap-2">
|
|
<div class="w-full">
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('TTS Voice')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
list="voice-list"
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={TTS_VOICE}
|
|
placeholder={$i18n.t('Select a voice')}
|
|
/>
|
|
|
|
<datalist id="voice-list">
|
|
{#each voices as voice}
|
|
<option value={voice.id}>{voice.name}</option>
|
|
{/each}
|
|
</datalist>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="w-full">
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('TTS Model')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
list="tts-model-list"
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={TTS_MODEL}
|
|
placeholder={$i18n.t('Select a model')}
|
|
/>
|
|
|
|
<datalist id="tts-model-list">
|
|
{#each models as model}
|
|
<option value={model.id} class="bg-gray-50 dark:bg-gray-700" />
|
|
{/each}
|
|
</datalist>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
|
|
<div class="w-full">
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Additional Parameters')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<Textarea
|
|
className="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={TTS_OPENAI_PARAMS}
|
|
placeholder={$i18n.t('Enter additional parameters in JSON format')}
|
|
minSize={100}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{:else if TTS_ENGINE === 'elevenlabs'}
|
|
<div class=" flex gap-2">
|
|
<div class="w-full">
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('TTS Voice')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
list="voice-list"
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={TTS_VOICE}
|
|
placeholder={$i18n.t('Select a voice')}
|
|
/>
|
|
|
|
<datalist id="voice-list">
|
|
{#each voices as voice}
|
|
<option value={voice.id}>{voice.name}</option>
|
|
{/each}
|
|
</datalist>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="w-full">
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('TTS Model')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
list="tts-model-list"
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={TTS_MODEL}
|
|
placeholder={$i18n.t('Select a model')}
|
|
/>
|
|
|
|
<datalist id="tts-model-list">
|
|
{#each models as model}
|
|
<option value={model.id} class="bg-gray-50 dark:bg-gray-700" />
|
|
{/each}
|
|
</datalist>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{:else if TTS_ENGINE === 'azure'}
|
|
<div class=" flex gap-2">
|
|
<div class="w-full">
|
|
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('TTS Voice')}</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
list="voice-list"
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={TTS_VOICE}
|
|
placeholder={$i18n.t('Select a voice')}
|
|
/>
|
|
|
|
<datalist id="voice-list">
|
|
{#each voices as voice}
|
|
<option value={voice.id}>{voice.name}</option>
|
|
{/each}
|
|
</datalist>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="w-full">
|
|
<div class=" mb-1.5 text-xs font-medium">
|
|
{$i18n.t('Output format')}
|
|
<a
|
|
href="https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech?tabs=streaming#audio-outputs"
|
|
target="_blank"
|
|
>
|
|
<small>{$i18n.t('Available list')}</small>
|
|
</a>
|
|
</div>
|
|
<div class="flex w-full">
|
|
<div class="flex-1">
|
|
<input
|
|
list="tts-model-list"
|
|
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
|
|
bind:value={TTS_AZURE_SPEECH_OUTPUT_FORMAT}
|
|
placeholder={$i18n.t('Select an output format')}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="pt-0.5 flex w-full justify-between">
|
|
<div class="self-center text-xs font-medium">{$i18n.t('Response splitting')}</div>
|
|
<div class="flex items-center relative">
|
|
<select
|
|
class="dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
|
|
aria-label={$i18n.t('Select how to split message text for TTS requests')}
|
|
bind:value={TTS_SPLIT_ON}
|
|
>
|
|
{#each Object.values(TTS_RESPONSE_SPLIT) as split}
|
|
<option value={split}
|
|
>{$i18n.t(split.charAt(0).toUpperCase() + split.slice(1))}</option
|
|
>
|
|
{/each}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
|
|
{$i18n.t(
|
|
"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string."
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex justify-end text-sm font-medium">
|
|
<button
|
|
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
|
|
type="submit"
|
|
>
|
|
{$i18n.t('Save')}
|
|
</button>
|
|
</div>
|
|
</form>
|