[pull] main from open-webui:main - #105
Merged
Merged
Conversation
…-hidden fix: restore visibility of disabled models visible in the admin Models list
… pass (#29029) New **pt-BR** translations for items introduced in the latest releases, plus a consistency/quality pass across existing strings (grammar, tone, capitalization, pluralization). Placeholders and hotkeys preserved. No logic changes.
…ame on narrow screens (#29084)
Any `<$...>` run in a chat message was treated as an inline skill mention and removed before the request reached the model, so text like `<$(=MonthStart($(vMaxMonthEndINC)))"}, [Registration day] >` silently vanished mid-message and the model only saw the part before it. The mention regexes accepted any character except `|` and `>` as the skill id, so they matched far more than real mentions. Skill ids are already validated as `[a-z0-9_-]+` when a skill is created, so both regexes now require that charset. Ordinary text passes through untouched while `<$id>`, `<$id|Label>` and `</id|Label>` still resolve and strip as before. Verified against the reported message (now preserved verbatim) and the three mention forms. Co-authored-by: Claude <noreply@anthropic.com>
After a tool call, the model's thinking was streamed into the chat as if it were the main response, and only jumped into the collapsed Thoughts section once the turn finished. Every further tool call repeated it. Each tool round appended an empty placeholder message item to the output and sent it to the browser, then dropped it again from the copy used to offset the next round's item indices. The browser therefore held one item more than the backend counted, so the first thinking chunk of the next round was written into that leftover message item and rendered as normal text until the finished output replaced it. The placeholder is removed. It was never needed: a message item is already created when actual content arrives, and dropping it also stops an empty assistant message being sent back to the model on the follow-up request. Co-authored-by: Claude <noreply@anthropic.com>
) * fix: stop streaming responses breaking on a duplicate output key With reasoning-capable models the chat froze mid-stream: the first chunk of the answer appeared, nothing followed, and the whole message only showed up once generation finished. The browser console showed a Svelte each_key_duplicate error. When a stream event addresses an output slot past the end of the array, the missing slots were filled with the event's own item, id included, so a gap of two left two entries claiming the same id. The next chunk for that item was matched by id, landed in the first of the two, and the rendered list ended up with two items sharing a key, which Svelte refuses to update. Only the addressed slot now takes the event's item, and the slots before it are anonymous placeholders. Replayed the reported event sequence against the real code: keys are unique again and the chunks stay in order instead of being split across the copies. * fix: stream reasoning deltas when the provider also sends reasoning_details Providers such as OpenRouter emit reasoning_details alongside the reasoning text on the same delta. Merging those details cleared the pending event unconditionally, discarding the response.reasoning_text.delta that had just been built, so the client received no reasoning until the response completed and the thinking block only appeared after generation finished. The event is now only dropped when the details were all there was to report. Details persistence is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014uuEg4AXPs9zE3vVUfN1Fj --------- Co-authored-by: Claude <noreply@anthropic.com>
173 keys in the de-DE catalog still had empty values, so German users saw those strings rendered in English: the whole terminal file browser, the tool-call approval prompts, chat variables, the model manager, calendar navigation, the accessibility labels for zoom, camera and call controls, and several admin settings panels. Each string was translated against its actual call site rather than in isolation, so the grammatical form fits the widget it renders in (imperatives on buttons, nouns on labels and select options, participles on toasts). Terminology and the formal "Sie" register follow what the catalog already uses elsewhere, and technical literals were left alone on purpose: the CSV header hint mirrors the file that "Download CSV Template" actually produces, and the MIME pattern, the snake_case variable placeholder and the product names stay verbatim. Only value strings changed; key order and formatting are untouched.
… from the sidebar (#29058) Clicking a pinned model in the sidebar started a new chat with that model but sent the previous model's tool_ids and skill_ids, and they stayed until the page was reloaded. The model dropdown was unaffected, and so was temporary chat. A pinned entry links to /?model=<id>, which runs the new-chat path. That path applied the model's defaults and then restored the composer draft over the top, and the draft still held the selection from whichever model was active when it was written. The draft save is debounced, so the stale value was reliably the one read back. The draft is now restored before the defaults are applied, so the model always decides which tools and skills are active while the unsent prompt, files and approval mode are still kept. Starting a new chat with several models selected now clears the selection instead of carrying the draft's over, since there are no per-model defaults to apply in that case. Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: fengyufeiyang-dot <fengyufeiyang-dot@users.noreply.github.com>
process_pipeline_inlet_filter() and its outlet counterpart construct and tear down an aiohttp ClientSession, with its own connector and cookie jar, on every chat completion and every task generation request just to iterate an empty filter list. On deployments without pipelines, which is the default, that is wasted setup on every message. Both functions now return the payload untouched before the session is created when there is nothing to call. The per-call saving is small, a few microseconds of object construction per request on the pinned aiohttp; the point is that requests stop paying setup for a feature that is not configured.
On a phone the file preview zoom bar shows plus and minus buttons that duplicate pinch-to-zoom and sit on top of an already small preview. They are hidden on coarse pointers now, so touch users get the preview area back and still zoom the way they expect. The check is the pointer type rather than the viewport width, because what matters is whether the person can pinch, not how narrow their window is. A narrow desktop window keeps the buttons, a tablet does not. The zoom percentage doubles as the reset control and has no gesture equivalent, so it stays visible, along with all page and slide navigation. Keyboard zoom is unaffected. Word document previews are left alone: they have no pinch support at all, so hiding their buttons would remove zooming entirely. Fixes #29152
…rectory sync (#29135) * fix: surface files the browser cannot read during a knowledge base directory sync Syncing a local folder into a knowledge base could fail with nothing but "Error accessing directory": no failing file name, no network request, no server log, and no console output either, because production builds strip console.error. On Windows this happens once the absolute path of a file passes the platform limit, at which point the browser refuses to open a file it just listed. The directory scan now handles that per file. It names the first failing path and how many files are affected, and stops before anything is uploaded. Stopping is the point: a file missing from the manifest is treated as deleted by the sync, so continuing would remove the knowledge base copy of a file that still exists on disk. Dragging a folder in hit the same failure and reported nothing at all, and the Firefox picker path returned its promise without awaiting it, so a rejection escaped the error handler and surfaced only as an unhandled rejection. Both report through the existing handler now, and production builds keep console.error so the underlying exception stays visible. * fix: narrow the change to the silent drag-and-drop folder failure Dropping a folder onto a knowledge base did nothing at all when the browser refused to open one of the files inside it: the rejection escaped the async drop listener, so the user got no toast, no upload and no clue why. The listener now routes that failure through the same error handler the directory picker already uses, so one path and one message cover both ways of adding a folder. The rest of the branch is reverted. Dropping `console.error` from the esbuild `pure` list un-stripped 597 call sites across 103 files from every production bundle, which is a repo-wide logging policy change that needs its own argument. The picker-side collect-and-count machinery only reworded a toast the existing catch already showed, and the Firefox `return await` fix is a different bug in a different path.
…next-parser config (#29161)
…9142) Every chat completion request re-loads the target conversation's entire message history from the database inside drain_approved_tool_calls() before discovering there is nothing to drain: a fresh message always points at a newly minted assistant message with no stored output, so the full-history read (one SELECT of every chat_message row plus building the message map, uncached, on top of the identical read process_chat_payload already did) is pure overhead on every message. Queued tool approvals can only ever be acted on by a resume or continue request, and exactly those requests carry assistant_message_id in their payload. The drain now returns early when the field is absent, removing one O(conversation length) query per chat message while resume, continue, reject and pause flows behave exactly as before, independent of the approval mode.
The schedule label treated any rule whose text contained COUNT=1 as a single run, so counts such as 10, 12 and 14 were shown as "Once" together with the date of the first run, on the automations list and on the automation page alike. The label now matches a count of exactly one. This covers the two places that render the label. The schedule editor reads the count the same way and changes separately. Rules that carry a start date still fall through to the raw rule text, exactly as they already did without a count; that parsing gap changes separately too. Verified in a browser against the same build without these lines: ten ordinary schedules render identically in both places, and a genuine single-run schedule is still labelled as one.
Loading an automation whose rule the visual controls cannot represent switched the schedule to Custom but left the bookkeeping the seeding block reads on the previous value, so that block immediately replaced the rule with a freshly built default. The rule was lost when the editor opened, before anything was saved, and cloning carried the default across as well. Recording the switch alongside it leaves the stored rule in place. Verified in a browser against the same build without this line: a minutely rule and a yearly rule now survive reopen and save byte for byte, cloning keeps the original, and every schedule the editor itself produces, along with switching to Custom by hand, behaves exactly as before.
* chore: add changelog entries for 0.11.2 Documents the commits landed on dev after the 0.11.1 changelog entry. Added covers the richer terminal file previews with page thumbnails, the reduced per-message overhead on deployments without pipelines, more room in file previews on touch screens, and the wider accessibility coverage. Fixed covers twelve user-facing corrections, among them stalled streaming on reasoning models, post-tool-call thinking leaking into replies, banners with underlined text failing to render, pinned models carrying the previous model's tools, disabled admin models, skill-mention text loss, and the workspace Knowledge list staying empty. Changed records the rename of High Contrast Mode to Accessibility Mode. Also records the Polish, Simplified Chinese, German, Catalan, and Portuguese (Brazil) translation updates. Issue template, pull request template, Docker workflow and locale catalog regeneration edits are omitted as they are not user-facing. * chore: add the Redis Cluster stop and Valves overflow entries to 0.11.2 Documents the two user-facing commits landed on dev since the previous changelog entry. Fixed gains the Redis Cluster stop signal, where the stop button did not take effect when the request landed on a different instance than the one streaming the reply, placed with the streaming and thinking entries it shares a domain with; and the Valves dialog overflow, where a valve with a long line of selected options stretched its input past the edge of the dialog and over the page behind it, placed with the narrow screen layout entry. The section date moves to 2026-08-29 to cover the newer commits. The issue and pull request template wording and the German locale catalog are omitted, the former as contributor-facing rather than user-facing, the latter as German is already named in the translation entry. * chore: add the recurring calendar event entries to 0.11.2 Documents the calendar recurrence changes landed on dev after the previous changelog commit. Fixed records repeating events working out their occurrences from their own date and time rather than from a start date carried inside the repeat rule, which could place them on the wrong weekday or hour. Changed records the new limit refusing events that repeat more often than once a day. The EXRULE handling and the timezone resolution rewrite are omitted: both reach the same user-visible outcome as before, only by a clearer route. * chore: add the security advisory notice to 0.11.2 Adds the standard advisory notice as the first item in the Fixed section. The calendar recurrence work landed on dev under an unmarked commit message and bounds the occurrences a single stored event can force the server to walk, so the release carries a fix whose details are not spelled out in the entries below it. The notice is the fixed wording and takes no reference links of its own; the individual entries keep theirs. * chore: add the structured output crash entry to 0.11.2 Fixed records the conversation that failed in the browser and stopped showing the assistant reply until a reload, together with the recovery of chats already saved in that state. It sits directly below the advisory notice as the most disruptive correction in the section. The Irish catalog update joins the translation entry; the Portuguese (Brazil) pass needs no change there, as that language is already named. * chore: add the dropdown, SQLite search and tool server entries to 0.11.2 Fixed gains the dropdown that opened past the edge of a narrow screen and the dropdown list that ignored the interface theme, kept together as one group, plus case-insensitive matching for accented and non-Latin text on SQLite installs, placed beside the existing SQLite entry, and the tool server connection that was sent an empty authorization header when saved without a key. The advisory notice already stands at the top of the section, so the unmarked backend commit needs no further flag there. * chore: add the chat reload entry to 0.11.2 Fixed records the conversation that reloaded itself whenever any response in it finished while an older unfinished reply sat in the history, now narrowed to the reply the update concerns. It joins the response lifecycle group below the stop entry. The commit carries no pull request or issue, so it is referenced by commit. * chore: add the interface font and touch resize entries to 0.11.2 Added gains the font family field in Interface settings, which applies a locally installed font across the interface and falls back to the standard font when cleared, and the side panel divider that can now be dragged by touch or stylus while a mouse drag keeps tracking beyond the window edge. Both sit above the reserved accessibility, general improvements and translation entries, with the touch entry beside the existing touch screen one. Each aspect of the font setting arrived in a single commit, so it is recorded as one entry with no separate note for its configurability. * chore: add the automation schedule and model registry entries to 0.11.2 Added records the model list refresh that no longer has every worker rewrite the whole list to the shared cache when nothing changed, placed beside the existing performance entry. Fixed records the two automation schedule defects from the same pull request as separate entries, because the symptoms differ: a counted schedule shown as a single run and rewritten to one on save, and a schedule carrying a start date losing its weekly or monthly setting and printing raw rule text in the list. Both join the scheduling group below the recurring event entry. * chore: extend the touch resize entry to the main sidebar The sidebar divider received the same pointer handling the side panel dividers got, so the existing entry now names the sidebar and carries both commits rather than repeating itself as a second entry. The follow-up that moved the divider border to the matching edge is listed with them, being a further correction to the same divider and too small to record on its own. * chore: record the preview focus and caveat changes in 0.11.2 Fixed gains the arrow keys that paged an open document or slide preview from anywhere on the page, which also took those keys away from the field being typed in, now confined to the focused preview. The richer previews entry absorbs the removal of the notice warning that a preview might differ from the download, the caveat having gone with the approximation it described, and the accessibility entry absorbs the previews becoming reachable by keyboard and announcing themselves. Neither warranted an entry of its own, both being continuations of work already recorded.
…ound trips (#28835) * perf: stop the Socket.IO session pool blocking the websocket event loop With WEBSOCKET_MANAGER=redis the session pool is a synchronous Redis client, so every call into it blocks the whole worker's event loop, not just the caller. Two paths did it constantly: the orphan reaper walked the pool one round trip per session with no await anywhere, freezing the loop for the entire sweep every cycle, and nearly every socket event re-read the sender's session back out of Redis. Other users' events and every in-flight generation on that pod wait behind both. The reaper now walks the pool in HSCAN batches and deletes in bulk, yielding between batches, and no longer sleeps past half the lock TTL, which previously guaranteed a failed renew every cycle. The per-event reads are gone: Socket.IO events only reach the worker holding the connection, and that worker already saved the same session dict locally when the user authenticated, so it was asking Redis for its own data. The writes stay, since those are what other pods read. Measured at 4000 users / 16 containers, Redis 1.1 ms away: | | before | after | |---|---|---| | reaper sweep, 5k sessions | 5.6 s, loop frozen throughout | 62 ms, 4.2 ms worst block | | same, crash recovery with every session expired | 11.5 s | 96 ms | | heartbeat / usage ping / disconnect | 2 / 3 / 2 round trips | 1 / 2 / 1 | | 50-member channel post | 50 round trips, 57.2 ms block | 0 round trips, 0.02 ms | | loop time per wall second at rest | 103 ms (10.3%) | 67 ms (6.7%) | The alternative, converting RedisDict to the async client, fixes the same paths with a far larger blast radius (every call site gains await, and `in`/`[]`/`del` cannot be awaited so the dict interface goes) and still round-trips for data already in memory. Two deliberate behaviour changes: a heartbeat re-adds a session the reaper already removed, so a tab that survives a stall recovers instead of staying out of the pool until it reconnects; and disconnect no longer skips Yjs document cleanup when the pool entry is already gone, which previously leaked that document's update log forever. Closes #28172 * perf: cut disconnect and user session lookup pool round trips, harden the session reaper Follow-up on top of the session pool reaper branch. With WEBSOCKET_MANAGER=redis two paths still blocked the worker's event loop on synchronous Redis calls. Every disconnect listed all models in use cluster-wide and fetched each one individually, one blocking round trip per model. Disconnecting all sessions of a user (admin role change or deletion) pulled the entire session pool in one HGETALL and decoded every entry in a single uninterrupted block. Disconnect now fetches the usage pool once with items(), going from 2+N+M round trips to 2+M (N models in use cluster-wide, M models the session used), and its delete of an emptied model entry is KeyError-guarded because another node can remove the same key between snapshot and delete; unguarded, that race aborted the handler and skipped its Yjs document cleanup. The user session lookup reuses the reaper's HSCAN batches and yields to the loop between pages. The reaper previously died permanently on the first Redis connection error, on every node at once during an outage; it now logs, releases the lock and returns to retrying acquisition. * refac: keep the socket pool perf work to the round trips A review pass on this branch turned up four changes riding along with the round-trip work without belonging to it, so they are backed out here. The `disconnect` handler keeps its `if sid in SESSION_POOL:` guard, so USAGE_POOL and ydoc cleanup stay off the path for sockets that never authenticated. `RedisDict.set()` keeps its own inline HDEL. `get_session_ids_by_user_id` stays synchronous over one HGETALL, since it runs on user delete and role change rather than per message. The crash-resilience wrapper around the reaper loop is dropped; if that guard is worth having, it belongs in its own change. What stays is the perf part. The reaper now sweeps the pool in bounded HSCAN batches and deletes expired sids with one HDEL per batch, down from HKEYS plus an HGET and a per-sid HDEL across the whole pool. The `disconnect` handler reads USAGE_POOL with a single HGETALL, down from HKEYS plus one HGET per model in use. Session lookups in the socket handlers come from the local Socket.IO store, which removes one Redis GET from every heartbeat, usage, channel and ydoc event. Naming and annotations follow the file: `get_session_pool_batches` for the module's `get_` prefix, `RedisDict.pop_many` so both reaper branches use one word for removing keys, a named `SCAN_BATCH_SIZE`, and types on the new helpers. * fix: invalidate the RedisDict write signature on batch delete RedisDict.set() skips the write when the payload fingerprint matches the last one this process wrote, so a mutation that goes around set() has to clear that fingerprint. The new batch delete did not, leaving a stale fingerprint behind: the next refresh with identical content is treated as already written and silently skipped, so the hash stays empty. Renamed pop_many to delete_many. In a dict emulation pop removes and returns; this returns nothing and cannot without an extra HMGET, so the name promised something it does not do. delete_many matches __delitem__ and the HDEL underneath. Its only call site is the session pool reaper, whose behaviour is unchanged: same fields deleted, same batching, same return.
…d chunk (#28858) * perf: stop re-parsing the whole tool-argument buffer on every streamed chunk Converting an OpenAI stream to Anthropic events buffers each tool call's arguments and, to find out when the JSON is complete, parsed the entire buffer again on every chunk. A tool call with large arguments pays that parse thousands of times, and the cost grows with the square of the argument size. The parse now runs only when the buffer could actually be complete. A JSON object can only close on its final brace, so a chunk that does not end there cannot complete it. Arguments that are not an object, or that start with whitespace, keep parsing on every chunk exactly as before. Measured on CPython 3.12 with 130 KB of tool arguments over 7648 chunks: | | before | after | |---|---|---| | parses | 7648 | 1 | | time | 382 ms | 0.82 ms | The block closes on exactly the same chunk as before, verified by replaying randomized fragmentations of objects with braces inside strings, escaped characters, unicode escapes, arrays, bare scalars, leading and trailing whitespace and a buffer that never completes, against both JSON backends. * refactor: read tool['arguments'] directly in the JSON completion guard Restores the pre-existing comment above the guard to its original wording and drops the `buffered` local, so the guard and the parse call both read `tool['arguments']`, the name the rest of the file already uses for that buffer. Behaviour is unchanged: same three conditions in the same order, same short-circuit result. * perf: strip whitespace in the tool-argument completion guard The character guard only looked at the first and last byte of the buffer, so a chunk that ended in a space still triggered a full parse and a tool argument with leading whitespace fell back to parsing on every chunk. Stripping first collapses both cases to a single parse at the end of the stream. Measured on a streamed tool call, parses and wall time for the whole stream, orjson on the left of the slash and stdlib json on the right: | argument shape | before | after | |---|---|---| | 20 KB string, char-by-char deltas | 3678 parses, 56 / 28 ms | 1 parse, 3.1 / 3.1 ms | | 200 KB, 20-char deltas | 1473 parses, 176 / 63 ms | 1 parse, 3.1 / 2.8 ms | | 8 KB prose, leading whitespace | 715 parses, 5.5 / 2.5 ms | 1 parse, 0.18 ms | | 8 KB of spaces inside a value | 713 parses, 6.0 / 2.9 ms | 1 parse, 0.83 / 0.72 ms | The strip costs about 20 ns per delta on arguments that have no whitespace at either end, which is where the old form was already optimal: a 20 KB compact argument goes from 191 to 216 us over 1786 deltas. Soundness is unchanged, the guard can still only skip a parse that would have failed: 2660892 buffers (exhaustive to length 6 over a JSON-lexical alphabet, every prefix of 26 named cases with a trailing byte appended, and every codepoint below U+3000 after a complete document) with zero cases where a parse would have succeeded.
* perf: stop scanning every socket.io payload for binary data Every socket.io event the backend sends was first walked recursively to check whether any value was a bytes object needing binary attachment framing. Open WebUI never emits binary, so the walk always came back empty and the work was thrown away. It has no early exit and allocates at every level, so it scaled with the full size of the message, and the messages are the big ones: chat streaming re-emits the whole assistant message on every update, note collaboration sends document state as a JSON array with one entry per byte. With the Redis manager it ran once per instance per emit on top of that, since every instance builds its own copy of the packet. The server now installs a Packet subclass with binary events off, through python-socketio's own serializer hook, the same mechanism its msgpack serializer uses. Inbound binary attachments are decoded to int lists rather than refused, so the one frontend path that sends a raw Uint8Array keeps working and handlers can still echo client data straight back out. One scan remains in multi-instance setups: python-socketio's Redis manager calls it on the base Packet class directly, where the serializer hook cannot reach. Measured per encode: | payload | before | after | |---|---|---| | chat completion re-emit (7.5 KB JSON) | 30 us | 13 us | | collaborative document state (292 KB JSON) | 9.0 ms | 1.7 ms | With ENABLE_ORJSON=true, where the scan is nearly the whole encode cost: 20 us to 2.3 us, and 7.8 ms to 0.14 ms. Closes #28164 * fix: match the other Yjs emits and send the full state as an array Collaboration.ts sent the initial full-document state as a raw Uint8Array while the other two Yjs emit sites convert with Array.from first. socket.io framed that one as a binary attachment, so with the JSON-only packet class the server turns it into a list of ints and re-broadcasts it as JSON: a 10240-byte state update becomes 36561 JSON characters. Converting at the emit site keeps the wire form uniform across all three sites. Also trims the JSONOnlyPacket docstring, which claimed attachments already arrive as int lists when the override is what converts them, and annotates the new reconstruct_binary parameters.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )