Commit Graph

52 Commits

Author SHA1 Message Date
Aleksander Grygier 7c8ece9a3a ui : strip trailing container-format segments from parsed model names 2026-08-25 15:33:37 +02:00
Aleksander Grygier f1357e4998 ui: ESLint config updates (#27700)
* chore: Spacing between sibling elements in html markup

* chore: Formatting and linting rules
2026-08-25 14:34:34 +02:00
Aleksander Grygier 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>
2026-08-23 10:46:49 +02:00
Aleksander Grygier 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.
2026-08-20 19:02:04 +02:00
Pascal 77acca437f ui: read persisted settings before the API key probe (#27365)
The route loads run ahead of the root layout script, so validateApiKey
read the settings store while it still held factory defaults and probed
/props without the stored key. initStores() now hands the same startup
promise to every caller and the chat loads await it before probing.

The one-time admin baseline no longer overwrites a key the user has
already set: on a first visit the config carries factory values only, so
a diverging key comes from the user and wins.
2026-08-19 14:02:08 +02:00
Aleksander Grygier 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
2026-08-18 16:39:32 +02:00
Aleksander Grygier 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
2026-08-17 22:23:22 +02:00
Cristiano Pinto ece963f41b ui: mask API Key field in settings and error splash to stop browser a… (#26562)
* ui: mask API Key field in settings and error splash to stop browser autofill

* ui: set autocomplete=new-password on private fields

The password input type makes browsers offer to save the API key
in the password manager and autofill saved site credentials into
the field. The new-password autocomplete value disables both.

---------

Co-authored-by: Pascal <admin@serveurperso.com>
2026-08-15 22:52:55 +02:00
Gautam0507 0d9ceae1e3 ui: read structuredContent from MCP tool result when content is empty (#26691) 2026-08-15 20:00:18 +02:00
Aleksander Grygier 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
2026-08-13 20:01:12 +02:00
Aleksander Grygier fa4ec4590c refactor: Naming (#27001) 2026-08-13 19:45:32 +02:00
Aleksander Grygier 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
2026-08-13 08:21:15 +02:00
Aleksander Grygier 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
2026-08-13 08:11:30 +02:00
Aleksander Grygier a6040c925c refactor: Clean up UI types (#26909) 2026-08-13 08:07:34 +02:00
Aleksander Grygier e21152dc96 ui: Constants refactor (#26908)
* refactor: Constants

* refactor: Constants/Enums cleanup

* refactor: Constant objects instead of multiple single value constants

* refactor: Cleanup constants
2026-08-13 06:53:56 +02:00
parabelboi 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>
2026-08-12 12:03:32 +02:00
Pascal 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.
2026-08-10 13:32:51 +02:00
Aleksander Grygier 92d1bb0c99 ui: Linting & Formatting scripts (#26819) 2026-08-10 08:38:37 +02:00
Pascal 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.
2026-08-08 16:36:21 +02:00
Aleksander Grygier 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
2026-08-07 20:40:10 +02:00
Aleksander Grygier 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
2026-08-07 20:20:01 +02:00
Aleksander Grygier 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
2026-08-07 18:45:54 +02:00
Aleksander Grygier 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>
2026-08-04 19:05:48 +02:00
Aleksander Grygier 6e2bc65fb2 ui: rendering performance follow-up (#26097) 2026-07-28 17:13:25 +02:00
Pascal 55b7d6c4c7 ui: detect the conversation import format from file contents (#26121)
* ui: detect the conversation import format from file contents

iOS resolves every accept entry to a UTI and has none for ".jsonl", so the
picker greyed out exported conversations. Drop the accept filter and pick the
parser from the file contents: ZIP magic bytes, then a first "session" record
for JSONL, otherwise the legacy JSON format.

Also remove the unused importConversations() picker and an orphan doc comment,
and cover each format with unit tests.

* ui: report what a conversation import actually wrote

The import summary echoed the selection back, so re-importing conversations
already in the database claimed success while nothing was written and only a
console warning said otherwise.

Return the imported and skipped conversations from the database layer, list the
written ones in the summary, and count the rest in a toast.

* ui: name the literals of the JSONL conversation format

Introduce SessionRecordType and SESSION_HARNESS, and reuse the existing
NEWLINE constant, so the record format lives in one place.

This also covers the writer side, which predates the import path under
review and carried the same literals: an enum stated by the reader alone
lets the two sides drift.

Values are unchanged, so an export stays byte identical.
2026-07-26 23:32:58 +02:00
Zach Winter 555881ebc8 ui: reduce per-token render cost when streaming (#26053)
* performance harness - the empirical root

Assisted-by: Claude Opus 4.8

* 210.36ms -> 2.67ms per streamed token

Assisted-by: Claude Opus 4.8

* 11.58ms -> 0.62ms per streamed token

Assisted-by: Claude Opus 4.8

* 22.02ms -> 3.33ms per streamed token

Assisted-by: Claude Opus 4.8

* 3.07ms -> 1.36ms per streamed token at 40 messages

Assisted-by: Claude Opus 4.8

---------

Co-authored-by: Zach Winter <dmtommy@icloud.com>
2026-07-24 22:09:46 +02:00
Pascal 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.
2026-07-24 19:28:14 +02:00
Pascal 54ce507b6f UI: Fix settings precedence, Factory < Admin (--ui-config-file) < Users (Settings panel) (#26002) 2026-07-24 15:09:55 +02:00
rankaiyx 61328e6a91 feat(ui): add symbolic math support to JS sandbox via nerdamer (#25948)
* feat(ui): add symbolic math support to JS sandbox via nerdamer

Preload nerdamer (with decimal.js) in the sandboxed worker,
exposing the `nerdamer` global for symbolic computation:
simplify, expand, factor, diff, integrate, solve, laplace,
ilt, limit, partfrac, gcd/lcm, roots, coefficients, and more.

Mirrors the math.js integration pattern from the
feature/sandbox-symbolic-math branch, but uses nerdamer
for a lighter, more focused symbolic math engine.

* Update sandbox-harness.ts

* docs(ui): update sandbox tool description with detailed nerdamer usage guide

* Clarify nerdamer usage in sandbox tool description

Updated the description of the sandbox tool to clarify usage of nerdamer.

* ui: build nerdamer sandbox prelude from vendored source

Replace the vendored all.min.js with the readable nerdamer-prime
source and its two bundled deps (big-integer, decimal.js), licenses
included. A vite plugin bundles and minifies them at build time with
the upstream esbuild flags, exposed as virtual:nerdamer and imported
lazily on first sandbox use. The vendors package.json pins commonjs
so the project level type: module does not break esbuild format
detection. The harness gains a CSP removing network egress from the
worker, and browser tests cover the prelude, exact arithmetic, the
fetch block and the timeout.

Upstream snapshot: together-science/nerdamer-prime@1936145

* feat(ui): make symbolic math (nerdamer) a user-toggleable setting

- Add SYMBOLIC_MATH_ENABLED setting key and registry entry (checkbox, default false)
- Convert SANDBOX_TOOL_DEFINITION to buildSandboxToolDefinition(includeSymbolicMath)
  so the tool description includes/excludes nerdamer API docs dynamically
- Cache sandbox harness per variant ('nerdamer' / 'plain') for instant toggle
- Deprecate SANDBOX_TOOL_DEFINITION constant alias for backward compatibility
- Update tools store to pass symbolic math config into tool definition

* docs(ui): tell LLM to list nerdamer functions first, do not guess

* test(ui): enable symbolic math in sandbox tests via settingsStore config

* style(ui): fix formatting for tools.svelte.ts

---------

Co-authored-by: Pascal <admin@serveurperso.com>
2026-07-22 17:52:55 +02:00
Aleksander Grygier 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
2026-07-15 20:31:45 +02:00
Emanuil Rusev dfba90db63 webui: parse effective-parameter sizes (E2B, E4B) as params (#25529) 2026-07-14 17:12:22 +02:00
Pascal 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.
2026-07-14 16:50:44 +02:00
Aleksander Grygier 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
2026-07-13 08:45:04 +02:00
Xuan-Son Nguyen a935fbffe1 server: remove loading.html (#25500)
* server: remove loading.html

* apply ui changes
2026-07-10 14:42:17 +02:00
Xuan-Son Nguyen 898b08854d ui: fake 200 for proxy DELETE req (#25298) 2026-07-06 08:41:39 +02:00
Aleksander Grygier 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
2026-07-03 12:16:29 +02:00
Aleksander Grygier 7af4279f45 ui: Remove PWA navigate fallback to prevent caching API endpoint requests (#25174) 2026-07-01 07:32:55 +02:00
Pascal 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>
2026-06-26 09:31:29 +02:00
Aleksander Grygier ef9c13d4c2 ui: New Logo + Navigation cleanup & Mobile UI/UX improvements (#24897)
* chore: `npm audit fix --force`

* feat: Update sidebar toggle to use Logo

* refactor: Clean up favicon SVG

* feat: Refactor logo component and implement theme-aware favicon generation

* feat: Add configurable padding to generated PWA assets

* test: Add unit tests for writeThemeFavicons

* refactor: Componentization

* feat: WIP

* feat: WIP

* feat: WIP

* feat: Mobile UI

* feat: add SEARCH route constant

* feat: create SidebarNavigationSearchResults component

* refactor: use SidebarNavigationSearchResults in conversation list

* feat: enable mobile search navigation in sidebar actions

* feat: add mobile search route and page

* fix: prevent sidebar overflow on mobile viewports

* fix: Mobile sidebar

* feat: Mobile Search WIP

* feat: Mobile WIP

* feat: Add PWA standalone detection and refine mobile UI

* feat: Improve mobile layout, sidebar handling, and chat scrolling

* feat: Improve mobile sidebar visibility and iOS Safari chat spacing

* fix: Disable auto-scroll on mobile

* chore: Linting

* fix: Wrong condition

* feat: Mobile chat scroll

* refactor: WIP

* fix: Desktop initial scroll always working again

* fix: Partial fix for mobile auto-scroll / initial scroll

* fix: Desktop auto-scroll on initial load and during streaming

* fix: Mobile scrolling logic

* refactor: Clean up

* feat: Improve start UI

* feat: Add `delay` to `fadeInView`

* feat: Auto-scroll button

* refactor: Cleanup

* refactor: Extract chat dialogs and alerts into dedicated component

* refactor: Reorganize ChatScreen component structure and initialization

* feat: Improve auto-scroll after sending message

* feat: UI improvements

* fix: Settings link

* feat: UI improvements

* fix: better UI spacing

* fix: Remove unneeded logic

* fix: Chat Processing Info UI rendering

* feat: Improve mobile UI

* feat: UI improvement

* fix: Conditional transition delay for Chat Messages based on route from

* fix: Delay mobile sidebar collapse for smoother transitions

* fix: Mobile scroll down button + sidebar pointer events

* fix: Mobile UI

* fix: Auto scrolling

* fix: Implement dynamic height calculations for chat auto-scroll positioning and UI elements

* fix: Retrieve `autofocus` for Chat Form textarea

* fix: Use proper class

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* refactor: extract scroll-to-bottom logic and fix message send flow

* fix: update viewport store usage and remove conflicting autofocus

* feat: add accessibility labels to scroll down button

* fix: correct HTML structure in sidebar empty states

* fix: dynamically toggle processing info visibility

* chore: remove commented exports and fix formatting

* fix

* fix: Mobile Chat Form Add Action Sheet interactions

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-24 10:21:33 +02:00
Matti4 e27f308597 server: avoid forwarding auth headers in CORS proxy (#24373)
* server: avoid forwarding auth headers in CORS proxy

* format

* fix test

* fix e2e test

---------

Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
2026-06-20 15:34:47 +02:00
Amos Wong 20832179e2 ui: provide touch accessible model selection UI (#24604)
* ui : add model selector storybook stories

Covers list, favorites, single-model, all status states
(loading/loaded/sleeping/failed/idle), and selection states.

* ui : improve model selector mobile UX with hover media queries

Use @media (hover:none) to show action buttons directly on touch
devices and color-code them by model status (amber=sleeping,
green=loaded, muted=idle). Status dots hidden on touch. Desktop
hover behavior unchanged.
2026-06-18 13:14:20 +02:00
Aleksander Grygier f7ca93d12c ui: PWA support (#23871)
* feat: Add basic PWA support and service worker for offline caching

* feat: Vite PWA implementation WIP

* feat: Improve PWA icons generation

* feat: Add PWA workbox to server routes

* feat: Include `version.json` in static assets

* feat: Add HTTP cache headers for PWA static assets

* feat: Update app name for `apple-mobile-web-app-title`

* feat: Implement PWA versioning and automatic update detection

* chore: Update `.gitignore` files

* feat: Splash Screens

* feat: Add dark mode favicon support

* refactor: Cleanup

* fix: Use dark logo for dark splash screens

* refactor: Simplify favicons SVG code

* fix: Adjust caching and polling for reliable service worker updates

* fix: Add missing favicon entry

* fix: Align PWA service worker configuration with SvelteKit build structure

* fix: Replace hashed bundle paths with versioned static paths

* test: Add PWA tests

* ci: Add build output for unit tests

* refactor: Cleanup

* fix: Server build & release versioning

* chore: Update package-lock.json

* chore: Increase PWA cache size

* chore: Update packages

* feat: Update favicons

* refactor: Post-merge fix

* feat: support explicit build version for PWA cache busting

* fix: CI

* feat: Improve PWA Refresh Alert UI

* feat: Add toggleable build version display

* refactor: Cleanup

* feat: Add version mismatch detection and manual app reload

* refactor: replace dynamic imports with static

* refactor: Cleanup

* feat: Add safe space for `pwa-<size>.png` rendered icons

* fix: use relative paths for PWA assets to support base path deployment

* feat: add PWA mode detection via URL query parameter

* feat: Use ?cache=true for SW-cached PWA assets

* refactor: Build process cleanup

* refactor: Decouple PWA versioning and remove ?cache=true workaround

* chore: Update README logo

* feat: Include PWA Assets generation in build script

* refactor: `usePwa` hook for core layout

* fix: Relativize base vite plugin

* fix: remove unnecessary backslash escapes in test regexes

* test: update static asset paths for API Key test

* refactor: Move SvelteKit PWA Options config to constants

* ui: fix update notification never appearing

Keep the PWA hook object intact instead of destructuring needRefreshByStorage,
which freezes the reactive getter. Also exclude loading.html from PWA
precache to prevent 404 errors and broken SW installation.
2026-06-12 15:53:26 +02:00
Pascal 6471e3c090 UI/jpeg exif orientation (#24196)
* ui: bake jpeg exif orientation into uploaded images

stb_image in mtmd ignores exif metadata, so rotated smartphone photos
reach the model with raw pixel orientation. The webui now reads the
exif orientation tag at send time and feeds it into the existing
capImageDataURLSize canvas pass: the browser applies the rotation when
decoding, so capped images come out upright for free, and images under
the cap threshold get a single plain redraw when orientation > 1.

At most one re-encode ever happens per image. Upright jpegs with
capping disabled pass through untouched, bit perfect.

Adds jpeg-orientation.ts with a minimal exif parser working on a
bounded base64 prefix (both endianness, returns 1 on any malformed
input) and unit tests against handcrafted jpeg byte streams.

* ui: move jpeg exif constants into lib/constants

* ui: add browser test for jpeg orientation and capping

Covers capImageDataURLSize end to end in chromium with real Pillow
generated jpeg fixtures across exif orientations 1/3/5/6/8: upright
quadrant colors checked pixel-wise, expected dimensions with and
without capping, no orientation tag left in the output, and strict
passthrough when nothing needs rewriting.
2026-06-12 10:20:27 +02:00
viggy 42b2d60e57 webui: [a11y] fix keyboard navigation issues in chat interface and sidebar (#23132)
* use child snippets for landing and chat message elements

* make ... icon visible in conversation history menu

* conversation history forward tab fix

* add snippet fix for fork icon in conversation history

* focus/keyboard fix for attachment x icon and scroll left/right

* formatting

* fix scroll down issue

* simply Statistics and pointer events in scrolldown

* create storybook tests and move to folder

* improve tests to actually assert on element
2026-06-04 17:59:00 +02:00
Aleksander Grygier 21444c822e ui: Fixed packages (#24119)
* 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
2026-06-04 16:23:08 +02:00
Aleksander Grygier f8e67fc583 ui: Add Thinking mode toggle with reasoning effort levels + improvements for Chat Form Add Action UI (#23434)
* 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 <aleksander.grygier@gmail.com>

* fix: Model tags

* refactor: Cleanup

* refactor: Remove unneeded components

* refactor: Cleanup
2026-06-02 10:23:19 +02:00
Pascal 5a4126adc1 ui: fix stop/continue during an agentic loop (#23356) 2026-05-25 14:18:59 +02:00
Aleksander Grygier 1ff0fc1384 ui: Refactor models store, MCP service, and gate logs behind VITE_DEBUG (#23236)
* 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
2026-05-18 16:09:40 +02:00
Judd 4f13cb7424 webui: support video files as input (#22830) 2026-05-17 02:13:44 +02:00
kubawoo b81c2cdd74 ui: Fix handling of MCP resource template parameters (#23117)
* Fix handling of MCP resource template parameters

* Fix formatting for uri-template.test.ts

---------

Co-authored-by: kuba <kuba@laptop.local.net>
2026-05-16 13:25:41 +02:00