webui: Server tools (#21237)

* wip: server_tools

* feat: Integrate with `/tools` endpoint

* feat: Builtin + MCP + JSON Schema Tools WIP

* refactor

* displayName -> display_name

* snake_case everywhere

* rm redundant field

* feat: Improvements

* chore: update webui build output

* refactor: Updates after server updates

* chore: update webui build output

* change arg to --tools all

* feat: UI improvements

* chore: update webui build output

* add readme mention

* llama-gen-docs

* chore: update webui build output

* chore: update webui build output

* chore: update webui build output

* feat: Reorganize settings sections

* feat: Separate dialogs for MCP Servers Settings and Import/Export

* feat: WIP

* feat: WIP

* feat: WIP

* feat: WIP

* feat: WIP

* feat: WIP

* WIP on allozaur/20677-webui-server-tools

* feat: UI improvements

* chore: Update package lock

* chore: Run `npm audit fix`

* feat: UI WIP

* feat: UI

* refactor: Desktop Icon Strip DRY

* feat: Cleaner rendering and transition for ChatScreen

* feat: UI improvements

* feat: UI improvement

* feat: Remove MCP Server "enable" switch from Tools submenu

* chore: Run `npm audit fix`

* feat: WIP

* feat: Logic improvements

* refactor: Cleanup

* refactor: DRY

* test: Fix Chat Sidebar UI Tests

* chore: Update package lock

* refactor: Cleanup

* feat: Chat Message Action Card with Continue and Permission flow implementations

* feat: Add agentic steering messages, draft messages and improve chat UX

* fix: Search results UI

* test: Fix unit test

* feat: UI/UX improvements

* refactor: Simplify `useToolsPanel` access in components

* feat: Implement Processing Info Context API

* feat: Implement 'Go back to chat' functionality for settings

* feat: Enhance MCP Server management in Chat Form Attachments

* style: Minor UI and branding adjustments

* chore: Update webui static build output

* chore: Formatting, linting & type checks

* feat: Draft messages logic

* feat: UI improvements

* feat: Steering Messages improvements

* refactor: Cleanup

* refactor: Cleanup

* feat: Improve UI

* refactor: Settings navigation hook

* refactor: DRY code

* refactor: DRY ChatMessageUser UI components

* refactor: Desktop Icon Strip DRY

* refactor: Tools & permissions

* fix: Navigation condition

* refactor: Cleanup

* refactor: Cleanup

* refactor: Cleanup

* fix: preserve reasoning_content in agentic flow

---------

Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
This commit is contained in:
Aleksander Grygier
2026-04-28 14:35:49 +03:00
committed by GitHub
parent 19821178be
commit f42e29fdf1
138 changed files with 11345 additions and 8326 deletions
@@ -4,8 +4,6 @@ export const ATTACHMENT_SAVED_REGEX = /\[Attachment saved: ([^\]]+)\]/;
export const NEWLINE_SEPARATOR = '\n';
export const TURN_LIMIT_MESSAGE = '\n\n```\nTurn limit reached\n```\n';
export const LLM_ERROR_BLOCK_START = '\n\n```\nUpstream LLM error:\n';
export const LLM_ERROR_BLOCK_END = '\n```\n';
@@ -4,5 +4,10 @@ export const API_MODELS = {
UNLOAD: '/models/unload'
};
export const API_TOOLS = {
LIST: '/tools',
EXECUTE: '/tools'
};
/** CORS proxy endpoint path */
export const CORS_PROXY_ENDPOINT = '/cors-proxy';
@@ -0,0 +1,103 @@
import type { Component } from 'svelte';
import { MessageSquare, Zap, FolderOpen } from '@lucide/svelte';
import { FILE_TYPE_ICONS } from '$lib/constants/icons';
import {
AttachmentAction,
AttachmentItemEnabledWhen,
AttachmentItemVisibleWhen,
AttachmentMenuItemId
} from '$lib/enums';
export interface AttachmentMenuItem {
/** Unique identifier for the item */
id: AttachmentMenuItemId;
/** Display label */
label: string;
/** Lucide icon component */
icon: Component;
/** Extra CSS class applied to the item (e.g. for test selectors) */
class?: string;
/** Whether the item requires a specific modality to be enabled */
enabledWhen?: AttachmentItemEnabledWhen;
/** Tooltip shown when the item is disabled */
disabledTooltip?: string;
/** Callback key on the Props interface to invoke when clicked */
action: AttachmentAction;
/** Whether the item is only shown when a specific capability is present */
visibleWhen?: AttachmentItemVisibleWhen;
/** Whether this item has a tooltip even when enabled (uses dynamic text) */
hasEnabledTooltip?: boolean;
}
/**
* File attachment menu items shown in both the desktop dropdown and mobile sheet.
* The "Tools" submenu is handled separately by each component.
*/
export const ATTACHMENT_FILE_ITEMS: AttachmentMenuItem[] = [
{
id: AttachmentMenuItemId.IMAGES,
label: 'Images',
icon: FILE_TYPE_ICONS.image,
class: 'images-button',
enabledWhen: AttachmentItemEnabledWhen.HAS_VISION_MODALITY,
disabledTooltip: 'Image processing requires a vision model',
action: AttachmentAction.FILE_UPLOAD
},
{
id: AttachmentMenuItemId.AUDIO,
label: 'Audio Files',
icon: FILE_TYPE_ICONS.audio,
class: 'audio-button',
enabledWhen: AttachmentItemEnabledWhen.HAS_AUDIO_MODALITY,
disabledTooltip: 'Audio files processing requires an audio model',
action: AttachmentAction.FILE_UPLOAD
},
{
id: AttachmentMenuItemId.TEXT,
label: 'Text Files',
icon: FILE_TYPE_ICONS.text,
enabledWhen: AttachmentItemEnabledWhen.ALWAYS,
action: AttachmentAction.FILE_UPLOAD
},
{
id: AttachmentMenuItemId.PDF,
label: 'PDF Files',
icon: FILE_TYPE_ICONS.pdf,
enabledWhen: AttachmentItemEnabledWhen.ALWAYS,
disabledTooltip: 'PDFs will be converted to text. Image-based PDFs may not work properly.',
hasEnabledTooltip: true,
action: AttachmentAction.FILE_UPLOAD
}
];
export const ATTACHMENT_EXTRA_ITEMS: AttachmentMenuItem[] = [
{
id: AttachmentMenuItemId.SYSTEM_MESSAGE,
label: 'System Message',
icon: MessageSquare,
enabledWhen: AttachmentItemEnabledWhen.ALWAYS,
hasEnabledTooltip: true,
action: AttachmentAction.SYSTEM_PROMPT_CLICK
}
];
export const ATTACHMENT_MCP_ITEMS: AttachmentMenuItem[] = [
{
id: AttachmentMenuItemId.MCP_PROMPT,
label: 'MCP Prompt',
icon: Zap,
enabledWhen: AttachmentItemEnabledWhen.ALWAYS,
action: AttachmentAction.MCP_PROMPT_CLICK,
visibleWhen: AttachmentItemVisibleWhen.HAS_MCP_PROMPTS_SUPPORT
},
{
id: AttachmentMenuItemId.MCP_RESOURCES,
label: 'MCP Resources',
icon: FolderOpen,
enabledWhen: AttachmentItemEnabledWhen.ALWAYS,
action: AttachmentAction.MCP_RESOURCES_CLICK,
visibleWhen: AttachmentItemVisibleWhen.HAS_MCP_RESOURCES_SUPPORT
}
];
export const ATTACHMENT_TOOLTIP_TEXT = 'Add files, system prompt or MCP Servers';
@@ -3,3 +3,4 @@ export const PROMPT_CONTENT_SEPARATOR = '\n\n';
export const CLIPBOARD_CONTENT_QUOTE_PREFIX = '"';
export const PROMPT_TRIGGER_PREFIX = '/';
export const RESOURCE_TRIGGER_PREFIX = '@';
export const NEW_CHAT_DRAFT_KEY = '__new_chat__';
@@ -1,3 +1,4 @@
export const CONTEXT_KEY_MESSAGE_EDIT = 'chat-message-edit';
export const CONTEXT_KEY_CHAT_ACTIONS = 'chat-actions';
export const CONTEXT_KEY_CHAT_SETTINGS_DIALOG = 'chat-settings-dialog';
export const CONTEXT_KEY_CHAT_SETTINGS_CONFIG = 'chat-settings-config';
export const CONTEXT_KEY_PROCESSING_INFO = 'processing-info';
@@ -4,6 +4,7 @@
export * from './agentic';
export * from './api-endpoints';
export * from './attachment-labels';
export * from './attachment-menu';
export * from './auto-scroll';
export * from './binary-detection';
export * from './cache';
@@ -35,6 +36,7 @@ export * from './settings-keys';
export * from './settings-sections';
export * from './supported-file-types';
export * from './table-html-restorer';
export * from './tools';
export * from './tooltip-config';
export * from './ui';
export * from './uri-template';
@@ -1,4 +1,6 @@
export const ALWAYS_ALLOWED_TOOLS_LOCALSTORAGE_KEY = 'LlamaCppWebui.alwaysAllowedTools';
export const CONFIG_LOCALSTORAGE_KEY = 'LlamaCppWebui.config';
export const USER_OVERRIDES_LOCALSTORAGE_KEY = 'LlamaCppWebui.userOverrides';
export const DISABLED_TOOLS_LOCALSTORAGE_KEY = 'LlamaCppWebui.disabledTools';
export const FAVORITE_MODELS_LOCALSTORAGE_KEY = 'LlamaCppWebui.favoriteModels';
export const MCP_DEFAULT_ENABLED_LOCALSTORAGE_KEY = 'LlamaCppWebui.mcpDefaultEnabled';
export const USER_OVERRIDES_LOCALSTORAGE_KEY = 'LlamaCppWebui.userOverrides';
+1 -1
View File
@@ -40,7 +40,7 @@ export const MCP_RECONNECT_MAX_DELAY = 30000;
export const MCP_RECONNECT_ATTEMPT_TIMEOUT_MS = 15_000;
/** Maximum number of MCP server avatars to display in the chat form */
export const MAX_DISPLAYED_MCP_AVATARS = 3;
export const MAX_DISPLAYED_MCP_AVATARS = 4;
/** Expected count when two theme-less icons represent a light/dark pair */
export const EXPECTED_THEMED_ICON_PAIR_COUNT = 2;
@@ -8,7 +8,7 @@ export const SETTING_CONFIG_DEFAULT: Record<string, string | number | boolean |
systemMessage: '',
showSystemMessage: true,
theme: ColorMode.SYSTEM,
showThoughtInProgress: false,
showThoughtInProgress: true,
disableReasoningParsing: false,
excludeReasoningFromContext: false,
showRawOutputSwitch: false,
@@ -24,7 +24,6 @@ export const SETTINGS_KEYS = {
RENDER_USER_CONTENT_AS_MARKDOWN: 'renderUserContentAsMarkdown',
DISABLE_AUTO_SCROLL: 'disableAutoScroll',
ALWAYS_SHOW_SIDEBAR_ON_DESKTOP: 'alwaysShowSidebarOnDesktop',
AUTO_SHOW_SIDEBAR_ON_NEW_CHAT: 'autoShowSidebarOnNewChat',
FULL_HEIGHT_CODE_BLOCKS: 'fullHeightCodeBlocks',
SHOW_RAW_MODEL_NAMES: 'showRawModelNames',
// Sampling
@@ -10,6 +10,8 @@ export const SETTINGS_SECTION_TITLES = {
SAMPLING: 'Sampling',
PENALTIES: 'Penalties',
IMPORT_EXPORT: 'Import/Export',
AGENTIC: 'Agentic',
TOOLS: 'Tools',
MCP: 'MCP',
DEVELOPER: 'Developer'
} as const;
@@ -17,3 +19,298 @@ export const SETTINGS_SECTION_TITLES = {
/** Type for settings section titles */
export type SettingsSectionTitle =
(typeof SETTINGS_SECTION_TITLES)[keyof typeof SETTINGS_SECTION_TITLES];
import {
Funnel,
AlertTriangle,
Code,
Monitor,
ListRestart,
Sliders,
PencilRuler
} from '@lucide/svelte';
import { SettingsFieldType } from '$lib/enums/settings';
import { SETTINGS_COLOR_MODES_CONFIG } from '$lib/constants/settings-config';
import { SETTINGS_KEYS } from '$lib/constants/settings-keys';
import type { Component } from 'svelte';
export interface SettingsSection {
fields?: SettingsFieldConfig[];
icon: Component;
slug: string;
title: SettingsSectionTitle;
}
export const SETTINGS_CHAT_SECTIONS: SettingsSection[] = [
{
title: SETTINGS_SECTION_TITLES.GENERAL,
slug: 'general',
icon: Sliders,
fields: [
{
key: SETTINGS_KEYS.THEME,
label: 'Theme',
type: SettingsFieldType.SELECT,
options: SETTINGS_COLOR_MODES_CONFIG
},
{ key: SETTINGS_KEYS.API_KEY, label: 'API Key', type: SettingsFieldType.INPUT },
{
key: SETTINGS_KEYS.SYSTEM_MESSAGE,
label: 'System Message',
type: SettingsFieldType.TEXTAREA
},
{
key: SETTINGS_KEYS.PASTE_LONG_TEXT_TO_FILE_LEN,
label: 'Paste long text to file length',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.COPY_TEXT_ATTACHMENTS_AS_PLAIN_TEXT,
label: 'Copy text attachments as plain text',
type: SettingsFieldType.CHECKBOX
},
{
key: SETTINGS_KEYS.ENABLE_CONTINUE_GENERATION,
label: 'Enable "Continue" button',
type: SettingsFieldType.CHECKBOX,
isExperimental: true
},
{
key: SETTINGS_KEYS.PDF_AS_IMAGE,
label: 'Parse PDF as image',
type: SettingsFieldType.CHECKBOX
},
{
key: SETTINGS_KEYS.ASK_FOR_TITLE_CONFIRMATION,
label: 'Ask for confirmation before changing conversation title',
type: SettingsFieldType.CHECKBOX
}
]
},
{
title: SETTINGS_SECTION_TITLES.DISPLAY,
slug: 'display',
icon: Monitor,
fields: [
{
key: SETTINGS_KEYS.SHOW_MESSAGE_STATS,
label: 'Show message generation statistics',
type: SettingsFieldType.CHECKBOX
},
{
key: SETTINGS_KEYS.SHOW_THOUGHT_IN_PROGRESS,
label: 'Show thought in progress',
type: SettingsFieldType.CHECKBOX
},
{
key: SETTINGS_KEYS.SHOW_TOOL_CALL_IN_PROGRESS,
label: 'Show tool call in progress',
type: SettingsFieldType.CHECKBOX
},
{
key: SETTINGS_KEYS.KEEP_STATS_VISIBLE,
label: 'Keep stats visible after generation',
type: SettingsFieldType.CHECKBOX
},
{
key: SETTINGS_KEYS.AUTO_MIC_ON_EMPTY,
label: 'Show microphone on empty input',
type: SettingsFieldType.CHECKBOX,
isExperimental: true
},
{
key: SETTINGS_KEYS.RENDER_USER_CONTENT_AS_MARKDOWN,
label: 'Render user content as Markdown',
type: SettingsFieldType.CHECKBOX
},
{
key: SETTINGS_KEYS.FULL_HEIGHT_CODE_BLOCKS,
label: 'Use full height code blocks',
type: SettingsFieldType.CHECKBOX
},
{
key: SETTINGS_KEYS.DISABLE_AUTO_SCROLL,
label: 'Disable automatic scroll',
type: SettingsFieldType.CHECKBOX
},
{
key: SETTINGS_KEYS.ALWAYS_SHOW_SIDEBAR_ON_DESKTOP,
label: 'Always show sidebar on desktop',
type: SettingsFieldType.CHECKBOX
},
{
key: SETTINGS_KEYS.SHOW_RAW_MODEL_NAMES,
label: 'Show raw model names',
type: SettingsFieldType.CHECKBOX
},
{
key: SETTINGS_KEYS.ALWAYS_SHOW_AGENTIC_TURNS,
label: 'Always show agentic turns in conversation',
type: SettingsFieldType.CHECKBOX
}
]
},
{
title: SETTINGS_SECTION_TITLES.SAMPLING,
slug: 'sampling',
icon: Funnel,
fields: [
{
key: SETTINGS_KEYS.TEMPERATURE,
label: 'Temperature',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.DYNATEMP_RANGE,
label: 'Dynamic temperature range',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.DYNATEMP_EXPONENT,
label: 'Dynamic temperature exponent',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.TOP_K,
label: 'Top K',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.TOP_P,
label: 'Top P',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.MIN_P,
label: 'Min P',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.XTC_PROBABILITY,
label: 'XTC probability',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.XTC_THRESHOLD,
label: 'XTC threshold',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.TYP_P,
label: 'Typical P',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.MAX_TOKENS,
label: 'Max tokens',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.SAMPLERS,
label: 'Samplers',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.BACKEND_SAMPLING,
label: 'Backend sampling',
type: SettingsFieldType.CHECKBOX
}
]
},
{
title: SETTINGS_SECTION_TITLES.PENALTIES,
slug: 'penalties',
icon: AlertTriangle,
fields: [
{
key: SETTINGS_KEYS.REPEAT_LAST_N,
label: 'Repeat last N',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.REPEAT_PENALTY,
label: 'Repeat penalty',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.PRESENCE_PENALTY,
label: 'Presence penalty',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.FREQUENCY_PENALTY,
label: 'Frequency penalty',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.DRY_MULTIPLIER,
label: 'DRY multiplier',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.DRY_BASE,
label: 'DRY base',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.DRY_ALLOWED_LENGTH,
label: 'DRY allowed length',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.DRY_PENALTY_LAST_N,
label: 'DRY penalty last N',
type: SettingsFieldType.INPUT
}
]
},
{
title: SETTINGS_SECTION_TITLES.AGENTIC,
slug: 'agentic',
icon: ListRestart,
fields: [
{
key: SETTINGS_KEYS.AGENTIC_MAX_TURNS,
label: 'Agentic turns',
type: SettingsFieldType.INPUT
},
{
key: SETTINGS_KEYS.AGENTIC_MAX_TOOL_PREVIEW_LINES,
label: 'Max lines per tool preview',
type: SettingsFieldType.INPUT
}
]
},
{
title: SETTINGS_SECTION_TITLES.TOOLS,
slug: 'tools',
icon: PencilRuler
},
{
title: SETTINGS_SECTION_TITLES.DEVELOPER,
slug: 'developer',
icon: Code,
fields: [
{
key: SETTINGS_KEYS.DISABLE_REASONING_PARSING,
label: 'Disable reasoning content parsing',
type: SettingsFieldType.CHECKBOX
},
{
key: SETTINGS_KEYS.EXCLUDE_REASONING_FROM_CONTEXT,
label: 'Exclude reasoning from context',
type: SettingsFieldType.CHECKBOX
},
{
key: SETTINGS_KEYS.SHOW_RAW_OUTPUT_SWITCH,
label: 'Enable raw output toggle',
type: SettingsFieldType.CHECKBOX
},
{
key: SETTINGS_KEYS.CUSTOM,
label: 'Custom JSON',
type: SettingsFieldType.TEXTAREA
}
]
}
];
@@ -0,0 +1,11 @@
import { ToolSource } from '$lib/enums/tools';
export const TOOL_GROUP_LABELS = {
[ToolSource.BUILTIN]: 'Built-in',
[ToolSource.CUSTOM]: 'JSON Schema'
} as const;
export const TOOL_SERVER_LABELS = {
[ToolSource.BUILTIN]: 'Built-in Tools',
[ToolSource.CUSTOM]: 'Custom Tools'
} as const;
@@ -1,2 +1,42 @@
import { Database, Settings, Search, SquarePen } from '@lucide/svelte';
import McpLogo from '$lib/components/app/mcp/McpLogo.svelte';
import type { Component } from 'svelte';
export const FORK_TREE_DEPTH_PADDING = 8;
export const SYSTEM_MESSAGE_PLACEHOLDER = 'System message';
export const APP_NAME = import.meta.env.VITE_PUBLIC_APP_NAME || 'llama.cpp';
export const ICON_STRIP_TRANSITION_DURATION = 150;
export const ICON_STRIP_TRANSITION_DELAY_MULTIPLIER = 50;
export interface DesktopIconStripItem {
icon: Component;
tooltip: string;
route?: string;
activeRouteId?: string;
activeRoutePrefix?: string;
keys?: string[];
}
export const SIDEBAR_ACTIONS_ITEMS: DesktopIconStripItem[] = [
{ icon: SquarePen, tooltip: 'New chat', route: '?new_chat=true#/', keys: ['shift', 'cmd', 'o'] },
{ icon: Search, tooltip: 'Search', keys: ['cmd', 'k'] },
{
icon: McpLogo,
tooltip: 'MCP Servers',
route: '#/settings/mcp',
activeRouteId: '/settings/mcp'
},
{
icon: Database,
tooltip: 'Import / Export',
route: '#/settings/import-export',
activeRouteId: '/settings/import-export'
},
{
icon: Settings,
tooltip: 'Settings',
route: '#/settings/chat/general',
activeRoutePrefix: '/settings/chat'
}
];