A response with exactly one citation never showed its relevance badge, even though the source carried a perfectly valid score. Adding a second citation made the badge appear for that same source, so the score looked like it came and went at random.
calculateShowRelevance hides relevance when a result set mixes distance metrics (cosine in -1..1 next to unbounded L2), since those numbers are not comparable. With a single distance that check degenerates: distances.length - 1 is 0, so the "all but one are out of range" clause matches whatever the value is, and the score is hidden. A lone distance cannot be a mix of two metrics, so it now returns before the outlier check runs.
Sets of two or more distances behave exactly as before, mixed-metric sets are still suppressed.
Fixes#29646
Sending a message to an arena model failed with "'JSONResponse' object has no attribute 'body_iterator'" whenever the backing provider answered with an HTTP error, so the real error never reached the user. Non-streaming requests on an arena model, such as title and tag generation, broke the same way with "'JSONResponse' object is not a mapping".
The arena wrapper assumed the sub-model call always returns a stream for a streaming request and a dict for everything else, but the OpenAI-compatible router returns a plain response object as soon as the provider answers 4xx or 5xx. Both arms now hand that response straight back, which is exactly what the non-arena path already does, so the existing error handling turns it into the usual error message in the chat.
Verified against a matrix of streaming and non-streaming requests with the sub-model returning a stream, a dict, a JSONResponse and a PlainTextResponse: both crashes are gone and the two success paths are unchanged, including the selected_model_id prelude on the stream.
Fixes#29658
Adopts the loanwords normally used in Spanish-language developer and AI
interfaces for three terms the locale currently calques, and applies each
one consistently across the file.
- prompt: Indicador -> Prompt (74 strings)
- pipeline: Tubería -> Pipeline (14 strings)
- delete: Borrar -> Eliminar (62 strings), matching the rendering the
locale already uses for "remove" and closing the Borrar /
Eliminar split that applied both words to the same action
- Admin -> Administrador, Email -> Correo electrónico (3 strings)
"Indicador" means indicator or gauge and is not how Spanish-language AI
tools refer to a prompt. "Tubería" is a physical pipe; the file already
hedged once with "Tuberías (Pipelines)". Gender agreement is updated where
it changes, since Pipeline is masculine and Tubería feminine.
Before this change the locale rendered "delete" as Borrar in 34 strings and
Eliminar in 28, with no distinction in meaning between them.
This changes wording contributed by the Spanish community rather than
fixing defects, so it is a terminology decision rather than a correctness
fix, and is kept separate from the translation work for that reason.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Two entries lost their interpolation placeholder, so they render
literally instead of being substituted:
- "{{ models }}" had the placeholder name itself translated
- the 50-word summary prompt replaced {{topic}} with a bracketed note
The rest change the meaning or contradict neighbouring strings:
- "Enter Key Behavior" was read as the verb "enter", turning the label
for how the Enter key behaves into "type in the behaviour key", while
its own description already explains the setting correctly
- "Regenerate Menu" was read as "refresh the menu" rather than the menu
of regenerate actions its description describes
- "Enter reasoning effort" was not meaningful Russian
- "Enter Bing Search V7 Subscription Key" mentioned an endpoint that the
source string does not
- "Web Search in Chat" called the feature a search engine
- "Enter Application DN" said ND; "Enter Bocha Search API Key" said APi
- "Toast Notifications for New Updates" dropped "new" and used the wrong
form of the preposition
- "Enter Top K Reranker" used a term its own label does not, and
"Select a reranking model engine" differed from every other reranking
string
- two of the four iframe sandbox switches did not follow the wording of
the other two
- "Follow-Up Auto-Generation" capitalised a word mid-phrase
Only values changed; the keys and their order are untouched.
Chunk metadata inserted into the vector DB carries arbitrary client-
supplied fields (e.g. custom metadata from file uploads), so it needs
the same size cap, type coercion and null-byte sanitization every
other backend already applies through process_metadata(). Four
backends never called it: Qdrant, Qdrant multitenancy, Milvus
multitenancy and Oracle23ai, so an oversized or malformed metadata
blob went into those unfiltered.
Wire process_metadata() in at each backend's single insert/upsert
chokepoint, matching the pattern already used by the other eleven
backends.
The built-in chat and knowledge grep tools awaited their matching helper directly, so the search ran on the event loop and held it for as long as the match took. Both call sites now hand the helper to a worker thread.
Output and error handling are unchanged: 17 cases (literal, regex, case-insensitive, count-only, no-match, invalid pattern, rejected quantifiers, missing file data, result truncation) compare byte-for-byte against the previous behaviour. The matcher's time budget lives in a contextvar, which asyncio.to_thread copies into the worker, so budget scoping still behaves as before.
Inter ships arrow substitutions as OpenType contextual alternates, and
Open WebUI sets no font-variant-ligatures, so a sequence like <--- is
stored and sent exactly as typed while being drawn as an arrow. Reported
as text conversion in #29594, where the memory rule had in fact worked
and only the display was misleading.
Reading the DOM back with the bundled Inter file loaded in isolation
confirms every field holds U+3C U+2D U+2D U+2D. Only the glyphs differ.
no-contextual rather than none: none, no-contextual, and
font-feature-settings "calt" 0 all fix it, which places the arrow in
contextual alternates. no-contextual disables that one feature and
leaves standard ligatures intact, so ordinary prose is unaffected.
Set on html, next to the existing document-wide typography, so it
inherits to editable fields and rendered content without a new selector.
Scoping it to inputs would fix the field you type into and leave the
memory list and chat messages still drawing arrows.
GET /api/version/updates returned the running version as latest whenever
the GitHub request failed, so an instance that cannot reach GitHub
reported itself up to date however far behind it was. The exception was
logged at debug, below the default level, so nothing recorded that the
check never happened.
The failure path now returns latest: None and logs at warning.
A null latest cannot be passed to compareVersion as it stood.
current.localeCompare(null) coerces to the string "null", and "0.10.2"
sorts before it, so the function returned true. The backend change alone
would have turned a false (latest) into a false update-available plus a
toast, so the guard is part of the fix.
The three callers stop substituting the running version in their catch,
and the two badge surfaces gain a third state. When latest is unknown
the badge is plain text, since there is no release to link to.
Admin Settings > General was wrong in a worse way than reported: it
initialised updateAvailable false with latest set to the running
version, and never checked on mount, so it claimed (latest) having made
no request at all. It now matches About.svelte, which starts unknown and
checks on mount.
Closes#29580
A web search is not something that can be cited. The search engine
returns a title, a link and a one line snippet for each hit, and the
model never opens any of those pages. Emitting them as citation sources
produced one <source> tag per result, all named search_web with empty
bodies, and the citation template then instructed the model to cite them
by id. Models either hesitated visibly or attached an id to content from
a different result, which the citations panel then resolved to a title
that looked plausible, so the misattribution read as correct.
Web search results now stay in the tool output the model reads, and stop
being offered as things to cite. Where the model needs to cite a page it
calls fetch_url, whose citation names the URL and already works.
Web search results no longer appear in the citations panel. That is the
point of the change: the panel was offering pages that nothing had read.
Scoped to the native tool-calling path. The legacy handler cites every
tool result as one opaque source and does not single out web search, so
it is left alone rather than special-cased.
Saving any tool or function in the admin UI threw "ModuleNotFoundError:
No module named 'click'" in the browser, even on a fresh tool with no
custom code, because the click name never appears in user code at all.
The in-browser formatter runs black through a Pyodide/micropip worker.
black needs click, mypy_extensions, pathspec, platformdirs and pytokens
at import time, but the vendored pyodide-lock.json records none of
these as black's dependencies, and micropip only walks a locked
package's declared deps when install() is given explicit constraints,
which this worker never does. So only black itself ever got installed.
Requesting the five packages explicitly alongside black fixes it
without touching the worker or the lock file.
Ten strings were still untranslated in de-DE, so German users saw raw English keys in the settings sidebar and in the interface settings. The settings group headings (Basics, Services, Preferences, Data, AI, Quality, Experience) all rendered in English above otherwise German tab names, plus the font family label and its description, and the multi-file upload failure toast in a knowledge base.
All ten now have German values, chosen to match the terminology the catalog already uses: AI as KI, Services as Dienste (matching Dienstkonto and the existing service endpoint strings), Interface as Benutzeroberfläche in the font description, and the third-person descriptive voice the neighbouring setting descriptions use. Experience is rendered as Benutzererlebnis rather than Darstellung because that group covers audio as well as interface and images. Both interpolation placeholders in the upload toast are preserved.
Only de-DE is touched. No key was added, removed or reordered, and no already-translated string was changed. The catalog now has no empty values left.
Fills the 917 empty strings in the es-ES locale and repairs 94 existing
entries that rendered incorrectly. No keys are added or removed, and the
terminology chosen by the Spanish community is preserved throughout: new
strings reuse the glossary already present in the file.
Filled strings:
- Reuse existing renderings so each English term keeps one Spanish form
(chunk -> Fragmento, knowledge base -> Base de Conocimiento,
workspace -> Espacio de Trabajo, skill -> Habilidad).
- Include the settings sidebar group headers added in 006f95ee5
(Basics, Services, Preferences, Data, AI, Quality, Experience), which
were otherwise falling back to English in the settings modal.
- Add real grammatical plural forms for every one/many/other variant.
Spanish requires the `many` CLDR category, which English does not have.
- Preserve every {{placeholder}}, backtick, URL, HTML tag and CLI flag.
Repaired strings:
- 4 broken interpolations, including "Deleted {{name}}", whose placeholder
had itself been translated to {{nombre}} and so rendered literally.
- 6 plural groups whose values carried the i18next key suffix as visible
text (e.g. "{{count}} seleccionados_únicos"), and one where "_one" had
been translated as the adjective "únicas".
- "Reset" now reads Restablecer rather than Reiniciar ("restart"), which
misdescribed destructive actions such as Reset Vector Storage/Knowledge.
- "Access" (Acceso) separated from "Permissions" (Permisos); the *Access
family was previously split between the two.
- "Chats Public Sharing" and "Chats Open Sharing" no longer render
identically in the group permissions panel.
- "You" corrected to "Tú"; unaccented "Tu" is the possessive "your", and
this string labels every user message bubble.
- "Channels" corrected from the singular "Canal".
- ~49 misspellings and missing accents (Conexxión, Interprete, Publicamente,
Busqueda, Incrustración, Añador, Wev, actualiada, mantentrá).
Verified: 0 empty strings, key set identical to dev, 0 placeholder
mismatches, prettier --check passes, and all strings render correctly
through i18next 23.16.8 using the application's own options.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The settings navigation group labels added in 006f95ee5 (Basics,
Services, Preferences, Data, Experience, AI, Quality) were left
empty in every locale, so ru-RU and uk-UA fell back to the English
key text. Fill them for both locales.
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Fill all remaining untranslated strings so both locales reach full
coverage:
- ru-RU: 967 strings translated
- uk-UA: 2110 strings translated
ru-RU also aligns terminology with the existing translations
(webhook -> вебхук, sub-agent -> субагент, reranker -> реранкер,
chunk -> чанк, parsing -> розбір/разбор) and standardizes "web search"
to «Поисковая система» across the whole file.
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Custom metadata attached to a file upload (e.g. via the API's
metadata field) was stored on the file row but silently dropped
when the file was chunked and embedded for RAG, so it never reached
the LLM through retrieved sources.
process_file() builds chunk metadata in several branches; three of
them already merge the file's meta dict in, but the branch used for
a fresh upload processed through a document loader did not. Bring
it in line with the others so custom metadata flows through
consistently regardless of upload path.
Reported in open-webui/open-webui#29486.
A direct-connection streaming request registers a per-request socket.io handler before it asks the browser to start the completion, and that handler was only removed once the response had been fully streamed. Every other way the request could end left it behind for the life of the process: the call to the browser raising, a non-success status, an ack with no arguments or an ack that is not an object, cancellation while waiting for that ack and a response body closed or garbage-collected before it finished. A client that repeatedly hits a failing direct connection grows the server's handler table and the closures it holds without bound, and nothing ever cleans it up.
The removal now runs on every exit from the request, through one named helper that pops with a default so it is safe to run twice on the paths where both the generator's finally and the response's background task fire. The guard covers the exchange up to the status read and catches BaseException, because cancellation is not an Exception, and re-raises it unchanged.
Measured across thirteen exit paths: twelve leak a handler on dev and none of those leak here. The thirteenth, a response body that is never iterated at all, behaves the same on both. 200 failing requests leave 200 handlers on dev and none on this branch. Streamed bytes on the success path, exception types and cancellation behaviour are unchanged.
The regex detection helpers wrote their backslash escapes in ordinary string
literals, so Python reported six invalid escape sequences on import. They work
today because Python leaves an unrecognised escape as its two characters, but
that behaviour is deprecated and becomes a syntax error in a future release, at
which point the knowledge filesystem tools stop importing at all.
The literals are now raw, which is the same two characters with no warning.
Pattern detection and normalisation are unchanged: verified identical output
over every string up to length five drawn from the characters these helpers
look at.
The `/` command menu is portalled into a tippy popup by
getSuggestionRenderer, and tippy's base stylesheet sets
.tippy-box { color: #fff }. The app's `transparent` theme neutralises
tippy's background, padding and margin but never its text colour, so
everything inside the popup inherits white.
DropdownMenu paints bg-white dark:bg-gray-850 dark:text-white, defining
a text colour only for dark mode, and the command rows in SlashCommands
set no colour of their own. In light theme that leaves Temporary, Model
and Settings as white text on a white menu.
Reset the colour on the transparent theme so the popup inherits from
body, which is #000 in light and #eee in dark. The secondary text and
the prompt rows were already unaffected because they set explicit
theme-aware colours.
Keeps the entries already on dev and merges in the two this branch carried, then
puts the section back into the shape the format asks for.
Added now holds the contrast accessibility mode gives the dropdown menus, their
submenus and the model picker, followed by the general improvements placeholder
and the translation entry in their reserved positions. The translation entry
moves out of Changed, takes the standard wording and drops its link, which that
entry never carries; Changed is left out, having nothing else in it.
Each entry is condensed to a single sentence, the backticked column name becomes
a quoted one, and the interface font entry gains the commit it came from, having
carried no reference at all. Fixed is ordered by reach, from the conversation
branches and the failed upgrade down to the font and the disconnect control.