fe235f434368dfbdf3091a6cea92c00a98d2cee4
70
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fe235f4343 |
ui: Replace per-conversation MCP overrides with per-conversation tool policy (#27745)
* 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 |
||
|
|
539f24529b |
ui: Move Settings and MCP Servers routes to dialog-based views (#27744)
* 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 |
||
|
|
f1357e4998 |
ui: ESLint config updates (#27700)
* chore: Spacing between sibling elements in html markup * chore: Formatting and linting rules |
||
|
|
8144f3192e |
ui: Chat Conversation Tabbed navigation (#27263)
* 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 <admin@serveurperso.com> |
||
|
|
521a64cd01 |
ui: Stores split refactor (#27240)
* 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.
|
||
|
|
3dc7285b4f |
ui: Services consolidation refactor (#27239)
* 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 |
||
|
|
fdf4c64604 |
ui: Stores consolidation refactor (#27238)
* 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. |
||
|
|
0021a77de0 |
ui: Refactor Built-In Tools naming (Server/Browser) (#27271)
* server: rename built-in tools to server tools * ui: rename built-in tools to server/browser tools |
||
|
|
666f8898a2 |
ui: move get_datetime tool to frontend (#27255)
* ui: move get_datetime tool to frontend * clarify docs * server: drop the now unused ctime include strftime() and gmtime_r() were the only users, both went away with the get_datetime tool. Also make the renderer's catch inert: the browser executor always emits JSON, so a non-JSON result is no longer a date to display. --------- Co-authored-by: Pascal <admin@serveurperso.com> |
||
|
|
bdffafa5df |
ui: Refactor data-attrs constants, enum for bool strings (#27002)
* refactor: Data-attribute constants + boolean string enum * refactor: Use CSS class string constants * refactor: Address review comments |
||
|
|
fa4ec4590c | refactor: Naming (#27001) | ||
|
|
d86c7d62df |
ui: Clean up contexts, remove prop drilling from Chat Form Actions (#26951)
* refactor: Remove dead context for Chat Settings and create a new one for Chat Messages Actions * refactor: Contexts & types |
||
|
|
094e53db1c |
ui: Stores architecture improvements (#26910)
* refactor: Stores barrel imports + SSR gates * refactor: Drop agenticStore wrapper exports * refactor: Drop chatStore wrapper exports * refactor: Drop modelsStore wrapper exports * refactor: Drop serverStore wrapper exports * refactor: Drop unused mcpStore wrapper exports * refactor: Drop mcpResourceStore wrapper exports * refactor: Drop conversationsStore wrapper exports + move buildConversationTree to utils * refactor: Drop settingsStore wrapper exports * refactor: Drop unused toolsStore wrapper exports * refactor: Fix lint errors from store wrapper removal * fix: Missing change * refactor: Cleanup * refactor: Context Stats store |
||
|
|
a6040c925c | refactor: Clean up UI types (#26909) | ||
|
|
e21152dc96 |
ui: Constants refactor (#26908)
* refactor: Constants * refactor: Constants/Enums cleanup * refactor: Constant objects instead of multiple single value constants * refactor: Cleanup constants |
||
|
|
4dd127584b |
ui: add read_media tool (#25877)
* server: add read_image tool (#25875) Adds a server-tool that allows vision models to analyze server-side images. This tool is reading a single file for now: The image data is base64 encoded and passed to the UI, which decodes it, fills the <img> tag and removes the data URI before passing the tool result back to the model. * cleanup read_image tool: move magic strings to constants * Add dedicated constants file: tools/ui/src/lib/constants/read-image.ts with PREFIX_IMAGE, PREFIX_SIZE, PREFIX_MIME constants * Use ATTACHMENT_SAVED_REGEX from agentic.ts in ChatMessageToolCallBlockReadImage.svelte * Use NEWLINE constant from code.ts instead of hardcoded '\n' * Use PREFIX_SIZE in regex pattern for size parsing * Add SERVER_TOOL_READ_IMAGE_PREFIX_* constants in C++ server-tools.cpp to match the TypeScript PREFIX_* constants for consistency * server: rename read_image tool to read_media for images and audio * Rename server_tool_read_image to server_tool_read_media in C++ * Rename enum BuiltInTool.READ_IMAGE to READ_MEDIA * Rename UI constants, parser, and Svelte component files * Update display label from 'Read image' to 'Read media' * ui: consolidate audio data URI handling into shared utility * Extract getAudioInputFormat to a shared utility (was duplicated inline) * Store raw base64 in base64Data on the message object * Use base64Data to construct data URIs for audio rendering * Update agentic store to build INPUT_AUDIO parts from base64Data * server: read_media: restrict audio to wav/mp3 and minor fixes * Server get_mime_from_extension now only advertises audio/wav and audio/mpeg (the only formats the model's input_audio API accepts) * Case-insensitive extension matching (fixes .MP3, .Wav, etc.) * Unknown extensions return an error instead of a multi-MB data URI that inflates model context with garbage * Updated tool description to document supported formats * Frontend AUDIO_MIME_TO_EXTENSION trimmed to match server * fix a missing import in tools/ui/src/lib/stores/agentic.svelte.ts * server: read_media: add to --tools help text and README tool list * ui: fix indentation in ChatMessageToolCallBlockDefault.svelte * server: read_media tool: fix a cast to use the correct type * server: read_media: multiple fixes * server-tools.cpp import cctype, remove UTF-8 char, check mime before reading file * ui: add MimeTypePrefix.AUDIO and use it in agentic.svelte.ts * server: make read_media inherit from read_file and add uses_cwd * ui: fix formating issues * rm from server * move it to frontend-only tool * correct partial commit * rm unused * ui: address review from allozaur Replace the magic strings, regexes and number in the read_media parser and service with named constants. Path splitting reuses FILE_PATH_SEPARATOR_REGEX, the size header regex moves to READ_MEDIA_SIZE_REGEX derived from PREFIX_SIZE, and FILE_EXTENSION_SEPARATOR lands next to it in constants/code.ts. --------- Co-authored-by: ckrafft <ckrafft@epyc> Co-authored-by: Xuan Son Nguyen <son@huggingface.co> Co-authored-by: Pascal <admin@serveurperso.com> |
||
|
|
8d274dd7c6 |
ui: fix context gauge for single-model usage (#25738)
* webui: hide loaded model in context gauge at single-model mode * webui: keep context gauge details open state across reopens |
||
|
|
4dee52f82d |
ui: UI/chat form follow ups (#26743)
* ui: split the markdown rendering setting per surface User content and thinking get their own toggle again, so turning off markdown for a message leaves reasoning blocks formatted. Both default to markdown. A stored renderContentAsRawText unfolds onto the user key and is dropped from the config. File mentions render as badges in the raw text path too, through a narrow pass over [name](file://path) that leaves everything else untouched. * ui: let the rich chat input scroll past its max height The contenteditable renderer caps its height with max-height but had no overflow rule, so a long buffer overflowed into the input area wrapper and got clipped by its overflow-hidden, leaving no way to reach the bottom of the message. The textarea renderer scrolls natively and was never affected. * ui: apply the new lint and format config * ui: move the render keys unfolding into the migration service Address review from @allozaur: the settings store no longer rewrites persisted config on load, the raw text toggle now unfolds onto the per-surface render keys in migration.service.ts, next to the other config migrations. The mention scanner flag and the directory path suffix become named constants. |
||
|
|
92d1bb0c99 | ui: Linting & Formatting scripts (#26819) | ||
|
|
74ce15741b |
ui: degrade the working directory picker when file search is off (#26811)
The picker mounts whenever a cwd-aware builtin tool is enabled, so it can open while file_glob_search is not served or was disabled by the user. Every typed query then fired a search that could only fail with a raw error. Gate the debounced search on the tool state, the same way the mention picker does, and show a message in place of the results list that explains why search is unavailable. Manual entry with Enter still commits a directory. The Browse button and the search scope footer are hidden as well: Browse resolves the picked folder name through file_glob_search, and the client-side toggle would not stop that call. |
||
|
|
18f7ad7fc9 |
server, ui: only offer a working directory when a tool reads it (#26762)
The working directory chip showed up as soon as the server exposed any builtin tool, so a server started with just get_datetime, or a user who turned every filesystem tool off in the settings, still got a control that nothing would read. Tools now declare whether they resolve their paths and run against the working directory, next to the write permission they already publish in the /tools listing. The WebUI shows the chip and enables the /cwd command only when at least one such tool is both served and left enabled. |
||
|
|
fc6545d322 |
allozaur/feat/chat form contenteditable (#26717)
* feat: Add contenteditable tokenizer for badge/code-chip chat input * feat: Add source-space undo/redo history for the rich input * feat: Split text glued to a closing code fence onto its own line * feat: Add ChatFormContenteditable rich input renderer * feat : wire the contenteditable into ChatForm with auto-switch gating |
||
|
|
6de1b63473 |
allozaur/feat/chat slash commands (#26716)
* base : slash-command/misc foundation - model icon and focus-selector constants * feat : slash-command picker and command parsing helpers * refactor : wire command and @-mention pickers into the chat form * ui : improve model selector keyboard navigation and load/dismiss * feat: Unify markdown/raw-text rendering under one setting with migration * fix: Misc fixes - tool-call subtitle, assistant wrap, progress guards * feat: Clamp and style numeric settings inputs from registry bounds |
||
|
|
23634783c5 |
ui: Filesystem @mentions for Chat Form (#26715)
* base : @-mention picker foundation - glob search, picker nav, highlight * feat : @-mention file/folder picker and mention badges in message bubbles * fix: Imports * feat : wire the @-mention picker into the chat form * fix: Bound the glob-search result cache key and prune stale entries |
||
|
|
f9e832c10e |
server: harden the file_glob_search directory walk (#26626)
* server: don't walk Windows junctions in file_glob_search std::filesystem reports a junction as a plain directory, so the symlink guard misses it and a junction pointing back at an ancestor is walked until the path length gives out read the reparse tag and treat a symlink and a mount point as links, leaving any other reparse point walkable so cloud placeholders and dedup stubs still get searched look junk directory names up case insensitively on Windows, where NTFS makes Build the same directory as build test that a junk directory stays selectable while its contents stay out of search results * server: report a directory the walk could not read a directory that fails to open or to iterate was skipped in silence, so a caller got a listing that looked complete while a whole subtree was missing: a path over the platform limit, a volume going away, a name the filesystem rejects skip_permission_denied never reaches this path, so an error here is an incomplete answer rather than a deliberate omission, and it now sets the truncated flag * server: simplify the file_glob_search listing plumbing return a small result struct instead of two out params and a caller path that only fed an error string, taking list_entries from six parameters down to three scope the error code to the directory being read, act on the status code the entry lookups already returned, and treat an unreadable link state as a link so the walk never descends on a guess check the deadline when a directory is popped, not only per entry, so a tree of empty directories cannot outlive the budget read the path parameter once, and reject an invalid limit the way an invalid type is already rejected, instead of silently falling back normalize the resolved path, so a "." or ".." a caller typed reaches neither git nor the client, and return the generic path form with '/' separators on every platform, so the base sent to clients no longer needs a local fixup * ui: expire cached picker searches the cache grew for the lifetime of the component: entries went stale after the TTL but were never removed, so every distinct query typed in a session stayed in memory drop expired entries when a new result is stored * server: address review from @ngxson trim comments to one line each, and drop two that restate the code rename junk_lookup_name to get_effective_name, and move it and the link check to private static members next to junk_dir_names merge the Windows and Linux link checks into one is_link, so symlinks are checked everywhere and junctions only add to it on Windows * server: convert tool paths as UTF-8 on Windows a narrow path uses the active code page there, so a file name came back mangled and a path with an accent could not be opened at all convert explicitly at every crossing between a std::string, which always carries UTF-8 here, and fs::path read the home directory through the wide environment, since the narrow one returns the profile path in the active code page too the walker no longer normalizes separators by hand, since paths now come back in generic form * server: fold the platform branch inside console_output_to_utf8 match the shape of the other helpers, one definition with the #if inside, instead of two definitions wrapped in #if and #else inline the single caller helper and trim the comment |
||
|
|
2f56fc3431 |
ui: CWD for agent (#26518)
* 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 <admin@serveurperso.com> |
||
|
|
21a5f5b7f9 |
ui: IndexedDB and Conversations data fixes (#26278)
* 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 |
||
|
|
3018a11e79 | fix: increase greeting spacing on md screens (#26287) | ||
|
|
6e2bc65fb2 | ui: rendering performance follow-up (#26097) | ||
|
|
d73c1d6b22 |
server + ui: fix stream routes for model names containing a slash (#26137)
* server + ui: refactor resumable stream routes to query string conv_id The conversation id can embed a model name containing slashes (ggml-org/...) in router mode, which the decoded path splits before the :conv_id param is captured, so stop and resume never matched the session. Move the id to the conv_id query string on the public routes and on the internal router -> child hop, where slashes survive encoding. Handlers are unchanged since query and path params land in the same map. Add a regression test with a slashed model name. * server: move stream route docs to server-stream.h Address review: ngxson wants the main server.cpp registration code kept clean and simple, with route-level explanations living in the header. Move the query string rationale and the lookup ownership note next to the handler declarations in server-stream.h, and shorten the wiring comment to a pointer. * server: cancel a pending request when its stream is stopped during model load The conversation was registered in the conv map only after the blocking autoload wait, so a stop issued while the model loaded found nothing to cancel and the request went on to generate an orphan once the load ended. Register the conversation before the wait and give the entry a ticket: a stop erases the entry, and the parked request checks its ticket after the wait and aborts with 400 instead of starting. A newer request on the same conversation replaces the entry, so only the stopped request is cancelled. Add a regression test that stops during the load window. * server + ui: resume a stream after a page reload during model load A pending request died with the client socket when the page was reloaded while its model was loading, so no session ever existed and the conversation had nothing to recover. A session request that waited for a load now detaches from the client socket and reaches the child regardless, the session buffer receives the generation, and the resume route answers 503 while the owner is loading so the client retries instead of dropping its state. The WebUI persists the pending stream at send time, quietly polls on 503, and attaches once the session exists. Add a regression test that drops the client during the load window. * ui: show the model load progress again after a page refresh The resume wait was invisible, so a conversation refreshed while its model was loading showed nothing until the first byte. On a 503 from the resume probe, mark the conversation as loading again so the assistant row persisted at send time renders the processing info, and target the model frozen in the persisted stream state for the progress, since the row has no model yet and the dropdown may not be restored. * fix CI * fix CI bis |
||
|
|
d4d057b6dd |
ui: fix system message edit box not expanding to fit content (#26006)
The in-conversation system-message edit textarea had a fixed min-height and no auto-resize, so long messages were crammed to 2 lines. Reused existing autoResizeTextarea helper on input and when the editor opens, and added max-height to cap growth. |
||
|
|
ff067f76dd |
ui: fix context gauge card regressions and land at the conversation end (#26099)
The context gauge card starts monitoring like the dial does, because its own processing state instance only follows the live stream while its monitoring flag is set. It also gets back the text-sm and ring classes the removed hover card wrapper used to inject, which restores its layout. Routing to a conversation now lands at the bottom instantly and keeps the pin one frame at a time until the page height settles, since content-visibility size realizations and syntax highlight passes grow the page without DOM mutations. |
||
|
|
96013c5112 |
ui: remove render effects (#26083)
* ui: remove viewport fade in and smooth autoscroll bottom snap fadeInView mounted every message and markdown block at opacity 0 and relied on an IntersectionObserver to reveal it. When the observer never fires (blocks mounted offscreen during long agentic loops) the content stays invisible forever while still present in the DOM. Remove the action, its orphaned isElementInViewport util and all call sites: blocks now render visible immediately. AutoScrollController.scrollToBottom defaulted to behavior smooth and is invoked every 100 ms while streaming. Each tick restarts an easing animation toward a moving scrollHeight, producing a random elastic bump of a few pixels when the user reaches the bottom and autoscroll reengages. Default to instant scrolling; the user facing scroll down button keeps its smooth behavior. * ui: skip rendering of offscreen chat messages via content-visibility Apply content-visibility auto with contain-intrinsic-size to chat messages so the browser skips layout and paint for messages outside the viewport. The DOM stays complete: component state, find-in-page, text selection, and the mutation based autoscroll are unaffected, and browsers without support simply ignore the properties. * ui: remove conversation switch fade Switching conversations faded the message list out and in over 500 ms plus a 300 ms route delay, deferring the message refresh behind two requestAnimationFrame calls. Remove the fade, its navigation hooks and dead state, and refresh messages directly so switching is only bound by actual render time. * ui: describe present behavior in comments and drop unused parameter * ui: anchor the context gauge popup to the form with plain CSS The stats card was portaled to body and repositioned in script on every ancestor scroll event, trailing the page by one frame while streaming. Render it as an absolutely positioned sibling of the input box inside the already relative form, so nothing runs during scroll. The card sits just above the dial, centered on it and overlapping the textarea, from a single measurement of the dial center and top taken when it opens; the dial and the card share the same positioning frame, so the values stay exact while the card is open. Mouse pointers open on hover with a grace delay to reach the card, touch pointers toggle on tap, any press outside the card and the dial closes it, and Enter and Space toggle from the keyboard. The card lives outside the input box because its overflow-hidden and backdrop-filter would clip any positioned descendant. * ui: extract context gauge popup constants and relocate its state store Move the placement values and the close grace delay to lib/constants/context-gauge-popup.ts, matching the auto-scroll constants layout, and move the popup state module from the component folder to lib/stores where runes modules live in this codebase. * ui: declare the context gauge popup card ref as $state |
||
|
|
95a923a64c |
ui: fix MCP server display name conflicts in tools lists (#26011)
* ui: fix MCP server display name conflicts in tools lists Tool groups were keyed by display label so two servers reporting the same name broke the keyed each blocks and only one was visible. Key rendering, expand state and toggles by the stable server id instead, and suffix duplicate labels with a counter in config order. * ui: customizable MCP server display name with autofill Add a display name field to the MCP server form, add and edit alike. The custom name takes precedence over the server-reported one, so two servers reporting the same name can be told apart; clearing the field returns to the automatic label. In the add dialog a debounced preview handshake prefills the field with the server-reported name: a manual edit freezes the autofill, stale responses are discarded, failures stay silent, and an unedited prefill is not persisted so the label keeps following the server. * ui: fix recursive fetch passthrough in the client test setup The original fetch was captured inside beforeEach, where it is the previous test's spy since vi.spyOn returns the existing one, so the default passthrough recursed on itself for any URL outside the mocked set. Capture the real fetch once at module load. |
||
|
|
cf512566dc |
ui: Add a "Default" option for the reasoning selector (#25846)
* ui: add Default reasoning option that defers to the server The webui always injected enable_thinking, overriding the chat template default and the --reasoning flag, breaking models that reason unconditionally (e.g. Gemma 4 E4B) on a fresh client. Default sends nothing so the server decides, Off and effort levels force the value as before. All choices are remembered. Also remove the boolean thinking API from the conversations store and drop ChatFormReasoningEffortSubmenu.svelte (dead code). * ui: close the whole menu tree on reasoning level selection The reasoning levels were raw buttons inside the SubContent, so selecting one only closed the submenu via manual state while the root dropdown stayed open. DropdownMenu.Item closes the full tree on select like the sibling entries and brings native keyboard navigation. * ui: prevent the add menu tooltip from flashing when the dropdown closes |
||
|
|
4ee6a9af71 |
ui: fix collapsed user bubble with markdown rendering (#25869)
Edge paragraph margins are now zeroed at the source in markdown-content.css, but the user bubble and the system message still carried the -my-4 compensation for them. The uncompensated negative margins shrank the wrapper 2rem below its content, collapsing single-line user bubbles into a scrollable sliver and skewing the system message expand threshold. |
||
|
|
43b5e63589 |
UI: fix Settings/Display tool call content toggle (#25783)
* ui: fix Show tool call in progress toggle ignored The showToolCallInProgress setting was disconnected from the render path during the agentic content rework: getDefaultExpanded() returns a hardcoded false for tool call sections and an unconditional effect auto-expands the currently executing tool call regardless of the setting. Drive default expansion of all tool call section types from the setting and remove the now redundant auto-expand effect. Manual toggling still takes precedence over the default in both directions. * ui: rename Show tool call in progress to Always show tool call content The previous name suggested symmetry with Show thought in progress, which only applies while inference is running, but tool call content stays expanded after completion. Rename the label, the settings key and the syncable server key to alwaysShowToolCallContent. The synced parameter never worked under its previous name so no migration is needed. |
||
|
|
32beb244f5 |
ui: Agentic Content UX improvements (#25450)
* 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 |
||
|
|
236ab574e0 | ui: Fix spacing in tool-call request (#25634) | ||
|
|
17a05e451f |
ui: fix mcp panel for toggle + timeout + proxy + ON/OFF state (#25631)
* ui: fix MCP panel regressions after settings rework Restore the llama-server proxy switch in the Add New Server dialog. The dialog never passed useProxy/onUseProxyChange to McpServerForm, which only renders the proxy switch when the handler is provided. The flag is now wired, persisted on addServer, and reset on close. Bound the MCP connection handshake with the configured timeout. handshakeTimeoutMs was set in the server config but never consumed. The SDK timeout only covers the initialize request, not transport.start(), which can hang forever on an unreachable host. The whole handshake now races against the timeout and closes the transport on expiry so the underlying fetch or socket is aborted. Keep disabled MCP servers visible in management and chat-add UIs. Collapsing mcpDefaultServerOverrides into mcpServers[i].enabled turned the visibleMcpServers enabled filter into a visibility trap: toggling a server off outside a conversation hid it from every surface with no way to re-enable it. The filter is dropped, tools derived from health checks still skip disabled servers, and the settings page and server card render the real card instead of a skeleton for disabled servers that never receive a startup health check. * ui: clarify MCP server list semantics and add regression test Remove the visibleMcpServers getter, a filterless alias of getServers whose name invites the next refactor to put a filter back. Call sites read getServers directly, the duplicate list in the chat submenu is merged, and the misleading local variable in the sheet is renamed. A parser unit test pins the invariant: enabled is an on/off state, never a visibility filter, so disabled servers stay listed and toggleable. * ui: apply the MCP request timeout setting live to all servers The per-server requestTimeoutSeconds field was never editable in any UI and froze the global setting at server creation time, so changing the timeout in Settings was a no-op for existing servers. The field is removed from the data model and parsers, the timeout is read live from the global setting wherever a request config is built, and the misleading "Can be overridden per server" help text is dropped. A parser unit test guards against reintroducing the stored field. * ui: move the MCP request timeout into the Agentic settings section The MCP section held a single setting. The timeout is a global tool execution parameter like the other Agentic entries, so it moves there and the section is removed. Same settings key, no migration needed. * ui: remove the dead tool preview lines setting The agenticMaxToolPreviewLines setting was read into AgenticConfig and consumed by nothing: the agentic loop only uses enabled and maxTurns. Its help text described a previous architecture where only truncated previews and the final response survived the loop; tool results and intermediate turns now persist as full DB messages, so the setting had no effect at any value. Stale keys in localStorage or a server ui-config are ignored. * ui: resolve absent MCP per-chat overrides to the server enabled flag New conversations started with every MCP server off: the settings rework stopped seeding a per-conversation override list, assuming the enabled check would fall back to mcpServers[i].enabled, but it fell back to false, and the send path passed the raw stored list with no fallback at all. The per-conversation list is now sparse by contract, holding only explicit toggles, and every access point resolves a missing entry to the server's own enabled flag: the toggle display, the resolved list handed to the agentic flow, and the enabled check itself. |
||
|
|
c9330ed0cf |
ui: add reasoning effort control to mobile add sheet (#25539)
The mobile "+" sheet was missing the reasoning effort section present in the desktop dropdown, so thinking could not be toggled on touch. Extract the shared derivation and selection logic into useReasoningMenu and consume it from both the desktop submenu and the mobile sheet, keeping a single source of truth and preserving each surface idiom. |
||
|
|
38fd5c9993 |
ui: Remove recommended MCP Servers + improve MCP Servers Settings UI/UX (#25535)
* 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 |
||
|
|
a935fbffe1 |
server: remove loading.html (#25500)
* server: remove loading.html * apply ui changes |
||
|
|
f1161b15f2 |
ui: Context usage gauge and panel (#25340)
* 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 <admin@serveurperso.com> |
||
|
|
d4cff114c0 |
ui: Improve performance when streaming (#25225)
* ui: Improve performance when streaming * ui: build sibling info map in branching utils Moves the node map and sibling map construction from the .by block into buildSiblingInfoMap() in branching.ts. The map is built once per structural change and only read afterwards, so it does not need SvelteMap reactivity. Keeping the construction in plain TypeScript fixes the svelte/prefer-svelte-reactivity lint error and groups the branching logic where it already lives. --------- Co-authored-by: Pascal <admin@serveurperso.com> |
||
|
|
94875285e4 |
ui: Add MCP Servers Opt-In for first time visitors (#25239)
* 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 |
||
|
|
9d88e7cedd |
ui Prevent tool messages from incorrectly appending to other conversations (#25177)
* 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 <admin@serveurperso.com> |
||
|
|
dbdaece23d | Revert "ui: fix accessibility for hover-gated interactive elements assisted by claude(in debugging and tests) (#24727)" (#25098) | ||
|
|
ded1561b42 | ui: fix accessibility for hover-gated interactive elements assisted by claude(in debugging and tests) (#24727) | ||
|
|
1a87dcdc45 |
server + ui: SSE Replay Buffer (#23226)
* server: SSE replay buffer, survives client disconnect
Opt in on POST /v1/chat/completions when the client sends
X-Stream-Resume: 1 and a non empty X-Conversation-Id. The conv id is
the session identity end to end, no extra opaque token. The drain
runs detached server side and buffers SSE bytes, the generation
survives HTTP disconnect, F5, or lets users switch from iOS Safari
to another app without losing the actively generated response.
Routes:
GET /v1/stream/<conv_id>?from=N replay
GET /v1/streams[?conversation_id=X] list, drives sidebar spinners
DELETE /v1/stream/<conv_id> Stop, idempotent
Router parent fans out to children for list and delete, probes on GET
to route to the owner, fans out DELETE on POST so "one session per
conv" holds across model swaps.
WebUI: the layout snapshots /v1/streams at mount and on
visibilitychange, the sidebar reflects live inferences across all
convs. The chat page reattaches on mount, append vs fresh is detected
from existing content so continue mid stream keeps its prefix.
update_slots: on llama_memory_seq_rm refusal at a deep position, full
clear of the seq and reprefill from zero instead of GGML_ABORT.
OAI strict path unchanged when the opt in headers are absent.
* server: create stream session only after post_tasks succeeds
* server, ui: drop X-Stream-Resume, X-Conversation-Id alone enables the replay buffer
* server: drop magic 17, derive the X-Conversation-Id header length from sizeof at build time
* refactor: address review feedback from ngxson
* server-context: cleaning
* server-stream: fix use-after-free on rd
Guard stop_producer with a shared alive flag, flipped by on_stream_end
before rd dies. Prevents a late cancel (session eviction by a later
POST on the same conv_id, or a DELETE arriving after the producer
ended) from touching a destroyed rd.
* ui: fix cross-conversation contamination
Scope streaming flags per conv so one finishing does not unflag the
others, guard discoverActiveStream against concurrent runs to avoid
duplicate attaches, and stop racing syncRemoteRunningStreams for the
sidebar set.
* server-http: keep request alive in detached SSE drain
The response next() lambda may reach into *request via &req long
after on_complete reset the request shared_ptr. Capture request in
the detached thread so it outlives the drain.
* ui: address review feedback from coder543
Forward Authorization to /v1/stream and /v1/streams fetches, the resumable routes
must obey --api-key like the rest of the API.
Wrap reader.read() in a try/catch, the underlying connection drop rejects with
TypeError instead of resolving done=true, treat it as a premature end of stream
so the existing resume loop kicks in.
Freeze the model at session start in chatStreamingStates.model and thread it
through cancel and resume, the dropdown selection may have changed since the
POST and the server side identity is fixed at that time.
* format
* ui: remove unused selectedModelName
* server-stream: poll session->is_cancelled() in stream_aware_should_stop
Address review feedback from coder543. The cancel propagation through
rd.stop() relies on the slot eventually processing the cancel task and
posting a result that notifies the recv condvar, remove_waiting_task_ids
does not notify directly. Add a defensive poll on session->is_cancelled()
so the producer-side next() loop exits on its next iteration after
cancel() without waiting for the cancel task to round trip through a slot.
* server-stream, ui: replace GET /v1/streams with POST /v1/streams/lookup
Address review feedback from coder543. Listing live sessions leaks the
conversation_id of every concurrent user, which defeats the random UUID
unguessability. The new route takes {conversation_ids: [...]} in the
body and returns matches only for the ids the caller already owns, so
foreign UUIDs stay private. The router fans out the same POST to every
child and aggregates, the WebUI passes the convs visible in its sidebar.
* ui: read conv ids from IndexedDB in syncRemoteRunningStreams
The conversations store is not hydrated yet at +layout onMount, so the
sidebar spinners stayed off for background convs until the user clicked
on them. Read straight from the DB to dodge the init race.
* server-models: deduplicate stream lookup timeouts behind one constant
* ui: extract visibility kick grace into a stream constant, bump to 1000 ms
* make it safer & more simple
* server-stream: survive client disconnect via stream_pipe::finish_producer
After the RAII rewrite the generation stopped the moment the client
disconnected. httplib bails its content provider on the is_peer_alive
check at the top of write_content_chunked, so returning true from the
provider never keeps it producing: the response resets, rd is destroyed
and its task gets cancelled.
Reinstate the disconnect survival inside the pipe. stream_pipe gains
finish_producer, which pumps the response next() into the ring buffer
until the generation ends, and mark_producer_done for the clean wire
end. server-http only triggers them: mark before sink.done on a clean
close, finish in on_complete when the peer left early. No detach, no
stream logic in server-http beyond the trigger, and the strict OAI path
is untouched when no pipe is attached.
Known limitation: finish_producer pumps synchronously on the http
worker, so a disconnected stream keeps its worker busy until the
generation ends. A follow-up will move the drain off the http worker so
no worker is held.
* server-stream: drain disconnected streams on a manager owned thread
The previous commit pumped the post disconnect drain synchronously in
on_complete, on the http worker, so a disconnected stream kept its
worker busy until the generation ended. Under a wave of reloads or tab
closes that pins workers from the pool.
Move the drain off the http worker. on_complete now hands the response
to stream_session_manager::adopt_orphan, which pumps it to completion on
a manager owned thread and releases the worker at once. One thread per
disconnected stream still generating, stored in a list, joined and
reaped on the next adopt, by the GC, and at shutdown. No detach, the
thread lifecycle is fully owned by the manager. needs_drain gates the
handoff so a cleanly finished stream never spawns a thread, and the
strict OAI path stays untouched when no pipe is attached.
stop_gc now cancels sessions before finalizing them, so an in flight
drain sees is_cancelled and exits instead of blocking the shutdown join
until the generation ends naturally.
* ui: add missing JSDoc
* server-stream: drain on the http worker, drop the manager thread
Address @ngxson review: httplib runs a large dynamic pool and a worker
blocked in next() sits on a condvar instead of burning cpu, so draining
the rest of the generation on that worker is fine and much simpler than
a dedicated thread.
on_complete calls finish_producer directly again. Removes adopt_orphan,
the orphan thread list and its reaping, the stop_gc session cancel that
only existed to unblock those threads, and the now dead drain_shutdown
flag.
* server-stream: split stream_pipe into producer and consumer classes
Address @ngxson review: one class covering both ends was messy. stream_pipe
is now a base holding the session and is_cancelled, with stream_pipe_producer
(write, mark_producer_done, finish_producer, cleanup, finalizes on destruct)
and stream_pipe_consumer (read only, no finalize) deriving from it.
Drops the is_producer_ discriminator and its runtime guards, the type now
encodes the role. res.spipe is retyped to shared_ptr<stream_pipe_producer>
since it is only ever a producer. No behavior change.
* server-stream: rename producer methods to unix pipe semantics
Address @ngxson review: mark_producer_done becomes done(), finish_producer
becomes close(), matching a unix pipe write end. The producer_done_ member
follows as done_. write() is unchanged. No behavior change.
* server, ui: route resumable streams via a conv map, persist resume identity
Address ngxson review: drop the polling probe, proxy_post records a conv_id ->
model map and the stream routes resolve the owning child with one lookup. The
map is the single source of truth, the ::model suffix stays for child session
uniqueness but the router never parses it.
UI: the server keys a session by the POST time identity (conv::model), but reload
probed with the bare conv id and missed model tagged sessions, so F5 stopped the
stream and sidebar spinners stayed off. Persist the model and rebuild the exact
identity on resume, single conv and bulk sidebar both send it.
Add unit coverage for the identity round trip.
* ui: resolve continue target by id to stop cross-conversation flash on switch
* ui: skip stream resume when the abort is intentional
* server: move the conv id to model map into a self contained tracker
Address review from ngxson: server_models held two mutexes side by side, the
global one and a bare conv_model_mu guarding a loose map, which made the locking
hard to follow. Wrap the map and its lock in a small conv_model_tracker struct
that owns its mutex, one mutex per struct. The remember, lookup and forget
methods move inline into the tracker, server_models exposes a single conv_models
member and the routes call models.conv_models.lookup and friends. No behavior
change, the map stays the single source of truth for routing resumable streams
to a child.
* ui: replace stream magic values with enums and shared constants
Address review from allozaur: lift the inline literals around the resumable
stream code into named symbols so the intent is explicit and reusable.
* ui: fold the stream resume and discovery helpers into ChatService
Address review from allozaur: drop the two standalone stream-*.service files.
They were used only by the chat service and store, carried no shared state, and
did not follow the static class pattern the other services use, so a separate
abstraction was not warranted. Move the helpers onto ChatService as static
methods. No behavior change, tests now exercise them through ChatService.
* docs: document the SSE replay buffer in server README-dev
Add the resumable streaming section, list stream_session_manager in the
backend component inventory, and link PR 23226 in the related PRs.
* ui: align attachServerStream call with onCompletionId param in handleStreamResponse
* server-http: rename del_ to del to match get and post
* ui: address review feedback from allozaur
* ui: drop duplicate SSE constants, keep sse.ts canonical
* ui: use svelte:document for the visibilitychange listener
address review from allozaur: replace the manual document.addEventListener
in onMount with a declarative <svelte:document onvisibilitychange>. svelte
handles attach, detach and SSR, so the typeof document guard and the onMount
cleanup go away. onMount keeps only the first load snapshot.
* server: trim redundant stream drain comments
Address review from ngxson
* server: balance and clean up stream comments
remove redundant comments and tighten the verbose ones across the resumable
stream code, keeping the concurrency and lifetime rationale that is not obvious
from the code. also fix two stale comments in server.cpp and server-models.h
that still described the old ::model suffix probe and fan out routing, now
replaced by the conv_id -> model map
Address review from ngxson
* ui: balance and clean up stream comments
dedup repeated rationale (frozen conv::model identity, the lookup privacy note,
the abort patterns) down to one canonical spot, tighten the verbose blocks, and
keep the concurrency and resume-offset reasoning. fix stale comments in
stream-identity.ts and chat.service.ts that still described the old loopback
probe and fan out routing, now the conv_id -> model map.
---------
Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
|