master
212
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8ab29faec4 |
ui: keep chats at the bottom until the reader scrolls up (#31311)
* ui: land at the bottom of a conversation every time it opens Opening a long conversation often stopped short of its end: - the press grace counted from page load (pressedAt = 0), so a chat that landed within 400 ms of a refresh did not follow the rows realizing after the jump - the click that opened a chat (sidebar row, tab) started the same grace, so navigating by click had the same problem - any scroll event away from the bottom stopped following, including the ones the browser fires itself while the chat settles - the chat screen outlives its routes, so going back to the last landed conversation through the new-chat screen skipped the landing entirely scrollToBottom() now clears the grace, only an upward scroll away from the bottom stops following, and every navigation lands anew. Navigations into a chat also disable SvelteKit's scroll reset, which moved the previous conversation to its top for a few frames. Where overflow-anchor is supported, a 1px pin after the messages is the only scroll anchor while following, so the browser keeps the bottom in place itself; WebKit keeps the resize observer. Assisted-by: Claude (Opus 5.5 Medium) * ui: let nested agentic content scrollers chain to the chat Reasoning, shell output and code blocks used overscroll-behavior: contain, so the wheel stopped dead at their edges instead of scrolling the conversation. Assisted-by: Claude (Opus 5.5 Medium) * ui: stop following the stream as soon as the reader scrolls up A trackpad or smooth wheel scrolls in steps smaller than the at-bottom threshold, so each step up was re-stuck and snapped back on the next growth while a response streamed. Any upward move that is not content shrinking now releases at once. The attach effect also read the hook's own state, so every flip re-attached and re-measured, which undid such a release. Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
f5d4d75419 |
ui: show submit button when the chat input has content (#31324)
ChatFormActions gated the mic and stop buttons on a `canSubmit` prop that ChatForm never passed, so it was always false and typing text did not swap them for the submit button. Use `canSend` instead and also hide the skip-reasoning button while there is content to send. Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
3ed915e309 |
ui: reveal the collapsed sidebar from its logo and toggle it there (#31307)
* ui: reveal the collapsed sidebar from its logo and toggle it there The collapsed sidebar now shows only the logo; hovering it reveals the icon strip until the pointer leaves. The expanded sidebar drops its top-right collapse button and the logo collapses it on hover instead. Assisted-by: Claude (Opus 5.5 Medium) * ui: fade the peeked sidebar in at full width and keep the toggle slide Hovering the logo of the collapsed sidebar now fades in the fully open sidebar, floating over the page at its full width, instead of resizing it. Opening or closing with the toggle still resizes and slides: the sidebar docks to strip width while it slides, then floats out of sight, and it stays hidden under the pointer after a toggle close. The unused icon-only strip is removed from the sidebar actions. Assisted-by: Claude (Opus 5.5 Medium) * ui: stack the sidebar above the chat tabs on desktop The peeked sidebar floats over the page, but the chat tab bar shared its z-index and came later in the page, so the tabs were drawn on top of it. Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
f8fcef2546 |
ui: add MCP group checkboxes in tools settings and fix indeterminate checkbox colors (#31303)
MCP server groups in the settings Tools tab now get a group checkbox like the built-in, server and browser groups, toggling the server-scoped tools key. Indeterminate checkboxes now use the checked state colors. Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
b7cde589e4 |
ui: request persistent storage on startup (#31299)
Ask the browser to keep IndexedDB and localStorage data under storage pressure, so conversations and settings are less likely to be evicted. Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
e486a4b559 |
ui: build the reasoning menu from the template effort levels (#31273)
* ui: build the reasoning menu from the template effort levels When the chat template reports effort levels, the menu offers Default, Off and those levels, and a pick is sent as reasoning_effort. A template that reports none keeps the token budget menu. Picking a model other than the one that produced the last answer resets the conversation effort to Default, and an effort the current menu lacks resolves to Default. * ui: wait for a cold router model before building the request A router serves the props of a loaded model only, so a request sent to a cold model was built without its template effort levels and fell back to a token budget. The request now waits for the model to load, joining a load already in flight, and a stop during the wait sends nothing. Status waiters hold a list per model so a load and the requests that wait on it each get theirs, and a props request in flight is shared with a second caller instead of answering null. |
||
|
|
f4d8af608a |
ui : show save and reset settings buttons only when relevant (#31248)
* ui : show save and reset settings buttons only when relevant Assisted-by: Claude (Opus 5.5 Medium) * ui : move reset settings button next to reload app, pad settings caption Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
02d502f314 |
ui: Support OGG audio attachments by converting them to WAV (#31237)
* ui: support OGG audio attachments by converting them to WAV Fixes #24164 Assisted-by: Claude (Opus 5.5 Medium) * ui: convert all non WAV/MP3 audio uploads to WAV Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
b1756b23f3 |
ui: Fix draft for new message disappearing after using "Add System Message" action (#31214)
* ui: keep new chat draft when adding a system prompt Fixes #24338 Assisted-by: Claude (Opus 5.5 Medium) * ui: remove unused initialMessage from chat form Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
6886628cb1 |
ui : Search dialog (#31173)
* ui : replace the sidebar search mode with a search dialog Search opens a spotlight dialog (Cmd+K or the Search entry under New chat in the sidebar) with keyboard navigation over recent chats and sidebar actions. Phones keep the search page. Sidebar actions show their shortcuts, with new ones for Settings (Cmd+,) and Models (Shift+Cmd+M). Assisted-by: Claude (Opus 5.5 Medium) * ui : ignore search dialog keys while an IME is composing Enter confirms an IME candidate and the arrow keys move through the candidates, so the search field leaves them to the IME instead of opening the highlighted result or moving the highlight. Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
3ab37aedfe |
ui: fix system message row stretching to full prompt height in WebKit (#31246)
The expand button wrapping the system message card defaulted to inline-block. WebKit takes an inline-block's baseline from its last line even when that line is clipped by the card's max-height, so the row grew to the height of the whole prompt and left a large empty gap below the card. Make the button a block so the row matches the card. Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
0d5ca60098 |
ui: list org-less models first in the model selector (#31215)
The org-less group renders without a header, so when it followed a named org its models looked like they belonged to that org. Put it ahead of the named org groups. Fixes #25227 Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
823df7d0fb |
ui: Highlight reasoning toggle on hover and align it with model selector (#31212)
On desktop, the reasoning effort toggle now gets a muted background on hover and while its menu is open, matches the model selector's 24px height, uses a 13px icon and sits 5px from the selector. Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
1a97e387d3 |
ui: add conversation compaction (#27401)
* ui: add conversation compaction Add a compaction action that asks the model to summarize the conversation so far and stores the summary in a new compaction node appended to the branch. Later requests slice the branch at the last compaction node, keeping the system prefix and continuing from the summary instead of the full history, which frees up context while the complete history stays visible and navigable in the UI. The slice is applied once at the request funnel, covering the plain send, the agentic flow and the KV cache pre-encode, plus the continue path that bypasses it. The summary request is a one-shot outside the SSE replay machinery, streamed live into the node with a full rollback on abort, error or empty result. The action is exposed as a /compact slash command and a Compact button in the context gauge popup, with an overridable summarization prompt in the settings. * ui: pre-fill KV cache and refresh context gauge after compaction Once the summary is stored, pre-encode the compacted branch and stamp the returned prompt timings on the compaction node: re-encoding the reduced prefix is part of the compaction itself, so the server KV cache and the context gauge reflect the summary right away. The summary stream is registered under a prefixed identity (compact::convId), so the visibility kick and byte-offset resume protect it through tab switches while the reload probe never reattaches it as a chat turn. The stamped node becomes the shared context truth for the gauge and the processing state restore, and the stale live state that kept the gauge full is cleared. * chore: format * ui: allow a dedicated model for conversation compaction An opt-in setting reveals a model selector whose options come from the live router model list. The chosen model writes the summary and is loaded on demand when it is not resident, while the default path keeps resolving to the conversation's own model. * ui: align the compaction model field with project conventions * ui: fix the model option helpers import path The compaction model selector pulls filterModelOptions and groupModelOptions from the models component folder, where they no longer live, so the build fails to resolve the module. Point the import at the navigation folder, which now owns them. Missed during the rebase: the helpers moved in a commit that does not touch any compaction file, so nothing conflicted. * ui: import the model option helpers from models/utils * ui: follow the moved models selector in the compaction model field The models manager moved ModelsSelectorList under ModelsSelector and the model option helpers to /utils, where groupModelOptions takes the options alone. Drop the stale copy the rebase kept and read the helpers from their new home. * ui: drop the dedicated compaction model A summary written by another model loses the prefix the conversation model cached, so the whole history is prefilled again, and the model then resumes from text it did not write. The conversation model always writes its own summary. * ui: keep the last steps of a compacted conversation A compaction now folds the steps the model reads but the last ones, a step being a user message or an assistant message with its tool results, and stores how many it kept on the compaction node. The model then reads the system prefix, the summary, the kept steps and what follows. The summary request is the request the chat sends, with its options, tools and message shaping, cut before the preserved steps: the server reuses the prefix it cached and only the instruction is new. The number of preserved steps is a setting, 10 by default. * ui: run every compaction summary through one path Three callers needed the same thing - the automatic pre-encode pass, the action in the context gauge, and re-running a summary from the block - and each of them had grown its own copy of the request, the streaming and the storing. There is one path now: a summarization owns a node, streams into it, and reports itself through a ledger the rest of the app reads (`isCompacting`, which node belongs to which conversation). Re-running a summary is the same call on an existing node, so the two entry points cannot drift apart again. * ui: give the compaction row its own summary UI The summary was a line of text in a tool-call-shaped collapsible, which is how a compaction reads as a side note rather than as the thing the rest of the conversation now stands on. The row is its own block: a divider that names the boundary, and the summary inside it with the model that wrote it, its statistics, and the same actions an assistant turn has - copy, edit, fork, regenerate and delete all apply to the summary, and it collapses to the divider once a summary is in, because the shrunk context is the headline and the prose is the detail. The divider is a variant of the shared collapsible block, which also needed the toggle and the title split into snippets so a caller can lay them out without copying the markup. The action icons no longer take a role: they render what a message offers, and a compaction offers the same as any other stored message. * ui: report compaction progress on the row The row owns the run, so the row says how it is going: the prompt progress in the header while the context is being read, the phase once the summary starts streaming, and the token count once it is in. Before the first summary token there is nothing to collapse, so the header stands on its own instead of offering an empty block. * ui: keep the summarized turns quiet while a compaction runs The turns being summarized used to keep their own "Processing..." indicator and live reading stats while the summary streamed, so the conversation said the same thing twice in two places. The row reports the run, so the turns it replaces stay silent until it is done. * ui: hand the compact action to the context gauge Compacting is a decision about the context, so the action belongs with the numbers that show the context filling up rather than inside the form: it sits at the bottom of both the hover card and the drawer, under the breakdown it acts on, quiet enough to stay a footnote and disabled - with the row's own spinner - while a summary is already running. * ui: follow the bottom, otherwise leave the view alone The screen scrolled itself with a controller of observers, intervals, direction heuristics and landing timers, and still managed to move the view while the reader was reading: any mutation that grew the branch was treated as a reason to keep following. The page is the scroller, so it follows when the reader sits at the bottom, and stays where it is otherwise. A resize observer plus native scroll anchoring does the rest, and `pointerdown` buys the reader a moment of grace to be caught mid-drag. * ui: expand collapsible bodies with a height transition Expanding a block snapped it open: the body was mounted the moment `open` flipped, so a tall block moved the conversation around it in one frame. The body now goes through the same region the models table uses. The height interpolates and the visibility follows it, so a body is unmounted once the collapse has finished rather than the moment it starts - a closed block still costs nothing while tokens stream past it. * ui: open a compaction summary at the top of the screen Toggling a summary left the reader wherever they were, so a conversation that had just compacted showed the divider at the bottom of the screen and read down from there. A toggle now scrolls the page once and lands the divider a breath under the tabs, both ways: the summary is read from its beginning, and closing it returns to the same boundary instead of wherever the page happens to come to rest. The scroll waits for the body to reach its height, because before that the page is too short to scroll the divider up there and the call would clamp. That wait is the shared collapse duration rather than a transition event: an engine without `interpolate-size` animates the region's grid row instead of its height, so a listener on `height` would never fire there and the page would not move at all. * ui: spin the compaction icon while the summary runs The divider reported the phase in its title while the icon still showed a finished block, so a run in progress and a summary that had landed looked the same in the one place the eye reads first. The icon spins while the summary is running and folds back once it is in. * ui: let the compaction summary speak without the reasoning trimmings The summary is the thing the conversation now stands on, not a note beside it, and the conversation model always writes it, so the body drops the reasoning block's rule and indent (`flushBody` keeps them for the blocks that borrow them) and the model selector under the summary is gone - there is nothing left to swap. * ui: drop the model override from the compaction path The summary row no longer offers a model selector, so nothing hands regenerateCompaction a model anymore. Remove the parameter from the whole path, with the on-demand load of another model it carried: the summary always comes from the model the request funnel resolves. * ui: let the compaction summary think as the chat does The summary request forced thinking off, overriding the reasoning effort the user chose. The summary is the text the conversation resumes from, the task where reasoning helps the most, so the request now carries the very options of the chat. * ui: pre-encode the compacted branch as the next turn reads it The post-compaction pre-encode sent the branch without tools, while the tools render ahead of the messages, so it warmed a prefix the next turn never reads and that turn prefilled the whole compacted context again. The pre-encode now sends the tools, message shaping and reasoning handling of the chat, so the next turn only prefills its new message. * ui: pre-encode the branch the next turn actually reads The per-turn pre-encode sent the messages of the request plus one assistant message holding the streamed text, without tools. With tools on, it replaced the slot the next turn extends with a prefix nobody reads again, and after an agentic turn its history did not match the stored calls and results. Without agentic, it never ran. Both pre-encodes now read the stored branch up to its leaf, cut at the last compaction, and send it with the tools, message shaping and reasoning handling of the chat. The plain completion path triggers it too. * ui: refuse a compaction summary the output limit cut The stream handler read every chunk but dropped the finish reason, so a summary cut by max_tokens folded the conversation into half a text. The handlers now hand the finish reason to onComplete, and a summary that ends on length rolls the compaction back with an error. The summary was framed through a replacement string, so $& or 32560 in it changed the text the model read; it is framed as written now. * ui: wait for the collapse transition in the tool call tests The collapsible body now unmounts once its collapse animation ends rather than as soon as the block closes, so the "folds back" assertions ran before the body was gone. Wait out COLLAPSIBLE_TRANSITION_MS first. Assisted-by: Claude (Opus 5.5 Medium) --------- Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com> |
||
|
|
bc262f33cc |
ui: Open model details in a drawer without leaving the page (#31176)
* ui: open model details in a global drawer without leaving the page The model info actions in the selector used to navigate to the models page to open its details pane, and a phone opened a separate dialog instead. The drawer now lives outside the models table, driven by uiStore, so the info icon opens it over whatever page is showing. The models dropdown and the phone picker stay open under the drawer's backdrop, and keep focus while the drawer opens and closes, so the trigger's tooltip no longer pops over the drawer. Assisted-by: Claude (Opus 5.5 Medium) * ui: let the model details drawer reopen after a drag to dismiss Dragging the drawer down wrote vaul's own open state, which the one-way open prop then kept closed, so the info action could not open it again until a refresh. Bind open both ways through the store instead. Assisted-by: Claude (Opus 5.5 Medium) * ui: close the models selector from the details drawer chat actions Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
f5892ca5ad |
ui: use shared confirmation dialog for conversation deletion (#31187)
Extract the keyboard-shortcut delete confirmation into DialogConversationDelete and reuse it in the sidebar and mobile search page instead of the native window.confirm prompt. Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
dc9e3bed42 |
ui: UX improvements (#31030)
* ui : land opened conversations at their bottom before the first paint Pin the page to its bottom with a ResizeObserver until it stops resizing or the reader interacts, and mount the last rows eagerly so the landing frame shows real content. Assisted by: Claude (Opus 5.5 Medium) * ui : make conversation tabs browser-style A plain navigation shows the chat in the current tab instead of adding one, and the new-chat tab turns into the conversation it starts. New tabs come only from explicit actions: the + button, Cmd/Ctrl-click or middle-click on a sidebar chat, and its Open in new tab menu item, which open in the background. Closing a tab moves to its neighbour among the tabs the view shows. Assisted by: Claude (Opus 5.5 Medium) * ui : share the tab surface and tab bar fade styles Move the tab item surface (shape, resting and active looks) to TAB_SURFACE_* class constants and the fade under the tab bar to a global chat-top-fade class, so other elements can match the tabs. Assisted by: Claude (Opus 5.5 Medium) * ui : fit the chat column below the tab bar The tab bar reported 1.25rem of its height while it takes 3.3125rem (pt-3.25 + h-10), so a short conversation still left a few pixels to scroll. Use the bar's full height and drop the extra 1rem from the column's minimum height, so it fills the viewport exactly; the centered new-chat form moves up by the same 1rem to keep its place. Assisted by: Claude (Opus 5.5 Medium) * ui : stop the page from bouncing past its ends Elastic overscroll on the root scroller dragged the sticky and fixed bars (tab bar, chat form) along at the top and bottom of a chat, and they jumped back on release. Set overscroll-behavior: none on html and body. Assisted by: Claude (Opus 5.5 Medium) * ui : stop scrolled dialogs from bouncing past their ends Dialogs scroll on their own (the settings panel, the search results), so the page rule did not reach them and their sticky navigation still jumped on overscroll. Turn off overscroll on dialog panels and on the scroll containers inside them. Only scroll containers: Firefox counts an overflow-hidden box such as truncated text as one, and `none` there kept the wheel from scrolling the list around it. Assisted by: Claude (Opus 5.5 Medium) * ui : fit a first exchange opened by the working directory row The last answer's minimum height leaves room for the last question only, so the synthetic row that a new chat starts with (Set working directory to ...) made a short first exchange overflow by that row. When everything above the last question is synthetic, count those rows as part of it. Assisted by: Claude (Opus 5.5 Medium) * ui : keep following a streaming response until the reader scrolls up Auto-scroll stopped whenever the page moved up, including moves the reader did not make: scroll anchoring and blocks re-rendering shorter while a response streams. Only the reader's wheel, touch, scroll keys or scrollbar drag now count as scrolling up. The send scroll also looked up the new user bubble by position, which picked an older message when the answer row had not rendered yet. Assisted by: Claude (Opus 5.5 Medium) * ui : fade the chat form between its centered and bottom positions The form no longer slides across the column between the new-chat center and the bottom. It fades out drifting toward where it goes and fades back in where it lands, quicker when opening a chat than when going back to a new one. Its wrapper keeps the same height in both positions, so the messages never shift while it moves. The new-chat greeting fades in as the form settles, also on a refresh or a first visit. Reduced motion keeps only the fades. Assisted by: Claude (Opus 5.5 Medium) |
||
|
|
69b3cc406e |
ui: Sidebar resizing improvements (#31119)
* ui : let the expanded sidebar be resized by dragging its edge Bring back the desktop sidebar resize handle that was dropped with the shadcn sidebar primitive. The width is clamped between 18rem and 32rem, persists across reloads, and resets on a double-click of the handle. The tab bar follows the same width so a wider sidebar never covers it. Assisted by: Claude (Opus 5.5 Medium) * ui : rubber-band the sidebar edge past its bounds and drag it shut Past the max width the edge resists harder the further it is pulled, and the same happens below the min width until the pull passes 9rem, where it snaps to the pointer and letting go hides the sidebar. A release outside the bounds springs the width back to the nearest one. Assisted by: Claude (Opus 5.5 Medium) * ui : show the sidebar edge strip faintly on hover and solid while dragging The strip is clipped to the sidebar's rounded corners through its own overlay, so the sidebar itself is never clipped. Assisted by: Claude (Opus 5.5 Medium) * ui : remember whether the desktop sidebar is open across reloads A phone opens the sidebar as an overlay, so it always starts closed there and its toggling never overwrites the desktop preference. Assisted by: Claude (Opus 5.5 Medium) * ui : fade the sidebar labels and conversation list with its width The fade follows the live width, so it tracks both the toggle's width transition and a drag below the minimum width. Assisted by: Claude (Opus 5.5 Medium) * ui : persist the sidebar width on release and clamp a restored one A drag now updates only the live width and writes it to localStorage once when released. A width restored from localStorage is clamped to the current bounds, so a value saved under older bounds does not stick. Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
e168e041ec |
ui: Models Manager as Route + UI improvements (#30988)
* ui : move models manager to its own route, keep Discover as a dialog Models now lives at #/models instead of a dialog. Discover opens as a standalone dialog over it, and leaving the page returns to the chat it was opened from. Assisted by: Claude (Opus 5.5 Medium) * ui : polish the models page layout and filters Give the models page a page header with the Discover action, calmer column labels, tinted section and family headers, and an empty state. Load the model list on a direct visit, slide the detail pane over the table below xl, and draw the filter toggles as one box matching the context select, with room for their shadow. Assisted by: Claude (Opus 5.5 Medium) * ui : round the models table family headers Assisted by: Claude (Opus 5.5 Medium) * ui : fix models page navigation and Discover gating Record the models page's way back after every navigation, so Back, a typed URL or a reload no longer return to a stale chat. Escape closes the detail pane, then leaves the page. Gate every Discover entry point on one shared check, recheck the API key on /models, skip the duplicate router fetch on a direct load, and share the route load between pages. Assisted by: Claude (Opus 5.5 Medium) * ui : show model details in a drawer Open the models manager's detail pane in a drawer, from the right on desktop and from the bottom on a phone, instead of an inline side panel and a full-screen cover. Pad the pane on both sides, drop its close button on a phone, and assert that filtered rows are gone from the page rather than merely hidden. Assisted by: Claude (Opus 5.5 Medium) * ui : make whole models table rows clickable A click anywhere on a model or quant row opens it, unless it lands on one of the row's own controls (load toggle, actions menu). The name button stays the keyboard path to the same action. Assisted by: Claude (Opus 5.5 Medium) |
||
|
|
ea1d7d66f3 |
fix(ui): align hover card and context popup styling with dropdown menu (#30978)
Use the dropdown menu surface (border, radius, background, shadow, padding) for the hover card and the context gauge popup, and fix the hover card open/close animation to use bits-ui data-state attributes. Assisted-by: Claude (Opus 5.5 Medium) |
||
|
|
e1e3b2c409 |
ui : bump packages to clear npm audit (#31038)
Run npm audit fix, then bump the remaining vulnerable packages within their current majors: storybook 10.6.2, vitest 4.1.12, vite 7.3.7, @modelcontextprotocol/sdk 1.32.1, dompurify 3.4.17, mermaid 11.17.2, @vite-pwa/assets-generator 1.0.4 and @tailwindcss/typography 0.5.20. Override katex to 0.18.11 and postcss-selector-parser to 7.1.6 since their dependents still pin vulnerable ranges, bump the sharp override to 0.35.5, and pin @storybook/svelte so npm does not resolve the 11.0 alpha through the addon-svelte-csf peer range. Storybook stays on 10.6.2 as 10.6.3 is newer than min-release-age. Assisted by: Claude (Opus 5.5 Medium) |
||
|
|
84746c5c94 |
ui: Add "Show tool call in progress" setting (#30681)
* feat(ui): add Show tool call in progress display setting
Add the showToolCallInProgress key and its checkbox to Settings > Display,
right next to its "always show" counterpart. Upstream dropped a setting of
this exact name in
|
||
|
|
a6b83d426f |
ui : key draft sidecar badges by model instead of repo (#30919)
Two quants of one repo pulled each other's draft: the badge read every --model-draft arg of the repo, so each row showed both drafts. Assisted-by: DeepSeek-V4.1-Flash-1017 |
||
|
|
1d88cb05f1 |
ui: hide the native clear button in SearchInput (#30719)
SearchInput already renders its own clear button. Hide the browser's native search cancel button to avoid showing both controls. Fixes #28627 Signed-off-by: aiqubits <aiqubits@hotmail.com> |
||
|
|
ab7ed0d4b5 |
ui : fix attachment text preview clipped when a line is longer than the container (#30806)
In the attachment preview, SyntaxHighlightedCode's wrapper (overflow-auto) was not the element that ended up owning horizontal overflow: the inner code box kept its content clipped at the wrapper's width, so a single line longer than the preview pane was cut off with no scrollbar (scrollWidth == clientWidth while the code's own content measured tens of thousands of px). Give the pre/code boxes their intrinsic width (width: max-content, min-width: 100%) so long-line overflow propagates to the scrolling wrapper. Verified in the running WebUI: with a 6000-char line, wrapper scrollWidth goes from 990 (clipped, unscrollable) to 50449 with a working horizontal scrollbar; short content still fills the container exactly as before (min-width: 100%). Fixes #30796 |
||
|
|
2b57148b08 |
ui: Fix markdown tables and lists being cut off on phones (#30687)
* ui : keep markdown tables inside the chat column on phones The phone rule made the table wrapper full-bleed with -1rem margins, so the first and the last column sat under the screen edge. Keep the wrapper inside the chat column and cap it at its width, so a wide table scrolls in place instead. Assisted-by: llama-ui (ggml-org/Qwen3.8-Flash-Next:IQ4_NL) * test : show a table-only model answer in the chat message story Adds a story with an answer that is mostly a table, so the table layout can be checked at a phone width. Assisted-by: llama-ui (ggml-org/Qwen3.8-Flash-Next:IQ4_NL) * ui : indent markdown lists by 2rem 1.5rem put the marker too close to the item text. Assisted-by: llama-ui (ggml-org/Qwen3.8-Flash-Next:IQ4_NL) |
||
|
|
c0fd786264 |
ui : Discover & Download Models from HF Hub (#29584)
* common : resolve <quant>-<sidecar> download tags and list cached sidecars A Q4_0-mtp style tag now resolves the sidecar file when no model file matches it, so a solo draft or mmproj download actually pulls the file. Cached sidecar files list as their own entries so the state survives a restart, and removing such a tag deletes only the sidecar. Assisted-by: pi:zai-org/GLM-5.3 * ui : optional sanitized raw HTML in markdown Add an allowHtml prop to MarkdownContent: raw HTML found in the markdown is rendered after DOMPurify sanitization instead of being escaped as literal text. Default stays escaped. Assisted-by: pi:GLM-5.3-Flash * ui : add the discover models components The Discover surface: the curated-catalog list with its search and skeletons, the details pane with its readme, metadata, Hub stats and download options, and the standalone download progress bar that replaces the plain one. The quant download button sizes its select with a new xs trigger, so the select gains that size and the muted box look. The store stays incomplete when a repo fails, so the next mount retries it. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash * ui : put the discover view in the models manager The manager dialog gains its second view: the Discover Models call to action fades the table out, slides the title with its compass mark, and fades the discover surface in; the arrow button returns to the table. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash # Conflicts: # tools/ui/src/lib/components/ui/button/button.svelte * ui : add the Enable Discover Models setting The flag gates browsing and downloading HuggingFace GGUF models, so it is declared on the discover layer; the repo-tree lookup in the manager layer above reads it from here. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash * ui : resume model downloads from the pause point A stopped download keeps its partial file instead of deleting it, so re-posting the tag resumes from disk. The first progress record now carries the resume offset, the paused snapshot survives into the resumed entry, and a pause settles as soon as it is requested. Assisted-by: pi * ui : lead the discover list with downloads The discover sidebar lists running downloads above the suggested models, and steps aside while a query is active. A paused download stays in the manager table, where it is resumed or dropped. Assisted-by: pi * ui : make resuming a paused download work Re-posting the tag of a paused download hit the router's "already exists" check, because a paused download parks its entry as DOWNLOADED until the next reload: a download entry that is not running is now replaced and resumed. The UI keeps the paused row when the request still fails, instead of dropping it and looking finished. Assisted-by: pi * ui : remember unfinished model downloads A partial file is named after its content hash, so nothing on disk says which model it belongs to once the router restarts. The router now keeps the tags of the downloads it started, clears a tag when the download finishes or is deleted, and lists what is left as still downloading without starting anything. The UI adopts those tags as paused on every list fetch, so a refresh or a fresh open shows an incomplete download and can resume it from the files already on disk. Assisted-by: pi * ui : show a paused download's state on its chip A paused quant chip reads as paused at rest, the way a running one shows its spinner, and offers the resume action on hover. The selector's download row gets a View in Discover action beside its cancel. Assisted-by: pi * ui : collapse the download options by default The copyable download command starts collapsed so the option rows lead; expanding it stays one click away. Assisted-by: pi * ui : keep decision models out of the hub lists Decision models answer with calibrated probabilities instead of text, so llama-ui cannot run them: the hub data layer drops them from the catalog and every search, keyed by the decision-model tag or a text-classification pipeline. Assisted-by: pi * ui : read download tags as quant and sidecar A `<repo>:<quant>-<sidecar>` tag now parses into its quant and sidecar instead of one opaque quant token, and ModelsService.draftSidecarsByRepo collects the router listing's sidecar-only entries per repo, replacing the inline copy in the models store. Assisted-by: pi * ui : cluster draft downloads under their main model Tracked downloads group by repo: the main weights lead and the draft sidecars ride inside their item as compact sub-lines, on the selector, the discover list and the manager table. The main model's controls pause, resume and cancel the whole cluster, and the manager's download rows select into the configuration pane. Assisted-by: pi * ui : act on tracked downloads from the table rows The table passes its clusters' draft sub-lines to the model rows. A row's actions cascade pause and resume, an in-flight row offers Cancel download while a paused one keeps Delete from disk, and the confirm dialog's copy follows the state. Assisted-by: pi * ui : give downloads a configuration pane of their own Selecting a tracked download opens the pane in a download mode: live progress with pause, resume and cancel, and See in Discover, instead of metadata and load actions that a model without files cannot offer. Assisted-by: pi * ui : make the discover details self-fetching and responsive ModelsDiscoverDetailsContainer owns the details, tree and readme fetches so hosts outside the discover view can render the pane, and the details layout adapts to narrow panes with container queries instead of viewport breakpoints. Assisted-by: pi * ui : download a matching draft with the main model A main quant download now pulls the quant-matched draft of the most preferred kind the repo ships (DFlash before MTP), fire-and-forget, so speculative decoding is ready when the main lands. Manual draft chips stay available; the options row also collapses to one column in narrow panes. Assisted-by: pi * ui : keep discover off by default and couple it with the hub setting Discover Models starts unchecked. Enabling it turns the Hugging Face Hub setting on and disabling it turns it back off, so the checkbox never lies; with discover off the hub setting stays available on its own for the display data. Stored and reset state are corrected the same way on load. Assisted-by: pi * ui : open the manage models dialog on the manager view The dialog now opens on the manager instead of returning to the last view, discover entries still land on the discover view while the setting allows it, and the discover surfaces follow the setting's state. Assisted-by: pi * ui : give the discover search room for its focus ring The sticky search bar insets from the panel edges so the focus ring stays inside the scroll container, and rows scroll behind an opaque bar instead of through it. Assisted-by: pi * common: settle sidecar names on the listing and drop unfinished partials An infix or trailing sidecar token is ambiguous: draft heads use it (Model-MTP-BF16.gguf), so do full models with embedded heads (Model-Q8D-MTP.gguf). Such a name is a sidecar only next to token-free main weights of the same family, while mmproj or imatrix anywhere in the name, or a leading token, decides alone (mmproj_f16.gguf). A quant tag always carries its bit count, so a sidecar named after no quant (mtp-Model-it.gguf) gets the bare tag: the listing, the plan and remove all use it. An imatrix tag resolves into a plan slot of its own, downloaded but never wired into the params. remove resolves the tag the way a download does and deletes the partial files of every planned blob, so cancelling a running download or dropping one left unfinished by an earlier run leaves nothing on disk. The router passes its token and releases its lock around the call, and records the unfinished downloads with an atomic write. llama download succeeds on a sidecar-only plan and prints its paths. * ui : move dialog views and draft states into enums and constants The models dialog's views become the ModelsDialogView enum, the draft subline's states join ModelRowDownloadState, the view fade and the copy-feedback reset move to ui constants, and the dialog's compass marks come from DISCOVER_ICON. Assisted-by: pi * ui : drop the unused discover surface The dead openDiscoverModels twin, the commented-out command divider, the details pane's unused onClose and its header's close button, and a redundant type cast leave; the command's copy feedback joins the shared reset constant. Assisted-by: pi * ui : trim the download option comments and highlight the chat template The comment verbosity the options panel and its chips carried shrinks to the non-obvious why, and the chat template dialog renders through SyntaxHighlightedCode as jinja instead of a plain pre block. Assisted-by: pi * common: only a .gguf file is a sidecar, and mmproj/imatrix as whole words A non-GGUF file named like a sidecar (quantization-mmproj-Q8_0.json) is picked as the mmproj, and a sidecar word inside a longer word of a model name (Phi-2-OmniMatrix.i1-Q4_K_M.gguf) drops the model. * ui: dedupe draft sidecar badges per quant The draft sidecar badges deduped by kind and repo without the quant, so with a draft cached at two quants only one badge survived, and a delete of the other main quant found no badge to remove its sidecar with. Also restores the getDownloadEntries doc lost when the clusters method landed above it. Assisted-by: pi (zai-org/GLM-5.3-Flash) * ui: adopt the incomplete download status and drop the paused-ids registry The store inferred an unfinished download from a downloading entry it held no local state for, which misread a download running in another tab as paused and offered a resume the server rejects. The listing carries the "incomplete" status now, so the adoption keys off it directly. The paused ids no longer need to survive in localStorage: the server remembers them across restarts, and the progress snapshot was stale after a reload anyway. The stale localStorage key is removed. Assisted-by: pi (zai-org/GLM-5.3-Flash) * ui: quant-match the pause and cancel cascades Pause, resume and cancel cascaded to every draft sidecar of the repo while delete only took the quant-matched ones, so canceling a Q4_0 download also killed a Q8_0 draft that belongs to another quant's main. Every cascade acts on the quant-matched drafts now, matching what a delete takes with it. Assisted-by: pi (zai-org/GLM-5.3-Flash) * ui: collapse the duplicated draft subline markup The downloaded and in-flight branches repeated the identical connector, label and badge markup; one block with a conditional tail keeps the two states in sync. Assisted-by: pi (zai-org/GLM-5.3-Flash) * ui: scrub positioned styles out of sanitized model card html The style attribute stays allowed for the KaTeX output, which let a third party card draw a fixed overlay over the app. The sanitize hook drops position:fixed and position:absolute values and leaves the KaTeX layout styles alone. Assisted-by: pi (zai-org/GLM-5.3-Flash) * ui: run the discover mount fetch once and reuse the shared repoOf helper fetch() toggles its loading flag synchronously inside the mount effect, which re-ran the fetch on every toggle and retried failed catalog repos endlessly; the effect now untracks the call. The local repoOf copy and the duplicate services import give way to the shared helpers. Assisted-by: pi (zai-org/GLM-5.3-Flash) * ui: narrow the spec-type map to the draft sidecars SPEC_TYPE carried empty entries for the aux sidecars it is never indexed with; the map and the command component now type the badge as a draft sidecar through a draftBadgeOf helper instead of a cast. Assisted-by: pi (zai-org/GLM-5.3-Flash) * ui: name the decision-model tags and markdown cache keys The decision-model hub tag and pipeline tag move into the huggingface constants next to the other pipeline tags, and the markdown transform cache prefixes get names instead of bare one-letter tokens. Assisted-by: pi (zai-org/GLM-5.3-Flash) * ui: read the discover setting through one truthiness check Three places read the setting with !== false and one with Boolean(); all four read it the same way now. Assisted-by: pi (zai-org/GLM-5.3-Flash) * ui: compare the models dialog view against its enum The template compared the view state against the 'manage' literal in three spots while the script used the enum. Assisted-by: pi (zai-org/GLM-5.3-Flash) * nit: Remove `+` before draft sidecar badge * server: list unfinished downloads as incomplete A download that never finished was reported as "downloading", the same status a live download carries, so a client could not tell a running download from a dead one. The tags remembered in unfinished-downloads now list under a dedicated "incomplete" status, without an instance. Assisted-by: pi (zai-org/GLM-5.3-Flash) * server: download a model with its sidecars as -hf does Resolution goes back to master. POST /models takes the sidecar kinds to pull with the model (mmproj by default) and the download child gets them as -hf flags, so the server picks the files. /models lists real models only, each with the sidecars it has on disk, and incomplete downloads keep their sidecars across restarts. A stopped download keeps its partial files for the next attempt, and DELETE purges them through the same plan the download follows. * ui: download sidecars as parameters of their model The download options keep their chips, but a chip no longer posts a sidecar tag: the store maps it to its model and the sidecar kinds to pull, the quant chip takes its companion draft in the same download, and draft badges come from the sidecars each cached model lists. The server adds the sidecars a cached model lacks when it is posted again, and DELETE removes a single sidecar of a cached model. * ui : mirror the server's sidecar lookup in draft badges The server resolves a sidecar by the case-sensitive `<kind>-` token in the same directory as the weights. The UI matched the token loosely, so a badge could offer a draft the server never pulls. Assisted-by: DeepSeek-V4.1-Flash * ui : post the model's sidecar kinds with its download The store keeps one download per model: the chip's kinds ride along with the model's own sidecars, and a download row no longer expands into one row per sidecar. Draft badges carry no quant of their own yet, so a sidecar of a cached model is never labelled with the model's quant. Assisted-by: DeepSeek-V4.1-Flash * ui : join a quant's draft into its download chip One chip per quant, its draft badged inside it and its size covering both files. The draft is the file the server would pick: the same directory, the exact quant, else the closest bit depth, so a 4-bit model can pair with a 16-bit head. Assisted-by: DeepSeek-V4.1-Flash * ui : drop the draft download rows from the manager and selector A download is one row per model again: the drafts ride with it, and the rows read the sidecars on disk off the listing instead of pairing sidecar entries with their model. Pause, resume and cancel act on the model's own download. Assisted-by: DeepSeek-V4.1-Flash * ui : keep the discover download list one row per model The list leads with one row per downloading model, its sidecars as sub-lines. A row badges only the draft kinds the server can resolve, so a badge never outlives the file its lookup would pick. Assisted-by: DeepSeek-V4.1-Flash * ui : keep the discover chips readable in dark mode The metadata chips, the chat template button and the View on Hugging Face link carried a faint border and no surface over the dialog background, so they were near invisible. They take the tertiary button surface now, and their icons size with size-* so the variant's base rule leaves them alone. Assisted-by: DeepSeek-V4.1-Flash * ui : carry the draft through the download options One chip per main quant, its draft badged on it and the chip's downloaded state covering both files. The drafts sit behind an opt-in checkbox with an inline type pick, and each row opens its own command: it pins the row's quant and only names the draft type, so -hfd is gone. Assisted-by: DeepSeek-V4.1-Flash * ui : line the details skeleton up with the loaded pane Same root padding and container context as the loaded view, so the container queries resolve alike. The download options card pads its blocks instead of itself, the readme gets its card, and a description line stands in for the catalog paragraph. Assisted-by: DeepSeek-V4.1-Flash * common : drop the partials of a removed sidecar Removing a sidecar alone left the .downloadInProgress files of the sidecars a download had planned in blobs/. The removal purges them through the same plan the download follows, and the test covers a finished draft beside an unfinished one, whose partial goes while the model keeps its files. Assisted-by: DeepSeek-V4.1-Flash * server : name why a download is refused A model that is already downloading, or that is running, answered "already exists". It now says which: "is already downloading", or "is running, unload it first", both before the resumable check. The router test covers the two. Assisted-by: DeepSeek-V4.1-Flash * ui : post a download once per click Two quick clicks on the same chip posted twice, and the second answer was "already exists". The store tracks the model as pending, returns early while it is pending or in flight, and reads it as in flight before the server answers, so the chip shows the download at once. The new client test pins the flows: one POST per double click, the chip reading downloaded once the list reports the model, a resume keeping its sidecars, a cancel dropping the whole download, and a reload restoring the paused state. Assisted-by: DeepSeek-V4.1-Flash * ui : keep a plain download row the height of the others The sub-lines wrapper of a download row was rendered even with nothing in it, so its padding made the row taller than a plain one and the overlaid progress bar sat in that empty strip. It renders only with sub-lines now, in the selector and in the discover list, and the selector row takes the phone padding its neighbours have. Assisted-by: DeepSeek-V4.1-Flash * ui : badge a repo's draft once The args of a loaded model and the listing of a cached one report the same draft in different words, one with the file's quant and one with the kind alone, so the merge kept both and a repo row showed the same kind twice. The badges merge per kind now, the args one winning as it knows the quant, and the test pins both sources of a real repo. Assisted-by: DeepSeek-V4.1-Flash * server : report the sidecars of a preset or a download on its way A model the preset also names is registered as a preset, so its drafts were never reported and no row could badge them. A download on its way reported none either, though the record of the kinds it pulls is right there: those win over the plan now, the plan resolves through the preset's hf-repo, and a models dir stays out of it. Assisted-by: DeepSeek-V4.1-Flash * ui : badge a download's drafts and open its model from the row A draft still coming down badges the model id now, the way a landed one does, while an on-disk draft keeps its checked sub-line. The row itself opens the model, the status slot beside it pauses or resumes, and the manager resolves a focus on a download row so its panel opens. Assisted-by: DeepSeek-V4.1-Flash * common : report a removed sidecar that only had a partial Removing a sidecar alone dropped the result of the partial purge, so a sidecar that only ever landed as a .downloadInProgress blob answered false and the UI had no reason to refetch. The purge result is folded into the return now, and the test covers a partial-only removal. Assisted-by: DeepSeek-V4.1-Flash * ui : ignore a pause while the download POST is still on its way The chip reads as downloading before the server answers, so a quick second click unloaded a model that had not started and left the store neither in flight nor paused. A pause returns early while the POST is pending, before it touches the feed or the paused marker. The client test holds the POST open and pins it. Assisted-by: DeepSeek-V4.1-Flash * ui : offer a missing draft as its own chip A model already on disk without its draft had no way to add it: the quant chip carried the draft, so it read as a download of both. The quant chip keeps to its own state now, its joined draft shows only while it rides along or is on disk, and a downloaded quant missing its draft gets a chip of its own - badge, size, download - that posts the model with that kind, so the server keeps the files already there and pulls the draft. Assisted-by: DeepSeek-V4.1-Flash * server : list a paused sidecar download by the files on disk A model on disk whose sidecar download stops keeps its unfinished record, and the listing reported the kinds that record plans as if they were on disk: the UI checked a draft that never landed and offered no way to add it. The planned kinds now stand in only while the download runs. * chore: Clean up comments * server : list sidecars only for a model that resolves from a HF repo A preset on a local file has no HF repo, yet the listing resolved its name as one: the plan threw on the invalid repo format and GET /models failed for every model. The sidecars now come from the repo a preset names or a cached model is listed under, and a local file lists without them. * common : fix download cancel regression Signed-off-by: Adrien Gallouët <angt@huggingface.co> * common : preserve model partials when removing sidecars Signed-off-by: Adrien Gallouët <angt@huggingface.co> * server : one sidecar table and an unfinished downloads class Address review from @ggerganov. The sidecar kinds live in one table in common/download, mapping each name to its download option and plan file; the server validates, builds its options and lists cached sidecars through it instead of its own lists. The unfinished downloads move into server_unfinished_downloads, a record with its own mutex and a struct per entry, and server_models exposes them through unfinished_downloads_get and unfinished_downloads_remove. * common: one sidecar table, and removals that keep shared files The resolution, the cached listing and the removal follow a single table of sidecar kinds, so a kind is declared once with its file keyword, whether it is a draft, its option and its plan file. The server reads the same table for the kinds it accepts, the default sidecars and the child flags. Removing a tag keeps the files another cached tag still loads, such as an mmproj named after the removed quant. A sidecar removed on its own goes for every tag that resolves it. An unfinished download records the exact paths of its partials when it starts. A removal deletes those without asking the hub, and keeps a partial another recorded download also writes. The record is taken once the child is spawned, and the files go under the models lock, so a download of the same tag cannot start while they are removed. * server: drop stale partials and refuse a tag that resolves nothing Recording a download again deletes the partials of the earlier record that the new plan no longer writes, such as those of an older hub revision or of a sidecar the new request leaves out. A posted tag that resolves no file of the repo is refused before the download starts. Removing a model follows a single path, whether the reload erased its entry during the wait or not. * server: fix the CI race between the router download tests The pytest workers run in parallel on one shared cache, so a download test could find the file another test had just fetched and see no progress, or start from the unfinished downloads record another test wrote. Each download test now runs on an empty cache of its own. * server: refuse a DELETE of an unknown name and decide downloads in load() A DELETE of a name that is neither a model nor a recorded download went to common_download_remove, and without a tag it wiped the whole repo cache, including the files of a loaded quant or of a download on its way. Such a name is now not found, as it was before. A POST that downloads over an existing entry checked its state outside the lock, so a load of the same model in between got its instance killed. The checks now run in load(), under the lock, right before the spawn. The fake load time of the tests applies to model loads only, since a download worker loads none. * server: add incomplete_sidecars to the /models API A sidecar added to a cached model and then paused kept its record, but the listing showed only the complete model, so a fresh UI lost the pause. The model entry now carries incomplete_sidecars, the kinds its unfinished download has not written yet; a POST with the kind resumes one and a DELETE with it drops its partial. * common : drop a kept partial when a resume gets 416 Now that partials survive between attempts, an old or complete partial makes every retry fail with the same 416. Delete it so the next attempt can start over from zero. Signed-off-by: Adrien Gallouët <angt@huggingface.co> * common: drop a kept partial when a resume gets 200 A server that ignores the range answers 200 to a resume, and the kept partial then fails every attempt the same way a 416 does. Drop it as well, so the next attempt starts over without a range. * server: refuse a download that shares a partial with one on its way Two downloads of quants that share a file, the mmproj or an mtp picked by the closest bits, appended to the same partial and renamed a file with interleaved bytes. A download whose plan writes a partial that a running download also writes is now refused until that one is done. * common : check the saved etag before resuming a partial download Before resuming, compare the etag saved with the partial to the one the server now serves. If it is missing or different, start over, so a resumed download never mixes bytes from two versions of the file. Signed-off-by: Adrien Gallouët <angt@huggingface.co> --------- Signed-off-by: Adrien Gallouët <angt@huggingface.co> Co-authored-by: Pascal <admin@serveurperso.com> Co-authored-by: Adrien Gallouët <angt@huggingface.co> |
||
|
|
fd55cb98fe |
fix(ui): take the agentic turns default from DEFAULT_AGENTIC_CONFIG (#30582)
The registry hard-coded 10 for AGENTIC_MAX_TURNS while DEFAULT_AGENTIC_CONFIG said 100. The registry seeds SETTING_CONFIG_DEFAULT, so the loop's `|| DEFAULT_AGENTIC_CONFIG.maxTurns` fallback never fires and users got 10 turns. Reference the shared constant, as the adjacent MCP timeout does. Assisted by: llama-ui (ggml-org/Qwen3.8-Flash-Next:IQ4_NL) |
||
|
|
2f160ba59a |
ui: fix text attachment preview overflow (#29898)
* ui: fix text attachment preview overflow Set the max height to 80dvh and the max width to full on screens below medium and 80vw from medium onward. * style: run formatter |
||
|
|
baef3ed9a1 |
ui: Models Manager Follow-up Improvements (#30228)
* common : read a GGUF's trained context from its metadata common_get_gguf_n_ctx_train opens only the file's metadata (no_alloc, like common_get_decision_type) and reads <arch>.context_length, so a caller can learn the trained context without loading the model. It accepts both u32 and u64 values and returns 0 when the file is missing, unreadable, invalid, or reports no context length. Assisted-by: pi:zai-org/GLM-5.3-Flash * server : report the trained context in the models listing update_caps already resolves the model file offline to read its modalities, so it now reads the trained context from the same GGUF metadata, and GET /models reports it as context_length when it is known. A router listing then carries the context without any Hub request, which lets the UI sort and filter by it offline. Assisted-by: pi:zai-org/GLM-5.3-Flash * ui : take the trained context from the models listing The router now reports context_length per model, so the option mapping fills contextLength from it and the manager reads it before the Hub record. The Context column, the context sort and the context filter then work with the Hugging Face Hub API turned off. A browser suite guards the sort and the search, the Hub-cache driven context filter and the re-sort when details arrive after the sort was clicked. Assisted-by: pi:zai-org/GLM-5.3-Flash * ui : mark favorite models with a heart A favorited model shows a rose heart in the selector even before its row is hovered, and the crossed heart takes its place on hover, so unfavoriting stays one hover away. The manager table marks its favorited rows with the same heart after the badges and capabilities. Assisted-by: pi:zai-org/GLM-5.3-Flash * fix: UI text nit * fix: UI nits * fix: Favorite models grouping in models table * feat: Remove sorting from Status column in Models Table * server: read the GGUF metadata once per model Read the decision type and the trained context in a single GGUF open, accept only a UINT32 context length like the model loader, and reset n_ctx_train with the other caps so a failed refresh drops it. * fix: Post-review fixes --------- Co-authored-by: Pascal <admin@serveurperso.com> |
||
|
|
b42b7e6d30 |
server: use port 9931 by default (#30159)
* server: use port 9931 by default * revert unrelated changes |
||
|
|
b013e56a71 |
ui : add the models manager (#29583)
* ui : add the drawer, sheet and grouped list primitives Add the drawer and sheet overlay components, the toggle and toggle group, the shared searchable input, the collapsible sections and the grouped list, and the near viewport helper they position with. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash * ui : add the shared model components and data layer Add the model row pieces shared by the selector and the manager, the models store with its download status feed, the huggingface and migration services, and the model utils, enums and constants they read through. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash * ui : rework the models selector around its providers Group the selector components under their own folder, rework the list around the provider grouping, add the mobile trigger and the download item, and derive the selection state in one hook. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash * ui : add the models manager Add the manager table with its repo, quant and status rows, the filters and the toolbar, the row actions in a drawer, the downloads section, the manage models dialog and the stories and tests. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash * ui : rework the chat form actions and the mobile experience Move the add actions into a drawer and a sheet, float the model actions in a bar on a phone, open the context panel in a drawer, and derive the attachment, reasoning and tools menus in hooks. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash * ui : move the mcp servers to the sidebar rail and tidy the dialogs Move the mcp servers dialog to the sidebar rail with its menu entries, even out the dialogs on a phone, and group the mcp and settings stores behind their own modules. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash * ui : polish the shell The secondary button gets its own look back and the chat add button its own light surface. Pressable elements get a pointer cursor again, the font rendering smooths on the app shell, and the agent skills stay out of prettier's way. The root layout props probe rework that came with this polish reads the providers api url and stays with the providers change instead. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash * ui : wrap the model id classes Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash * ui : load the model the new chat CTA picks Start a new chat selected the model but left it unloaded, so the chat opened against a server with nothing in memory. Both CTAs share a load helper now. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash * refactor: Post-review fixes |
||
|
|
4f92965a7b | ui: apply ui_settings on first visit in router mode (#29668) | ||
|
|
9d853bb36a |
webui: Use toLocaleString() format consistently across chat message statistics (#27990)
* webui: Use toLocaleString() format consistently across chat message statistics * Add missing semicolon * ui: respect lint |
||
|
|
4a096b8ff6 |
ui : shared model display primitives (#29644)
* ui : shared model display primitives Extract ModelCapabilityIcons (canonical Tools/Reasoning/Vision/Video/Audio order) out of ModelId and reuse it there, add the shared DialogConfirmDownload for destructive download actions, the discover org avatar with dark-mode inversion and the thin download progress bar, and rework ModelId badges to take thinking/tool-use support directly. Assisted-by: pi:GLM-5.3-Flash * ui : remember hub avatars that failed to load Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash * ui : render shared model row hints as native titles Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash * ui : fix badge guard for draft sidecars, keep parameter precision hasBadges now counts draft sidecar badges, so a sidecar-only model still renders. Billions keep one decimal for hub counts and stay bare for whole values. Avatar failures track the org instead of the instance, and the download progress bar no longer pulses while determinate. Assisted-by: pi:zai-org/GLM-5.3-Flash |
||
|
|
8664eaea30 |
ui : model download pipeline (#27959)
* ui : model download pipeline Track HuggingFace downloads end to end: the server download/cancel endpoints, a status manager fed by the /models/sse download progress events, and a models-discover store holding the catalog and detail state for the discover view. Downloaded and in-flight entries are excluded from the loadable model list. Assisted-by: pi:GLM-5.3-Flash * ui : route sidecar tag lookup through the sidecars util, validate the paused list Assisted-by: pi:zai-org/GLM-5.3-Flash |
||
|
|
4cfb6d1c75 |
ui : model memory-fit estimation (#27957)
* ui : model memory-fit estimation Replace the raw runtime-memory estimate with the app's compatibility check: the smallest real Mac memory tier that fits a model file, budgeted as RAM x 0.75 minus fixed overhead with headroom on the file size. The constants move to lib; the unused runtime-memory estimate is dropped. browser-info's OS detection is exported for reuse. Assisted-by: pi:GLM-5.3-Flash * ui : cover the memory-fit and tool-use heuristics in tests Assisted-by: pi:zai-org/GLM-5.3-Flash |
||
|
|
9b43336114 |
ui : Hugging Face Hub data layer (#27947)
* ui : Hugging Face Hub data layer Add HuggingFaceService and its constants/enums/types: GGUF repo search, file tree and model detail fetching, quant/sidecar filename analysis, shard-set collapsing and the llama.app catalog feed, plus an orgOf() helper on the model name utils. Assisted-by: pi:GLM-5.3-Flash * ui : strip provider tilde prefix from hub avatar urls Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash * ui : trim redundant comments in the HF data layer service Per review: drop JSDoc that restates the method name and inline comments that restate the code; keep only comments carrying non-obvious context. Assisted-by: pi:zai-org/GLM-5.3-Flash * ui : harden the HF data layer error typing, cover the helpers in tests Carries the HTTP status on retryable fetch errors instead of matching the message text. Marks expand-dependent catalog fields optional and documents the data/models index pairing. Adds table tests for the pure helpers. Assisted-by: pi:zai-org/GLM-5.3-Flash |
||
|
|
f653250407 |
ui : model id grammar for sidecars, quants and capability parsing (#27946)
* ui : model id grammar for sidecars, quants and capability parsing Extend the shared model id parser with sidecar tokens (draft variants and auxiliary imatrix/mmproj files), weight-file and custom-quant regexes, and add the tools capability to ModelCapabilities; the selector option row picks it up from the model's declared capabilities. Assisted-by: pi:GLM-5.3-Flash * ui : escape sidecar tokens in the regex alternation Assisted-by: pi:zai-org/GLM-5.3-Flash |
||
|
|
fa2bde5543 |
ui : type-safe API types, fetch helpers and download-ready models store plumbing (#29582)
* ui : type-safe API types, fetch helpers and download-ready models store plumbing Assisted-by: pi:GLM-5.3-Flash * ui : document the model list index pairing, fix an em-dash Assisted-by: pi:zai-org/GLM-5.3-Flash * Update tools/ui/src/lib/components/app/chat/index.ts Co-authored-by: Pascal <admin@serveurperso.com> --------- Co-authored-by: Pascal <admin@serveurperso.com> |
||
|
|
945064fcea |
ui : fix missing svg use and animation elements in preview and download (#28962)
* ui : allow svg use and animation tags in sanitizer * ui : neutralize href animation retargeting in svg sanitizer |
||
|
|
9b421fa946 | ui : Accept WEBM video files (#28622) | ||
|
|
8cfc315a8a |
Add close button to UI toasts (#28246)
This commit tweaks the Toaster element to include a close button. These toasts often cover other UI elements like the model selector, and this change avoids having to wait for them to disappear on their own (e.g. after a load failure). |
||
|
|
b23efaa2ef |
ui: Fix mobile breakpoint + content overflow issues (#29108)
* ui : let the chat column shrink below its content width The chat column is a flex item, so its automatic minimum size kept it as wide as the widest row inside it. Message rows cap at max-w-3xl plus padding, so a narrower window pushed a page-level horizontal scrollbar. Set min-w-0 on the column so the inner scroll containers take over. Assisted-by: pi:deepseek-ai/DeepSeek-V4.1-Flash * ui : wrap markdown tables in a scroll container Markdown tables render as a bare <table>, which keeps its content-driven minimum width and can stretch the chat column past the window. The table-wrapper CSS already existed, but nothing produced the wrapper. Add a rehype plugin that wraps each table in div.table-wrapper, following the existing enhance-* plugins. Assisted-by: pi:deepseek-ai/DeepSeek-V4.1-Flash * ui : scroll long inline content inside markdown blocks Long unbreakable content (inline code, paths, hashes) widened the message row and spilled over the neighbour elements. Give each markdown block a horizontal scroll container, and the content root one as well, since the trailing block renders with display: contents and has no box of its own. Assisted-by: pi:deepseek-ai/DeepSeek-V4.1-Flash * ui : use exact transition properties for markdown images transition: all repainted every property and 300ms felt sluggish. Name transform and box-shadow at 200ms ease-out, and gate the hover scale behind (hover: hover) and (pointer: fine) so touch taps do not trigger it. Assisted-by: pi:deepseek-ai/DeepSeek-V4.1-Flash * ui : fit wide image attachments to the message width Attachment thumbnails used a fixed height with w-auto, so a wide image kept its aspect-driven width and, being flex-shrink-0 in a right-aligned bubble, overflowed to the left of the message row. Cap the thumbnail with max-height and max-width instead of a fixed height so it scales down proportionally, and let it shrink outside the single-row carousel. Assisted-by: pi:deepseek-ai/DeepSeek-V4.1-Flash * ui : keep long tool call titles inside the message row A tool title could not shrink below its content, so a long path escaped the message row. Let the title span shrink and scroll, and for the file tools put the value on its own line only when it does not fit, with the value as the only scroll container. Assisted-by: pi:deepseek-ai/DeepSeek-V4.1-Flash * ui : render get info as a collapsible block with a table get_info rendered its own always-open row with the values trailing the label. Use the shared ToolCallBlock chrome so it collapses like the other tools, and list os and cwd as table rows with the key as a row header. The error and pending states now show inside the body, including the plain-string errors the server tools path produces. Assisted-by: pi:deepseek-ai/DeepSeek-V4.1-Flash * test : pin the server mode in the add menu a11y story The story asserts the add menu's first enabled item is the reasoning submenu, which is mounted only outside router mode. The vitest dev server proxies /props to whichever server is running, so the assertion depended on the machine's server mode and failed whenever a router was up. Pin the mode in the story, including props.role so a re-detection cannot flip it back. Assisted-by: pi:deepseek-ai/DeepSeek-V4.1-Flash * ui: wrap long markdown tokens instead of scrolling every block Making each markdown block and the content root a horizontal scroll container turns any hover transform into a scrollbar: the blockquote translate and the image zoom overflow their block and flash a scrollbar under it. Each block also becomes a block formatting context, so the paragraph margins stop collapsing across blocks and the spacing doubles. Drop both overflow-x rules and let long unbreakable tokens wrap with overflow-wrap: break-word on the content root. break-word leaves the min-content width untouched, so wide tables and code blocks keep scrolling inside their own containers. --------- Co-authored-by: Pascal <admin@serveurperso.com> |
||
|
|
4ff829ec2e |
ui: fix removed reasoning menu in single model mode on desktop (#27985)
* ui: fix accidentally removed reasoning menu in single model mode on desktop * ui: formatting task run to fix storybook test * ui: mount the add menu reasoning submenu outside router mode only The models selector already owns the reasoning submenu in router mode, so the add menu only mounts it in single model mode. The first enabled item of the add menu is now the reasoning submenu, the accessibility story expects it. --------- Co-authored-by: Ben Babik <work@benjaminbabik.com> Co-authored-by: Pascal <admin@serveurperso.com> |
||
|
|
1bc7a5af0d |
webui: stop re-probing disabled /tools endpoint on every message (#28646)
When /tools returns 403 (server started without tools), the web UI refetched the tool list before every chat message, since the guard treated an empty tool list as "not yet fetched". Each retry returned 403 and could trip fail2ban. Skip the refetch once the store flags the endpoint as disabled, and detect that state via the response status code instead of string- matching the error message. The tools panel keeps probing on open so the UI recovers once the server is restarted with tools enabled. Fixes #28299 |
||
|
|
0afb805b19 |
ui: Improve Chat Messages rendering performance (#28460)
* ui : update active conversation fields in place updateCurrentNode, applyConversationUpdate, updateConversationTimestamp and the pin toggle replaced the whole activeConversation object, so its identity changed on every send, tool result and rename. ChatMessages tracks that identity to refresh sibling info, so each replacement triggered a full refetch of every message in the conversation. Write the changed fields instead, mirroring updateMessageAtIndex. Assisted-by: pi:zai-org/GLM-5.3 * ui : reuse the conversation load read for sibling info Opening a conversation read every message from the database twice: once in loadConversation for the active path, once in ChatMessages for the sibling map. Hand the freshly read array over once so the chat screen builds sibling info from it, and set the conversation and its messages in one sync block so effects never see the new conversation paired with the previous one's messages. Assisted-by: pi:zai-org/GLM-5.3 * ui : memoize leaf walks in sibling map build buildSiblingInfoMap resolves each sibling's leaf by walking the last-child chain, once per sibling per message, so the walk repeats along the same chains for every message in the conversation ( O(messages^2) on long chats ). Memoize leaf resolution per build with path compression so each edge is walked once. Assisted-by: pi:zai-org/GLM-5.3 * ui : skip sibling refetch for in-place message edits refreshAllMessages refetches every message of the conversation just to rebuild sibling info, but preserve-responses and non-branching assistant edits never create branches, so the sibling map stays valid. Refresh only after actions that branch (editWithBranching kept) or delete. Assisted-by: pi:zai-org/GLM-5.3 * ui : drop unused currentResponse reactive writes Nothing reads chatStore.currentResponse, but setChatStreaming reassigned it on every streamed chunk, so each token paid a reactive write and string assignment for nothing. Remove the field and the clearUIState wrapper that only reset it. Assisted-by: pi:zai-org/GLM-5.3 * ui : reuse completed agentic turn sections during streaming deriveAgenticSections runs in a $derived invalidated per streamed chunk, but re-derived every turn of the session each time, so per-chunk cost grew with session length. Cache completed turns keyed by their assistant message plus reference checks on every field that feeds derivation; only the streaming turn recomputes. Cache hits return the same section objects, so tool block props stay stable and skip their per-chunk re-derive. Assisted-by: pi:zai-org/GLM-5.3 * ui : share markdown block infrastructure Every markdown block duplicated shared work: a full copy of the hljs theme CSS per instance, and the remark/rehype plugin chain rebuilt on every processMarkdown call ( once per block at mount, again per coalesced chunk while streaming ). Use the single theme style element already maintained by SyntaxHighlightedCode, and build pipelines once - shared process-wide for attachment-less blocks, cached by attachments identity otherwise. Assisted-by: pi:zai-org/GLM-5.3 * ui : measure assistant layout only for the last message Every assistant message ran getComputedStyle, getBoundingClientRect and a ResizeObserver over the previous user bubble at mount, even off-screen ones, forcing a layout pass per message while a long conversation renders. The measured vars only feed the :last-child min-height rule, so gate the effect on isLastAssistantMessage; one measurement and one observer remain, and the effect re-runs when the last message changes. Assisted-by: pi:zai-org/GLM-5.3 * ui : trim whole-blob scans in tool block headers Tool block headers parsed their entire blobs at mount, even collapsed, and most tool results and args are large plain text or embedded file content: skip JSON.parse unless the blob starts with a JSON container, prefilter search-result extraction with a Title:/URL: substring check, and match the end-anchored exit-code marker against only the tail of exec outputs. Assisted-by: pi:zai-org/GLM-5.3 * ui : parse write_file and edit_file titles without the content blob Both block headers parsed the full args JSON at mount, even collapsed, and write_file and edit_file args embed the whole file content or edit strings, so every block paid a full-blob JSON parse just to read the path. Split the meta into a title tier that extracts the path with a targeted key match (full parse only as fallback) and a body tier that keeps the full parse; Svelte deriveds are lazy, and the body snippet renders only while the block is expanded, so collapsed blocks no longer parse args. Assisted-by: pi:zai-org/GLM-5.3 * ui : mount chat messages lazily near the viewport Every message row mounted its full component tree on load, so the cycle collector, GC and layout invalidation kept walking every live object and DOM node even for rows the user never scrolls to - which dominated the profile of long conversations. Wrap each row in a placeholder with an IntersectionObserver ( two viewport heights of runway ) that swaps in the real ChatMessage when the row approaches the viewport; the row shell keeps the content-visibility sizing, and rows stay mounted once realized. Rows targeted by the pending-edit flow mount eagerly. Assisted-by: pi:zai-org/GLM-5.3 * ui : smooth the chat navigation animations Slide the centered new-chat form to the bottom edge with a transform instead of a bottom offset - layout-property transitions need the main thread every frame and stutter while a long conversation loads, while transform transitions run on the compositor. Fade the message list in with a CSS animation keyed to the conversation id, disabled under prefers-reduced-motion. Assisted-by: pi:zai-org/GLM-5.3 * ui : follow the svelte runes guidance in chat message code Two effects detected changes with manual previous-value refs and reset flags. The permission request carries object identity, so its dismissal is now a derived comparing the dismissed request; the continue request is a bare boolean, so its dismissal only shrinks to a reset while no request is pending. Also drop a dead if (browser) guard in the markdown theme loader - effects never run on the server. Assisted-by: pi:zai-org/GLM-5.3 * test : pin the chat perf invariants in the unit suite Cover the fixes whose silent regression would be stale or wrong UI rather than a crash: the turn-section cache must reuse unchanged turns yet recompute on every field it compares; the sibling map must resolve the same leaves after the leaf-walk memoization; the active conversation must keep its identity through field updates; and the blob gates ( exec tail window, plain-text result gate, search prefilter ) must keep accepting what they gate. Only the risky invariants are pinned - no coverage for coverage's sake. Assisted-by: pi:zai-org/GLM-5.3 * refactor : address review remarks Name the tool-arg string-field pattern, move the file tools' path field aliases and the JSON container gates into lib/constants, and export the write_file / edit_file meta types from $lib/types instead of the parser modules. Assisted-by: pi:zai-org/GLM-5.3 |
||
|
|
c457e3bf7f |
ui : embed assets directly with CMake (#28445)
Remove the build-time C++ helper and external gzip dependency, simplifying cross-compilation. Keep the generated C++ in templates for readability and preserve fully embedded UI assets. Signed-off-by: Adrien Gallouët <angt@huggingface.co> |
||
|
|
85d5703a3b |
ui : fix MCP image attachments not displayed in tool block (#25789) (#28089)
* ui : fix MCP image attachments not displayed in tool block (#25789) Fixes regression from #25450 where ChatMessageAgenticContent passed message.extra instead of section.toolResultExtras to tool blocks, leaving tool images invisible. Also fixes TOOL_RESULT_JSON_OPEN_REGEX which misclassified "[Attachment saved: ...]" as JSON. Fixes #25789 Assisted-by: Muse Spark * Addressed PR comments: 1.- Removed ·?? mesage?extra· as it has no case left to cover 2.- Added ·[\· to cover the case of ·[[1, 2], [3, 4]]· case suggested in the PR comment 3.- Added unit test for covering up this regex case * ui : fix MCP image attachments not displayed in tool block (ggml-org#25789) - Addressed lint error on regex (redundant \) |
||
|
|
1863ac0333 | ui: export conversations from database instead of cached store (#27432) |