* ui : strip trailing container-format segments from parsed model names
* ui : show reasoning and modality icons on model options and search by modality
* ui : keep reasoning submenu visible regardless of model state
* ui : add show-org-name-in-trigger display setting
* ui : move model list into a submenu within the model selector
* ui : make model option hover and focus highlight override the active state
* ui : add raw model id tooltip to model selector options
* feat: Enable microphone input as default for audio models
* ui : fix eslint issues in chat form and model selector
* ui: show modality icons instead of file submenu in chat add menu
Assisted-by: pi
* chore: Format
* chore: Format
* ui: add ModelCapability enum and shared modality/capability icon constants
Assisted by: pi:GLM-5.3-Flash
* ui: derive modality badge icons and labels from shared constants
Assisted by: pi:GLM-5.3-Flash
* ui: split model option icons into capabilities and modalities
Replace the supportsThinking flag on ModelId with a capabilities object
keyed like ModelModalities, so future capabilities (tool calls, etc.)
slot in alongside reasoning. Icons and labels now come from the shared
CAPABILITY_ICONS/MODALITY_ICONS constants.
Assisted by: pi:GLM-5.3-Flash
* ui: replace per-conversation MCP overrides with per-conversation tool policy
MCP server enabled state is now global (server.enabled); per-conversation
control moves to disabled tool keys and categories seeded into each new
conversation. Aligns the add sheet with the dropdown options and flattens
MCP tool groups in the tools submenu.
Assisted-by: pi
* ui: keep tool policy migration running when defaults parse fails
A corrupt disabledToolKeys localStorage entry no longer aborts the
migration; it falls through with empty defaults so legacy MCP server
overrides still get converted.
Assisted-by: pi
* ui: fall back to global defaults when agentic flow has no tool policy
Passing empty disabled sets bypassed the global defaults and could
enable tools for callers that do not pass a policy yet.
Assisted-by: pi
* ui: align preferences section headers with their methods
The Reasoning Effort and Working Directory headers sat above tool
policy methods; move them above setCwd and setReasoningEffort. Also
clarify the disabled tools JSDoc: existing rows with an unset field
have an empty policy, defaults apply only when there is no active
conversation.
Assisted-by: pi
* ui: gate MCP server avatars on conversation tool policy
Servers whose tools are disabled for the current conversation (MCP
category or server-scoped key) no longer show as enabled for the chat.
Assisted-by: pi
* ui: drop unused MCP category toggle from tools panel hook
Per-conversation MCP control is server-granular; no component renders
a whole-category toggle, so remove the dead API.
Assisted-by: pi
* ui: skip MCP init when flow policy disables the MCP category
Resolve the effective tool policy before deciding whether to
initialize MCP so flows that will not send any MCP tools skip the
init work. Callers without a policy keep falling back to global
defaults.
Assisted-by: pi
* chore: format
* ui: restore reasoning section in mobile add sheet
The sheet rewrite dropped it; the desktop dropdown still has it.
MCP Prompts and Resources stay out of the sheet on purpose.
Assisted-by: pi
* ui: clear MCP server group key in enableAllToolsForServer
The group key disables every tool of the server regardless of
per-tool keys, so re-enabling a server from Settings did nothing
while it was set.
Assisted-by: pi
* ui: skip MCP init when no policy-enabled server remains
Extends the category-level check: the flow also skips MCP init when
every globally-enabled server has its server-scoped group key
disabled in the tool policy.
Assisted-by: pi
* ui: make Settings tools tab edit defaults with category toggles
Adds per-category checkboxes and a caption stating the tab applies
to new conversations; tool picks inside a chat only affect that
chat.
Assisted-by: pi
* ui: gate cwd picker and mention picker on effective tool policy
Both checked the global disabled set directly, so a conversation
that disabled file_search still showed search as available.
Assisted-by: pi
* ui: clean up tool key helpers and store docs
Documents getEnabledToolsForLLM properly, unstacks the JSDoc at
isEntryEnabled, makes setToolEnabled persist like setCategoryEnabled
(toggleTool now delegates to it), and routes the serverId-less MCP
branch of toolKey through getMcpServerToolsKey so both key formats
come from one place. Preferences banner comments become plain
comments so they no longer read as class member docs.
Assisted-by: pi
* ui: indeterminate group checkboxes and inert grayed rows
A category that is on with nothing enabled under it now shows the
mixed checkbox state instead of a checked box next to 0/N. Rows
grayed out by a disabled parent no longer stay clickable behind
opacity.
Assisted-by: pi
* ui: gate MCP prompt and resource capabilities on tool policy
hasPromptsCapability and hasResourcesCapability accept an optional
set of usable server ids; ChatFormActions resolves it from global
enablement minus the active conversation's policy. Restores the
per-chat gating the old mcpServerOverrides provided; callers without
arguments keep global behavior.
Assisted-by: pi
* ui: remove unmounted MCP submenu component
Never rendered anywhere; its entries are duplicates (prompts and
resources live in the attachment menu, servers in the add menu and
sheet) that would need capability wiring maintained for nothing.
Assisted-by: pi
* ui: fix model information dialog width on all screen sizes
The dialog sets container-type: inline-size, so auto width ignores
its contents and collapses to padding. Give it an explicit viewport
width on mobile and cap at 60rem on desktop.
Assisted-by: pi
* ui: scroll wide chat template in model information dialog
Long unbreakable Jinja tokens blew out the table and dialog width;
the block now scrolls horizontally instead of stretching.
Assisted-by: pi
* ui: use fixed table layout in model information dialog
Auto table layout sizes columns to content min-content, so the chat
template's long lines kept inflating the dialog despite the scroll
wrapper. Fixed layout pins the first column and gives the value
column a definite width the wrapper can scroll within. min-w-0 on
the grid item guards the same path on the grid side.
Assisted-by: pi
* ui: make model information dialog full-screen on mobile
Matches the settings dialog pattern: full viewport below md,
calc-sized and capped at 60rem on desktop.
Assisted-by: pi
* ui: stack chat template row in model information dialog
Label above the block in a single full-width cell, so the template
gets the whole table width and its horizontal scroll is usable on
narrow screens.
Assisted-by: pi
* ui: scroll model information header with the content
The base dialog header is sticky; this dialog overrides it to
relative so the title and description scroll away with the body.
relative keeps the header as the close button's containing block.
Assisted-by: pi
* ui: replace literal comment text in sheet group snippet
A // line inside the Svelte snippet rendered as visible text; use an
HTML comment.
Assisted-by: pi
* ui: let indeterminate state win over checked in group checkboxes
The checkbox indicator snippet renders the check icon whenever
checked, so the mixed state never showed. Pass the checked prop
as false while indeterminate.
Assisted-by: pi
* ui: initialize only policy-enabled MCP servers for a flow
ensureInitialized accepts an optional server id set; the agentic
flow passes the servers its tool policy leaves usable, so servers
disabled for the conversation no longer get connected. Callers
without arguments keep the global behavior.
Assisted-by: pi
* ui: derive group checkbox state in useToolsPanel
Moves the mixed-state derivation out of the submenu and sheet
snippets into one getGroupCheckState accessor; the snippets just
consume checked and indeterminate.
Assisted-by: pi
* ui: gate /prompt command on the conversation tool policy
The slash command's availability now follows the same rule as the
agentic flow instead of the global capability check, so it disables
itself when the conversation's policy leaves no usable MCP server.
Assisted-by: pi
* ui: remove dead MCP prompt menu trigger chain
The /prompt slash command is the surviving trigger; the menu-button
path (onMcpPromptClick, hasMcpPromptsSupport, showMcpPromptButton,
the MCP_PROMPT attachment item and its unrendered item arrays) has
no consumer left. Message display for inserted prompts is untouched.
Assisted-by: pi
* ui: render dash for mixed-state group checkboxes
The accessor refactor dropped the checked-and-not-indeterminate
guard, so the category-on flag won and the dash never showed. The
tooltip keeps using the raw parent flag since clicking a mixed
group still disables it.
Assisted-by: pi
* ui: fix group checkbox sticking checked after disable
Clicking a mixed-state group box let bits-ui optimistically flip
its internal checked flag; the derived checked prop did not change
across the transition (both mixed and off map to checked=false),
so Svelte never applied the settled value and the check icon stuck
while the count already read 0/7.
Pass the parent flag as checked and the mix as indeterminate, so
every group toggle changes checked; render the dash on top of a
checked box for the mixed state.
Assisted-by: pi
* fix: UI for Model Information dialog
* ui: keep MCP connections stable across policy switches
ensureInitialized folds the policy into its config signature, so
alternating two conversations with different policies tore down and
reconnected every server with health checks included. Tool collection
already filters by the flow policy, so initialize every
settings-enabled server instead and never pass a policy into the MCP
config. The duplicated policy-server check becomes one accessor on
ConversationPreferences.
Assisted-by: pi
* ui: remove dead MCP resources menu trigger chain
Same shape as the earlier prompt trigger cleanup: nothing renders the
MCP resources menu button, and the only live entry into resource
browsing is Settings > MCP Servers plus the attachment resource
picker. Drop onMcpResourcesClick, hasMcpResourcesSupport,
MCP_RESOURCES_CLICK, the AttachmentItemVisibleWhen enum and
hasResourcesCapability; the resources display, browser and picker
components are untouched.
Assisted-by: pi
* ui : open MCP servers in a dialog from the chat form
Replace the MCP servers submenu with a single "MCP Servers" item that opens
a new DialogMcpServers dialog instead of navigating to the /mcp-servers route.
Assisted-by: pi
* ui : browse MCP resources from the server card
Make the Resources capability badge clickable so it opens the MCP resources
browser dialog, and drop the page-only chrome from SettingsMcpServers.
Assisted-by: pi
* ui : remove mcp-servers route and sidebar entry
MCP servers are now managed in a dialog, so drop the dedicated route and the
sidebar icon that navigated to it.
Assisted-by: pi
* ui : remove unused MCP servers submenu component
The submenu was replaced by the MCP servers dialog, so delete the component
and its export.
Assisted-by: pi
* feat(ui): add DialogSettingsChat dialog
* refactor(ui): switch SettingsChat to in-app section navigation
* feat(ui): open settings as dialog from sidebar
* refactor(ui): remove settings route and URL-based settings navigation
* fix(ui): adjust MCP dialogs for new base sizing
* chore: Formatting & linting
* feat(ui): make base dialog responsive and support sticky headers
* ui: move dialog close button to the sticky header
Assisted-by: pi
* chore: Formatting & linting
* ci : inline UI version resolution into ui-build.yml
* ci : build UI once and reuse the artifact in release jobs
Server jobs now extract the ui-build artifact into tools/ui/dist instead of npm-building the UI. Also removes the get-version job and the no-op -DHF_UI_VERSION flags.
Assisted-by: pi:Kimi-K3
* ui : disable the npm UI build by default (LLAMA_BUILD_UI=OFF)
The flag now only controls building the UI from source via npm. The UI
is still embedded by default from local tools/ui/dist or the prebuilt
download (LLAMA_USE_PREBUILT_UI=ON). CI jobs no longer npm-build the
UI; server-sanitize does not need node anymore.
Assisted-by: pi:Kimi-K3
* ci : rename the ui-build artifact to llama-ui.zip
Consistent with the other artifact names in the Actions summary.
Assisted-by: pi:Kimi-K3
* ci : clarify the windows artifact merge in release.yml
The windows-cuda/vulkan/sycl jobs build only the backend library;
llama-server (with the embedded UI) is injected into their zips from
the windows-cpu package during the release. State this in the job
comments and use accurate wording in the merge step.
Assisted-by: pi:Kimi-K3
* ui : add browser-style conversation tabs store
Track open conversation tabs in order, persisted to localStorage and
pruned against the loaded conversation list on init. The chat layout
syncs the route's tab on every navigation, so any way of reaching a
conversation opens a tab for it.
* ui : add temporary new-chat tabs
New-chat tabs are unsaved conversations carrying a temporary id used
directly as the route (#/chat/<id>). They live in memory and are only
persisted to the database - keeping the same id so the route and tab
stay stable - when the first message is sent. Deleting one drops it
without confirmation, and deleting conversations now closes their tabs.
* ui : render conversation tab bar in chat layout
Desktop-only tab bar above the chat screen, one tab per open
conversation or new-chat tab. The active tab follows the route id;
clicking navigates, middle-click or the close button closes (switching
to the left neighbor), and a trailing + starts a new chat. Tabs appear
only on chat-id routes; the bare #/ new-chat view has none. The bare
route stays put unless a prompt/model deep-link routes it to a new-chat
tab.
* ui : route new-chat entry points through tabs
The sidebar New chat item, Cmd+Shift+O, the search page and the
arrow-key fallback now open a new-chat tab instead of navigating to the
?new_chat URL, which is removed. New chat is no longer a special route
but a tab like any other conversation.
* ui : track sidebar expanded state in a shared ui store
Move the desktop sidebar expanded/collapsed state out of deviceStore into a
dedicated uiStore so the chat tab bar can react to it.
Assisted-by: pi
* chat : add opt-in conversation tabs setting
Add a Display setting that turns browser-style conversation tabs on or off,
enabled by default.
Assisted-by: pi
* chat : add browser-style conversation tabs with a new-chat screen
Track open conversations as tabs above the chat, one per open chat, plus a
single New chat tab for the bare `#/` route. New chat is just the `#/`
screen - no temporary conversations - and its tab is dropped when navigating
away. Sending the first message creates a real conversation and opens a tab
for it.
Assisted-by: pi
* chat : turn tab bar into a horizontally scrollable carousel
Make the tab bar a horizontally scrollable carousel with edge scroll buttons
and active-tab centering, and align its styling with the sidebar.
Assisted-by: pi
* chat : restyle the scroll-to-bottom button to match tab styling
Assisted-by: pi
* chat : add close-tab keyboard shortcut
Assisted-by: pi
* chat : soften tab bar fade and dim inactive tabs
Assisted-by: pi
* feat: Add stop button to tabs
* refactor: Componentize
* ui : fix carousel scrollability detection
Observe the content wrapper as well as the container, since adding overflowing items does not change the container's own box size. Also expose an onScrollableChange callback.
Assisted-by: pi
* ui : add unified ScrollCarousel component
Single carousel component with top/center variants, gap and scroll options, and hover-revealed chevrons. Rename the HorizontalScrollCarousel accessibility story accordingly.
Assisted-by: pi
* ui : migrate carousels to ScrollCarousel
Switch the settings mobile header, attachments list, thumbnail strip, and MCP resources to the unified component, and drop HorizontalScrollCarousel.
Assisted-by: pi
* ui : improve chat tabs carousel UX
Scroll newly added tabs into view, fade overflowing tabs at the edges, and hide the New chat button while a new-chat tab is open.
Assisted-by: pi
* refactor: Naming
* chat : add keyboard shortcut to jump between conversation tabs
Shift+Cmd/Ctrl+Left/Right cycles the open tabs, mirroring the existing
Shift+Cmd/Ctrl+Up/Down conversation navigation.
Assisted-by: pi
* chat : make the whole tab item act as a link
The full tab is now a link instead of only the inner label button, while
the stop and close buttons stay interactive by swallowing their clicks.
Assisted-by: pi
* chat : adjust tab bar width and use a shared offset variable
Widen the tab bar for the expanded sidebar and rename the tab bar height
variable to --chat-tabs-offset with a smaller value so the chat screen
min-height accounts for the overlay without overshooting.
Assisted-by: pi
* chat : account for the tab bar offset in the assistant min-height
Subtract the tab bar offset when it is shown so the last assistant message
does not overflow the available viewport space.
Assisted-by: pi
* refactor: Post-review fixes
* ui : restore deep links on the chat start page
- handle ?model selection, with ?load=true eager router loading
- ?q now creates a conversation, sends the prompt, and clears the params
- show the not-available-model dialog for unknown models
- never block mount on the conversation list
Assisted-by: pi
* ui : fix tab item link nesting and centralize tab constants
- the tab anchor covers the whole item while stop/close stay siblings,
so interactive elements are never nested inside the anchor
- cmd/ctrl/middle clicks are left to the browser (new window)
- extract the tab labels, the active-tab data attribute, and the
sidebar-offset max widths into constants
Assisted-by: pi
* ui : tidy scroll carousel hook and keep mobile header arrows on
- drop the dead scrollLeft/scrollRight helpers and the unused
onScrollableChange/scrollBy props
- init the carousel once instead of inside a derived
- restore items-start on the center variant
- always show the settings header arrows on touch
Assisted-by: pi
* ui : keep the new-chat tab across reloads and fall back on close
- the new-chat sentinel is no longer pruned on init, so reloading on
the bare new-chat route keeps the tab the user is on
- closing the active conversation falls back to the new-chat screen
when Conversation tabs are off
Assisted-by: pi
* ui : don't block startup on the conversation list
- prune persisted tabs after the list loads in the background instead
of awaiting it during init
- openNewChat now returns void; its return value was never read
Assisted-by: pi
* ui: fix routing nits
* chore: Update doc comments
* refactor: Mark fire-and-forget openNewChat calls as `void`
* chat: fix the deep-linked prompt, the tab width and the tab shortcuts
The chat start page creates the conversation and hands the prompt over
to the chat route, which still sees it in the query string. Sending it
on both sides queues the second copy as a pending message, which shows
up as a stray user bubble once the answer lands and vanishes on reload
since it never reaches the database.
The tab bar takes the max width of the collapsed sidebar while it is
expanded, and the other way round.
The tab list is pruned against a snapshot of the loaded conversations,
so a conversation created while that list is still loading loses its
tab even though the route just opened it. The active tab then falls out
of the list and the cycling shortcut jumps to an edge on every keypress
instead of moving one tab over. Tabs synced from the route are kept as
they are, only the persisted ones are pruned.
The rich chat input claims ctrl or alt with shift and an arrow for its
badge-aware word jump, which now belongs to the tab cycling shortcut.
Holding shift hands the key combination over, the plain word jump is
unchanged.
The close-tab shortcut consumes the event before checking whether the
setting is on, and the logo background loses its importance flag.
---------
Co-authored-by: Pascal <[email protected]>
* ui : rework the settings registry into ordered raw-data sections
SETTINGS_REGISTRY becomes an ordered SettingsSectionEntry[] array; the
array order is the sidebar display order. Section titles, color mode
options and title radio options are declared inline in their section or
entry. Entries gain showInUi; MCP servers, the system-message toggle and
the title LLM flag become hidden entries of their own section.
Derived values (config defaults, help info, chat sections, numeric field
lists, syncable parameters) are still derived here; they move to their
actual consumers in follow-up commits.
* ui : extract settings localStorage persistence into SettingsService
Stateless load/save of the settings config and user-override keys, plus the
legacy theme key migration. Business logic (default merging, mobile
sendOnEnter default, applying the migrated theme) stays in the store.
* ui : move the settings exit route into ROUTES
SETTINGS_FALLBACK_EXIT_ROUTE is just a route, so it lives with the other
routes as ROUTES.SETTINGS_EXIT.
* ui : derive the syncable parameter list in the parameter sync service
The syncable parameter mapping is only consumed by the sync service, so
derive it there from the registry instead of exporting it from the
constants file.
* ui : restore isPrivate for API key masking
* ui : clean up settings registry and router fetch guard
Drop the per-entry section field (duplicates the parent slug and is
never read) and guard the router model fetch on fields?.length so the
Tools/Import-Export pages with empty fields are excluded again.
Assisted-by: pi
* ui : merge sampling and penalties settings into one section
Assisted-by: pi
* ui: Extract server stream lifecycle from chatStore into ChatStreamManager
Discovery, attach/replay, resume retry and the remote-running snapshot
formed a cohesive cluster inside chatStore. It now lives in
chat-streams.svelte.ts as ChatStreamManager, owned by chatStore, which
keeps the public entry points as delegates so components are
unchanged. chatStore: 2877 -> 2418 lines.
* ui: Extract user interaction gates from agenticStore into AgenticGates
Tool permission requests, turn-limit continue prompts and queued
steering messages are the state the loop waits on between turns. They
had no coupling to session state, so they now live in
agentic-gates.svelte.ts; agenticStore keeps delegates so components
are unchanged. agenticStore: 1196 -> 1073 lines.
* ui: Compose MCP resources under mcpStore.resources
Resource state was a second import scope next to mcpStore. Consumers
now go through mcpStore.resources, so the MCP surface is one store;
mcp-resources.svelte.ts stays a separate file owned by mcpStore.
* ui: Reorganize stores into domain namespaces
* fix: Update stale doc comments
* ui: Consolidate conv running-state into a chat activity ledger
Running-state was split across chatStore.chatLoadingStates (local
pipes), ChatStreamManager.remoteRunningConvs (backend sessions) and
attachingConvs (attach lifecycle), unioned by hand in
getAllLoadingChats and cross-cleaned by setChatLoading calling
streams.clearRemoteRunning - the 'spinner ghosts until tab toggle'
workaround.
chatActivityStore now owns both sets with one transition per event:
markLocal / localEnded (local pipe end also drops the stale remote
hint, no cross-owner call) / applyRemoteSnapshot (diffed). The
sidebar reads chatStore.activity.loadingConvs through the unchanged
getAllLoadingChats entry point.
Consequences:
- isStreamingActive and its five manual writers are gone; isStreaming()
now reports whether the active conversation has a live streaming
pipe, which is what all four consumers (assistant row, stop action,
context gauge, chat screen) actually check
- isLoading/isReasoning become derived from the per-conv maps plus
the active conversation, dropping the manual resync in
syncLoadingStateForChat and clearUIState
- attachingConvs and the last-attach coordination disappear from
ChatStreamManager
- getAllStreamingChats (no consumers) is removed
* ui: Give store collaborators narrow host interfaces
Collaborators took 'host: typeof <store>', i.e. the store's entire
public surface, which is how chatStore's streamChatCompletion,
createAssistantMessage, getApiOptions and setStreamingActive got
widened to public. Replace with per-collaborator interfaces carrying
only the members each one drives:
- ChatStreamHost (chat/streams) - activity, processing, streaming
states, abort controller, loading/streaming setters
- ChatFlowsHost (chat/flows) - streaming core, message creation,
per-conv state setters
- McpHealthHost (mcp/health) - connection registry + reconnection
- ModelPropsHost / ModelStatusHost (models) - model rows, feed
updates; the managers write modalities/status back onto the host's
rows, so those members stay writable
- ConversationsPreferencesHost (conversations) - the active row and
the conversation list
The store classes now declare 'implements <Host>' so the contract is
visible at the class level, and the 'import type { <store> }' back
references in the collaborators disappear entirely - the host
contract is local to each collaborator file, and collaborators can
no longer reach around their slice. Members stay public (structural
typing), but the collaborator side is now compiler-enforced.
* test: Chat Activity store test
* refactor: Cleanup
* chore: Remove legacy architecture docs
* ui: Memoize findMessageIndex for the streaming hot path
Streaming looks up the same message index on every chunk, a linear
scan of activeMessages each time. Cache the last lookup and reuse it
after validating the id still sits at the same position (O(1)); any
structural change to the array fails validation and falls back to a
full scan.
* ui: Throttle per-chunk stream state writes to localStorage
saveStreamState ran JSON.stringify + a synchronous localStorage.setItem
on every decoded chunk of the stream. The read loop now goes through a
new saveStreamStateThrottled (one write per conversation per 500ms,
latest value held pending); the public saveStreamState keeps its
immediate-write contract for stream start and pre-fetch, and also
resets the throttle window.
A pending offset is force-flushed at resume boundaries (resumeStream
reads the offset back from localStorage), on visibilitychange->hidden
and on pagehide, so a reload always finds a usable offset. The resume
offset only needs to be roughly current since the server retransmits
from a line boundary and the client discards its partial line.
Adds unit tests for the throttled/flush/clear interplay.
* ui: Compute context gauge timing stats in one pass
currentRead/Fresh/Cache/Output were separate deriveds, each running a
full reverse scan of activeMessages for the last assistant timings,
and cumulative ran its own forward scan plus an agentic filter - 4-5
O(n) passes per chunk while streaming. Replace with a single
summarizeAssistantTimings() pass (last assistant timings, last
agentic llm totals and the cumulative sums) feeding a shared derived
snapshot. Semantics unchanged, including the live-stats overrides and
the agentic llm-totals branch.
* agentic : clear session state when a conversation is deleted
Every conversation that ran an agentic flow left an AgenticSession in the
store forever; clearSession was never called. conversationsStore now
notifies deletion listeners and agenticStore drops the matching sessions,
avoiding a circular import back into conversationsStore.
* chat : extract ChatService.normalizeMessagesForApi
The DB->API message normalization (convert + drop empty system messages)
was duplicated in sendMessage, preEncode and the agentic flow. Extract it
into one shared method and call it from all three.
* sse : share record splitting and data extraction
splitSseRecords and extractSseDataPayload centralize the record-boundary
splitting and data: line extraction used by parseSseJsonStream and the
models status feed. chat.service keeps its own line-based parser for
resume support.
* api : delegate apiFetchWithParams to apiFetch
apiFetchWithParams duplicated apiFetch's headers/fetch/error handling
body-for-body; it only differs in URL construction. Build the URL and
delegate.
* chat flows : dedupe title, timings and cleanup handling
- conversationsStore.applyTitleFromContent centralizes the title-from-first-
message logic duplicated in 5 places
- ChatProcessingStore.applyStreamTimings centralizes the onTimings handler
shared by the chat and continue flows
- host.cleanupStreaming centralizes the loading/streaming/processing reset
repeated across the continue flow's exit paths
* conversations : centralize conversation update mirroring
rename, pin, mcp override, reasoning effort and cwd all repeated the same
write-DB-then-mirror-into-list-and-active dance. A single
applyConversationUpdate(id, updates) on the host collapses all five and
removes the forgot-to-mirror-one-field bug class. Drops the redundant
array reassignment in setCwd (deep field assignment is reactive).
* mcp : dedupe tool execution, server parsing and tool indexing
- executeTool delegates to executeToolByName (only diff was argument parsing)
- drop the private #parseServerSettings copy; use parseMcpServerSettings
- cache getServers() keyed on the raw config value (hot path)
- indexServerTools() unifies the three identical toolsIndex rebuild loops
Assisted-by: Claude
* mcp : share cursor pagination and tool indexing
- MCPService.paginate() collapses the identical do-while loops in
listAllResources and listAllResourceTemplates
- promoteHealthCheckToConnection now uses indexServerTools like the other
connect paths
Assisted-by: Claude
* database : share message parent-child bookkeeping
- addChildToParent() dedups the append-to-children update in createMessageBranch
and createSystemMessage
- removeChildFromParent() dedups the remove-from-children cleanup in deleteMessage
and deleteMessageCascading
- bulkAdd the cloned messages when forking a conversation instead of one add
per message
Assisted-by: Claude
* chore: Lint/format
* fix: `pagehide` event from `window`
* refactor: Api Fetch util
* docs : rewrite architecture sections in README
Update the high-level diagram, routes, hooks, stores, services and data
flow tables to match the current UI structure (mcp/settings/search
routes, agentic/tools/mcp stores, MCPService/ToolsService/SandboxService,
/tools API). Fix stale architectural patterns for per-conversation state
and modality validation.
* chore : add ESLint rule for blank lines between accessors
Enforce a blank line between consecutive class accessors. The core
padding-line-between-statements rule does not cover class members, so a
local rule is needed.
* refactor : reorder store members and unify naming
Order store class members as public fields, private fields, constructor,
getters, public methods, then private methods. Normalize private naming
to the `private` keyword (drop `#` and the `_` prefix where there is no
matching public getter). Rename conversationsStore.init() to
initialize() to match the other stores.
* refactor : prefix lookup methods with get in agentic and chat stores
Unify bare-name lookup methods with the get* prefix used across the
other stores (mcp, models, tools, settings). Renames currentTurn,
totalToolCalls, lastError, streamingToolCall, executingToolCallId,
pendingPermissionRequest, pendingContinueRequest,
pendingSteeringMessageContent, pendingSteeringMessageExtras in the
agentic store and pendingMessageContent, pendingMessageExtras in the
chat store. Updates the two consuming components and a doc comment.
* refactor: Clean up comments in stores' and services' code
* chore : add ESLint rule for class member ordering
Enforce structural order (public fields -> private fields -> constructor ->
getters -> setters -> public methods -> private methods) with alphabetical
sorting within each group via perfectionist/sort-classes. Dependency
detection keeps Svelte $derived fields in a valid dependency order instead
of alphabetizing them, since Svelte rejects forward references.
Assisted-by: Claude
* refactor : reorder class members to match new ESLint rule
Apply the sort-classes rule across stores, services, hooks and utils.
Pure reordering - verified no logic changes by comparing sorted line
multisets before/after. All tests and svelte-check pass.
* ui: Move stream lookup and replay fetches into ChatService
chatStore called fetch() directly for /v1/streams/lookup and the
/v1/stream replay. These now live next to the other stream-session
methods in ChatService, so services stay the only API I/O layer.
* ui: Move /models/sse feed reader into ModelsService
ModelsService.watchModelEvents owns the byte stream, reconnect loop
and SSE record parsing; modelsStore keeps only event routing and
state.
* ui: Extract conversation import/export into ConversationTransferService
The JSONL session format, ZIP archiving and browser downloads are
pure I/O with no store state, so they move out of
conversationsStore. The store keeps the DB orchestration
(bulkExportConversations, downloadConversation,
importConversationsData) and delegates the format work.
* ui: Consolidate active model resolution into modelsStore.activeModelId
The same resolution chain was duplicated in useChatScreenActiveModel,
ChatForm, ChatFormActionModels and contextStatsStore, with slight
drift in the single-model fallback. The canonical getter now lives in
modelsStore, and the shared last-assistant-model lookup moved to
utils as getConversationModel.
* ui: Initialize stores explicitly via initStores()
Store constructors and module-level side effects ran migrations and
localStorage reads in import order. Migrations rename and rewrite
localStorage keys, so a settings load racing ahead of them could
clobber migrated values. initStores() is called once from the root
layout and runs migrations first, then the stores that read
localStorage, then the conversations DB load.
* refactor: Constants for stream query params
* ui: Remove dead code from stores
- persisted() helper was exported but never used
- messageUpdateCallback / registerMessageUpdateCallback were never wired up
- conversationsStore.initialize() alias, single caller moved to init()
* ui: Merge device, theme and viewport into a single deviceStore
All three are reactive browser-environment signals, now exposed as one
class store: deviceStore.isMobile, deviceStore.isIOSDevice / isIOSSafari
/ isWKWebView / isStandalone and deviceStore.systemTheme.isDark. The
systemTheme name disambiguates the OS preference from the user theme
preference in settingsStore. Drops the unused viewport export (only
isMobile was consumed).
* ui: Merge build info into version store
One VersionStore class with build (llama.cpp build number from
build.json) and frontend (PWA version from _app/version.json),
matching the class pattern of the other stores.
* ui: Colocate context gauge popup state with its components
The gauge popup state is local UI state shared only by the
ChatFormContextGauge subtree, so it lives next to its consumers
instead of the app-scope stores barrel.
* server : extend file_glob_search for UI pickers
* ui : add per-conversation working directory with picker
* ui : add path navigation and search scope to cwd picker
Treat path-like queries (starting with / or ~) as directory navigation
instead of glob-matching the whole query: search the parent for the last
segment, and descend into an exactly-typed directory by listing its
children. Show the effective search scope in the footer and auto-search
on open so the current directory and its siblings appear immediately.
Assisted-by: Claude
* db : persist per-call tool cwd on tool result messages
* ui : abbreviate tool paths under home with a tilde
* ui : show the per-call cwd on exec shell rows
* ui : clarify the synthetic cwd message for the model
* ui : reuse the trailing cwd row on a repeated pick
* ui : don't jump when a cwd row is injected mid-chat
* chore: Formatting
* refactor: Cleanup comments
* ui : unify working directory naming and add a synthetic-message flag
* ui : render synthetic cwd rows without a scroll jump
* ui : decouple the working directory picker into utils and sub-components
* ui : add get_info tool call block
* chore: Formatting
* refactor: Cleanup
* refactor: Cleanup
* refactor: Cleanup
* fix: UI
* server : harden file_glob_search listing (kind enum, timeout, symlink guard, absolute base)
* ui : use persisted isSynthetic flag for cwd rows, drop legacy formats
* ui : cache picker search, fail visibly on native resolve
* ui : escape glob metacharacters in picker search glob
* ui : simplify auto-scroll pin
* chore: Format
* fix: Use `SvelteMap`
* refactor: Post-review fixes
* ui: accept Windows roots in the working directory picker
recognize a drive root (C:) and a UNC share (//host/share) as path
navigation, alongside the POSIX root and ~, so a query like D:\repos
lists that directory instead of glob-matching it under the home dir
split below the root, so a bare drive resolves to its root rather than
to a drive-relative prefix
rewrite backslashes into forward slashes only when the query carries a
Windows root, since a backslash is a legal POSIX filename character
paths keep travelling with forward slashes, which is what the server
returns and what Windows accepts
---------
Co-authored-by: Pascal <[email protected]>
* fix: single-flight conversations store init
* refactor: remove unused legacy-migration util
* fix: make createSystemMessage transactional
* fix: delete message branches cascading on edit/regenerate
* fix: stop stamping lastModified on conversation metadata updates
* fix: count cascaded forks in bulk delete toast, bulkify deleteAll
* refactor: drop redundant conversation list respreads
* refactor: create conversation in a single write
* fix: use table constant in toggleConversationPin
* fix: keep the system message placeholder out of the edit form
* fix: keep focus in the system message editor after opening it
* fix: focus the main chat form after submitting a system message
* fix: update timestamp of the correct conversation on stream completion
* feat: WIP
* feat: Replace conversation rename flow with unified AlertDialog component
* feat: Add radio group component and consolidate title generation settings
* refactor: Remove JS Sandbox global toggle and migrate legacy user state
* chore: Formatting
* refactor: Cleanup
Co-authored-by: Aleksander Grygier <[email protected]>
* refactor: Cleanup
* refactor: Marquee selection hook
* feat: UI improvements
* refactor: Bulk db operations
* fix: optimize bulk conversation deletion to handle ancestor chains
* refactor: remove pairedKey mechanism from settings system
* fix: remove redundant onclick handler from dialog cancel button
* chore: pin @lucide/svelte to exact version
* feat: Run JavaScript tool disabled by default
* fix: correct active conversation deletion tracking in bulk delete
* feat: improve shift-key multi-selection support in sidebar via keyboard
* refactor: Retrieve JS Tool enabling via Developer Settings
* nits: sync, dialog wording, cycle guard, and lockfile follow-ups
- Restore titleGenerationUseLLM registry entry so it syncs across devices again
- Mention fork cascade in the bulk delete confirmation dialog
- Clear newParent on cycle guard break so children never point at a deleted conversation
- Align @lucide/svelte in package-lock.json with the exact pin in package.json
---------
Co-authored-by: Pascal <[email protected]>
* feat: Add shimmer text animation for processing state indicators
* feat: Redesign CollapsibleContentBlock component with improved UX
* feat: Add conditional setting display support with dependsOn field
* feat: Add showAgenticTurnStats setting for per-turn statistics
* feat: Update ChatMessageAgenticContent with improved UI and new features
* feat: Enhance file read tool UI/UX
* feat: Refine styling of collapsible content and code preview blocks
* feat: add terminal variant to CollapsibleContentBlock
* feat: add built-in tools UI registry
* feat: extract ChatMessageReasoningBlock and ChatMessageToolCallBlock
* refactor: simplify ChatMessageAgenticContent to use extracted blocks
* fix: correct markdown content block margin spacing
* fix: reorganize SettingsChatFields layout and reset button positioning
* fix: use direct map access in agentic store session methods
* refactor: remove reasoning preview/throttle system from CollapsibleContentBlock
* feat: add auto-scroll to reasoning block and remove showThoughtInProgress
* feat: add ChatMessageToolCallDateTime component and support for new tool types
* feat: improve auto-scroll reliability in reasoning block with RAF coalescing and MutationObserver
* feat: show MCP server favicon for tools without a built-in icon
* feat: add search-results parsing utilities and tests
* feat: add ChatMessageToolCallSearchResults component
* feat: integrate search results rendering into ChatMessageAgenticContent
* feat: display tool call input alongside output in ChatMessageToolCallBlock
* style: use muted foreground color in reasoning block content
* chore: Format
* feat: Refine reasoning block layout and make pending thoughts display configurable
* feat: Stream tool call code blocks with auto-scroll and handle partial JSON
* feat: add streaming permission gate infrastructure
* feat: wire permission gate into the agentic loop
* fix: bail out on abort and skip already-approved tool calls
* fix: clear partial tool calls on abort and savePartialResponse
* test: cover partial tool call cleanup end-to-end
* refactor: Remove streaming permission gate logic
* fix: Correct autoscroll and streaming gates for tool calls and reasoning blocks
* refactor: Chat Message Assistant componentization
* fix: Show health metadata for disabled MCP servers and promote connections on enable
* fix: Inherit global enabled state for missing MCP per-chat overrides
* refactor: Cleanup
* refactor: Split ChatMessageToolCallBlock into dedicated components
* feat: Add live streaming and auto-scroll for tool execution output
* feat: Add line numbers and change markers to file edit diffs
* chore: Formatting
* feat: Add type definitions and utilities for recommended MCP servers
* feat: Add recommended MCP servers configuration and storage key
* feat: Add McpServerCardCompact component for recommended servers
* feat: Add recommended servers section to Add New Server dialog
* feat: Update McpServerForm to support authorization requirements
* feat: Add select-none classes for text selection prevention
* feat: Add recommended MCP server icon assets
* refactor: Store dismissed MCP recommendations as a boolean flag
* feat: Render tool results as JSON or Markdown based on detected content type
* feat: UI improvement
* feat: Render search block early and update heading to show execution state
* fix: Prevent non-web-search tools from triggering the search UI block
* refactor: Cleanup
* refactor: Extract hardcoded icon size classes into shared constants
* refactor: Extract hardcoded tool result separator into a shared constant
* refactor: Tool Calls UI/logic
* refactor: Cleanup
* refactor: Cleanup
* refactor: Cleanup
* fix: drop MCP recommendations auto-popup and silent preloads
* feat: Add consent-driven MCP recommendations inside Add New Server dialog
* refactor: Drop mcpDefaultServerOverrides for mcpServers[i].enabled
* feat: Center the empty state on the MCP settings page
* fix: keep existing MCP cards intact when adding a new server
* fix: keep MCP cards stable when a new server is added
* refactor: keep MCP server list in config insertion order
* feat: shrink the recommended-MCP cards to two tools each and fit them in one row
* feat: make recommended MCP cards click-to-fill and tighten copy
* feat: highlight the selected MCP recommendation and stop auto-focus on dialog open
* feat: derive MCP recommendation selection from the form URL
* fix: make recommendation MCP cards fully non-focusable
* fix: redirect focus from first card to the URL input on consent
* chore: Formatting
* refactor: Remove Recommended MCP Servers completely
* fix: Preserve legacy mcpDefaultServerOverrides key after merge migration for downgrade compatibility
* feat: WIP
* feat: Retire ChatScreenProcessingInfo component, context, and keepStatsVisible settings
* feat: Always-on gauge with active-model /props, conversation stats and live-reactive reading/output/avg
* feat: Add /tokenize endpoint, TokenizeService, FNV-1a and JSON Schema utilities
* feat: Surface enabled-tools token count in context hover card
* refactor(tools): make toolsStore the sole owner of the OpenAI wire format
Previously mcpStore.getToolDefinitionsForLLM() owned the MCP->OpenAI
shape conversion (plus normalizeSchemaProperties). That created two
sources of truth for what gets sent to the LLM, with the
duplication-prone risk of the deduplicated enabled list (which feeds
the token-count cache) drifting from the bytes actually shipped on
chat.
Now:
- mcpStore: pure protocol state + routing. Drop getToolDefinitionsForLLM
and the inline OpenAIToolDefinition conversion + normalizeSchemaProperties.
Doc comment adjusted to declare wire-format ownership as belonging
to toolsStore. Connection lifecycle, health checks, executeTool,
and the connections/toolsIndex remain.
- toolsStore: owns the wire shape (added earlier this series). mcpEntries()
inlines the MCP tool conversion; uses normalizeJsonSchema (the JSON
Schema util extracted in the prior commit) so missing 'type' fields
are inferred from defaults. mcpTools getter iterates mcpEntries() so
the Settings UI and the deduplicated enabled list see the same
definitions. getEnabledToolsForLLM iterates mcpEntries() instead of
calling mcpStore, so the JSON sent to the LLM is identical to what
toolsStore.refreshEnabledToolsTokenCount tokenizes.
- agentic: the chat-completion tools field's type was annotated as
ReturnType<typeof mcpStore.getToolDefinitionsForLLM>, claiming the
shape was owned by mcpStore. Switch to ReturnType<typeof
toolsStore.getEnabledToolsForLLM>, the actual source.
Assisted-by: Claude
* feat: UI WIP
* feat: UI WIP
* feat: UI WIP
* feat: Adjust reasoning submenu layout and spacing
* feat: Adjust context usage gauge thresholds and styling
* feat: Split context usage gauge stats into current and cumulative breakdowns
* chore: Format
* refactor: Cleanup
* refactor: Cleanup
* feat: improve token gauge accuracy and display
* refactor: remove MCP recommendation gating and simplify server visibility
* feat: add token audit logging to ChatStore for debugging
* refactor: Simplify context token reading to use server promptTokens directly
* feat: Replace last-known token tracking with live server-derived stats for accurate streaming gauges
* feat: UI Improvements
* feat: Move prompt processing stats to the preceding user message
* feat: Fix context token double-counting and refine gauge layout
* refactor: remove always-show-agentic-turns setting and simplify agentic turn display
* feat: track and display cache tokens in context gauge
* feat: add diagnostic logging for chat completion requests
* refactor: improve token audit console output with fresh/cached breakdown
* fix: invalidate enabled tools token count cache on tool changes
* test: add unit tests for tools store token count invalidation
* refactor: Remove tools token counting infrastructure
* refactor: Update ChatFormContextGauge to use simplified token tracking
* refactor: Update ChatStore to remove tools token counting
* chore: Formatting
* feat: Improve UI text
* feat: simplify context usage derivation and refine gauge labels
* refactor: cleanup logs
* cleaning
* fix: UI
* refactor: Enums
* refactor: Extract context gauge logic into hook and split UI into sub-components
* refactor: Cleanup comments
---------
Co-authored-by: Pascal <[email protected]>
* feat: ui: Add predefined recommended MCP servers to settings
* feat: ui: Add MCP server recommendation dialog with custom server support
* feat: Auto-focus input fields on mount and dynamic addition
* feat: Add header validation to MCP server add and edit forms
* feat: Persist recommended MCP server opt-in selections
* test: Cover MCP configuration with tests
* chore: Format & cleanup
* feat: Centralize MCP server overrides to settings config and improve recommendation UI
* fix: Capture index before mutation to prevent focus drift
* refactor: Extract MCP_CARD_VISIBLE_TOOL_LIMIT to shared constants
* refactor: Support arbitrary authorization header schemes
* refactor: Consolidate MCP recommendations dismissal into existing storage key
* fix: Use case-insensitive comparison for MCP server ID prefix check
* refactor: Centralize MCP server visibility logic and extract recommendations hook
* refactor: Cleanup
* fix: Prevent tool messages from incorrectly appending to other conversations
* ui: prevent agentic loop from poisoning another conv's currNode
* ui: make editedContent a so background recompute does not wipe in-progress edits
---------
Co-authored-by: Pascal <[email protected]>
* chore(ui): pin package versions to currently installed
- Update all dependencies and devDependencies to match exactly what's in package-lock.json
- This ensures reproducible builds by locking to specific versions rather than semver ranges
* chore: Update packages
* chore: Move remaining dependencies to devDependencies
* fix: Add missing `mermaid` package
* chore: Update `cookie` package to `v1.1.1`
* chore: Formatting
* test: Update test configs
* feat: Add "Thinking" toggle and status icon + redesign Chat Form Actions Add panel
* test: Update test reference
* fix: Icon
* fix: E2E test command
* fix: wait for greeting h1 to be visible in e2e test
* fix: remove duplicate PDF option in attachment dropdown
* fix: use label-based group toggle to avoid stale references
* refactor: inline MCP server and tool toggles in mobile sheet
* fix: serve correct build directory in e2e playwright config
* feat: add reasoning effort levels selector in model dropdown
* feat: Reasoning effort
* refactor: Make server origin configurable via environment variable
* feat: Add chat template thinking detector utility
* feat: Add thinking support detection to models store
* refactor: Update model selector components with thinking detection and message-specific indicators
* feat: Update chat form components for model selection and thinking support
* feat: Improve Reasoning controls UI
* refactor: Apply suggestions from code review
Co-authored-by: Aleksander Grygier <[email protected]>
* fix: Model tags
* refactor: Cleanup
* refactor: Remove unneeded components
* refactor: Cleanup
* refactor: Scope console logs to `DEV` + `VITE_DEBUG` env vars
* refactor: skip MCP proxy probe when no server requires it
* refactor: suppress expected disconnect errors during MCP client shutdown
* refactor: Deduplicate requests
* refactor: deduplicate model fetching across ROUTER and MODEL modes
* refactor: Clean up models logic
* chore: Add `.env.example` file
* refactor: replace client-side CORS proxy probe with server status flag
* refactor: Post-review fixes
* test: add vitest client setup with API fetch mocks
* fix: Propagate version tag to WebUI asset download in self-hosted CI
* refactor: Apply suggestions from @CISC
Co-authored-by: Sigbjørn Skjæret <[email protected]>
* fix: Skip npm build when Node.js is not installed
Avoid 'no such file or directory' errors on CI runners that lack
Node.js. Check if npm is available via find_program before attempting
npm install + npm run build. Falls back to HF Bucket download.
* fix: Use + separator for ASSETS list to fix Windows build
Replace fragile \; escaping with a + separator when passing the
WebUI asset list via -DASSETS to the download script. On Windows,
the \; escaping was not reliably preserved through the CMake build
system, causing all asset filenames to be concatenated into one
(e.g., 'index.html;bundle.js;bundle.css;loading.html' as a single
file), which broke the HF Bucket download and subsequent xxd.cmake
step.
+ is safe because it is not special in cmd.exe (unlike | which is a
pipe operator), not special in CMake's -D argument parser, and not
a valid Windows filename character. CMakeLists.txt joins assets
with + and webui-download.cmake splits them back via regex.
* fix: Validate HF_WEBUI_VERSION environment variable with regex
Add input validation for the HF_WEBUI_VERSION env var to prevent
CMake list separator or path-traversal issues in stamp filenames
and download URLs. Rejects non-conforming characters early.
* fix: Remove 'latest' fallback for HF_WEBUI_VERSION
When needs.determine-tag.outputs.tag_name is empty, let CMake's
default resolution handle it (empty -> git-based version lookup)
instead of falling back to 'latest'. This ensures the sentinel
stamp file is consistent with CMake's resolution logic.
* fix: Demote checksum verification failure to warning instead of hard gate
* fix: End line character
---------
Co-authored-by: Sigbjørn Skjæret <[email protected]>
* Add MCP Connection diagnostics and CORS hint to web-ui
* tidy up test
* webui: Refactor and improve MCP diagnostic logging
---------
Co-authored-by: evalstate <[email protected]>
* fix: Bypass API Key validation for static bundle assets
* refactor: All bypassed routes in `public_endpoints`
* test: Update static assets API Key test
* add tests for model id parser
* add test case having activated params
* add structured tests for model id parser
* add ToDo
* feat: Improve model parsing logic + tests
* chore: update webui build output
---------
Co-authored-by: bluemoehre <[email protected]>
* feat: Enable adding System Prompt per-chat
* fix: Save draft message in Chat Form when adding System Prompt from new chat view
* fix: Proper system message deletion logic
* chore: Formatting
* chore: update webui build output