add the configuration for selecting the languages codes son they can be used for translations

This commit is contained in:
u80861711 2025-08-27 19:18:05 +02:00
parent 058de98975
commit a280f65249
5 changed files with 159 additions and 2 deletions

View file

@ -1659,6 +1659,12 @@ ENABLE_AUTOCOMPLETE_GENERATION = PersistentConfig(
os.environ.get("ENABLE_AUTOCOMPLETE_GENERATION", "False").lower() == "true", 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 = PersistentConfig(
"AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH", "AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH",
"task.autocomplete.input_max_length", "task.autocomplete.input_max_length",

View file

@ -397,6 +397,7 @@ from open_webui.config import (
QUERY_GENERATION_PROMPT_TEMPLATE, QUERY_GENERATION_PROMPT_TEMPLATE,
AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE, AUTOCOMPLETE_GENERATION_PROMPT_TEMPLATE,
AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH, AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH,
TRANSLATION_LANGUAGES,
AppConfig, AppConfig,
reset_config, 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_TAGS_GENERATION = ENABLE_TAGS_GENERATION
app.state.config.ENABLE_TITLE_GENERATION = ENABLE_TITLE_GENERATION app.state.config.ENABLE_TITLE_GENERATION = ENABLE_TITLE_GENERATION
app.state.config.ENABLE_FOLLOW_UP_GENERATION = ENABLE_FOLLOW_UP_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 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_admin_chat_access": ENABLE_ADMIN_CHAT_ACCESS,
"enable_google_drive_integration": app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION, "enable_google_drive_integration": app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
"enable_onedrive_integration": app.state.config.ENABLE_ONEDRIVE_INTEGRATION, "enable_onedrive_integration": app.state.config.ENABLE_ONEDRIVE_INTEGRATION,
"translation_languages": app.state.config.TRANSLATION_LANGUAGES,
} }
if user is not None if user is not None
else {} else {}

View file

@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, Response, status, Request
from fastapi.responses import JSONResponse, RedirectResponse from fastapi.responses import JSONResponse, RedirectResponse
from pydantic import BaseModel from pydantic import BaseModel
from typing import Optional from typing import Optional, List
import logging import logging
import re 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, "ENABLE_RETRIEVAL_QUERY_GENERATION": request.app.state.config.ENABLE_RETRIEVAL_QUERY_GENERATION,
"QUERY_GENERATION_PROMPT_TEMPLATE": request.app.state.config.QUERY_GENERATION_PROMPT_TEMPLATE, "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, "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 ENABLE_RETRIEVAL_QUERY_GENERATION: bool
QUERY_GENERATION_PROMPT_TEMPLATE: str QUERY_GENERATION_PROMPT_TEMPLATE: str
TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE: str TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE: str
TRANSLATION_LANGUAGES: Optional[List[str]] = []
@router.post("/config/update") @router.post("/config/update")
@ -135,6 +137,10 @@ async def update_task_config(
request.app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = ( request.app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = (
form_data.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE form_data.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE
) )
request.app.state.config.TRANSLATION_LANGUAGES = (
form_data.TRANSLATION_LANGUAGES
)
return { return {
"TASK_MODEL": request.app.state.config.TASK_MODEL, "TASK_MODEL": request.app.state.config.TASK_MODEL,
@ -152,6 +158,7 @@ async def update_task_config(
"ENABLE_RETRIEVAL_QUERY_GENERATION": request.app.state.config.ENABLE_RETRIEVAL_QUERY_GENERATION, "ENABLE_RETRIEVAL_QUERY_GENERATION": request.app.state.config.ENABLE_RETRIEVAL_QUERY_GENERATION,
"QUERY_GENERATION_PROMPT_TEMPLATE": request.app.state.config.QUERY_GENERATION_PROMPT_TEMPLATE, "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, "TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE": request.app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
"TRANSLATION_LANGUAGES": request.app.state.config.TRANSLATION_LANGUAGES,
} }

View file

@ -22,6 +22,8 @@
import Spinner from '$lib/components/common/Spinner.svelte'; import Spinner from '$lib/components/common/Spinner.svelte';
import Banners from './Interface/Banners.svelte'; import Banners from './Interface/Banners.svelte';
import LangPicker from './LangPicker.svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const i18n = getContext('i18n'); const i18n = getContext('i18n');
@ -41,7 +43,8 @@
ENABLE_SEARCH_QUERY_GENERATION: true, ENABLE_SEARCH_QUERY_GENERATION: true,
ENABLE_RETRIEVAL_QUERY_GENERATION: true, ENABLE_RETRIEVAL_QUERY_GENERATION: true,
QUERY_GENERATION_PROMPT_TEMPLATE: '', QUERY_GENERATION_PROMPT_TEMPLATE: '',
TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE: '' TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE: '',
TRANSLATION_LANGUAGES: []
}; };
let promptSuggestions = []; let promptSuggestions = [];
@ -430,6 +433,12 @@
</div> </div>
{#if $user?.role === 'admin'} {#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=" space-y-3">
<div class="flex w-full justify-between mb-2"> <div class="flex w-full justify-between mb-2">
<div class=" self-center text-xs"> <div class=" self-center text-xs">

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