mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-11 20:05:19 +00:00
add the configuration for selecting the languages codes son they can be used for translations
This commit is contained in:
parent
058de98975
commit
a280f65249
5 changed files with 159 additions and 2 deletions
|
|
@ -1659,6 +1659,12 @@ ENABLE_AUTOCOMPLETE_GENERATION = PersistentConfig(
|
|||
os.environ.get("ENABLE_AUTOCOMPLETE_GENERATION", "False").lower() == "true",
|
||||
)
|
||||
|
||||
TRANSLATION_LANGUAGES = PersistentConfig(
|
||||
"TRANSLATION_LANGUAGES",
|
||||
"translation_languages",
|
||||
os.getenv("TRANSLATION_LANGUAGES", "en").split(","),
|
||||
)
|
||||
|
||||
AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH = PersistentConfig(
|
||||
"AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH",
|
||||
"task.autocomplete.input_max_length",
|
||||
|
|
|
|||
|
|
@ -397,6 +397,7 @@ from open_webui.config import (
|
|||
QUERY_GENERATION_PROMPT_TEMPLATE,
|
||||
AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE,
|
||||
AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH,
|
||||
TRANSLATION_LANGUAGES,
|
||||
AppConfig,
|
||||
reset_config,
|
||||
)
|
||||
|
|
@ -1099,6 +1100,7 @@ app.state.config.ENABLE_AUTOCOMPLETE_GENERATION = ENABLE_AUTOCOMPLETE_GENERATION
|
|||
app.state.config.ENABLE_TAGS_GENERATION = ENABLE_TAGS_GENERATION
|
||||
app.state.config.ENABLE_TITLE_GENERATION = ENABLE_TITLE_GENERATION
|
||||
app.state.config.ENABLE_FOLLOW_UP_GENERATION = ENABLE_FOLLOW_UP_GENERATION
|
||||
app.state.config.TRANSLATION_LANGUAGES = TRANSLATION_LANGUAGES
|
||||
|
||||
|
||||
app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE = TITLE_GENERATION_PROMPT_TEMPLATE
|
||||
|
|
@ -1695,6 +1697,7 @@ async def get_app_config(request: Request):
|
|||
"enable_admin_chat_access": ENABLE_ADMIN_CHAT_ACCESS,
|
||||
"enable_google_drive_integration": app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
|
||||
"enable_onedrive_integration": app.state.config.ENABLE_ONEDRIVE_INTEGRATION,
|
||||
"translation_languages": app.state.config.TRANSLATION_LANGUAGES,
|
||||
}
|
||||
if user is not None
|
||||
else {}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, Response, status, Request
|
|||
from fastapi.responses import JSONResponse, RedirectResponse
|
||||
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from typing import Optional, List
|
||||
import logging
|
||||
import re
|
||||
|
||||
|
|
@ -68,6 +68,7 @@ async def get_task_config(request: Request, user=Depends(get_verified_user)):
|
|||
"ENABLE_RETRIEVAL_QUERY_GENERATION": request.app.state.config.ENABLE_RETRIEVAL_QUERY_GENERATION,
|
||||
"QUERY_GENERATION_PROMPT_TEMPLATE": request.app.state.config.QUERY_GENERATION_PROMPT_TEMPLATE,
|
||||
"TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE": request.app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
|
||||
"TRANSLATION_LANGUAGES": request.app.state.config.TRANSLATION_LANGUAGES,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -87,6 +88,7 @@ class TaskConfigForm(BaseModel):
|
|||
ENABLE_RETRIEVAL_QUERY_GENERATION: bool
|
||||
QUERY_GENERATION_PROMPT_TEMPLATE: str
|
||||
TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE: str
|
||||
TRANSLATION_LANGUAGES: Optional[List[str]] = []
|
||||
|
||||
|
||||
@router.post("/config/update")
|
||||
|
|
@ -136,6 +138,10 @@ async def update_task_config(
|
|||
form_data.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE
|
||||
)
|
||||
|
||||
request.app.state.config.TRANSLATION_LANGUAGES = (
|
||||
form_data.TRANSLATION_LANGUAGES
|
||||
)
|
||||
|
||||
return {
|
||||
"TASK_MODEL": request.app.state.config.TASK_MODEL,
|
||||
"TASK_MODEL_EXTERNAL": request.app.state.config.TASK_MODEL_EXTERNAL,
|
||||
|
|
@ -152,6 +158,7 @@ async def update_task_config(
|
|||
"ENABLE_RETRIEVAL_QUERY_GENERATION": request.app.state.config.ENABLE_RETRIEVAL_QUERY_GENERATION,
|
||||
"QUERY_GENERATION_PROMPT_TEMPLATE": request.app.state.config.QUERY_GENERATION_PROMPT_TEMPLATE,
|
||||
"TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE": request.app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
|
||||
"TRANSLATION_LANGUAGES": request.app.state.config.TRANSLATION_LANGUAGES,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import Banners from './Interface/Banners.svelte';
|
||||
|
||||
import LangPicker from './LangPicker.svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
|
@ -41,7 +43,8 @@
|
|||
ENABLE_SEARCH_QUERY_GENERATION: true,
|
||||
ENABLE_RETRIEVAL_QUERY_GENERATION: true,
|
||||
QUERY_GENERATION_PROMPT_TEMPLATE: '',
|
||||
TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE: ''
|
||||
TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE: '',
|
||||
TRANSLATION_LANGUAGES: []
|
||||
};
|
||||
|
||||
let promptSuggestions = [];
|
||||
|
|
@ -430,6 +433,12 @@
|
|||
</div>
|
||||
|
||||
{#if $user?.role === 'admin'}
|
||||
<div class="flex w-full justify-between">
|
||||
<div class=" self-center text-xs">
|
||||
{$i18n.t('Select languages for translations')}
|
||||
</div>
|
||||
<LangPicker bind:selected={taskConfig.TRANSLATION_LANGUAGES} />
|
||||
</div>
|
||||
<div class=" space-y-3">
|
||||
<div class="flex w-full justify-between mb-2">
|
||||
<div class=" self-center text-xs">
|
||||
|
|
|
|||
132
src/lib/components/admin/Settings/LangPicker.svelte
Normal file
132
src/lib/components/admin/Settings/LangPicker.svelte
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
<script lang="ts">
|
||||
export let selected: string[] = []; // array of selected codes
|
||||
|
||||
let query = '';
|
||||
let open = false;
|
||||
let highlighted = 0;
|
||||
let inputEl: HTMLInputElement | null = null;
|
||||
|
||||
const codes: string[] = [
|
||||
"aa","ab","ae","af","ak","am","an","ar","as","av","ay","az",
|
||||
"ba","be","bg","bh","bi","bm","bn","bo","br","bs",
|
||||
"ca","ce","ch","co","cr","cs","cu","cv","cy",
|
||||
"da","de","dv","dz",
|
||||
"ee","el","en","eo","es","et","eu",
|
||||
"fa","ff","fi","fj","fo","fr","fy",
|
||||
"ga","gd","gl","gn","gu","gv",
|
||||
"ha","he","hi","ho","hr","ht","hu","hy","hz",
|
||||
"ia","id","ie","ig","ii","ik","io","is","it","iu",
|
||||
"ja","jv",
|
||||
"ka","kg","ki","kj","kk","kl","km","kn","ko","kr","ks","ku","kv","kw","ky",
|
||||
"la","lb","lg","li","ln","lo","lt","lu","lv",
|
||||
"mg","mh","mi","mk","ml","mn","mr","ms","mt","my",
|
||||
"na","nb","nd","ne","ng","nl","nn","no","nr","nv","ny",
|
||||
"oc","oj","om","or","os",
|
||||
"pa","pi","pl","ps","pt",
|
||||
"qu",
|
||||
"rm","rn","ro","ru","rw",
|
||||
"sa","sc","sd","se","sg","si","sk","sl","sm","sn","so","sq","sr","ss","st","su","sv","sw",
|
||||
"ta","te","tg","th","ti","tk","tl","tn","to","tr","ts","tt","tw","ty",
|
||||
"ug","uk","ur","uz",
|
||||
"ve","vi","vo",
|
||||
"wa","wo",
|
||||
"xh",
|
||||
"yi","yo",
|
||||
"za","zh","zu"
|
||||
];
|
||||
|
||||
const allOptions = codes.map(code => ({ code, label: code }));
|
||||
|
||||
$: suggestions = query
|
||||
? allOptions
|
||||
.filter(o => !selected.includes(o.code))
|
||||
.filter(o => o.code.includes(query.toLowerCase()))
|
||||
.slice(0, 10)
|
||||
: allOptions.filter(o => !selected.includes(o.code)).slice(0, 10);
|
||||
|
||||
function add(code: string) {
|
||||
if (!selected.includes(code)) selected = [...selected, code];
|
||||
query = '';
|
||||
highlighted = 0;
|
||||
inputEl?.focus();
|
||||
}
|
||||
|
||||
function remove(code: string) {
|
||||
selected = selected.filter(c => c !== code);
|
||||
inputEl?.focus();
|
||||
}
|
||||
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
if (!open && (e.key === 'ArrowDown' || e.key === 'Enter')) open = true;
|
||||
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
highlighted = Math.min(highlighted + 1, suggestions.length - 1);
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
highlighted = Math.max(highlighted - 1, 0);
|
||||
} else if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (suggestions.length > 0) add(suggestions[highlighted].code);
|
||||
} else if (e.key === 'Escape') {
|
||||
open = false;
|
||||
}
|
||||
}
|
||||
|
||||
function clickOutside(node: HTMLElement) {
|
||||
const onDocClick = (e: MouseEvent) => {
|
||||
if (!node.contains(e.target as Node)) open = false;
|
||||
};
|
||||
document.addEventListener('mousedown', onDocClick);
|
||||
return { destroy() { document.removeEventListener('mousedown', onDocClick); } };
|
||||
}
|
||||
</script>
|
||||
|
||||
<div use:clickOutside class="w-full relative">
|
||||
<div class="flex flex-wrap gap-1 border rounded px-2 py-1 min-h-[2.5rem] items-center">
|
||||
{#each selected as code (code)}
|
||||
<span class="px-2 py-0.5 bg-gray-200 dark:bg-gray-700 rounded flex items-center gap-1 text-xs">
|
||||
{code}
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`Remove ${code}`}
|
||||
class="text-xs font-bold"
|
||||
on:click={() => remove(code)}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
{/each}
|
||||
|
||||
<input
|
||||
bind:this={inputEl}
|
||||
class="flex-1 outline-none bg-transparent min-w-[5ch]"
|
||||
placeholder="Type code..."
|
||||
bind:value={query}
|
||||
on:input={() => { open = true; highlighted = 0; }}
|
||||
on:keydown={onKeydown}
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={open}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if open && suggestions.length > 0}
|
||||
<ul
|
||||
class="absolute z-10 mt-1 w-full max-h-40 overflow-auto border rounded bg-white dark:bg-gray-900 shadow-md"
|
||||
role="listbox"
|
||||
>
|
||||
{#each suggestions as s, i (s.code)}
|
||||
<li
|
||||
role="option"
|
||||
aria-selected={i === highlighted}
|
||||
class="px-3 py-1 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 {i === highlighted ? 'bg-gray-200 dark:bg-gray-700' : ''}"
|
||||
tabindex="0"
|
||||
on:click={() => add(s.code)}
|
||||
on:keydown={(e) => { if(e.key==='Enter') add(s.code) }}
|
||||
>
|
||||
{s.code}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
Loading…
Reference in a new issue