From: Aleksander Grygier Date: Thu, 13 Aug 2026 04:53:56 +0000 (+0200) Subject: ui: Constants refactor (#26908) X-Git-Tag: upstream/0.0.10438~39 X-Git-Url: https://git.djapps.eu/?a=commitdiff_plain;h=e21152dc9636c6d2db6edc9b4531dfc5b2d1cba3;p=pkg%2Fggml%2Fsources%2Fllama.cpp ui: Constants refactor (#26908) * refactor: Constants * refactor: Constants/Enums cleanup * refactor: Constant objects instead of multiple single value constants * refactor: Cleanup constants --- diff --git a/tools/ui/pwa-assets-dark.config.ts b/tools/ui/pwa-assets-dark.config.ts index 1446b47a8..4d8114ee7 100644 --- a/tools/ui/pwa-assets-dark.config.ts +++ b/tools/ui/pwa-assets-dark.config.ts @@ -1,5 +1,5 @@ import { writeThemeFavicons } from './scripts/favicon-colorize'; -import { FAVICON_COLORS, PWA_ASSET_GENERATOR } from './src/lib/constants/pwa'; +import { FAVICON_COLORS, PWA_ASSET_GENERATOR } from './src/lib/constants/pwa.constants'; import { defineConfig } from '@vite-pwa/assets-generator/config'; writeThemeFavicons(FAVICON_COLORS.LIGHT, FAVICON_COLORS.DARK, { diff --git a/tools/ui/pwa-assets.config.ts b/tools/ui/pwa-assets.config.ts index 5fed0a595..f9f8662a2 100644 --- a/tools/ui/pwa-assets.config.ts +++ b/tools/ui/pwa-assets.config.ts @@ -4,7 +4,7 @@ import { PWA_ASSET_GENERATOR, PWA_GENERATOR_DEVICES, THEME_COLORS -} from './src/lib/constants/pwa'; +} from './src/lib/constants/pwa.constants'; import { SplashOrientation } from './src/lib/enums/splash.enums'; import { combinePresetAndAppleSplashScreens, diff --git a/tools/ui/scripts/vite-plugin-build-info.ts b/tools/ui/scripts/vite-plugin-build-info.ts index 800238630..ec864e8d0 100644 --- a/tools/ui/scripts/vite-plugin-build-info.ts +++ b/tools/ui/scripts/vite-plugin-build-info.ts @@ -1,4 +1,4 @@ -import { BUILD_CONFIG } from '../src/lib/constants/pwa'; +import { BUILD_CONFIG } from '../src/lib/constants/pwa.constants'; import { existsSync, writeFileSync } from 'node:fs'; import { resolve } from 'path'; import type { Plugin } from 'vite'; diff --git a/tools/ui/scripts/vite-plugin-relativize-base.ts b/tools/ui/scripts/vite-plugin-relativize-base.ts index f8eac1d66..0e47741ae 100644 --- a/tools/ui/scripts/vite-plugin-relativize-base.ts +++ b/tools/ui/scripts/vite-plugin-relativize-base.ts @@ -1,4 +1,4 @@ -import { BUILD_CONFIG } from '../src/lib/constants/pwa'; +import { BUILD_CONFIG } from '../src/lib/constants/pwa.constants'; import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import { resolve } from 'path'; import type { Plugin } from 'vite'; diff --git a/tools/ui/scripts/vite-plugin-splash-screen.ts b/tools/ui/scripts/vite-plugin-splash-screen.ts index 45d931bab..62b7a063a 100644 --- a/tools/ui/scripts/vite-plugin-splash-screen.ts +++ b/tools/ui/scripts/vite-plugin-splash-screen.ts @@ -1,5 +1,10 @@ -import { NEWLINE, TAB } from '../src/lib/constants/code'; -import { APPLE_DEVICES, BUILD_CONFIG, REGEX_PATTERNS, SPLASH_LINK } from '../src/lib/constants/pwa'; +import { + APPLE_DEVICES, + BUILD_CONFIG, + REGEX_PATTERNS, + SPLASH_LINK +} from '../src/lib/constants/pwa.constants'; +import { NEWLINE, TAB } from '../src/lib/constants/special-characters.constants'; import { SplashOrientation } from '../src/lib/enums/splash.enums'; import type { SplashDimensions } from '../src/lib/types'; import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; diff --git a/tools/ui/src/lib/components/app/actions/ActionIconCopyToClipboard.svelte b/tools/ui/src/lib/components/app/actions/ActionIconCopyToClipboard.svelte index 7655f18e8..2d54df89d 100644 --- a/tools/ui/src/lib/components/app/actions/ActionIconCopyToClipboard.svelte +++ b/tools/ui/src/lib/components/app/actions/ActionIconCopyToClipboard.svelte @@ -1,7 +1,7 @@ diff --git a/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenServerError.svelte b/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenServerError.svelte index ae5d91bee..cae37d6f8 100644 --- a/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenServerError.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenServerError.svelte @@ -1,7 +1,7 @@ diff --git a/tools/ui/src/lib/constants/agentic.constants.ts b/tools/ui/src/lib/constants/agentic.constants.ts new file mode 100644 index 000000000..e57104e8a --- /dev/null +++ b/tools/ui/src/lib/constants/agentic.constants.ts @@ -0,0 +1,67 @@ +import type { AgenticConfig } from '$lib/types/agentic'; + +export const ATTACHMENT_SAVED_REGEX = /\[Attachment saved: ([^\]]+)\]/; + +// JSON detection: trimmed content opens with an object or array literal. +export const TOOL_RESULT_JSON_OPEN_REGEX = /^[[{]/; + +// Search-summary wire format used by file-glob and grep tools: +// +// --- +// Total matches: N +export const SEARCH_SUMMARY = { + SEPARATOR: '---\n', + TOTAL_REGEX: /Total matches:\s*(\d+)/ +} as const; + +// Separator rendered between stats in the tool-result footer (e.g. between a +// result message and the byte/edit count). Plain ASCII spaces bracket a hyphen +// so the whole " - " sits on one visual line even when the surrounding text +// wraps mid-paragraph. +export const RESULT_STAT_SEPARATOR = ' - '; + +export const DEFAULT_AGENTIC_CONFIG: AgenticConfig = { + enabled: true, + maxTurns: 100 +} as const; + +export const REASONING_TAGS = { + END: '', + START: '' +} as const; + +/** + * @deprecated Legacy marker tags - only used for migration of old stored messages. + * New messages use structured fields (reasoningContent, toolCalls, toolCallId). + */ +export const LEGACY_AGENTIC_TAGS = { + TAG_SUFFIX: '>>>', + TOOL_ARGS_END: '<<>>', + TOOL_ARGS_START: '<<>>', + TOOL_CALL_END: '<<>>', + TOOL_CALL_START: '<<>>', + TOOL_NAME_PREFIX: '<<>>', + START: '<<>>' +} as const; + +/** + * @deprecated Legacy regex patterns - only used for migration of old stored messages. + */ +export const LEGACY_AGENTIC_REGEX = { + AGENTIC_TOOL_CALL_BLOCK: /\n*<<>>[\s\S]*?<<>>/g, + AGENTIC_TOOL_CALL_OPEN: /\n*<<>>[\s\S]*$/, + COMPLETED_TOOL_CALL: + /<<>>\n<<>>\n<<>>([\s\S]*?)<<>>([\s\S]*?)<<>>/g, + HAS_LEGACY_MARKERS: /<<<(?:AGENTIC_TOOL_CALL_START|reasoning_content_start)>>>/, + REASONING_BLOCK: /<<>>[\s\S]*?<<>>/g, + REASONING_EXTRACT: /<<>>([\s\S]*?)<<>>/, + REASONING_OPEN: /<<>>[\s\S]*$/ +} as const; diff --git a/tools/ui/src/lib/constants/agentic.ts b/tools/ui/src/lib/constants/agentic.ts deleted file mode 100644 index 582572a29..000000000 --- a/tools/ui/src/lib/constants/agentic.ts +++ /dev/null @@ -1,75 +0,0 @@ -import type { AgenticConfig } from '$lib/types/agentic'; - -export const ATTACHMENT_SAVED_REGEX = /\[Attachment saved: ([^\]]+)\]/; - -// JSON detection: trimmed content opens with an object or array literal. -export const TOOL_RESULT_JSON_OPEN_REGEX = /^[[{]/; - -// Markdown structural markers used by `looksLikeMarkdown`. Inline / line-level. -export const MARKDOWN_CODE_FENCE_REGEX = /^(```|~~~)/m; -export const MARKDOWN_ATX_HEADING_REGEX = /^#{1,6}\s+\S/; -export const MARKDOWN_BLOCKQUOTE_REGEX = /^>\s+\S/; -export const MARKDOWN_LIST_BULLET_REGEX = /^\s*[-*+]\s+\S/; -export const MARKDOWN_LIST_NUMBERED_REGEX = /^\s*\d+[.)]\s+\S/; -export const MARKDOWN_LINK_REGEX = /\[[^\]\n]+\]\([^)\s]+\)/; -export const MARKDOWN_BOLD_REGEX = /\*\*[^*\n]+\*\*|__[^_\n]+__/; -export const MARKDOWN_TABLE_SEPARATOR_REGEX = /^\s*\|?[\s:|-]+\|?\s*$/; - -// Search-summary wire format used by file-glob and grep tools: -// -// --- -// Total matches: N -export const SEARCH_SUMMARY_SEPARATOR = '---\n'; -export const SEARCH_SUMMARY_TOTAL_REGEX = /Total matches:\s*(\d+)/; - -// Separator rendered between stats in the tool-result footer (e.g. between a -// result message and the byte/edit count). Plain ASCII spaces bracket a hyphen -// so the whole " - " sits on one visual line even when the surrounding text -// wraps mid-paragraph. -export const RESULT_STAT_SEPARATOR = ' - '; - -export const DEFAULT_AGENTIC_CONFIG: AgenticConfig = { - enabled: true, - maxTurns: 100 -} as const; - -export const REASONING_TAGS = { - END: '', - START: '' -} as const; - -/** - * @deprecated Legacy marker tags - only used for migration of old stored messages. - * New messages use structured fields (reasoningContent, toolCalls, toolCallId). - */ -export const LEGACY_AGENTIC_TAGS = { - TAG_SUFFIX: '>>>', - TOOL_ARGS_END: '<<>>', - TOOL_ARGS_START: '<<>>', - TOOL_CALL_END: '<<>>', - TOOL_CALL_START: '<<>>', - TOOL_NAME_PREFIX: '<<>>', - START: '<<>>' -} as const; - -/** - * @deprecated Legacy regex patterns - only used for migration of old stored messages. - */ -export const LEGACY_AGENTIC_REGEX = { - AGENTIC_TOOL_CALL_BLOCK: /\n*<<>>[\s\S]*?<<>>/g, - AGENTIC_TOOL_CALL_OPEN: /\n*<<>>[\s\S]*$/, - COMPLETED_TOOL_CALL: - /<<>>\n<<>>\n<<>>([\s\S]*?)<<>>([\s\S]*?)<<>>/g, - HAS_LEGACY_MARKERS: /<<<(?:AGENTIC_TOOL_CALL_START|reasoning_content_start)>>>/, - REASONING_BLOCK: /<<>>[\s\S]*?<<>>/g, - REASONING_EXTRACT: /<<>>([\s\S]*?)<<>>/, - REASONING_OPEN: /<<>>[\s\S]*$/ -} as const; diff --git a/tools/ui/src/lib/constants/api-endpoints.constants.ts b/tools/ui/src/lib/constants/api-endpoints.constants.ts new file mode 100644 index 000000000..74f1c7302 --- /dev/null +++ b/tools/ui/src/lib/constants/api-endpoints.constants.ts @@ -0,0 +1,35 @@ +export const API_MODELS = { + LIST: '/v1/models', + LOAD: '/models/load', + SSE: '/models/sse', + UNLOAD: '/models/unload' +}; + +// chat completion routes, the control route drives realtime inference (e.g. end reasoning) +export const API_CHAT = { + COMPLETIONS: './v1/chat/completions', + CONTROL: './v1/chat/completions/control' +}; + +// slot introspection, requires the --slots flag on the server +export const API_SLOTS = { + LIST: './slots' +}; + +export const API_TOOLS = { + EXECUTE: '/tools', + LIST: '/tools' +}; + +// resumable stream routes, the conv::model identity travels as the conv_id query param +// because model names can contain slashes that a path segment cannot carry +// resume retry cadence while the owning model is still loading (server answers 503) +export const STREAM_RESUME_RETRY_MS = 2000; + +export const API_STREAM = { + BASE: './v1/stream', + LOOKUP: './v1/streams/lookup' +}; + +/** CORS proxy endpoint path */ +export const CORS_PROXY_ENDPOINT = '/cors-proxy'; diff --git a/tools/ui/src/lib/constants/api-endpoints.ts b/tools/ui/src/lib/constants/api-endpoints.ts deleted file mode 100644 index 74f1c7302..000000000 --- a/tools/ui/src/lib/constants/api-endpoints.ts +++ /dev/null @@ -1,35 +0,0 @@ -export const API_MODELS = { - LIST: '/v1/models', - LOAD: '/models/load', - SSE: '/models/sse', - UNLOAD: '/models/unload' -}; - -// chat completion routes, the control route drives realtime inference (e.g. end reasoning) -export const API_CHAT = { - COMPLETIONS: './v1/chat/completions', - CONTROL: './v1/chat/completions/control' -}; - -// slot introspection, requires the --slots flag on the server -export const API_SLOTS = { - LIST: './slots' -}; - -export const API_TOOLS = { - EXECUTE: '/tools', - LIST: '/tools' -}; - -// resumable stream routes, the conv::model identity travels as the conv_id query param -// because model names can contain slashes that a path segment cannot carry -// resume retry cadence while the owning model is still loading (server answers 503) -export const STREAM_RESUME_RETRY_MS = 2000; - -export const API_STREAM = { - BASE: './v1/stream', - LOOKUP: './v1/streams/lookup' -}; - -/** CORS proxy endpoint path */ -export const CORS_PROXY_ENDPOINT = '/cors-proxy'; diff --git a/tools/ui/src/lib/constants/app.constants.ts b/tools/ui/src/lib/constants/app.constants.ts new file mode 100644 index 000000000..c598f480f --- /dev/null +++ b/tools/ui/src/lib/constants/app.constants.ts @@ -0,0 +1 @@ +export const APP_NAME = import.meta.env?.VITE_PUBLIC_APP_NAME || 'llama-ui'; diff --git a/tools/ui/src/lib/constants/app.ts b/tools/ui/src/lib/constants/app.ts deleted file mode 100644 index c598f480f..000000000 --- a/tools/ui/src/lib/constants/app.ts +++ /dev/null @@ -1 +0,0 @@ -export const APP_NAME = import.meta.env?.VITE_PUBLIC_APP_NAME || 'llama-ui'; diff --git a/tools/ui/src/lib/constants/attachment-labels.ts b/tools/ui/src/lib/constants/attachment-labels.ts deleted file mode 100644 index be9999c0f..000000000 --- a/tools/ui/src/lib/constants/attachment-labels.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const ATTACHMENT_LABEL_FILE = 'File'; -export const ATTACHMENT_LABEL_PDF_FILE = 'PDF File'; -export const ATTACHMENT_LABEL_MCP_PROMPT = 'MCP Prompt'; -export const ATTACHMENT_LABEL_MCP_RESOURCE = 'MCP Resource'; diff --git a/tools/ui/src/lib/constants/attachment-menu.constants.ts b/tools/ui/src/lib/constants/attachment-menu.constants.ts new file mode 100644 index 000000000..62e03bea6 --- /dev/null +++ b/tools/ui/src/lib/constants/attachment-menu.constants.ts @@ -0,0 +1,93 @@ +import { FolderOpen, MessageSquare, Zap } from '@lucide/svelte'; +import { FILE_TYPE_ICONS } from '$lib/constants'; +import { + AttachmentAction, + AttachmentItemEnabledWhen, + AttachmentItemVisibleWhen, + AttachmentMenuItemId +} from '$lib/enums'; +import type { AttachmentMenuItem } from '$lib/types'; + +/** + * 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[] = [ + { + action: AttachmentAction.FILE_UPLOAD, + class: 'images-button', + disabledTooltip: 'Image processing requires a vision model', + enabledWhen: AttachmentItemEnabledWhen.HAS_VISION_MODALITY, + icon: FILE_TYPE_ICONS.image, + id: AttachmentMenuItemId.IMAGES, + label: 'Images' + }, + { + action: AttachmentAction.FILE_UPLOAD, + class: 'audio-button', + disabledTooltip: 'Audio files processing requires an audio model', + enabledWhen: AttachmentItemEnabledWhen.HAS_AUDIO_MODALITY, + icon: FILE_TYPE_ICONS.audio, + id: AttachmentMenuItemId.AUDIO, + label: 'Audio Files' + }, + { + action: AttachmentAction.FILE_UPLOAD, + class: 'video-button', + disabledTooltip: 'Video files processing requires a video model', + enabledWhen: AttachmentItemEnabledWhen.HAS_VIDEO_MODALITY, + icon: FILE_TYPE_ICONS.video, + id: AttachmentMenuItemId.VIDEO, + label: 'Video Files' + }, + { + action: AttachmentAction.FILE_UPLOAD, + enabledWhen: AttachmentItemEnabledWhen.ALWAYS, + icon: FILE_TYPE_ICONS.text, + id: AttachmentMenuItemId.TEXT, + label: 'Text Files' + }, + { + action: AttachmentAction.FILE_UPLOAD, + disabledTooltip: 'PDFs will be converted to text. Image-based PDFs may not work properly.', + enabledWhen: AttachmentItemEnabledWhen.ALWAYS, + hasEnabledTooltip: true, + icon: FILE_TYPE_ICONS.pdf, + id: AttachmentMenuItemId.PDF, + label: 'PDF Files' + } +]; + +export const ATTACHMENT_EXTRA_ITEMS: AttachmentMenuItem[] = []; + +export const ATTACHMENT_PROMPT_ITEMS: AttachmentMenuItem[] = [ + { + action: AttachmentAction.SYSTEM_PROMPT_CLICK, + enabledWhen: AttachmentItemEnabledWhen.ALWAYS, + hasEnabledTooltip: true, + icon: MessageSquare, + id: AttachmentMenuItemId.SYSTEM_MESSAGE, + label: 'System Message' + }, + { + action: AttachmentAction.MCP_PROMPT_CLICK, + enabledWhen: AttachmentItemEnabledWhen.ALWAYS, + icon: Zap, + id: AttachmentMenuItemId.MCP_PROMPT, + label: 'MCP Prompt', + visibleWhen: AttachmentItemVisibleWhen.HAS_MCP_PROMPTS_SUPPORT + } +]; + +export const ATTACHMENT_MCP_ITEMS: AttachmentMenuItem[] = [ + { + action: AttachmentAction.MCP_RESOURCES_CLICK, + enabledWhen: AttachmentItemEnabledWhen.ALWAYS, + icon: FolderOpen, + id: AttachmentMenuItemId.MCP_RESOURCES, + label: 'MCP Resources', + visibleWhen: AttachmentItemVisibleWhen.HAS_MCP_RESOURCES_SUPPORT + } +]; + +export const ATTACHMENT_TOOLTIP_TEXT = 'Add files, prompts, tools or MCP Servers'; diff --git a/tools/ui/src/lib/constants/attachment-menu.ts b/tools/ui/src/lib/constants/attachment-menu.ts deleted file mode 100644 index 1cd7f9ba5..000000000 --- a/tools/ui/src/lib/constants/attachment-menu.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { FolderOpen, MessageSquare, Zap } from '@lucide/svelte'; -import { FILE_TYPE_ICONS } from '$lib/constants/icons'; -import { - AttachmentAction, - AttachmentItemEnabledWhen, - AttachmentItemVisibleWhen, - AttachmentMenuItemId -} from '$lib/enums'; -import type { Component } from 'svelte'; - -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[] = [ - { - action: AttachmentAction.FILE_UPLOAD, - class: 'images-button', - disabledTooltip: 'Image processing requires a vision model', - enabledWhen: AttachmentItemEnabledWhen.HAS_VISION_MODALITY, - icon: FILE_TYPE_ICONS.image, - id: AttachmentMenuItemId.IMAGES, - label: 'Images' - }, - { - action: AttachmentAction.FILE_UPLOAD, - class: 'audio-button', - disabledTooltip: 'Audio files processing requires an audio model', - enabledWhen: AttachmentItemEnabledWhen.HAS_AUDIO_MODALITY, - icon: FILE_TYPE_ICONS.audio, - id: AttachmentMenuItemId.AUDIO, - label: 'Audio Files' - }, - { - action: AttachmentAction.FILE_UPLOAD, - class: 'video-button', - disabledTooltip: 'Video files processing requires a video model', - enabledWhen: AttachmentItemEnabledWhen.HAS_VIDEO_MODALITY, - icon: FILE_TYPE_ICONS.video, - id: AttachmentMenuItemId.VIDEO, - label: 'Video Files' - }, - { - action: AttachmentAction.FILE_UPLOAD, - enabledWhen: AttachmentItemEnabledWhen.ALWAYS, - icon: FILE_TYPE_ICONS.text, - id: AttachmentMenuItemId.TEXT, - label: 'Text Files' - }, - { - action: AttachmentAction.FILE_UPLOAD, - disabledTooltip: 'PDFs will be converted to text. Image-based PDFs may not work properly.', - enabledWhen: AttachmentItemEnabledWhen.ALWAYS, - hasEnabledTooltip: true, - icon: FILE_TYPE_ICONS.pdf, - id: AttachmentMenuItemId.PDF, - label: 'PDF Files' - } -]; - -export const ATTACHMENT_EXTRA_ITEMS: AttachmentMenuItem[] = []; - -export const ATTACHMENT_PROMPT_ITEMS: AttachmentMenuItem[] = [ - { - action: AttachmentAction.SYSTEM_PROMPT_CLICK, - enabledWhen: AttachmentItemEnabledWhen.ALWAYS, - hasEnabledTooltip: true, - icon: MessageSquare, - id: AttachmentMenuItemId.SYSTEM_MESSAGE, - label: 'System Message' - }, - { - action: AttachmentAction.MCP_PROMPT_CLICK, - enabledWhen: AttachmentItemEnabledWhen.ALWAYS, - icon: Zap, - id: AttachmentMenuItemId.MCP_PROMPT, - label: 'MCP Prompt', - visibleWhen: AttachmentItemVisibleWhen.HAS_MCP_PROMPTS_SUPPORT - } -]; - -export const ATTACHMENT_MCP_ITEMS: AttachmentMenuItem[] = [ - { - action: AttachmentAction.MCP_RESOURCES_CLICK, - enabledWhen: AttachmentItemEnabledWhen.ALWAYS, - icon: FolderOpen, - id: AttachmentMenuItemId.MCP_RESOURCES, - label: 'MCP Resources', - visibleWhen: AttachmentItemVisibleWhen.HAS_MCP_RESOURCES_SUPPORT - } -]; - -export const ATTACHMENT_TOOLTIP_TEXT = 'Add files, prompts, tools or MCP Servers'; diff --git a/tools/ui/src/lib/constants/auto-scroll.constants.ts b/tools/ui/src/lib/constants/auto-scroll.constants.ts new file mode 100644 index 000000000..67c5f9301 --- /dev/null +++ b/tools/ui/src/lib/constants/auto-scroll.constants.ts @@ -0,0 +1,22 @@ +export const AUTO_SCROLL_INTERVAL = 100; +// Conversation landing: the page keeps growing after the first bottom pin +// without DOM mutations (content-visibility size realizations, syntax +// highlight passes), so the pin repeats every frame until the height holds +// for this many consecutive frames, bounded by the time cap below. +export const LANDING_STABLE_FRAMES = 10; +export const LANDING_SETTLE_MAX_MS = 1000; +// Chat main view: tight threshold because scroll-here events come from +// discrete assistant-message appends. +export const AUTO_SCROLL_AT_BOTTOM_THRESHOLD = 10; +// Reasoning block: stickier because reasoning fires many small +// incremental DOM writes that easily drift a few pixels off bottom. +export const REASONING_SCROLL_AT_BOTTOM_THRESHOLD_PX = 64; +// Syntax-highlighted code: stickier than the chat main view because line +// wrap reflows while the highlight.js pass settles can drift a few pixels +// off bottom. +export const SYNTAX_CODE_SCROLL_AT_BOTTOM_THRESHOLD_PX = 32; +// Streaming tool output (e.g. exec_shell_command): shell commands produce +// lots of small line writes and the exit-code line appended at the tail +// past the last user-visible frame is what triggers DOM drift, so use a +// threshold generous enough to capture that tail flush. +export const TOOL_RUNTIME_SCROLL_AT_BOTTOM_THRESHOLD_PX = 64; diff --git a/tools/ui/src/lib/constants/auto-scroll.ts b/tools/ui/src/lib/constants/auto-scroll.ts deleted file mode 100644 index 67c5f9301..000000000 --- a/tools/ui/src/lib/constants/auto-scroll.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const AUTO_SCROLL_INTERVAL = 100; -// Conversation landing: the page keeps growing after the first bottom pin -// without DOM mutations (content-visibility size realizations, syntax -// highlight passes), so the pin repeats every frame until the height holds -// for this many consecutive frames, bounded by the time cap below. -export const LANDING_STABLE_FRAMES = 10; -export const LANDING_SETTLE_MAX_MS = 1000; -// Chat main view: tight threshold because scroll-here events come from -// discrete assistant-message appends. -export const AUTO_SCROLL_AT_BOTTOM_THRESHOLD = 10; -// Reasoning block: stickier because reasoning fires many small -// incremental DOM writes that easily drift a few pixels off bottom. -export const REASONING_SCROLL_AT_BOTTOM_THRESHOLD_PX = 64; -// Syntax-highlighted code: stickier than the chat main view because line -// wrap reflows while the highlight.js pass settles can drift a few pixels -// off bottom. -export const SYNTAX_CODE_SCROLL_AT_BOTTOM_THRESHOLD_PX = 32; -// Streaming tool output (e.g. exec_shell_command): shell commands produce -// lots of small line writes and the exit-code line appended at the tail -// past the last user-visible frame is what triggers DOM drift, so use a -// threshold generous enough to capture that tail flush. -export const TOOL_RUNTIME_SCROLL_AT_BOTTOM_THRESHOLD_PX = 64; diff --git a/tools/ui/src/lib/constants/binary-detection.constants.ts b/tools/ui/src/lib/constants/binary-detection.constants.ts new file mode 100644 index 000000000..69bd4d48e --- /dev/null +++ b/tools/ui/src/lib/constants/binary-detection.constants.ts @@ -0,0 +1,7 @@ +import type { BinaryDetectionOptions } from '$lib/types'; + +export const DEFAULT_BINARY_DETECTION_OPTIONS: BinaryDetectionOptions = { + maxAbsoluteNullBytes: 2, + prefixLength: 1024 * 10, // Check the first 10KB of the string + suspiciousCharThresholdRatio: 0.15 // Allow up to 15% suspicious chars +}; diff --git a/tools/ui/src/lib/constants/binary-detection.ts b/tools/ui/src/lib/constants/binary-detection.ts deleted file mode 100644 index 69bd4d48e..000000000 --- a/tools/ui/src/lib/constants/binary-detection.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { BinaryDetectionOptions } from '$lib/types'; - -export const DEFAULT_BINARY_DETECTION_OPTIONS: BinaryDetectionOptions = { - maxAbsoluteNullBytes: 2, - prefixLength: 1024 * 10, // Check the first 10KB of the string - suspiciousCharThresholdRatio: 0.15 // Allow up to 15% suspicious chars -}; diff --git a/tools/ui/src/lib/constants/built-in-tools.constants.ts b/tools/ui/src/lib/constants/built-in-tools.constants.ts new file mode 100644 index 000000000..679c61459 --- /dev/null +++ b/tools/ui/src/lib/constants/built-in-tools.constants.ts @@ -0,0 +1,52 @@ +// Registry of built-in and frontend (browser) tools whose renderer +// shows a recognizable icon and friendly label inline in the chat UI. +// +// To add a new built-in tool, add an entry to BUILTIN_TOOL_UI. To give a +// tool a custom title or body renderer, add a dedicated component under +// ChatMessageToolCall/ and route it in ChatMessageToolCallBlock.svelte +// (see ChatMessageToolCallBlockGetDatetime and +// ChatMessageToolCallBlockSearchResults for prior art). + +import { + Braces, + Clock, + Eye, + FilePen, + FilePlus, + FileSearch, + FileText, + Info, + SearchCode, + Terminal +} from '@lucide/svelte'; +import { BuiltInTool, ToolSource } from '$lib/enums'; +import type { BuiltinToolUiEntry } from '$lib/types'; + +export const BUILTIN_TOOL_UI: Readonly> = { + [BuiltInTool.EDIT_FILE]: { icon: FilePen, label: 'Edit file', source: ToolSource.BUILTIN }, + [BuiltInTool.EXEC_SHELL_COMMAND]: { + icon: Terminal, + label: 'Run command', + source: ToolSource.BUILTIN + }, + [BuiltInTool.FILE_GLOB_SEARCH]: { + icon: FileSearch, + label: 'Search files', + source: ToolSource.BUILTIN + }, + [BuiltInTool.GET_DATETIME]: { icon: Clock, label: 'Current time', source: ToolSource.BUILTIN }, + [BuiltInTool.GET_INFO]: { icon: Info, label: 'Runtime info', source: ToolSource.BUILTIN }, + [BuiltInTool.GREP_SEARCH]: { + icon: SearchCode, + label: 'Search in files', + source: ToolSource.BUILTIN + }, + [BuiltInTool.READ_FILE]: { icon: FileText, label: 'Read file', source: ToolSource.BUILTIN }, + [BuiltInTool.READ_MEDIA]: { icon: Eye, label: 'Read media', source: ToolSource.FRONTEND }, + [BuiltInTool.RUN_JAVASCRIPT]: { + icon: Braces, + label: 'Run JavaScript', + source: ToolSource.FRONTEND + }, + [BuiltInTool.WRITE_FILE]: { icon: FilePlus, label: 'Write file', source: ToolSource.BUILTIN } +} as const; diff --git a/tools/ui/src/lib/constants/built-in-tools.ts b/tools/ui/src/lib/constants/built-in-tools.ts deleted file mode 100644 index 5bd24ffe8..000000000 --- a/tools/ui/src/lib/constants/built-in-tools.ts +++ /dev/null @@ -1,64 +0,0 @@ -// Registry of built-in and frontend (browser) tools whose renderer -// shows a recognizable icon and friendly label inline in the chat UI. -// -// To add a new built-in tool, add an entry to BUILTIN_TOOL_UI. To give a -// tool a custom title or body renderer, add a dedicated component under -// ChatMessageToolCall/ and route it in ChatMessageToolCallBlock.svelte -// (see ChatMessageToolCallBlockGetDatetime and -// ChatMessageToolCallBlockSearchResults for prior art). - -import { - Braces, - Clock, - Eye, - FilePen, - FilePlus, - FileSearch, - FileText, - Info, - SearchCode, - Terminal -} from '@lucide/svelte'; -import { BuiltInTool, ToolSource } from '$lib/enums'; -import type { Component } from 'svelte'; - -export interface BuiltinToolUiEntry { - icon: Component; - label: string; - source: ToolSource.BUILTIN | ToolSource.FRONTEND; -} - -export const BUILTIN_TOOL_UI: Readonly> = { - [BuiltInTool.EDIT_FILE]: { icon: FilePen, label: 'Edit file', source: ToolSource.BUILTIN }, - [BuiltInTool.EXEC_SHELL_COMMAND]: { - icon: Terminal, - label: 'Run command', - source: ToolSource.BUILTIN - }, - [BuiltInTool.FILE_GLOB_SEARCH]: { - icon: FileSearch, - label: 'Search files', - source: ToolSource.BUILTIN - }, - [BuiltInTool.GET_DATETIME]: { icon: Clock, label: 'Current time', source: ToolSource.BUILTIN }, - [BuiltInTool.GET_INFO]: { icon: Info, label: 'Runtime info', source: ToolSource.BUILTIN }, - [BuiltInTool.GREP_SEARCH]: { - icon: SearchCode, - label: 'Search in files', - source: ToolSource.BUILTIN - }, - [BuiltInTool.READ_FILE]: { icon: FileText, label: 'Read file', source: ToolSource.BUILTIN }, - [BuiltInTool.READ_MEDIA]: { icon: Eye, label: 'Read media', source: ToolSource.FRONTEND }, - [BuiltInTool.RUN_JAVASCRIPT]: { - icon: Braces, - label: 'Run JavaScript', - source: ToolSource.FRONTEND - }, - [BuiltInTool.WRITE_FILE]: { icon: FilePlus, label: 'Write file', source: ToolSource.BUILTIN } -} as const; - -export function getBuiltinToolUi(toolName: string | undefined): BuiltinToolUiEntry | null { - if (!toolName) return null; - - return (BUILTIN_TOOL_UI as Record)[toolName] ?? null; -} diff --git a/tools/ui/src/lib/constants/cache.constants.ts b/tools/ui/src/lib/constants/cache.constants.ts new file mode 100644 index 000000000..b60792d99 --- /dev/null +++ b/tools/ui/src/lib/constants/cache.constants.ts @@ -0,0 +1,44 @@ +/** + * Cache configuration constants + */ + +/** + * Default cache limits when no per-cache overrides are given. + */ +export const CACHE = { + /** Default maximum number of entries in a cache */ + DEFAULT_MAX_ENTRIES: 100, + /** Default TTL (Time-To-Live) for cache entries in milliseconds (5 minutes) */ + DEFAULT_TTL_MS: 5 * 60 * 1000 +} as const; + +/** + * TTL and size for the model props cache. + * Props don't change frequently, so we can cache them longer. + */ +export const MODEL_PROPS_CACHE = { + /** Maximum number of model props to cache */ + MAX_ENTRIES: 50, + /** TTL for model props cache entries in milliseconds (10 minutes) */ + TTL_MS: 10 * 60 * 1000 +} as const; + +/** + * TTL and size for the MCP resource cache. + */ +export const MCP_RESOURCE_CACHE = { + /** Maximum number of MCP resources to cache */ + MAX_ENTRIES: 50, + /** TTL for MCP resource cache entries in milliseconds (5 minutes) */ + TTL_MS: 5 * 60 * 1000 +} as const; + +/** + * Limits for pruning inactive conversation states held in memory. + */ +export const INACTIVE_CONVERSATION = { + /** Maximum age (in ms) for inactive conversation states before cleanup (30 minutes) */ + MAX_AGE_MS: 30 * 60 * 1000, + /** Maximum number of inactive conversation states to keep in memory */ + MAX_STATES: 10 +} as const; diff --git a/tools/ui/src/lib/constants/cache.ts b/tools/ui/src/lib/constants/cache.ts deleted file mode 100644 index 07fe86834..000000000 --- a/tools/ui/src/lib/constants/cache.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Cache configuration constants - */ - -/** - * Default TTL (Time-To-Live) for cache entries in milliseconds - * @default 5 minutes - */ -export const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000; - -/** - * Default maximum number of entries in a cache - * @default 100 - */ -export const DEFAULT_CACHE_MAX_ENTRIES = 100; - -/** - * TTL for model props cache in milliseconds - * Props don't change frequently, so we can cache them longer - * @default 10 minutes - */ -export const MODEL_PROPS_CACHE_TTL_MS = 10 * 60 * 1000; - -/** - * Maximum number of model props to cache - * @default 50 - */ -export const MODEL_PROPS_CACHE_MAX_ENTRIES = 50; - -/** - * Maximum number of MCP resources to cache - * @default 50 - */ -export const MCP_RESOURCE_CACHE_MAX_ENTRIES = 50; - -/** - * TTL for MCP resource cache entries in milliseconds - * @default 5 minutes - */ -export const MCP_RESOURCE_CACHE_TTL_MS = 5 * 60 * 1000; - -/** - * Maximum number of inactive conversation states to keep in memory - * States for conversations beyond this limit will be cleaned up - * @default 10 - */ -export const MAX_INACTIVE_CONVERSATION_STATES = 10; - -/** - * Maximum age (in ms) for inactive conversation states before cleanup - * States older than this will be removed during cleanup - * @default 30 minutes - */ -export const INACTIVE_CONVERSATION_STATE_MAX_AGE_MS = 30 * 60 * 1000; diff --git a/tools/ui/src/lib/constants/chat-commands.ts b/tools/ui/src/lib/constants/chat-commands.ts deleted file mode 100644 index c34c635fd..000000000 --- a/tools/ui/src/lib/constants/chat-commands.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { SET_WORKING_DIRECTORY_LABEL } from '$lib/constants/working-directory'; -import { ChatFormCommandAction } from '$lib/enums'; -import type { ChatFormCommand } from '$lib/types'; - -interface ChatCommandsOptions { - /** Gates `/model`. */ - showModelSelector: boolean; - /** Gates `/prompt`. */ - hasPrompts: () => boolean; - /** Gates `/cwd`. */ - hasCwdTools: () => boolean; -} - -/** - * The slash commands surfaced by the `/` command picker, in display order. - * - * Availability is supplied as predicates rather than store imports: this - * module is re-exported through the `$lib/constants` barrel, and importing - * stores at module load would create a circular dependency (the stores - * themselves import from `$lib/constants`). - */ -export function getChatCommands(options: ChatCommandsOptions): ChatFormCommand[] { - return [ - { - action: ChatFormCommandAction.PROMPT, - description: 'Insert an MCP prompt', - disabled: !options.hasPrompts(), - name: 'prompt' - }, - { - action: ChatFormCommandAction.CWD, - description: SET_WORKING_DIRECTORY_LABEL, - disabled: !options.hasCwdTools(), - keywords: ['current working directory'], - name: 'cwd' - }, - { - action: ChatFormCommandAction.MODEL, - description: 'Select model', - disabled: !options.showModelSelector, - name: 'model' - } - ]; -} diff --git a/tools/ui/src/lib/constants/chat-form.constants.ts b/tools/ui/src/lib/constants/chat-form.constants.ts new file mode 100644 index 000000000..6c413ed70 --- /dev/null +++ b/tools/ui/src/lib/constants/chat-form.constants.ts @@ -0,0 +1,5 @@ +export const INITIAL_FILE_SIZE = 0; +export const PROMPT_CONTENT_SEPARATOR = '\n\n'; +export const CLIPBOARD_CONTENT_QUOTE_PREFIX = '"'; +export const PROMPT_TRIGGER_PREFIX = '/'; +export const NEW_CHAT_DRAFT_KEY = '__new_chat__'; diff --git a/tools/ui/src/lib/constants/chat-form.ts b/tools/ui/src/lib/constants/chat-form.ts deleted file mode 100644 index 6c413ed70..000000000 --- a/tools/ui/src/lib/constants/chat-form.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const INITIAL_FILE_SIZE = 0; -export const PROMPT_CONTENT_SEPARATOR = '\n\n'; -export const CLIPBOARD_CONTENT_QUOTE_PREFIX = '"'; -export const PROMPT_TRIGGER_PREFIX = '/'; -export const NEW_CHAT_DRAFT_KEY = '__new_chat__'; diff --git a/tools/ui/src/lib/constants/cli-flags.constants.ts b/tools/ui/src/lib/constants/cli-flags.constants.ts new file mode 100644 index 000000000..4fbee8a36 --- /dev/null +++ b/tools/ui/src/lib/constants/cli-flags.constants.ts @@ -0,0 +1,6 @@ +export const CLI_FLAGS = { + API_KEY: '--api-key', + MCP_PROXY: '--ui-mcp-proxy', + SLOTS: '--slots', + TOOLS: '--tools' +} as const; diff --git a/tools/ui/src/lib/constants/cli-flags.ts b/tools/ui/src/lib/constants/cli-flags.ts deleted file mode 100644 index 4fbee8a36..000000000 --- a/tools/ui/src/lib/constants/cli-flags.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const CLI_FLAGS = { - API_KEY: '--api-key', - MCP_PROXY: '--ui-mcp-proxy', - SLOTS: '--slots', - TOOLS: '--tools' -} as const; diff --git a/tools/ui/src/lib/constants/code-block.constants.ts b/tools/ui/src/lib/constants/code-block.constants.ts new file mode 100644 index 000000000..05db575f9 --- /dev/null +++ b/tools/ui/src/lib/constants/code-block.constants.ts @@ -0,0 +1,50 @@ +// Constants for the markdown code-block renderer: language/fence handling and CSS classes. + +/** Parsing and escaping helpers for the markdown code-block renderer. */ +export const CODE_BLOCK = { + AMPERSAND_REGEX: /&/g, + /** Language fallback used when no language is specified. */ + DEFAULT_LANGUAGE: 'text', + /** Matches opening/closing markdown code fences. */ + FENCE_PATTERN: /^```|\n```/g, + GT_REGEX: />/g, + /** Matches the language specifier at the start of a code fence. */ + LANG_PATTERN: /^(\w*)\n?/, + LT_REGEX: //g; -export const FENCE_PATTERN = /^```|\n```/g; - -// Whitespace-only empty lines (between start of string and first non-empty line). -// Used by trimCodePadding to drop leading/trailing phantom blank rows from LLM -// payload wrappers without touching internal blank lines. -export const TRIM_LEADING_PADDING_REGEX = /^(?:[ \t]*\n)+/; -export const TRIM_TRAILING_PADDING_REGEX = /(?:\n[ \t]*)+$/; - -// Matches either Unix or Windows path separators so `String.split(REGEX)` can -// recover the trailing file-name segment from either `/foo/bar.txt` or -// `C:\foo\bar.txt`. Used wherever a parameter accepts a user-supplied path. -export const FILE_PATH_SEPARATOR_REGEX = /[\\/]/; - -// Separates a file name from its extension, e.g. the '.' in `cover.png`. -export const FILE_EXTENSION_SEPARATOR = '.'; - -// Matches the `text:` prefix that file-type identifiers use to denote a -// plain-text language (e.g. `text:typescript`). Used by tool-call renderers -// to recover the underlying highlight.js language. -export const TEXT_LANGUAGE_PREFIX_REGEX = /^text:/; diff --git a/tools/ui/src/lib/constants/content-detection.constants.ts b/tools/ui/src/lib/constants/content-detection.constants.ts new file mode 100644 index 000000000..c5c05819a --- /dev/null +++ b/tools/ui/src/lib/constants/content-detection.constants.ts @@ -0,0 +1,20 @@ +/** + * String patterns for detecting content kind from MIME types and URIs. + * Used with startsWith/includes checks, not as discriminated values. + */ + +export const MIME_TYPE_PREFIXES = { + IMAGE: 'image/', + TEXT: 'text' +} as const; + +export const MIME_TYPE_SUBSTRINGS = { + JAVASCRIPT: 'javascript', + JSON: 'json', + TYPESCRIPT: 'typescript' +} as const; + +export const URI_PATTERNS = { + DATABASE_KEYWORD: 'database', + DATABASE_SCHEME: 'db://' +} as const; diff --git a/tools/ui/src/lib/constants/context-gauge-popup.constants.ts b/tools/ui/src/lib/constants/context-gauge-popup.constants.ts new file mode 100644 index 000000000..fe2e6a1b5 --- /dev/null +++ b/tools/ui/src/lib/constants/context-gauge-popup.constants.ts @@ -0,0 +1,8 @@ +// Half of the card width, matching the w-64 class on the card. +export const CONTEXT_GAUGE_CARD_HALF_WIDTH_PX = 128; +// Minimum distance kept between the card and the form edges. +export const CONTEXT_GAUGE_EDGE_MARGIN_PX = 8; +// Gap between the top of the dial and the bottom edge of the card. +export const CONTEXT_GAUGE_DIAL_GAP_PX = 8; +// Grace delay before closing, letting the pointer travel from dial to card. +export const CONTEXT_GAUGE_CLOSE_GRACE_MS = 150; diff --git a/tools/ui/src/lib/constants/context-gauge-popup.ts b/tools/ui/src/lib/constants/context-gauge-popup.ts deleted file mode 100644 index fe2e6a1b5..000000000 --- a/tools/ui/src/lib/constants/context-gauge-popup.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Half of the card width, matching the w-64 class on the card. -export const CONTEXT_GAUGE_CARD_HALF_WIDTH_PX = 128; -// Minimum distance kept between the card and the form edges. -export const CONTEXT_GAUGE_EDGE_MARGIN_PX = 8; -// Gap between the top of the dial and the bottom edge of the card. -export const CONTEXT_GAUGE_DIAL_GAP_PX = 8; -// Grace delay before closing, letting the pointer travel from dial to card. -export const CONTEXT_GAUGE_CLOSE_GRACE_MS = 150; diff --git a/tools/ui/src/lib/constants/context-keys.constants.ts b/tools/ui/src/lib/constants/context-keys.constants.ts new file mode 100644 index 000000000..0bd733b37 --- /dev/null +++ b/tools/ui/src/lib/constants/context-keys.constants.ts @@ -0,0 +1,3 @@ +export const CONTEXT_KEY_MESSAGE_EDIT = 'chat-message-edit'; +export const CONTEXT_KEY_CHAT_ACTIONS = 'chat-actions'; +export const CONTEXT_KEY_CHAT_SETTINGS_CONFIG = 'chat-settings-config'; diff --git a/tools/ui/src/lib/constants/context-keys.ts b/tools/ui/src/lib/constants/context-keys.ts deleted file mode 100644 index 0bd733b37..000000000 --- a/tools/ui/src/lib/constants/context-keys.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const CONTEXT_KEY_MESSAGE_EDIT = 'chat-message-edit'; -export const CONTEXT_KEY_CHAT_ACTIONS = 'chat-actions'; -export const CONTEXT_KEY_CHAT_SETTINGS_CONFIG = 'chat-settings-config'; diff --git a/tools/ui/src/lib/constants/control-actions.constants.ts b/tools/ui/src/lib/constants/control-actions.constants.ts new file mode 100644 index 000000000..c8ebf701b --- /dev/null +++ b/tools/ui/src/lib/constants/control-actions.constants.ts @@ -0,0 +1,5 @@ +// actions accepted by the realtime inference control endpoint (API_CHAT.CONTROL) +// kept separate from the endpoint paths since these are protocol level verbs +export const CONTROL_ACTION = { + END_REASONING: 'reasoning_end' +} as const; diff --git a/tools/ui/src/lib/constants/control-actions.ts b/tools/ui/src/lib/constants/control-actions.ts deleted file mode 100644 index 935ae9542..000000000 --- a/tools/ui/src/lib/constants/control-actions.ts +++ /dev/null @@ -1,7 +0,0 @@ -// actions accepted by the realtime inference control endpoint (API_CHAT.CONTROL) -// kept separate from the endpoint paths since these are protocol level verbs -export const CONTROL_ACTION = { - END_REASONING: 'reasoning_end' -} as const; - -export type ControlAction = (typeof CONTROL_ACTION)[keyof typeof CONTROL_ACTION]; diff --git a/tools/ui/src/lib/constants/conversation-import.constants.ts b/tools/ui/src/lib/constants/conversation-import.constants.ts new file mode 100644 index 000000000..ed500440a --- /dev/null +++ b/tools/ui/src/lib/constants/conversation-import.constants.ts @@ -0,0 +1,3 @@ +// First bytes of every ZIP local file header ("PK"). Import detects an archive +// from these bytes rather than from the filename, which the OS may not preserve. +export const ZIP_MAGIC = [0x50, 0x4b]; diff --git a/tools/ui/src/lib/constants/conversation-import.ts b/tools/ui/src/lib/constants/conversation-import.ts deleted file mode 100644 index ed500440a..000000000 --- a/tools/ui/src/lib/constants/conversation-import.ts +++ /dev/null @@ -1,3 +0,0 @@ -// First bytes of every ZIP local file header ("PK"). Import detects an archive -// from these bytes rather than from the filename, which the OS may not preserve. -export const ZIP_MAGIC = [0x50, 0x4b]; diff --git a/tools/ui/src/lib/constants/css-classes.constants.ts b/tools/ui/src/lib/constants/css-classes.constants.ts new file mode 100644 index 000000000..4e3310544 --- /dev/null +++ b/tools/ui/src/lib/constants/css-classes.constants.ts @@ -0,0 +1,30 @@ +export const BOX_BORDER = + 'border border-border/30 focus-within:border-border dark:border-border/20 dark:focus-within:border-border'; + +export const INPUT_CLASSES = ` + bg-muted/60 dark:bg-muted/75 + ${BOX_BORDER} + shadow-sm + outline-none + text-foreground +`; + +export const PANEL_CLASSES = ` + bg-background + border border-border/30 dark:border-border/20 + shadow-sm backdrop-blur-lg! + rounded-t-lg! +`; + +export const CHAT_FORM_POPOVER_MAX_HEIGHT = 'max-h-80'; +export const DIALOG_SUBMENU_CONTENT = 'w-60'; + +/** Selects the focused chat-form input (either renderer) to restore focus after model actions. */ +export const CHAT_INPUT_FOCUS_SELECTOR = + '[data-slot="input-area"] textarea, [data-slot="input-area"] [contenteditable="true"]'; + +/** Default Tailwind size class for inline icon components (lucide, etc.). */ +export const ICON_CLASS_DEFAULT = 'h-4 w-4'; + +/** Icon size + spinning animation; used for live-streaming tool indicators. */ +export const ICON_CLASS_SPIN = 'h-4 w-4 animate-spin'; diff --git a/tools/ui/src/lib/constants/css-classes.ts b/tools/ui/src/lib/constants/css-classes.ts deleted file mode 100644 index 4e3310544..000000000 --- a/tools/ui/src/lib/constants/css-classes.ts +++ /dev/null @@ -1,30 +0,0 @@ -export const BOX_BORDER = - 'border border-border/30 focus-within:border-border dark:border-border/20 dark:focus-within:border-border'; - -export const INPUT_CLASSES = ` - bg-muted/60 dark:bg-muted/75 - ${BOX_BORDER} - shadow-sm - outline-none - text-foreground -`; - -export const PANEL_CLASSES = ` - bg-background - border border-border/30 dark:border-border/20 - shadow-sm backdrop-blur-lg! - rounded-t-lg! -`; - -export const CHAT_FORM_POPOVER_MAX_HEIGHT = 'max-h-80'; -export const DIALOG_SUBMENU_CONTENT = 'w-60'; - -/** Selects the focused chat-form input (either renderer) to restore focus after model actions. */ -export const CHAT_INPUT_FOCUS_SELECTOR = - '[data-slot="input-area"] textarea, [data-slot="input-area"] [contenteditable="true"]'; - -/** Default Tailwind size class for inline icon components (lucide, etc.). */ -export const ICON_CLASS_DEFAULT = 'h-4 w-4'; - -/** Icon size + spinning animation; used for live-streaming tool indicators. */ -export const ICON_CLASS_SPIN = 'h-4 w-4 animate-spin'; diff --git a/tools/ui/src/lib/constants/database.constants.ts b/tools/ui/src/lib/constants/database.constants.ts new file mode 100644 index 000000000..f2c961039 --- /dev/null +++ b/tools/ui/src/lib/constants/database.constants.ts @@ -0,0 +1,29 @@ +/** + * Database-related constants (IndexedDB, Dexie). + * + * Centralized to ensure consistency across the app and simplify future + * naming changes. + */ + +import { STORAGE_APP_NAME } from './storage.constants'; + +/** IndexedDB database name */ +export const DB_NAME = STORAGE_APP_NAME; + +/** IndexedDB store / table names */ +export const IDXDB_TABLES = { + conversations: 'conversations', + messages: 'messages' +} as const; + +/** IndexedDB store schemas */ +export const IDXDB_STORE_SCHEMAS = { + conversations: 'id, lastModified, currNode, name', + messages: 'id, convId, type, role, timestamp, parent, children' +} as const; + +/** Combined Dexie stores definition — keys are table names, values are schemas */ +export const IDXDB_STORES = { + [IDXDB_TABLES.conversations]: IDXDB_STORE_SCHEMAS.conversations, + [IDXDB_TABLES.messages]: IDXDB_STORE_SCHEMAS.messages +} as const; diff --git a/tools/ui/src/lib/constants/database.ts b/tools/ui/src/lib/constants/database.ts deleted file mode 100644 index 95e698f40..000000000 --- a/tools/ui/src/lib/constants/database.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Database-related constants (IndexedDB, Dexie). - * - * Centralized to ensure consistency across the app and simplify future - * naming changes. - */ - -import { STORAGE_APP_NAME } from './storage'; - -/** IndexedDB database name */ -export const DB_NAME = STORAGE_APP_NAME; - -/** IndexedDB store / table names */ -export const IDXDB_TABLES = { - conversations: 'conversations', - messages: 'messages' -} as const; - -/** IndexedDB store schemas */ -export const IDXDB_STORE_SCHEMAS = { - conversations: 'id, lastModified, currNode, name', - messages: 'id, convId, type, role, timestamp, parent, children' -} as const; - -/** Combined Dexie stores definition — keys are table names, values are schemas */ -export const IDXDB_STORES = { - [IDXDB_TABLES.conversations]: IDXDB_STORE_SCHEMAS.conversations, - [IDXDB_TABLES.messages]: IDXDB_STORE_SCHEMAS.messages -} as const; diff --git a/tools/ui/src/lib/constants/diagram-blocks.constants.ts b/tools/ui/src/lib/constants/diagram-blocks.constants.ts new file mode 100644 index 000000000..caeb6b5b3 --- /dev/null +++ b/tools/ui/src/lib/constants/diagram-blocks.constants.ts @@ -0,0 +1,9 @@ +// Shared constants for diagram blocks (mermaid and svg) that toggle between a +// rendered view and a source view. The wrapper carries the active mode, css +// drives the visibility, the click handler only flips the attribute. + +export const DIAGRAM_VIEW_MODE_ATTR = 'data-view-mode'; +export const DIAGRAM_VIEW_RENDERED = 'rendered'; +export const DIAGRAM_VIEW_SOURCE = 'source'; +export const DIAGRAM_SOURCE_CLASS = 'diagram-source'; +export const TOGGLE_SOURCE_BTN_CLASS = 'toggle-source-btn'; diff --git a/tools/ui/src/lib/constants/diagram-blocks.ts b/tools/ui/src/lib/constants/diagram-blocks.ts deleted file mode 100644 index caeb6b5b3..000000000 --- a/tools/ui/src/lib/constants/diagram-blocks.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Shared constants for diagram blocks (mermaid and svg) that toggle between a -// rendered view and a source view. The wrapper carries the active mode, css -// drives the visibility, the click handler only flips the attribute. - -export const DIAGRAM_VIEW_MODE_ATTR = 'data-view-mode'; -export const DIAGRAM_VIEW_RENDERED = 'rendered'; -export const DIAGRAM_VIEW_SOURCE = 'source'; -export const DIAGRAM_SOURCE_CLASS = 'diagram-source'; -export const TOGGLE_SOURCE_BTN_CLASS = 'toggle-source-btn'; diff --git a/tools/ui/src/lib/constants/error.constants.ts b/tools/ui/src/lib/constants/error.constants.ts new file mode 100644 index 000000000..17527fc1e --- /dev/null +++ b/tools/ui/src/lib/constants/error.constants.ts @@ -0,0 +1,23 @@ +export const ERROR_MESSAGES = { + HTTP: { + ACCESS_DENIED: 'Access denied', + GENERIC: 'Request failed', + INTERNAL_ERROR: 'Server error - check server logs', + NOT_FOUND: 'Not found', + TEMPORARILY_UNAVAILABLE: 'Server temporarily unavailable' + }, + NETWORK: { + GENERIC: 'Failed to connect to server', + NXDOMAIN: 'Server not found - check server address', + REFUSED: 'Connection refused - server may be offline', + TIMEOUT: 'Request timed out', + UNREACHABLE: 'Server is not running or unreachable' + } +}; + +export const HTTP_CODE_TO_STRING: Record = { + 401: ERROR_MESSAGES.HTTP.ACCESS_DENIED, + 403: ERROR_MESSAGES.HTTP.ACCESS_DENIED, + 500: ERROR_MESSAGES.HTTP.INTERNAL_ERROR, + 503: ERROR_MESSAGES.HTTP.TEMPORARILY_UNAVAILABLE +}; diff --git a/tools/ui/src/lib/constants/error.ts b/tools/ui/src/lib/constants/error.ts deleted file mode 100644 index 17527fc1e..000000000 --- a/tools/ui/src/lib/constants/error.ts +++ /dev/null @@ -1,23 +0,0 @@ -export const ERROR_MESSAGES = { - HTTP: { - ACCESS_DENIED: 'Access denied', - GENERIC: 'Request failed', - INTERNAL_ERROR: 'Server error - check server logs', - NOT_FOUND: 'Not found', - TEMPORARILY_UNAVAILABLE: 'Server temporarily unavailable' - }, - NETWORK: { - GENERIC: 'Failed to connect to server', - NXDOMAIN: 'Server not found - check server address', - REFUSED: 'Connection refused - server may be offline', - TIMEOUT: 'Request timed out', - UNREACHABLE: 'Server is not running or unreachable' - } -}; - -export const HTTP_CODE_TO_STRING: Record = { - 401: ERROR_MESSAGES.HTTP.ACCESS_DENIED, - 403: ERROR_MESSAGES.HTTP.ACCESS_DENIED, - 500: ERROR_MESSAGES.HTTP.INTERNAL_ERROR, - 503: ERROR_MESSAGES.HTTP.TEMPORARILY_UNAVAILABLE -}; diff --git a/tools/ui/src/lib/constants/floating-ui-constraints.ts b/tools/ui/src/lib/constants/floating-ui-constraints.ts deleted file mode 100644 index 003fc77ac..000000000 --- a/tools/ui/src/lib/constants/floating-ui-constraints.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const VIEWPORT_GUTTER = 8; -export const MENU_OFFSET = 6; diff --git a/tools/ui/src/lib/constants/formatters.constants.ts b/tools/ui/src/lib/constants/formatters.constants.ts new file mode 100644 index 000000000..d6d1b883f --- /dev/null +++ b/tools/ui/src/lib/constants/formatters.constants.ts @@ -0,0 +1,8 @@ +export const MS_PER_SECOND = 1000; +export const SECONDS_PER_MINUTE = 60; +export const SECONDS_PER_HOUR = 3600; +export const SHORT_DURATION_THRESHOLD = 1; +export const MEDIUM_DURATION_THRESHOLD = 10; + +/** Default display value when no performance time is available */ +export const DEFAULT_PERFORMANCE_TIME = '0s'; diff --git a/tools/ui/src/lib/constants/formatters.ts b/tools/ui/src/lib/constants/formatters.ts deleted file mode 100644 index d6d1b883f..000000000 --- a/tools/ui/src/lib/constants/formatters.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const MS_PER_SECOND = 1000; -export const SECONDS_PER_MINUTE = 60; -export const SECONDS_PER_HOUR = 3600; -export const SHORT_DURATION_THRESHOLD = 1; -export const MEDIUM_DURATION_THRESHOLD = 10; - -/** Default display value when no performance time is available */ -export const DEFAULT_PERFORMANCE_TIME = '0s'; diff --git a/tools/ui/src/lib/constants/headers.constants.ts b/tools/ui/src/lib/constants/headers.constants.ts new file mode 100644 index 000000000..f40df0571 --- /dev/null +++ b/tools/ui/src/lib/constants/headers.constants.ts @@ -0,0 +1,35 @@ +/** HTTP header handling for API and MCP requests. */ +export const HEADERS = { + /** Canonical casing for the Authorization header (RFC 7235) */ + AUTHORIZATION: 'Authorization', + /** Bearer scheme prefix used for Authorization headers (RFC 6750) */ + BEARER: 'Bearer ', + /** Content-Type HTTP header name */ + CONTENT_TYPE: 'Content-Type', + /** Partial-redaction rules for MCP headers: header name -> visible trailing chars */ + PARTIAL_REDACT: new Map([['mcp-session-id', 5]]), + + /** Header names whose values should be redacted in diagnostic logs */ + REDACTED: new Set([ + 'authorization', + 'api-key', + 'cookie', + 'mcp-session-id', + 'proxy-authorization', + 'set-cookie', + 'x-auth-token', + 'x-api-key' + ]), + + /** Header carrying the stream-session identity (conversation id, optionally with a model suffix) */ + X_CONVERSATION_ID_HEADER: 'X-Conversation-Id', + + /** Header asking the server to encode a tool's output differently, e.g. read_file returning base64. */ + X_RESP_TYPE_HEADER: 'x-resp-type', + + /** Header carrying the working directory a tool call runs in; the model cannot override it */ + X_TOOL_CWD_HEADER: 'x-tool-cwd' +}; + +/** `X_RESP_TYPE_HEADER` value that makes read_file return raw bytes as base64 instead of text. */ +export const RESP_TYPE_BASE64 = 'base64'; diff --git a/tools/ui/src/lib/constants/icons.constants.ts b/tools/ui/src/lib/constants/icons.constants.ts new file mode 100644 index 000000000..556374050 --- /dev/null +++ b/tools/ui/src/lib/constants/icons.constants.ts @@ -0,0 +1,43 @@ +/** + * Icon mappings for file types and model modalities + * Centralized configuration to ensure consistent icon usage across the app + */ + +import { + Eye as VisionIcon, + File as FileIcon, + FileText as FileTextIcon, + Image as ImageIcon, + Mic as AudioIcon, + Video as VideoIcon +} from '@lucide/svelte'; +import { FileTypeCategory, ModelModality } from '$lib/enums'; + +export const FILE_TYPE_ICONS = { + [FileTypeCategory.AUDIO]: AudioIcon, + [FileTypeCategory.IMAGE]: ImageIcon, + [FileTypeCategory.PDF]: FileIcon, + [FileTypeCategory.TEXT]: FileTextIcon, + [FileTypeCategory.VIDEO]: VideoIcon +} as const; + +export const DEFAULT_FILE_ICON = FileIcon; + +export const MODALITY_ICONS = { + [ModelModality.AUDIO]: AudioIcon, + [ModelModality.VIDEO]: VideoIcon, + [ModelModality.VISION]: VisionIcon +} as const; + +export const MODALITY_LABELS = { + [ModelModality.AUDIO]: 'Audio', + [ModelModality.VIDEO]: 'Video', + [ModelModality.VISION]: 'Vision' +} as const; + +// Shared SVG icon strings for copy and preview buttons +export const COPY_ICON_SVG = ``; + +export const PREVIEW_ICON_SVG = ``; + +export const CODE_ICON_SVG = ``; diff --git a/tools/ui/src/lib/constants/icons.ts b/tools/ui/src/lib/constants/icons.ts deleted file mode 100644 index 556374050..000000000 --- a/tools/ui/src/lib/constants/icons.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Icon mappings for file types and model modalities - * Centralized configuration to ensure consistent icon usage across the app - */ - -import { - Eye as VisionIcon, - File as FileIcon, - FileText as FileTextIcon, - Image as ImageIcon, - Mic as AudioIcon, - Video as VideoIcon -} from '@lucide/svelte'; -import { FileTypeCategory, ModelModality } from '$lib/enums'; - -export const FILE_TYPE_ICONS = { - [FileTypeCategory.AUDIO]: AudioIcon, - [FileTypeCategory.IMAGE]: ImageIcon, - [FileTypeCategory.PDF]: FileIcon, - [FileTypeCategory.TEXT]: FileTextIcon, - [FileTypeCategory.VIDEO]: VideoIcon -} as const; - -export const DEFAULT_FILE_ICON = FileIcon; - -export const MODALITY_ICONS = { - [ModelModality.AUDIO]: AudioIcon, - [ModelModality.VIDEO]: VideoIcon, - [ModelModality.VISION]: VisionIcon -} as const; - -export const MODALITY_LABELS = { - [ModelModality.AUDIO]: 'Audio', - [ModelModality.VIDEO]: 'Video', - [ModelModality.VISION]: 'Vision' -} as const; - -// Shared SVG icon strings for copy and preview buttons -export const COPY_ICON_SVG = ``; - -export const PREVIEW_ICON_SVG = ``; - -export const CODE_ICON_SVG = ``; diff --git a/tools/ui/src/lib/constants/image-size.ts b/tools/ui/src/lib/constants/image-size.ts deleted file mode 100644 index 8a7f921fa..000000000 --- a/tools/ui/src/lib/constants/image-size.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const MEGAPIXELS_TO_PIXELS = 1_000_000; - -export const HEIC_JPEG_QUALITY = 0.85; diff --git a/tools/ui/src/lib/constants/image.constants.ts b/tools/ui/src/lib/constants/image.constants.ts new file mode 100644 index 000000000..53a90eaa4 --- /dev/null +++ b/tools/ui/src/lib/constants/image.constants.ts @@ -0,0 +1,32 @@ +/** Image handling constants */ + +export const IMAGE = { + /** JPEG quality used when transcoding HEIC images. */ + HEIC_JPEG_QUALITY: 0.85, + /** Unit conversion: pixels per megapixel. */ + MEGAPIXELS_TO_PIXELS: 1_000_000 +} as const; + +/** + * JPEG and EXIF binary format constants for orientation parsing. + */ +export const EXIF = { + /** APP1 segment marker byte, carries the EXIF payload */ + APP1_MARKER: 0xe1, + /** "Exif" signature opening the APP1 payload, big endian uint32 */ + EXIF_SIGNATURE: 0x45786966, + /** Size in bytes of one IFD directory entry */ + IFD_ENTRY_SIZE: 12, + /** JPEG start of image marker */ + JPEG_SOI_MARKER: 0xffd8, + /** EXIF tag id holding the orientation value */ + ORIENTATION_TAG: 0x0112, + /** Bytes of file prefix to scan, the APP1 EXIF segment sits near the start */ + SCAN_BYTE_LIMIT: 128 * 1024, + /** Start of scan marker byte, compressed data begins and no EXIF follows */ + SOS_MARKER: 0xda, + /** TIFF byte order mark for little endian ("II") */ + TIFF_LITTLE_ENDIAN: 0x4949, + /** TIFF magic number following the byte order mark */ + TIFF_MAGIC: 42 +} as const; diff --git a/tools/ui/src/lib/constants/index.ts b/tools/ui/src/lib/constants/index.ts index 357a33a62..17e5f4d00 100644 --- a/tools/ui/src/lib/constants/index.ts +++ b/tools/ui/src/lib/constants/index.ts @@ -1,67 +1,61 @@ // Central constants export file // All constants should be imported from '$lib/constants' -export * from './agentic'; -export * from './api-endpoints'; -export * from './app'; -export * from './attachment-labels'; -export * from './database'; -export * from './reasoning-effort'; -export * from './reasoning-effort-tokens'; -export * from './recommended-mcp-servers'; -export * from './storage'; -export * from './attachment-menu'; -export * from './auto-scroll'; -export * from './context-gauge-popup'; -export * from './conversation-import'; -export * from './binary-detection'; -export * from './built-in-tools'; -export * from './cache'; -export * from './chat-form'; -export * from './chat-commands'; -export * from './cli-flags'; -export * from './code-blocks'; -export * from './icons'; -export * from './code'; -export * from './context-keys'; -export * from './control-actions'; -export * from './css-classes'; -export * from './floating-ui-constraints'; -export * from './formatters'; -export * from './key-value-pairs'; -export * from './icons'; -export * from './latex-protection'; -export * from './literal-html'; -export * from './markdown'; -export * from './mermaid-blocks'; -export * from './svg-blocks'; -export * from './diagram-blocks'; -export * from './max-bundle-size'; -export * from './mcp'; -export * from './mcp-form'; -export * from './mcp-resource'; -export * from './mention-badge'; -export * from './message-export'; -export * from './path-display'; -export * from './model-id'; -export * from './model-loading'; -export * from './sse'; -export * from './precision'; -export * from './processing-info'; -export * from './pwa'; +export * from './agentic.constants'; +export * from './api-endpoints.constants'; +export * from './app.constants'; +export * from './database.constants'; +export * from './reasoning-effort.constants'; +export * from './recommended-mcp-servers.constants'; +export * from './storage.constants'; +export * from './icons.constants'; +export * from './attachment-menu.constants'; +export * from './auto-scroll.constants'; +export * from './context-gauge-popup.constants'; +export * from './conversation-import.constants'; +export * from './binary-detection.constants'; +export * from './content-detection.constants'; +export * from './built-in-tools.constants'; +export * from './cache.constants'; +export * from './chat-form.constants'; +export * from './cli-flags.constants'; +export * from './code-block.constants'; +export * from './context-keys.constants'; +export * from './control-actions.constants'; +export * from './css-classes.constants'; +export * from './formatters.constants'; +export * from './headers.constants'; +export * from './key-value-pairs.constants'; +export * from './latex-protection.constants'; +export * from './literal-html.constants'; +export * from './markdown.constants'; +export * from './mermaid-blocks.constants'; +export * from './svg-blocks.constants'; +export * from './diagram-blocks.constants'; +export * from './max-bundle-size.constants'; +export * from './error.constants'; +export * from './image.constants'; +export * from './mcp.constants'; +export * from './mcp-form.constants'; +export * from './mcp-resource.constants'; +export * from './mention-badge.constants'; +export * from './message-export.constants'; +export * from './path-display.constants'; +export * from './model-id.constants'; +export * from './model-loading.constants'; +export * from './precision.constants'; +export * from './pwa.constants'; +export * from './routes.constants'; +export * from './sandbox.constants'; +export * from './settings-keys.constants'; +export * from './settings-registry.constants'; +export * from './special-characters.constants'; +export * from './stream.constants'; +export * from './supported-file-types.constants'; +export * from './table-html-restorer.constants'; +export * from './title-generation.constants'; +export * from './ui.constants'; +export * from './uri-template.constants'; +export * from './url.constants'; +export * from './working-directory.constants'; export * from './read-media'; -export * from './routes'; -export * from './sandbox'; -export * from './settings-keys'; -export * from './settings-registry'; -export * from './stream'; -export * from './supported-file-types'; -export * from './table-html-restorer'; -export * from './title-generation'; -export * from './tools'; -export * from './tooltip-config'; -export * from './ui'; -export * from './uri-template'; -export * from './url'; -export * from './viewport'; -export * from './working-directory'; diff --git a/tools/ui/src/lib/constants/jpeg-exif.ts b/tools/ui/src/lib/constants/jpeg-exif.ts deleted file mode 100644 index 5b2591b04..000000000 --- a/tools/ui/src/lib/constants/jpeg-exif.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * JPEG and EXIF binary format constants for orientation parsing. - */ - -/** Bytes of file prefix to scan, the APP1 EXIF segment sits near the start */ -export const EXIF_SCAN_BYTE_LIMIT = 128 * 1024; - -/** JPEG start of image marker */ -export const JPEG_SOI_MARKER = 0xffd8; - -/** APP1 segment marker byte, carries the EXIF payload */ -export const APP1_MARKER = 0xe1; - -/** Start of scan marker byte, compressed data begins and no EXIF follows */ -export const SOS_MARKER = 0xda; - -/** "Exif" signature opening the APP1 payload, big endian uint32 */ -export const EXIF_SIGNATURE = 0x45786966; - -/** TIFF byte order mark for little endian ("II") */ -export const TIFF_LITTLE_ENDIAN = 0x4949; - -/** TIFF magic number following the byte order mark */ -export const TIFF_MAGIC = 42; - -/** EXIF tag id holding the orientation value */ -export const EXIF_ORIENTATION_TAG = 0x0112; - -/** Size in bytes of one IFD directory entry */ -export const IFD_ENTRY_SIZE = 12; diff --git a/tools/ui/src/lib/constants/key-value-pairs.constants.ts b/tools/ui/src/lib/constants/key-value-pairs.constants.ts new file mode 100644 index 000000000..48dadbec4 --- /dev/null +++ b/tools/ui/src/lib/constants/key-value-pairs.constants.ts @@ -0,0 +1,20 @@ +/** + * Key-value pair form constraints and sanitization patterns. + * + * Both regexes target characters dangerous in HTTP-header / env-var contexts: + * \x00 – null byte (injection) + * \x0A (\n) – LF (HTTP header injection / response splitting) + * \x0D (\r) – CR (HTTP header injection / response splitting) + * \x01–\x08, \x0B–\x0C, \x0E–\x1F, \x7F – other C0/DEL control chars + * + * KEY_UNSAFE_RE additionally strips TAB (\x09); values keep TAB because it is + * a valid header-value continuation character per RFC 7230. + */ + +export const KEY_VALUE_PAIR_KEY_MAX_LENGTH = 256; +export const KEY_VALUE_PAIR_VALUE_MAX_LENGTH = 8192; + +// eslint-disable-next-line no-control-regex +export const KEY_VALUE_PAIR_UNSAFE_KEY_RE = /[\x00-\x1F\x7F]/g; +// eslint-disable-next-line no-control-regex +export const KEY_VALUE_PAIR_UNSAFE_VALUE_RE = /[\x00-\x08\x0A-\x0D\x0E-\x1F\x7F]/g; diff --git a/tools/ui/src/lib/constants/key-value-pairs.ts b/tools/ui/src/lib/constants/key-value-pairs.ts deleted file mode 100644 index 48dadbec4..000000000 --- a/tools/ui/src/lib/constants/key-value-pairs.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Key-value pair form constraints and sanitization patterns. - * - * Both regexes target characters dangerous in HTTP-header / env-var contexts: - * \x00 – null byte (injection) - * \x0A (\n) – LF (HTTP header injection / response splitting) - * \x0D (\r) – CR (HTTP header injection / response splitting) - * \x01–\x08, \x0B–\x0C, \x0E–\x1F, \x7F – other C0/DEL control chars - * - * KEY_UNSAFE_RE additionally strips TAB (\x09); values keep TAB because it is - * a valid header-value continuation character per RFC 7230. - */ - -export const KEY_VALUE_PAIR_KEY_MAX_LENGTH = 256; -export const KEY_VALUE_PAIR_VALUE_MAX_LENGTH = 8192; - -// eslint-disable-next-line no-control-regex -export const KEY_VALUE_PAIR_UNSAFE_KEY_RE = /[\x00-\x1F\x7F]/g; -// eslint-disable-next-line no-control-regex -export const KEY_VALUE_PAIR_UNSAFE_VALUE_RE = /[\x00-\x08\x0A-\x0D\x0E-\x1F\x7F]/g; diff --git a/tools/ui/src/lib/constants/latex-protection.constants.ts b/tools/ui/src/lib/constants/latex-protection.constants.ts new file mode 100644 index 000000000..c42aec41a --- /dev/null +++ b/tools/ui/src/lib/constants/latex-protection.constants.ts @@ -0,0 +1,125 @@ +/** + * Matches common Markdown code blocks to exclude them from further processing (e.g. LaTeX). + * - Fenced: ```...``` + * - Inline: `...` (does NOT support nested backticks or multi-backtick syntax) + * + * Note: This pattern does not handle advanced cases like: + * `` `code with `backticks` `` or \\``...\\`` + */ +export const CODE_BLOCK_REGEXP = /(```[\s\S]*?```|`[^`\n]+`)/g; + +/** + * Matches LaTeX math delimiters \(...\) and \[...\] only when not preceded by a backslash (i.e., not escaped), + * while also capturing code blocks (```, `...`) so they can be skipped during processing. + * + * Uses negative lookbehind `(? ` or `>`) on a markdown line. */ +export const LATEX_BLOCKQUOTE_PREFIX_REGEXP = /^(>\s*)/; + +/** Matches the placeholder inserted by the protect/restore pipeline for a + * protected LaTeX expression. Group 1 is the index into `latexExpressions`. */ +export const LATEX_PLACEHOLDER_REGEXP = /<>/g; + +/** Matches the placeholder inserted by the protect/restore pipeline for a + * protected code block. Group 1 is the index into `codeBlocks`. */ +export const CODE_BLOCK_PLACEHOLDER_REGEXP = /<>/g; + +/** Matches a `$` immediately followed by a digit, which is treated as a + * currency amount (e.g. `$5`) and escaped to `\$5` so it isn't parsed as math. */ +export const LATEX_CURRENCY_DOLLAR_REGEXP = /\$(?=\d)/g; + +/** Captures remaining `$$...$$`, `\[...\]`, `\(...\)` (only unescaped via + * `(? ` or `>`) on a markdown line. */ -export const LATEX_BLOCKQUOTE_PREFIX_REGEXP = /^(>\s*)/; - -/** Matches the placeholder inserted by the protect/restore pipeline for a - * protected LaTeX expression. Group 1 is the index into `latexExpressions`. */ -export const LATEX_PLACEHOLDER_REGEXP = /<>/g; - -/** Matches the placeholder inserted by the protect/restore pipeline for a - * protected code block. Group 1 is the index into `codeBlocks`. */ -export const CODE_BLOCK_PLACEHOLDER_REGEXP = /<>/g; - -/** Matches a `$` immediately followed by a digit, which is treated as a - * currency amount (e.g. `$5`) and escaped to `\$5` so it isn't parsed as math. */ -export const LATEX_CURRENCY_DOLLAR_REGEXP = /\$(?=\d)/g; - -/** Captures remaining `$$...$$`, `\[...\]`, `\(...\)` (only unescaped via - * `(?\s+\S/, + BOLD_REGEX: /\*\*[^*\n]+\*\*|__[^_\n]+__/, + CODE_FENCE_REGEX: /^(```|~~~)/m, + LINK_REGEX: /\[[^\]\n]+\]\([^)\s]+\)/, + LIST_BULLET_REGEX: /^\s*[-*+]\s+\S/, + LIST_NUMBERED_REGEX: /^\s*\d+[.)]\s+\S/, + TABLE_SEPARATOR_REGEX: /^\s*\|?[\s:|-]+\|?\s*$/ +} as const; diff --git a/tools/ui/src/lib/constants/markdown.ts b/tools/ui/src/lib/constants/markdown.ts deleted file mode 100644 index 1cace78a3..000000000 --- a/tools/ui/src/lib/constants/markdown.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const IMAGE_NOT_ERROR_BOUND_SELECTOR = 'img:not([data-error-bound])'; -export const DATA_ERROR_BOUND_ATTR = 'errorBound'; -export const DATA_ERROR_HANDLED_ATTR = 'errorHandled'; -export const BOOL_TRUE_STRING = 'true'; -export const BOOL_FALSE_STRING = 'false'; diff --git a/tools/ui/src/lib/constants/max-bundle-size.constants.ts b/tools/ui/src/lib/constants/max-bundle-size.constants.ts new file mode 100644 index 000000000..e04348feb --- /dev/null +++ b/tools/ui/src/lib/constants/max-bundle-size.constants.ts @@ -0,0 +1 @@ +export const MAX_BUNDLE_SIZE = 2 * 1024 * 1024; diff --git a/tools/ui/src/lib/constants/max-bundle-size.ts b/tools/ui/src/lib/constants/max-bundle-size.ts deleted file mode 100644 index e04348feb..000000000 --- a/tools/ui/src/lib/constants/max-bundle-size.ts +++ /dev/null @@ -1 +0,0 @@ -export const MAX_BUNDLE_SIZE = 2 * 1024 * 1024; diff --git a/tools/ui/src/lib/constants/mcp-form.constants.ts b/tools/ui/src/lib/constants/mcp-form.constants.ts new file mode 100644 index 000000000..7a1ccffb0 --- /dev/null +++ b/tools/ui/src/lib/constants/mcp-form.constants.ts @@ -0,0 +1,2 @@ +export const MCP_SERVER_URL_PLACEHOLDER = 'https://mcp.example.com/sse'; +export const MIN_AUTOCOMPLETE_INPUT_LENGTH = 1; diff --git a/tools/ui/src/lib/constants/mcp-form.ts b/tools/ui/src/lib/constants/mcp-form.ts deleted file mode 100644 index 7a1ccffb0..000000000 --- a/tools/ui/src/lib/constants/mcp-form.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const MCP_SERVER_URL_PLACEHOLDER = 'https://mcp.example.com/sse'; -export const MIN_AUTOCOMPLETE_INPUT_LENGTH = 1; diff --git a/tools/ui/src/lib/constants/mcp-resource.constants.ts b/tools/ui/src/lib/constants/mcp-resource.constants.ts new file mode 100644 index 000000000..c2639daa1 --- /dev/null +++ b/tools/ui/src/lib/constants/mcp-resource.constants.ts @@ -0,0 +1,73 @@ +import { MimeTypeAudio, MimeTypeImage } from '$lib/enums'; + +// File extension patterns for resource type detection +export const IMAGE_FILE_EXTENSION_REGEX = /\.(png|jpg|jpeg|gif|svg|webp)$/i; +export const CODE_FILE_EXTENSION_REGEX = + /\.(js|ts|json|yaml|yml|xml|html|css|py|rs|go|java|cpp|c|h|rb|sh|toml)$/i; +export const TEXT_FILE_EXTENSION_REGEX = /\.(txt|md|log)$/i; + +// URI protocol prefix pattern +export const PROTOCOL_PREFIX_REGEX = /^[a-z]+:\/\//; + +// File extension regex for display name extraction +export const FILE_EXTENSION_REGEX = /\.[^.]+$/; + +// Separator regex for splitting display names (kebab-case/snake_case) +export const DISPLAY_NAME_SEPARATOR_REGEX = /[-_]/; + +// Regex for matching base64-encoded data URIs +export const DATA_URI_BASE64_REGEX = /^data:([^;]+);base64,([A-Za-z0-9+/]+=*)$/; + +// Prefix for MCP attachment filenames +export const MCP_ATTACHMENT_NAME_PREFIX = 'mcp-attachment'; + +// Prefix for MCP resource attachment IDs +export const MCP_RESOURCE_ATTACHMENT_ID_PREFIX = 'res'; + +// Default file extension for unknown image types +export const DEFAULT_IMAGE_EXTENSION = 'img'; + +// Default file extension for unknown audio types +export const DEFAULT_AUDIO_EXTENSION = 'mp3'; + +// Default filename for resource content downloads +export const DEFAULT_RESOURCE_FILENAME = 'resource.txt'; + +// Path separator for resource URI parsing +export const PATH_SEPARATOR = '/'; + +// Separator for joining text content from multiple resource parts +export const RESOURCE_TEXT_CONTENT_SEPARATOR = '\n\n'; + +// Fallback text for unknown content types +export const RESOURCE_UNKNOWN_TYPE = 'unknown type'; + +// Label prefix for binary blob content +export const BINARY_CONTENT_LABEL = 'Binary content'; + +/** + * Mapping from image MIME types to file extensions. + * Used for generating attachment filenames from MIME types. + */ +export const IMAGE_MIME_TO_EXTENSION: Record = { + [MimeTypeImage.GIF]: 'gif', + [MimeTypeImage.JPEG]: 'jpg', + [MimeTypeImage.JPG]: 'jpg', + [MimeTypeImage.PNG]: 'png', + [MimeTypeImage.WEBP]: 'webp' +} as const; + +/** + * Mapping from audio MIME types to file extensions. + * Used for generating attachment filenames from MIME types. + */ +export const AUDIO_MIME_TO_EXTENSION: Record = { + [MimeTypeAudio.MP3]: 'mp3', + [MimeTypeAudio.MP3_MPEG]: 'mp3', + [MimeTypeAudio.VND_WAVE]: 'wav', + [MimeTypeAudio.WAV]: 'wav', + [MimeTypeAudio.WAVE]: 'wav', + [MimeTypeAudio.X_PN_WAV]: 'wav', + [MimeTypeAudio.X_WAV]: 'wav', + [MimeTypeAudio.X_WAVE]: 'wav' +} as const; diff --git a/tools/ui/src/lib/constants/mcp-resource.ts b/tools/ui/src/lib/constants/mcp-resource.ts deleted file mode 100644 index c2639daa1..000000000 --- a/tools/ui/src/lib/constants/mcp-resource.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { MimeTypeAudio, MimeTypeImage } from '$lib/enums'; - -// File extension patterns for resource type detection -export const IMAGE_FILE_EXTENSION_REGEX = /\.(png|jpg|jpeg|gif|svg|webp)$/i; -export const CODE_FILE_EXTENSION_REGEX = - /\.(js|ts|json|yaml|yml|xml|html|css|py|rs|go|java|cpp|c|h|rb|sh|toml)$/i; -export const TEXT_FILE_EXTENSION_REGEX = /\.(txt|md|log)$/i; - -// URI protocol prefix pattern -export const PROTOCOL_PREFIX_REGEX = /^[a-z]+:\/\//; - -// File extension regex for display name extraction -export const FILE_EXTENSION_REGEX = /\.[^.]+$/; - -// Separator regex for splitting display names (kebab-case/snake_case) -export const DISPLAY_NAME_SEPARATOR_REGEX = /[-_]/; - -// Regex for matching base64-encoded data URIs -export const DATA_URI_BASE64_REGEX = /^data:([^;]+);base64,([A-Za-z0-9+/]+=*)$/; - -// Prefix for MCP attachment filenames -export const MCP_ATTACHMENT_NAME_PREFIX = 'mcp-attachment'; - -// Prefix for MCP resource attachment IDs -export const MCP_RESOURCE_ATTACHMENT_ID_PREFIX = 'res'; - -// Default file extension for unknown image types -export const DEFAULT_IMAGE_EXTENSION = 'img'; - -// Default file extension for unknown audio types -export const DEFAULT_AUDIO_EXTENSION = 'mp3'; - -// Default filename for resource content downloads -export const DEFAULT_RESOURCE_FILENAME = 'resource.txt'; - -// Path separator for resource URI parsing -export const PATH_SEPARATOR = '/'; - -// Separator for joining text content from multiple resource parts -export const RESOURCE_TEXT_CONTENT_SEPARATOR = '\n\n'; - -// Fallback text for unknown content types -export const RESOURCE_UNKNOWN_TYPE = 'unknown type'; - -// Label prefix for binary blob content -export const BINARY_CONTENT_LABEL = 'Binary content'; - -/** - * Mapping from image MIME types to file extensions. - * Used for generating attachment filenames from MIME types. - */ -export const IMAGE_MIME_TO_EXTENSION: Record = { - [MimeTypeImage.GIF]: 'gif', - [MimeTypeImage.JPEG]: 'jpg', - [MimeTypeImage.JPG]: 'jpg', - [MimeTypeImage.PNG]: 'png', - [MimeTypeImage.WEBP]: 'webp' -} as const; - -/** - * Mapping from audio MIME types to file extensions. - * Used for generating attachment filenames from MIME types. - */ -export const AUDIO_MIME_TO_EXTENSION: Record = { - [MimeTypeAudio.MP3]: 'mp3', - [MimeTypeAudio.MP3_MPEG]: 'mp3', - [MimeTypeAudio.VND_WAVE]: 'wav', - [MimeTypeAudio.WAV]: 'wav', - [MimeTypeAudio.WAVE]: 'wav', - [MimeTypeAudio.X_PN_WAV]: 'wav', - [MimeTypeAudio.X_WAV]: 'wav', - [MimeTypeAudio.X_WAVE]: 'wav' -} as const; diff --git a/tools/ui/src/lib/constants/mcp.constants.ts b/tools/ui/src/lib/constants/mcp.constants.ts new file mode 100644 index 000000000..11013d2cb --- /dev/null +++ b/tools/ui/src/lib/constants/mcp.constants.ts @@ -0,0 +1,81 @@ +import { Globe, Radio, Zap } from '@lucide/svelte'; +import { MCPTransportType } from '$lib/enums'; +import { MimeTypeImage } from '$lib/enums/files.enums'; +import type { ClientCapabilities, Implementation } from '$lib/types'; +import type { Component } from 'svelte'; + +export const DEFAULT_CLIENT_VERSION = '1.0.0'; +export const MCP_CLIENT_NAME = 'llama-ui-mcp'; +export const DEFAULT_IMAGE_MIME_TYPE = MimeTypeImage.PNG; + +/** MIME types considered safe for rendering MCP server icons */ +export const MCP_ALLOWED_ICON_MIME_TYPES = new Set([ + MimeTypeImage.PNG, + MimeTypeImage.JPEG, + MimeTypeImage.JPG, + MimeTypeImage.SVG, + MimeTypeImage.WEBP, + MimeTypeImage.ICO, + MimeTypeImage.ICO_MICROSOFT +]); + +/** + * MCP specification version this client targets. + * Update when the upstream MCP spec introduces a new stable version: + * https://spec.modelcontextprotocol.io/ + */ +export const MCP_PROTOCOL_VERSION = '2025-06-18'; + +export const DEFAULT_MCP_CONFIG = { + capabilities: { tools: { listChanged: true } } as ClientCapabilities, + clientInfo: { name: MCP_CLIENT_NAME, version: DEFAULT_CLIENT_VERSION } as Implementation, + connectionTimeoutMs: 10_000, // 10 seconds for connection establishment + protocolVersion: MCP_PROTOCOL_VERSION, + requestTimeoutSeconds: 300 // 5 minutes for long-running tools +} as const; + +export const MCP_SERVER_ID_PREFIX = 'LlamaUI-MCP-Server'; + +/** Backoff policy for reconnecting to a dropped MCP server. */ +export const MCP_RECONNECT = { + /** Per-attempt timeout for a single reconnection attempt before giving up and backing off. */ + ATTEMPT_TIMEOUT_MS: 15_000, + BACKOFF_MULTIPLIER: 2, + INITIAL_DELAY: 1000, + MAX_DELAY: 30000 +}; + +/** Maximum number of MCP server avatars to display in the chat form */ +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; + +/** CORS proxy connection settings */ +export const CORS_PROXY = { + /** Header prefix for headers that should be forwarded by the CORS proxy */ + HEADER_PREFIX: 'x-llama-server-proxy-header-', + /** CORS proxy URL query parameter name */ + URL_PARAM: 'url' +} as const; + +/** Standard SSE endpoint path indicators */ +export const MCP_SSE = { + ENDPOINT: '/sse', + ENDPOINT_QUERY: '/sse?', + ENDPOINT_SLASH: '/sse/' +} as const; + +/** Human-readable labels for MCP transport types */ +export const MCP_TRANSPORT_LABELS: Record = { + [MCPTransportType.SSE]: 'SSE', + [MCPTransportType.STREAMABLE_HTTP]: 'HTTP', + [MCPTransportType.WEBSOCKET]: 'WebSocket' +}; + +/** Icon components for MCP transport types */ +export const MCP_TRANSPORT_ICONS: Record = { + [MCPTransportType.SSE]: Radio, + [MCPTransportType.STREAMABLE_HTTP]: Globe, + [MCPTransportType.WEBSOCKET]: Zap +}; diff --git a/tools/ui/src/lib/constants/mcp.ts b/tools/ui/src/lib/constants/mcp.ts deleted file mode 100644 index 854cbf9f7..000000000 --- a/tools/ui/src/lib/constants/mcp.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { Globe, Radio, Zap } from '@lucide/svelte'; -import { MCPTransportType } from '$lib/enums'; -import { MimeTypeImage } from '$lib/enums/files.enums'; -import type { ClientCapabilities, Implementation } from '$lib/types'; -import type { Component } from 'svelte'; - -export const DEFAULT_CLIENT_VERSION = '1.0.0'; -export const MCP_CLIENT_NAME = 'llama-ui-mcp'; -export const DEFAULT_IMAGE_MIME_TYPE = MimeTypeImage.PNG; - -/** MIME types considered safe for rendering MCP server icons */ -export const MCP_ALLOWED_ICON_MIME_TYPES = new Set([ - MimeTypeImage.PNG, - MimeTypeImage.JPEG, - MimeTypeImage.JPG, - MimeTypeImage.SVG, - MimeTypeImage.WEBP, - MimeTypeImage.ICO, - MimeTypeImage.ICO_MICROSOFT -]); - -/** - * MCP specification version this client targets. - * Update when the upstream MCP spec introduces a new stable version: - * https://spec.modelcontextprotocol.io/ - */ -export const MCP_PROTOCOL_VERSION = '2025-06-18'; - -export const DEFAULT_MCP_CONFIG = { - capabilities: { tools: { listChanged: true } } as ClientCapabilities, - clientInfo: { name: MCP_CLIENT_NAME, version: DEFAULT_CLIENT_VERSION } as Implementation, - connectionTimeoutMs: 10_000, // 10 seconds for connection establishment - protocolVersion: MCP_PROTOCOL_VERSION, - requestTimeoutSeconds: 300 // 5 minutes for long-running tools -} as const; - -export const MCP_SERVER_ID_PREFIX = 'LlamaUI-MCP-Server'; - -export const MCP_RECONNECT_INITIAL_DELAY = 1000; -export const MCP_RECONNECT_BACKOFF_MULTIPLIER = 2; -export const MCP_RECONNECT_MAX_DELAY = 30000; -/** Per-attempt timeout for a single reconnection attempt before giving up and backing off. */ -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 = 4; - -/** Expected count when two theme-less icons represent a light/dark pair */ -export const EXPECTED_THEMED_ICON_PAIR_COUNT = 2; - -/** CORS proxy URL query parameter name */ -export const CORS_PROXY_URL_PARAM = 'url'; - -/** Header prefix for headers that should be forwarded by the CORS proxy */ -export const CORS_PROXY_HEADER_PREFIX = 'x-llama-server-proxy-header-'; - -/** Number of trailing characters to keep visible when partially redacting mcp-session-id */ -export const MCP_SESSION_ID_VISIBLE_CHARS = 5; - -/** Partial-redaction rules for MCP headers: header name -> visible trailing chars */ -export const MCP_PARTIAL_REDACT_HEADERS = new Map([ - ['mcp-session-id', MCP_SESSION_ID_VISIBLE_CHARS] -]); - -/** Bearer scheme prefix used for Authorization headers (RFC 6750) */ -export const BEARER_PREFIX = 'Bearer '; - -/** Canonical casing for the Authorization header (RFC 7235) */ -export const AUTHORIZATION_HEADER = 'Authorization'; - -/** Content-Type HTTP header name */ -export const CONTENT_TYPE_HEADER = 'Content-Type'; - -/** Header names whose values should be redacted in diagnostic logs */ -export const REDACTED_HEADERS = new Set([ - 'authorization', - 'api-key', - 'cookie', - 'mcp-session-id', - 'proxy-authorization', - 'set-cookie', - 'x-auth-token', - 'x-api-key' -]); - -/** Human-readable labels for MCP transport types */ -export const MCP_TRANSPORT_LABELS: Record = { - [MCPTransportType.SSE]: 'SSE', - [MCPTransportType.STREAMABLE_HTTP]: 'HTTP', - [MCPTransportType.WEBSOCKET]: 'WebSocket' -}; - -/** Icon components for MCP transport types */ -export const MCP_TRANSPORT_ICONS: Record = { - [MCPTransportType.SSE]: Radio, - [MCPTransportType.STREAMABLE_HTTP]: Globe, - [MCPTransportType.WEBSOCKET]: Zap -}; - -/** Standard SSE endpoint path indicators */ -export const MCP_SSE_ENDPOINT = '/sse'; -export const MCP_SSE_ENDPOINT_SLASH = '/sse/'; -export const MCP_SSE_ENDPOINT_QUERY = '/sse?'; diff --git a/tools/ui/src/lib/constants/mention-badge.constants.ts b/tools/ui/src/lib/constants/mention-badge.constants.ts new file mode 100644 index 000000000..a9ef963c3 --- /dev/null +++ b/tools/ui/src/lib/constants/mention-badge.constants.ts @@ -0,0 +1,45 @@ +/** + * Shared visual contract between the two DOM-only badge paths (the + * contenteditable tokenizer + the rehype plugin). Svelte cannot be + * mounted at the per-keystroke tokenizer hot path nor from a hast tree, + * so both emit the badge with the same class string literal; Tailwind's + * scanner picks it up in both sources. + */ +export const MENTION_BADGE_CLASSNAME = + 'inline-flex w-fit shrink-0 items-center gap-1 whitespace-nowrap rounded-md border border-border/50 bg-foreground/5 px-1.5 py-0.5 text-xs font-mono text-foreground hover:bg-foreground/10 dark:bg-foreground/10 dark:text-secondary-foreground'; + +export const MENTION_BADGE_ICON_CLASSNAME = 'h-3 w-3 shrink-0'; + +/** Regex flag that makes the mention scanner walk every link in a message instead of the first. */ +export const MENTION_LINK_SCAN_FLAGS = 'g'; + +/** + * SVG attributes shared by the DOM-built and hast-built badge icons. + * The tokenizer applies them via `setAttribute`, the rehype plugin + * spreads them onto the hast `` `properties`; string values are + * valid for both. + */ +export const MENTION_BADGE_SVG_ATTRIBUTES: Readonly> = { + 'aria-hidden': 'true', + fill: 'none', + stroke: 'currentColor', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + 'stroke-width': '2', + viewBox: '0 0 24 24', + xmlns: 'http://www.w3.org/2000/svg' +}; + +/** + * SVG path strings for the badge's inline icon; each entry becomes one + * `` child of the wrapper ``. Paths match `lucide-svelte`'s + * current `File` and `Folder` glyphs. + */ +export const MENTION_BADGE_FILE_ICON_PATHS: readonly string[] = [ + 'M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z', + 'M14 2v5a1 1 0 0 0 1 1h5' +]; + +export const MENTION_BADGE_FOLDER_ICON_PATHS: readonly string[] = [ + 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z' +]; diff --git a/tools/ui/src/lib/constants/mention-badge.ts b/tools/ui/src/lib/constants/mention-badge.ts deleted file mode 100644 index a9ef963c3..000000000 --- a/tools/ui/src/lib/constants/mention-badge.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Shared visual contract between the two DOM-only badge paths (the - * contenteditable tokenizer + the rehype plugin). Svelte cannot be - * mounted at the per-keystroke tokenizer hot path nor from a hast tree, - * so both emit the badge with the same class string literal; Tailwind's - * scanner picks it up in both sources. - */ -export const MENTION_BADGE_CLASSNAME = - 'inline-flex w-fit shrink-0 items-center gap-1 whitespace-nowrap rounded-md border border-border/50 bg-foreground/5 px-1.5 py-0.5 text-xs font-mono text-foreground hover:bg-foreground/10 dark:bg-foreground/10 dark:text-secondary-foreground'; - -export const MENTION_BADGE_ICON_CLASSNAME = 'h-3 w-3 shrink-0'; - -/** Regex flag that makes the mention scanner walk every link in a message instead of the first. */ -export const MENTION_LINK_SCAN_FLAGS = 'g'; - -/** - * SVG attributes shared by the DOM-built and hast-built badge icons. - * The tokenizer applies them via `setAttribute`, the rehype plugin - * spreads them onto the hast `` `properties`; string values are - * valid for both. - */ -export const MENTION_BADGE_SVG_ATTRIBUTES: Readonly> = { - 'aria-hidden': 'true', - fill: 'none', - stroke: 'currentColor', - 'stroke-linecap': 'round', - 'stroke-linejoin': 'round', - 'stroke-width': '2', - viewBox: '0 0 24 24', - xmlns: 'http://www.w3.org/2000/svg' -}; - -/** - * SVG path strings for the badge's inline icon; each entry becomes one - * `` child of the wrapper ``. Paths match `lucide-svelte`'s - * current `File` and `Folder` glyphs. - */ -export const MENTION_BADGE_FILE_ICON_PATHS: readonly string[] = [ - 'M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z', - 'M14 2v5a1 1 0 0 0 1 1h5' -]; - -export const MENTION_BADGE_FOLDER_ICON_PATHS: readonly string[] = [ - 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z' -]; diff --git a/tools/ui/src/lib/constants/mermaid-blocks.constants.ts b/tools/ui/src/lib/constants/mermaid-blocks.constants.ts new file mode 100644 index 000000000..cd9467fb6 --- /dev/null +++ b/tools/ui/src/lib/constants/mermaid-blocks.constants.ts @@ -0,0 +1,9 @@ +export const MERMAID_WRAPPER_CLASS = 'mermaid-block-wrapper'; +export const MERMAID_SCROLL_CONTAINER_CLASS = 'mermaid-scroll-container'; +export const MERMAID_BLOCK_CLASS = 'mermaid'; + +export const MERMAID_LANGUAGE = 'mermaid'; + +export const MERMAID_SYNTAX_ATTR = 'data-mermaid-syntax'; +export const MERMAID_ID_ATTR = 'data-mermaid-id'; +export const MERMAID_RENDERED_ATTR = 'data-mermaid-rendered'; diff --git a/tools/ui/src/lib/constants/mermaid-blocks.ts b/tools/ui/src/lib/constants/mermaid-blocks.ts deleted file mode 100644 index cd9467fb6..000000000 --- a/tools/ui/src/lib/constants/mermaid-blocks.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const MERMAID_WRAPPER_CLASS = 'mermaid-block-wrapper'; -export const MERMAID_SCROLL_CONTAINER_CLASS = 'mermaid-scroll-container'; -export const MERMAID_BLOCK_CLASS = 'mermaid'; - -export const MERMAID_LANGUAGE = 'mermaid'; - -export const MERMAID_SYNTAX_ATTR = 'data-mermaid-syntax'; -export const MERMAID_ID_ATTR = 'data-mermaid-id'; -export const MERMAID_RENDERED_ATTR = 'data-mermaid-rendered'; diff --git a/tools/ui/src/lib/constants/message-export.constants.ts b/tools/ui/src/lib/constants/message-export.constants.ts new file mode 100644 index 000000000..f6c576d79 --- /dev/null +++ b/tools/ui/src/lib/constants/message-export.constants.ts @@ -0,0 +1,24 @@ +// Conversation exporter / filename constants + +export const EXPORT_CONV = { + // Producer marker carried by the session record of a JSONL export + HARNESS: 'llama.app', + // Length of the trimmed conversation ID in the filename + ID_TRIM_LENGTH: 8, + // Replacements to the ISO date for use in the export filename + ISO_DATE_TIME_SEPARATOR: 'T', + + ISO_DATE_TIME_SEPARATOR_REPLACEMENT: '_', + + ISO_TIME_SEPARATOR: ':', + ISO_TIME_SEPARATOR_REPLACEMENT: '-', + // Characters to keep in the ISO timestamp. 19 keeps 2026-01-01T00:00:00 + ISO_TIMESTAMP_SLICE: 19, + + MULTIPLE_UNDERSCORE_REGEX: /_+/g, + // Maximum length of the sanitized conversation name snippet + NAME_SUFFIX_MAX_LENGTH: 20, + // Replacements for making the conversation title filename-friendly + NON_ALPHANUMERIC_REGEX: /[^a-z0-9]/gi, + NONALNUM_REPLACEMENT: '_' +} as const; diff --git a/tools/ui/src/lib/constants/message-export.ts b/tools/ui/src/lib/constants/message-export.ts deleted file mode 100644 index fc4dbe259..000000000 --- a/tools/ui/src/lib/constants/message-export.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Conversation filename constants - -// Length of the trimmed conversation ID in the filename -export const EXPORT_CONV_ID_TRIM_LENGTH = 8; -// Maximum length of the sanitized conversation name snippet -export const EXPORT_CONV_NAME_SUFFIX_MAX_LENGTH = 20; -// Characters to keep in the ISO timestamp. 19 keeps 2026-01-01T00:00:00 -export const ISO_TIMESTAMP_SLICE_LENGTH = 19; - -// Producer marker carried by the session record of a JSONL export -export const SESSION_HARNESS = 'llama.app'; - -// Replacements for making the conversation title filename-friendly -export const NON_ALPHANUMERIC_REGEX = /[^a-z0-9]/gi; -export const EXPORT_CONV_NONALNUM_REPLACEMENT = '_'; -export const MULTIPLE_UNDERSCORE_REGEX = /_+/g; - -// Replacements to the ISO date for use in the export filename -export const ISO_DATE_TIME_SEPARATOR = 'T'; -export const ISO_DATE_TIME_SEPARATOR_REPLACEMENT = '_'; - -export const ISO_TIME_SEPARATOR = ':'; -export const ISO_TIME_SEPARATOR_REPLACEMENT = '-'; diff --git a/tools/ui/src/lib/constants/model-id.constants.ts b/tools/ui/src/lib/constants/model-id.constants.ts new file mode 100644 index 000000000..081a13e0e --- /dev/null +++ b/tools/ui/src/lib/constants/model-id.constants.ts @@ -0,0 +1,43 @@ +/** + * Parsing of `org/ModelName[-tag][:quant]` style model IDs. + */ + +export const MODEL_ID = { + /** + * Matches an activated-parameter-count segment, e.g. `A10B`, `a2.4b`. + * The leading `A`/`a` distinguishes it from a regular params segment. + */ + ACTIVATED_PARAMS_RE: /^[Aa]\d+(\.\d+)?[BbMmKkTt]$/, + + /** Matches prefix for custom quantization types, e.g. `UD-Q8_K_XL`. */ + CUSTOM_QUANTIZATION_PREFIX_RE: /^UD$/i, + /** Container format segments to exclude from tags (every model uses these). */ + IGNORED_SEGMENTS: new Set(['GGUF', 'GGML']), + /** Sentinel value returned by `indexOf` when a substring is not found. */ + NOT_FOUND: -1, + + /** Separates `` from `` in a model ID, e.g. `org/ModelName`. */ + ORG_SEPARATOR: '/', + + /** + * Matches a parameter-count segment, e.g. `7B`, `1.5b`, `120M`. + * The optional leading `E` covers effective-parameter sizes, e.g. Gemma's + * `E2B`/`E4B` (MatFormer models sized by resident params). + */ + PARAMS_RE: /^[Ee]?\d+(\.\d+)?[BbMmKkTt]$/, + + /** + * Matches a quantization/precision segment, e.g. `Q4_K_M`, `IQ4_XS`, `F16`, `BF16`, `MXFP4`. + * Case-insensitive to handle both uppercase and lowercase inputs. + */ + QUANTIZATION_SEGMENT_RE: /^(I?Q\d+(_[A-Z0-9]+)*|F\d+|BF\d+|MXFP\d+(_[A-Z0-9]+)*)$/i, + + /** Separates the model path from the quantization tag, e.g. `model:Q4_K_M`. */ + QUANTIZATION_SEPARATOR: ':', + + /** Separates named segments within the model path, e.g. `ModelName-7B-GGUF`. */ + SEGMENT_SEPARATOR: '-', + + /** Matches a trailing weight file extension, e.g. `model.gguf` -> `model`. */ + WEIGHT_EXTENSION_RE: /\.(gguf|ggml)$/i +}; diff --git a/tools/ui/src/lib/constants/model-id.ts b/tools/ui/src/lib/constants/model-id.ts deleted file mode 100644 index 4108a2132..000000000 --- a/tools/ui/src/lib/constants/model-id.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** Sentinel value returned by `indexOf` when a substring is not found. */ -export const MODEL_ID_NOT_FOUND = -1; - -/** Separates `` from `` in a model ID, e.g. `org/ModelName`. */ -export const MODEL_ID_ORG_SEPARATOR = '/'; - -/** Separates named segments within the model path, e.g. `ModelName-7B-GGUF`. */ -export const MODEL_ID_SEGMENT_SEPARATOR = '-'; - -/** Separates the model path from the quantization tag, e.g. `model:Q4_K_M`. */ -export const MODEL_ID_QUANTIZATION_SEPARATOR = ':'; - -/** - * Matches a quantization/precision segment, e.g. `Q4_K_M`, `IQ4_XS`, `F16`, `BF16`, `MXFP4`. - * Case-insensitive to handle both uppercase and lowercase inputs. - */ -export const MODEL_QUANTIZATION_SEGMENT_RE = - /^(I?Q\d+(_[A-Z0-9]+)*|F\d+|BF\d+|MXFP\d+(_[A-Z0-9]+)*)$/i; - -/** - * Matches prefix for custom quantization types, e.g. `UD-Q8_K_XL`. - */ -export const MODEL_CUSTOM_QUANTIZATION_PREFIX_RE = /^UD$/i; - -/** - * Matches a parameter-count segment, e.g. `7B`, `1.5b`, `120M`. - * The optional leading `E` covers effective-parameter sizes, e.g. Gemma's - * `E2B`/`E4B` (MatFormer models sized by resident params). - */ -export const MODEL_PARAMS_RE = /^[Ee]?\d+(\.\d+)?[BbMmKkTt]$/; - -/** - * Matches an activated-parameter-count segment, e.g. `A10B`, `a2.4b`. - * The leading `A`/`a` distinguishes it from a regular params segment. - */ -export const MODEL_ACTIVATED_PARAMS_RE = /^[Aa]\d+(\.\d+)?[BbMmKkTt]$/; - -/** - * Container format segments to exclude from tags (every model uses these). - */ -export const MODEL_IGNORED_SEGMENTS = new Set(['GGUF', 'GGML']); - -/** - * Matches a trailing weight file extension, e.g. `model.gguf` -> `model`. - */ -export const MODEL_WEIGHT_EXTENSION_RE = /\.(gguf|ggml)$/i; diff --git a/tools/ui/src/lib/constants/model-loading.constants.ts b/tools/ui/src/lib/constants/model-loading.constants.ts new file mode 100644 index 000000000..0d0ca3263 --- /dev/null +++ b/tools/ui/src/lib/constants/model-loading.constants.ts @@ -0,0 +1,14 @@ +/** + * Labels shown while a model loads, keyed by the stage reported on /models/sse. + */ +export const MODEL_LOAD_STAGE_LABELS: Record = { + mmproj_model: 'Loading projector', + spec_model: 'Loading draft', + text_model: 'Loading weights' +}; + +/** + * Share of the bar reserved for each load phase after text_model. + * text_model fills the rest, so a plain model reaches 100% on its own. + */ +export const MODEL_LOAD_TAIL_SHARE = 0.1; diff --git a/tools/ui/src/lib/constants/model-loading.ts b/tools/ui/src/lib/constants/model-loading.ts deleted file mode 100644 index 0d0ca3263..000000000 --- a/tools/ui/src/lib/constants/model-loading.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Labels shown while a model loads, keyed by the stage reported on /models/sse. - */ -export const MODEL_LOAD_STAGE_LABELS: Record = { - mmproj_model: 'Loading projector', - spec_model: 'Loading draft', - text_model: 'Loading weights' -}; - -/** - * Share of the bar reserved for each load phase after text_model. - * text_model fills the rest, so a plain model reaches 100% on its own. - */ -export const MODEL_LOAD_TAIL_SHARE = 0.1; diff --git a/tools/ui/src/lib/constants/path-display.constants.ts b/tools/ui/src/lib/constants/path-display.constants.ts new file mode 100644 index 000000000..fd1001761 --- /dev/null +++ b/tools/ui/src/lib/constants/path-display.constants.ts @@ -0,0 +1,25 @@ +/** + * Constants for synthetic working-directory messages. + * + * The synthetic cwd-change message is text the UI renders as a folder row + * and the model sees as a turn reminder. The prefix and cleared marker keep + * the human-readable wording; the file-link regexes parse the + * `[file:///abs/path](display)` payload back out on the UI side. + */ + +import { UrlProtocol } from '$lib/enums'; + +export const CWD_CHANGED_PREFIX = 'Set working directory to '; +export const CWD_CLEARED_TEXT = 'Working directory cleared'; + +/** Trailing separator that marks a path as a directory. */ +export const DIRECTORY_PATH_SUFFIX = '/'; + +export const HOME_TILDE = '~'; +export const HOME_TILDE_PREFIX = '~/'; // tilde plus path separator + +/** Scheme prefix of the file link embedded in a synthetic cwd message. */ +export const FILE_URI_PREFIX = `${UrlProtocol.FILE}//`; + +/** Matches the leading `[file:///abs/path](display)` link; not anchored to the end so trailing guidance may follow. */ +export const CWD_LINK_REGEX = /^\[file:\/\/([\s\S]*?)\]\(([\s\S]*?)\)/; diff --git a/tools/ui/src/lib/constants/path-display.ts b/tools/ui/src/lib/constants/path-display.ts deleted file mode 100644 index fd1001761..000000000 --- a/tools/ui/src/lib/constants/path-display.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Constants for synthetic working-directory messages. - * - * The synthetic cwd-change message is text the UI renders as a folder row - * and the model sees as a turn reminder. The prefix and cleared marker keep - * the human-readable wording; the file-link regexes parse the - * `[file:///abs/path](display)` payload back out on the UI side. - */ - -import { UrlProtocol } from '$lib/enums'; - -export const CWD_CHANGED_PREFIX = 'Set working directory to '; -export const CWD_CLEARED_TEXT = 'Working directory cleared'; - -/** Trailing separator that marks a path as a directory. */ -export const DIRECTORY_PATH_SUFFIX = '/'; - -export const HOME_TILDE = '~'; -export const HOME_TILDE_PREFIX = '~/'; // tilde plus path separator - -/** Scheme prefix of the file link embedded in a synthetic cwd message. */ -export const FILE_URI_PREFIX = `${UrlProtocol.FILE}//`; - -/** Matches the leading `[file:///abs/path](display)` link; not anchored to the end so trailing guidance may follow. */ -export const CWD_LINK_REGEX = /^\[file:\/\/([\s\S]*?)\]\(([\s\S]*?)\)/; diff --git a/tools/ui/src/lib/constants/precision.constants.ts b/tools/ui/src/lib/constants/precision.constants.ts new file mode 100644 index 000000000..8df5c4f96 --- /dev/null +++ b/tools/ui/src/lib/constants/precision.constants.ts @@ -0,0 +1,2 @@ +export const PRECISION_MULTIPLIER = 1000000; +export const PRECISION_DECIMAL_PLACES = 6; diff --git a/tools/ui/src/lib/constants/precision.ts b/tools/ui/src/lib/constants/precision.ts deleted file mode 100644 index 8df5c4f96..000000000 --- a/tools/ui/src/lib/constants/precision.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const PRECISION_MULTIPLIER = 1000000; -export const PRECISION_DECIMAL_PLACES = 6; diff --git a/tools/ui/src/lib/constants/processing-info.ts b/tools/ui/src/lib/constants/processing-info.ts deleted file mode 100644 index 2c3f7dc53..000000000 --- a/tools/ui/src/lib/constants/processing-info.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const PROCESSING_INFO_TIMEOUT = 2000; - -/** - * Statistics units labels - */ -export const STATS_UNITS = { - TOKENS_PER_SECOND: 't/s' -} as const; diff --git a/tools/ui/src/lib/constants/pwa.constants.ts b/tools/ui/src/lib/constants/pwa.constants.ts new file mode 100644 index 000000000..e807f4a97 --- /dev/null +++ b/tools/ui/src/lib/constants/pwa.constants.ts @@ -0,0 +1,361 @@ +/** + * Centralized PWA constants to avoid magic strings, regexes, and duplicated + * definitions across the codebase. + */ + +import { APP_NAME } from './app.constants'; + +export const MEDIA_QUERIES = { + DISPLAY_MODE_STANDALONE: '(display-mode: standalone)', + PREFERS_DARK: '(prefers-color-scheme: dark)', + PREFERS_LIGHT: '(prefers-color-scheme: light)' +} as const; + +export const THEME_COLORS = { + ACCENT_BLUE: '#2563eb', + ACCENT_BLUE_HOVER: '#1d4ed8', + BACKGROUND_DARK: '#111111', + BACKGROUND_LIGHT: 'white', + DARK: '#0d0d0d', + LIGHT: '#ffffff', + TITLE_UPDATE_ALERT: { + BG_DARK: 'zinc-800', + BG_LIGHT: 'white', + BORDER_DARK: 'zinc-700', + BORDER_LIGHT: 'zinc-200', + TEXT_DARK: 'zinc-400', + TEXT_LIGHT: 'zinc-500' + } +} as const; + +export const FAVICON_PATHS = { + ICO_DARK: 'favicon-dark.ico', + ICO_LIGHT: 'favicon.ico', + SVG_DARK: 'favicon-dark.svg', + SVG_LIGHT: 'favicon.svg' +} as const; + +// Substituted for `currentColor` in src/lib/assets/logo.svg when generating +// the light/dark static sources consumed by the PWA asset generator. +export const FAVICON_COLORS = { + DARK: '#fafafa', + LIGHT: '#111111' +} as const; + +export const FAVICON_SELECTORS = { + ICO_48X48: 'link[rel="icon"][sizes="48x48"]', + SVG_ANY: 'link[rel="icon"][type="image/svg+xml"]' +} as const; + +export const APPLE_ASSETS = { + TOUCH_ICON: 'apple-touch-icon-180x180.png' +} as const; + +export const PWA_MANIFEST = { + background_color: THEME_COLORS.BACKGROUND_LIGHT, + description: 'Local AI chat interface powered by llama.cpp', + display: 'standalone' as const, + icons: [ + { sizes: '64x64', src: 'pwa-64x64.png', type: 'image/png' }, + { sizes: '192x192', src: 'pwa-192x192.png', type: 'image/png' }, + { purpose: 'any' as const, sizes: '512x512', src: 'pwa-512x512.png', type: 'image/png' }, + { + purpose: 'maskable' as const, + sizes: '512x512', + src: 'maskable-icon-512x512.png', + type: 'image/png' + } + ], + name: APP_NAME, + short_name: APP_NAME, + start_url: './', + theme_color: THEME_COLORS.BACKGROUND_LIGHT +}; + +export const PWA_ICON_PATHS = { + MASKABLE_512: '/maskable-icon-512x512.png', + PWA_64: '/pwa-64x64.png', + PWA_192: '/pwa-192x192.png', + PWA_512: '/pwa-512x512.png' +} as const; + +/** Apple device dimensions (logical points) and DPR, from Apple HIG. */ +export const APPLE_DEVICES = { + '640x1136': { dpr: 2, height: 568, width: 320 }, // iPhone 6/7/8 Plus + '744x1133': { dpr: 2, height: 573, width: 376 }, // iPad mini 8.3" + '750x1334': { dpr: 2, height: 667, width: 375 }, // iPhone 6/7/8, 14 + '1032x1376': { dpr: 2, height: 1376, width: 1032 }, // iPad Air 13" + // iPhones (DPR 3) + '1170x2532': { dpr: 3, height: 844, width: 390 }, // iPhone 13, 15 + '1179x2556': { dpr: 3, height: 852, width: 393 }, // iPhone 14, 15 Pro, 16 + '1206x2622': { dpr: 3, height: 874, width: 402 }, // iPhone 16 Plus, 16e + '1284x2778': { dpr: 3, height: 926, width: 428 }, // iPhone 15 Plus + '1290x2796': { dpr: 3, height: 932, width: 430 }, // iPhone 15 Pro Max, 16 Pro + '1320x2868': { dpr: 3, height: 956, width: 440 }, // iPhone 16 Pro Max + '1640x2360': { dpr: 2, height: 1180, width: 820 }, // iPad Air 10.9" + // iPads (DPR 2) + '1668x2388': { dpr: 2, height: 1194, width: 834 }, // iPad Air 11", iPad 11" + '2048x2732': { dpr: 2, height: 1366, width: 1024 } // iPad Pro 12.9" +} as const; + +export type AppleDeviceKey = keyof typeof APPLE_DEVICES; + +export const PWA_FILE_PATHS = { + MANIFEST: '/manifest.webmanifest', + SERVICE_WORKER: '/sw.js', + VERSION: '/version.json', + WORKBOX: '/workbox-.js' +} as const; + +// Used by the server middleware to skip API key validation. +// Keep in sync with tools/server/server-http.cpp public_endpoints list. + +export const PUBLIC_ENDPOINTS = [ + '/health', + '/v1/health', + '/models', + '/v1/models', + '/props', + '/metrics', + '/', + '/index.html', + + '/favicon.ico', + '/favicon-dark.ico', + '/favicon.svg', + '/favicon-dark.svg', + '/pwa-64x64.png', + '/pwa-192x192.png', + '/pwa-512x512.png', + '/maskable-icon-512x512.png', + '/apple-touch-icon-180x180.png', + '/apple-splash-portrait-640x1136.png', + '/apple-splash-landscape-640x1136.png', + '/apple-splash-portrait-750x1334.png', + '/apple-splash-landscape-750x1334.png', + '/apple-splash-portrait-1170x2532.png', + '/apple-splash-landscape-1170x2532.png', + '/apple-splash-portrait-1179x2556.png', + '/apple-splash-landscape-1179x2556.png', + '/apple-splash-portrait-1206x2622.png', + '/apple-splash-landscape-1206x2622.png', + '/apple-splash-portrait-1284x2778.png', + '/apple-splash-landscape-1284x2778.png', + '/apple-splash-portrait-1290x2796.png', + '/apple-splash-landscape-1290x2796.png', + '/apple-splash-portrait-1320x2868.png', + '/apple-splash-landscape-1320x2868.png', + '/apple-splash-portrait-1488x2266.png', + '/apple-splash-landscape-1488x2266.png', + '/apple-splash-portrait-1640x2360.png', + '/apple-splash-landscape-1640x2360.png', + '/apple-splash-portrait-1668x2388.png', + '/apple-splash-landscape-1668x2388.png', + '/apple-splash-portrait-2048x2732.png', + '/apple-splash-landscape-2048x2732.png', + '/apple-splash-portrait-dark-640x1136.png', + '/apple-splash-landscape-dark-640x1136.png', + '/apple-splash-portrait-dark-750x1334.png', + '/apple-splash-landscape-dark-750x1334.png', + '/apple-splash-portrait-dark-1170x2532.png', + '/apple-splash-landscape-dark-1170x2532.png', + '/apple-splash-portrait-dark-1179x2556.png', + '/apple-splash-landscape-dark-1179x2556.png', + '/apple-splash-portrait-dark-1206x2622.png', + '/apple-splash-landscape-dark-1206x2622.png', + '/apple-splash-portrait-dark-1284x2778.png', + '/apple-splash-landscape-dark-1284x2778.png', + '/apple-splash-portrait-dark-1290x2796.png', + '/apple-splash-landscape-dark-1290x2796.png', + '/apple-splash-portrait-dark-1320x2868.png', + '/apple-splash-landscape-dark-1320x2868.png', + '/apple-splash-portrait-dark-1488x2266.png', + '/apple-splash-landscape-dark-1488x2266.png', + '/apple-splash-portrait-dark-1640x2360.png', + '/apple-splash-landscape-dark-1640x2360.png', + '/apple-splash-portrait-dark-1668x2388.png', + '/apple-splash-landscape-dark-1668x2388.png', + '/apple-splash-portrait-dark-2048x2732.png', + '/apple-splash-landscape-dark-2048x2732.png', + '/manifest.webmanifest', + '/sw.js', + '/version.json', + '/workbox-.js' +] as const; +export const BUILD_CONFIG = { + GUIDE_COMMENT: ` + +`.trim(), + OUTPUT_DIR: './dist' +} as const; + +export const REGEX_PATTERNS = { + HEAD_CLOSE: /\t*<\/head>/, + SPLASH_FILE: /^apple-splash-(portrait|landscape)-(dark-)?(\d+)x(\d+)\.png$/ +} as const; + +// Device names used by @vite-pwa/assets-generator for splash screen generation. +// Keep in sync with pwa-assets.config.ts. +export const PWA_GENERATOR_DEVICES = [ + 'iPhone 13', + 'iPhone 13 Pro', + 'iPhone 13 Pro Max', + 'iPhone 14', + 'iPhone 14 Plus', + 'iPhone 14 Pro', + 'iPhone 14 Pro Max', + 'iPhone 15', + 'iPhone 15 Plus', + 'iPhone 15 Pro', + 'iPhone 15 Pro Max', + 'iPhone 16', + 'iPhone 16 Plus', + 'iPhone 16 Pro', + 'iPhone 16 Pro Max', + 'iPhone 16e', + 'iPhone SE 4"', + 'iPhone SE 4.7"', + 'iPad 11"', + 'iPad Air 10.9"', + 'iPad Air 11"', + 'iPad Air 13"', + 'iPad Pro 11"', + 'iPad Pro 12.9"', + 'iPad mini 8.3"' +] as const; + +// PWA assets generator configuration — used by pwa-assets.config.ts +// FAVICON_PADDING: fraction (0..1) of the icon reserved as equal margin on +// each side. Applied to icon PNG/ICO outputs by @vite-pwa/assets-generator and +// post-processed into the static favicon.svg so the in-app logo (which reads +// src/lib/assets/logo.svg directly) is unaffected. +export const PWA_ASSET_GENERATOR = { + ADD_MEDIA_SCREEN: true, + BASE_PATH: './', + DARK_PREFIX: 'dark-', + FAVICON_PADDING: 0.04, + FIT_MODE: 'contain', + LINK_PRESET: '2023', + PNG_COMPRESSION_LEVEL: 9, + PNG_QUALITY: 60, + SPLASH_PADDING: 0.75, + XHTML: false +} as const; + +export const CACHE_SETTINGS = { + API_CACHE_MAX_AGE_SECONDS: 60 * 60 * 24, + API_CACHE_MAX_ENTRIES: 50, + IMMUTABLE_MAX_AGE_SECONDS: 31536000, + MAX_FILE_SIZE_BYTES: 10 * 1024 * 1024 +} as const; + +export const GLOB_PATTERNS: string[] = [ + '**/*.{js,css,html,ico,svg,png,webp,woff,woff2,json,webmanifest}' +]; + +export const SW_CONFIG = { + CHECK_INTERVAL_MS: 60000, + UPDATE_FETCH_OPTIONS: { + CACHE: 'no-store', + HEADERS: { + CACHE: 'no-store', + CACHE_CONTROL: 'no-cache' + } + } +} as const; + +// Runtime caching configuration for Workbox +export const RUNTIME_CACHING = { + CACHE_NAME: 'api-cache', + HANDLER: 'NetworkFirst' +} as const; + +// Workbox runtime caching patterns +export const API_CACHING_PATTERNS = { + STATIC_API: /^\/(health|props|models|tools|slots|cors-proxy).*/, + V1_API: /^\/v1\/.*/ +} as const; + +// SvelteKit PWA plugin options +export const PWA_KIT_OPTIONS = {} as const; + +export const APPLE_META_TAGS = { + MOBILE_WEB_APP_CAPABLE: { content: 'yes', name: 'apple-mobile-web-app-capable' }, + MOBILE_WEB_APP_TITLE: { name: 'apple-mobile-web-app-title' }, + STATUS_BAR_STYLE: { content: 'black-translucent', name: 'apple-mobile-web-app-status-bar-style' } +} as const; + +// Splash screen HTML link tag prefix used by generateSplashScreenLinks +export const SPLASH_LINK = { + DARK_MEDIA_SUFFIX: ' and (prefers-color-scheme: dark)', + HTML: '.js' -} as const; - -// Used by the server middleware to skip API key validation. -// Keep in sync with tools/server/server-http.cpp public_endpoints list. - -export const PUBLIC_ENDPOINTS = [ - '/health', - '/v1/health', - '/models', - '/v1/models', - '/props', - '/metrics', - '/', - '/index.html', - - '/favicon.ico', - '/favicon-dark.ico', - '/favicon.svg', - '/favicon-dark.svg', - '/pwa-64x64.png', - '/pwa-192x192.png', - '/pwa-512x512.png', - '/maskable-icon-512x512.png', - '/apple-touch-icon-180x180.png', - '/apple-splash-portrait-640x1136.png', - '/apple-splash-landscape-640x1136.png', - '/apple-splash-portrait-750x1334.png', - '/apple-splash-landscape-750x1334.png', - '/apple-splash-portrait-1170x2532.png', - '/apple-splash-landscape-1170x2532.png', - '/apple-splash-portrait-1179x2556.png', - '/apple-splash-landscape-1179x2556.png', - '/apple-splash-portrait-1206x2622.png', - '/apple-splash-landscape-1206x2622.png', - '/apple-splash-portrait-1284x2778.png', - '/apple-splash-landscape-1284x2778.png', - '/apple-splash-portrait-1290x2796.png', - '/apple-splash-landscape-1290x2796.png', - '/apple-splash-portrait-1320x2868.png', - '/apple-splash-landscape-1320x2868.png', - '/apple-splash-portrait-1488x2266.png', - '/apple-splash-landscape-1488x2266.png', - '/apple-splash-portrait-1640x2360.png', - '/apple-splash-landscape-1640x2360.png', - '/apple-splash-portrait-1668x2388.png', - '/apple-splash-landscape-1668x2388.png', - '/apple-splash-portrait-2048x2732.png', - '/apple-splash-landscape-2048x2732.png', - '/apple-splash-portrait-dark-640x1136.png', - '/apple-splash-landscape-dark-640x1136.png', - '/apple-splash-portrait-dark-750x1334.png', - '/apple-splash-landscape-dark-750x1334.png', - '/apple-splash-portrait-dark-1170x2532.png', - '/apple-splash-landscape-dark-1170x2532.png', - '/apple-splash-portrait-dark-1179x2556.png', - '/apple-splash-landscape-dark-1179x2556.png', - '/apple-splash-portrait-dark-1206x2622.png', - '/apple-splash-landscape-dark-1206x2622.png', - '/apple-splash-portrait-dark-1284x2778.png', - '/apple-splash-landscape-dark-1284x2778.png', - '/apple-splash-portrait-dark-1290x2796.png', - '/apple-splash-landscape-dark-1290x2796.png', - '/apple-splash-portrait-dark-1320x2868.png', - '/apple-splash-landscape-dark-1320x2868.png', - '/apple-splash-portrait-dark-1488x2266.png', - '/apple-splash-landscape-dark-1488x2266.png', - '/apple-splash-portrait-dark-1640x2360.png', - '/apple-splash-landscape-dark-1640x2360.png', - '/apple-splash-portrait-dark-1668x2388.png', - '/apple-splash-landscape-dark-1668x2388.png', - '/apple-splash-portrait-dark-2048x2732.png', - '/apple-splash-landscape-dark-2048x2732.png', - '/manifest.webmanifest', - '/sw.js', - '/version.json', - '/workbox-.js' -] as const; -export const BUILD_CONFIG = { - GUIDE_COMMENT: ` - -`.trim(), - OUTPUT_DIR: './dist' -} as const; - -export const REGEX_PATTERNS = { - HEAD_CLOSE: /\t*<\/head>/, - SPLASH_FILE: /^apple-splash-(portrait|landscape)-(dark-)?(\d+)x(\d+)\.png$/ -} as const; - -// Device names used by @vite-pwa/assets-generator for splash screen generation. -// Keep in sync with pwa-assets.config.ts. -export const PWA_GENERATOR_DEVICES = [ - 'iPhone 13', - 'iPhone 13 Pro', - 'iPhone 13 Pro Max', - 'iPhone 14', - 'iPhone 14 Plus', - 'iPhone 14 Pro', - 'iPhone 14 Pro Max', - 'iPhone 15', - 'iPhone 15 Plus', - 'iPhone 15 Pro', - 'iPhone 15 Pro Max', - 'iPhone 16', - 'iPhone 16 Plus', - 'iPhone 16 Pro', - 'iPhone 16 Pro Max', - 'iPhone 16e', - 'iPhone SE 4"', - 'iPhone SE 4.7"', - 'iPad 11"', - 'iPad Air 10.9"', - 'iPad Air 11"', - 'iPad Air 13"', - 'iPad Pro 11"', - 'iPad Pro 12.9"', - 'iPad mini 8.3"' -] as const; - -// PWA assets generator configuration — used by pwa-assets.config.ts -// FAVICON_PADDING: fraction (0..1) of the icon reserved as equal margin on -// each side. Applied to icon PNG/ICO outputs by @vite-pwa/assets-generator and -// post-processed into the static favicon.svg so the in-app logo (which reads -// src/lib/assets/logo.svg directly) is unaffected. -export const PWA_ASSET_GENERATOR = { - ADD_MEDIA_SCREEN: true, - BASE_PATH: './', - DARK_PREFIX: 'dark-', - FAVICON_PADDING: 0.04, - FIT_MODE: 'contain', - LINK_PRESET: '2023', - PNG_COMPRESSION_LEVEL: 9, - PNG_QUALITY: 60, - SPLASH_PADDING: 0.75, - XHTML: false -} as const; - -export const CACHE_SETTINGS = { - API_CACHE_MAX_AGE_SECONDS: 60 * 60 * 24, - API_CACHE_MAX_ENTRIES: 50, - IMMUTABLE_MAX_AGE_SECONDS: 31536000, - MAX_FILE_SIZE_BYTES: 10 * 1024 * 1024 -} as const; - -export const GLOB_PATTERNS: string[] = [ - '**/*.{js,css,html,ico,svg,png,webp,woff,woff2,json,webmanifest}' -]; - -export const SW_CONFIG = { - CHECK_INTERVAL_MS: 60000, - UPDATE_FETCH_OPTIONS: { - CACHE: 'no-store', - HEADERS: { - CACHE: 'no-store', - CACHE_CONTROL: 'no-cache' - } - } -} as const; - -// Runtime caching configuration for Workbox -export const RUNTIME_CACHING = { - CACHE_NAME: 'api-cache', - HANDLER: 'NetworkFirst' -} as const; - -// Workbox runtime caching patterns -export const API_CACHING_PATTERNS = { - STATIC_API: /^\/(health|props|models|tools|slots|cors-proxy).*/, - V1_API: /^\/v1\/.*/ -} as const; - -// SvelteKit PWA plugin options -export const PWA_KIT_OPTIONS = {} as const; - -export const APPLE_META_TAGS = { - MOBILE_WEB_APP_CAPABLE: { content: 'yes', name: 'apple-mobile-web-app-capable' }, - MOBILE_WEB_APP_TITLE: { name: 'apple-mobile-web-app-title' }, - STATUS_BAR_STYLE: { content: 'black-translucent', name: 'apple-mobile-web-app-status-bar-style' } -} as const; - -// Splash screen HTML link tag prefix used by generateSplashScreenLinks -export const SPLASH_LINK = { - DARK_MEDIA_SUFFIX: ' and (prefers-color-scheme: dark)', - HTML: ' = { - [ReasoningEffort.HIGH]: 8192, - [ReasoningEffort.LOW]: 512, - [ReasoningEffort.MAX]: -1, // unlimited - [ReasoningEffort.MEDIUM]: 2048 -}; diff --git a/tools/ui/src/lib/constants/reasoning-effort.constants.ts b/tools/ui/src/lib/constants/reasoning-effort.constants.ts new file mode 100644 index 000000000..e8ec5f0e8 --- /dev/null +++ b/tools/ui/src/lib/constants/reasoning-effort.constants.ts @@ -0,0 +1,35 @@ +import { ReasoningEffort } from '$lib/enums'; +import type { ReasoningEffortLevel } from '$lib/types'; + +/** + * Reasoning effort UI labels. + * Keys match the ReasoningEffort enum values for type-safe lookups. + */ +export const REASONING_EFFORT_LABELS: Record = { + [ReasoningEffort.DEFAULT]: 'Default', + [ReasoningEffort.HIGH]: 'High', + [ReasoningEffort.LOW]: 'Low', + [ReasoningEffort.MAX]: 'Max', + [ReasoningEffort.MEDIUM]: 'Medium', + [ReasoningEffort.OFF]: 'Off' +}; + +export const REASONING_EFFORT_LEVELS: ReasoningEffortLevel[] = [ + { label: 'Default', value: ReasoningEffort.DEFAULT }, + { label: 'Off', value: ReasoningEffort.OFF }, + { label: 'Low', value: ReasoningEffort.LOW }, + { label: 'Medium', value: ReasoningEffort.MEDIUM }, + { label: 'High', value: ReasoningEffort.HIGH }, + { hasInfo: true, label: 'Max', value: ReasoningEffort.MAX } +]; + +/** + * Reasoning effort to token budget mapping. + * Maps the ReasoningEffort enum values to concrete token counts for the server. + */ +export const REASONING_EFFORT_TOKENS: Record = { + [ReasoningEffort.HIGH]: 8192, + [ReasoningEffort.LOW]: 512, + [ReasoningEffort.MAX]: -1, // unlimited + [ReasoningEffort.MEDIUM]: 2048 +}; diff --git a/tools/ui/src/lib/constants/reasoning-effort.ts b/tools/ui/src/lib/constants/reasoning-effort.ts deleted file mode 100644 index 4cbb7388b..000000000 --- a/tools/ui/src/lib/constants/reasoning-effort.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ReasoningEffort } from '$lib/enums'; -import type { ReasoningEffortLevel } from '$lib/types'; - -/** - * Reasoning effort UI labels. - * Keys match the ReasoningEffort enum values for type-safe lookups. - */ -export const REASONING_EFFORT_LABELS: Record = { - [ReasoningEffort.DEFAULT]: 'Default', - [ReasoningEffort.HIGH]: 'High', - [ReasoningEffort.LOW]: 'Low', - [ReasoningEffort.MAX]: 'Max', - [ReasoningEffort.MEDIUM]: 'Medium', - [ReasoningEffort.OFF]: 'Off' -}; - -export const REASONING_EFFORT_LEVELS: ReasoningEffortLevel[] = [ - { label: 'Default', value: ReasoningEffort.DEFAULT }, - { label: 'Off', value: ReasoningEffort.OFF }, - { label: 'Low', value: ReasoningEffort.LOW }, - { label: 'Medium', value: ReasoningEffort.MEDIUM }, - { label: 'High', value: ReasoningEffort.HIGH }, - { hasInfo: true, label: 'Max', value: ReasoningEffort.MAX } -]; diff --git a/tools/ui/src/lib/constants/recommended-mcp-servers.constants.ts b/tools/ui/src/lib/constants/recommended-mcp-servers.constants.ts new file mode 100644 index 000000000..6a550ee96 --- /dev/null +++ b/tools/ui/src/lib/constants/recommended-mcp-servers.constants.ts @@ -0,0 +1,38 @@ +import type { RecommendedMCPServer } from '$lib/types'; + +// Suggested MCP servers shown as opt-in cards in the "Add New Server" dialog. +// Rendering these cards never reaches the upstream domain - favicons come +// from local bundles in static/recommended-mcp/ and the URL is only used +// after the user clicks Add. +export const RECOMMENDED_MCP_SERVERS: RecommendedMCPServer[] = [ + { + description: 'Search the web and fetch full page content as clean markdown.', + iconUrl: '/recommended-mcp/exa.ico', + id: 'exa', + name: 'Exa', + url: 'https://mcp.exa.ai/mcp' + }, + { + description: 'Search and browse AI models, datasets, spaces, and docs on the Hugging Face Hub.', + iconUrl: '/recommended-mcp/huggingface.ico', + id: 'huggingface', + name: 'Hugging Face', + url: 'https://huggingface.co/mcp' + }, + { + description: 'Search repositories, issues, pull requests and interact with code on GitHub.', + iconUrlDark: '/recommended-mcp/github-dark.png', + iconUrlLight: '/recommended-mcp/github-light.png', + id: 'github', + name: 'GitHub', + needsAuthorization: true, + url: 'https://api.githubcopilot.com/mcp' + }, + { + description: 'Browse up-to-date documentation and code examples for libraries and frameworks.', + iconUrl: '/recommended-mcp/context7.png', + id: 'context7', + name: 'Context7', + url: 'https://mcp.context7.com/mcp' + } +]; diff --git a/tools/ui/src/lib/constants/recommended-mcp-servers.ts b/tools/ui/src/lib/constants/recommended-mcp-servers.ts deleted file mode 100644 index 6a550ee96..000000000 --- a/tools/ui/src/lib/constants/recommended-mcp-servers.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { RecommendedMCPServer } from '$lib/types'; - -// Suggested MCP servers shown as opt-in cards in the "Add New Server" dialog. -// Rendering these cards never reaches the upstream domain - favicons come -// from local bundles in static/recommended-mcp/ and the URL is only used -// after the user clicks Add. -export const RECOMMENDED_MCP_SERVERS: RecommendedMCPServer[] = [ - { - description: 'Search the web and fetch full page content as clean markdown.', - iconUrl: '/recommended-mcp/exa.ico', - id: 'exa', - name: 'Exa', - url: 'https://mcp.exa.ai/mcp' - }, - { - description: 'Search and browse AI models, datasets, spaces, and docs on the Hugging Face Hub.', - iconUrl: '/recommended-mcp/huggingface.ico', - id: 'huggingface', - name: 'Hugging Face', - url: 'https://huggingface.co/mcp' - }, - { - description: 'Search repositories, issues, pull requests and interact with code on GitHub.', - iconUrlDark: '/recommended-mcp/github-dark.png', - iconUrlLight: '/recommended-mcp/github-light.png', - id: 'github', - name: 'GitHub', - needsAuthorization: true, - url: 'https://api.githubcopilot.com/mcp' - }, - { - description: 'Browse up-to-date documentation and code examples for libraries and frameworks.', - iconUrl: '/recommended-mcp/context7.png', - id: 'context7', - name: 'Context7', - url: 'https://mcp.context7.com/mcp' - } -]; diff --git a/tools/ui/src/lib/constants/routes.constants.ts b/tools/ui/src/lib/constants/routes.constants.ts new file mode 100644 index 000000000..84b0f5300 --- /dev/null +++ b/tools/ui/src/lib/constants/routes.constants.ts @@ -0,0 +1,38 @@ +/** Query params the chat routes read from the URL. */ +export const URL_PARAMS = { + /** Load the selected model instead of waiting for the first message. */ + LOAD: 'load', + /** Model to select. */ + MODEL: 'model', + /** Start a new chat. */ + NEW_CHAT: 'new_chat', + /** Prompt to send on arrival. */ + QUERY: 'q' +} as const; + +/** Settings section slugs — used for routes and navigation. */ +export const SETTINGS_SECTION_SLUGS = { + AGENTIC: 'agentic', + DEVELOPER: 'developer', + DISPLAY: 'display', + GENERAL: 'general', + IMPORT_EXPORT: 'import-export', + PENALTIES: 'penalties', + SAMPLING: 'sampling', + TOOLS: 'tools' +} as const; + +export const ROUTES = { + /** Chat base — for dynamic chat URLs use RouterService. */ + CHAT: '#/chat', + /** MCP servers. */ + MCP_SERVERS: '#/mcp-servers', + /** New chat — root with new chat query param. */ + NEW_CHAT: `?${URL_PARAMS.NEW_CHAT}=true#/`, + /** Search — mobile-only full-page conversation search. */ + SEARCH: '#/search', + /** Settings base — for dynamic settings URLs use RouterService. */ + SETTINGS: '#/settings', + /** Root — start of the app. */ + START: '#/' +} as const; diff --git a/tools/ui/src/lib/constants/routes.ts b/tools/ui/src/lib/constants/routes.ts deleted file mode 100644 index 84b0f5300..000000000 --- a/tools/ui/src/lib/constants/routes.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** Query params the chat routes read from the URL. */ -export const URL_PARAMS = { - /** Load the selected model instead of waiting for the first message. */ - LOAD: 'load', - /** Model to select. */ - MODEL: 'model', - /** Start a new chat. */ - NEW_CHAT: 'new_chat', - /** Prompt to send on arrival. */ - QUERY: 'q' -} as const; - -/** Settings section slugs — used for routes and navigation. */ -export const SETTINGS_SECTION_SLUGS = { - AGENTIC: 'agentic', - DEVELOPER: 'developer', - DISPLAY: 'display', - GENERAL: 'general', - IMPORT_EXPORT: 'import-export', - PENALTIES: 'penalties', - SAMPLING: 'sampling', - TOOLS: 'tools' -} as const; - -export const ROUTES = { - /** Chat base — for dynamic chat URLs use RouterService. */ - CHAT: '#/chat', - /** MCP servers. */ - MCP_SERVERS: '#/mcp-servers', - /** New chat — root with new chat query param. */ - NEW_CHAT: `?${URL_PARAMS.NEW_CHAT}=true#/`, - /** Search — mobile-only full-page conversation search. */ - SEARCH: '#/search', - /** Settings base — for dynamic settings URLs use RouterService. */ - SETTINGS: '#/settings', - /** Root — start of the app. */ - START: '#/' -} as const; diff --git a/tools/ui/src/lib/constants/sandbox.constants.ts b/tools/ui/src/lib/constants/sandbox.constants.ts new file mode 100644 index 000000000..9846e471a --- /dev/null +++ b/tools/ui/src/lib/constants/sandbox.constants.ts @@ -0,0 +1,13 @@ +import { BuiltInTool } from '$lib/enums'; + +export const SANDBOX_TOOL_NAME = BuiltInTool.RUN_JAVASCRIPT; + +export const SANDBOX_TIMEOUT_MS_DEFAULT = 10000; + +export const SANDBOX_TIMEOUT_MS_MAX = 30000; + +export const SANDBOX_OUTPUT_MAX_CHARS = 8192; + +export const SANDBOX_EMPTY_OUTPUT = '(no output)'; + +export const SANDBOX_TRUNCATION_NOTICE = '[output truncated]'; diff --git a/tools/ui/src/lib/constants/sandbox.ts b/tools/ui/src/lib/constants/sandbox.ts deleted file mode 100644 index 11e5fcf8f..000000000 --- a/tools/ui/src/lib/constants/sandbox.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { BuiltInTool, JsonSchemaType, ToolCallType } from '$lib/enums'; -import type { OpenAIToolDefinition } from '$lib/types'; - -export const SANDBOX_TOOL_NAME = BuiltInTool.RUN_JAVASCRIPT; - -export const SANDBOX_TIMEOUT_MS_DEFAULT = 10000; - -export const SANDBOX_TIMEOUT_MS_MAX = 30000; - -export const SANDBOX_OUTPUT_MAX_CHARS = 8192; - -export const SANDBOX_EMPTY_OUTPUT = '(no output)'; - -export const SANDBOX_TRUNCATION_NOTICE = '[output truncated]'; - -const NERDAMER_DESCRIPTION = ` -Symbolic/numeric math via \`nerdamer\` -nerdamer(expr,subs?,opts?)/nerdamer.func(...)→Expression Format via .text(fmt?) (fmt: 'decimals'|'fractions'|'scientific') eval via .evaluate(subs?) -nerdamer(expr,{x:2}) substitutes numeric via opts 'numer' or .evaluate() -simplify/expand/factor(expr) div/gcd/lcm(...) coeffs/partfrac(expr,var) -diff/integrate(expr,var) defint(expr,lo,hi,var?) sum/product(expr,var,lo,hi) limit(expr,var,pt) -solve(expr,var) solveEquations([eq1,eq2],[var1,var2]) -polarform/rectform/arg/realpart/imagpart(z) -set/get Var/Constant(name,val?) setFunction(name,[params],body) -IMPORTANT:Identifier 'nerdamer' has already been declared, use it directly`; - -/** - * Build the sandbox tool definition. When `includeSymbolicMath` is true, - * the description includes nerdamer API documentation; otherwise it - * describes a plain JavaScript sandbox. - */ -export function buildSandboxToolDefinition(includeSymbolicMath: boolean): OpenAIToolDefinition { - return { - function: { - description: includeSymbolicMath - ? `Execute JS in a sandboxed browser worker (no DOM/page access). Top-level await ok; console.log for intermediates; top-level return is captured as result.${NERDAMER_DESCRIPTION}` - : 'Execute JS in a sandboxed browser worker (no DOM/page access). Top-level await ok; console.log for intermediates; top-level return is captured as result.', - name: SANDBOX_TOOL_NAME, - parameters: { - properties: { - code: { - description: 'JavaScript source to execute', - type: JsonSchemaType.STRING - }, - timeout_ms: { - description: `Execution timeout in milliseconds, default ${SANDBOX_TIMEOUT_MS_DEFAULT}, max ${SANDBOX_TIMEOUT_MS_MAX}`, - type: JsonSchemaType.NUMBER - } - }, - required: ['code'], - type: JsonSchemaType.OBJECT - } - }, - type: ToolCallType.FUNCTION - }; -} - -/** @deprecated Use {@link buildSandboxToolDefinition} instead. Kept for backward compatibility. */ -export const SANDBOX_TOOL_DEFINITION = buildSandboxToolDefinition(true); diff --git a/tools/ui/src/lib/constants/settings-keys.constants.ts b/tools/ui/src/lib/constants/settings-keys.constants.ts new file mode 100644 index 000000000..b53d11048 --- /dev/null +++ b/tools/ui/src/lib/constants/settings-keys.constants.ts @@ -0,0 +1,76 @@ +/** + * Settings key constants for ChatSettings configuration. + * + * These keys correspond to properties in SettingsConfigType and are used + * in settings field configurations to ensure consistency. + */ +export const SETTINGS_KEYS = { + AGENTIC_MAX_TURNS: 'agenticMaxTurns', + ALWAYS_SHOW_SIDEBAR_ON_DESKTOP: 'alwaysShowSidebarOnDesktop', + ALWAYS_SHOW_TOOL_CALL_CONTENT: 'alwaysShowToolCallContent', + API_KEY: 'apiKey', + AUTO_MIC_ON_EMPTY: 'autoMicOnEmpty', + BACKEND_SAMPLING: 'backend_sampling', + COPY_TEXT_ATTACHMENTS_AS_PLAIN_TEXT: 'copyTextAttachmentsAsPlainText', + CUSTOM_CSS: 'customCss', + // PY_INTERPRETER_ENABLED: 'pyInterpreterEnabled', + CUSTOM_JSON: 'customJson', + DISABLE_AUTO_SCROLL: 'disableAutoScroll', + // Developer + DISABLE_REASONING_PARSING: 'disableReasoningParsing', + DRY_ALLOWED_LENGTH: 'dry_allowed_length', + DRY_BASE: 'dry_base', + DRY_MULTIPLIER: 'dry_multiplier', + DRY_PENALTY_LAST_N: 'dry_penalty_last_n', + DYNATEMP_EXPONENT: 'dynatemp_exponent', + DYNATEMP_RANGE: 'dynatemp_range', + ENABLE_CONTINUE_GENERATION: 'enableContinueGeneration', + EXCLUDE_REASONING_FROM_CONTEXT: 'excludeReasoningFromContext', + FREQUENCY_PENALTY: 'frequency_penalty', + FULL_HEIGHT_CODE_BLOCKS: 'fullHeightCodeBlocks', + JS_SANDBOX_ENABLED: 'jsSandboxEnabled', + MAX_IMAGE_RESOLUTION: 'maxImageMPixels', + MAX_TOKENS: 'max_tokens', + MCP_REQUEST_TIMEOUT_SECONDS: 'mcpRequestTimeoutSeconds', + // MCP + MCP_SERVERS: 'mcpServers', + MENTION_SEARCH_MAX_DEPTH: 'mentionSearchMaxDepth', + MIN_P: 'min_p', + PASTE_LONG_TEXT_TO_FILE_LEN: 'pasteLongTextToFileLen', + PDF_AS_IMAGE: 'pdfAsImage', + // Performance + PRE_ENCODE_CONVERSATION: 'preEncodeConversation', + PRESENCE_PENALTY: 'presence_penalty', + RENDER_THINKING_AS_MARKDOWN: 'renderThinkingAsMarkdown', + RENDER_USER_CONTENT_AS_MARKDOWN: 'renderUserContentAsMarkdown', + // Penalties + REPEAT_LAST_N: 'repeat_last_n', + REPEAT_PENALTY: 'repeat_penalty', + SAMPLERS: 'samplers', + SEND_ON_ENTER: 'sendOnEnter', + SHOW_AGENTIC_TURN_STATS: 'showAgenticTurnStats', + SHOW_BUILD_VERSION: 'showBuildVersion', + SHOW_FULL_PATH_IN_MENTIONS: 'showFullPathInMentions', + // Display + SHOW_MESSAGE_STATS: 'showMessageStats', + SHOW_MODEL_QUANTIZATION: 'showModelQuantization', + SHOW_MODEL_TAGS: 'showModelTags', + SHOW_RAW_MODEL_NAMES: 'showRawModelNames', + SHOW_RAW_OUTPUT_SWITCH: 'showRawOutputSwitch', + SHOW_SYSTEM_MESSAGE: 'showSystemMessage', + SHOW_THOUGHT_IN_PROGRESS: 'showThoughtInProgress', + SYMBOLIC_MATH_ENABLED: 'symbolicMathEnabled', + SYSTEM_MESSAGE: 'systemMessage', + // Sampling + TEMPERATURE: 'temperature', + // General + THEME: 'theme', + TITLE_GENERATION_PROMPT: 'titleGenerationPrompt', + TITLE_GENERATION_USE_FIRST_LINE: 'titleGenerationUseFirstLine', + TITLE_GENERATION_USE_LLM: 'titleGenerationUseLLM', + TOP_K: 'top_k', + TOP_P: 'top_p', + TYP_P: 'typ_p', + XTC_PROBABILITY: 'xtc_probability', + XTC_THRESHOLD: 'xtc_threshold' +} as const; diff --git a/tools/ui/src/lib/constants/settings-keys.ts b/tools/ui/src/lib/constants/settings-keys.ts deleted file mode 100644 index b53d11048..000000000 --- a/tools/ui/src/lib/constants/settings-keys.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Settings key constants for ChatSettings configuration. - * - * These keys correspond to properties in SettingsConfigType and are used - * in settings field configurations to ensure consistency. - */ -export const SETTINGS_KEYS = { - AGENTIC_MAX_TURNS: 'agenticMaxTurns', - ALWAYS_SHOW_SIDEBAR_ON_DESKTOP: 'alwaysShowSidebarOnDesktop', - ALWAYS_SHOW_TOOL_CALL_CONTENT: 'alwaysShowToolCallContent', - API_KEY: 'apiKey', - AUTO_MIC_ON_EMPTY: 'autoMicOnEmpty', - BACKEND_SAMPLING: 'backend_sampling', - COPY_TEXT_ATTACHMENTS_AS_PLAIN_TEXT: 'copyTextAttachmentsAsPlainText', - CUSTOM_CSS: 'customCss', - // PY_INTERPRETER_ENABLED: 'pyInterpreterEnabled', - CUSTOM_JSON: 'customJson', - DISABLE_AUTO_SCROLL: 'disableAutoScroll', - // Developer - DISABLE_REASONING_PARSING: 'disableReasoningParsing', - DRY_ALLOWED_LENGTH: 'dry_allowed_length', - DRY_BASE: 'dry_base', - DRY_MULTIPLIER: 'dry_multiplier', - DRY_PENALTY_LAST_N: 'dry_penalty_last_n', - DYNATEMP_EXPONENT: 'dynatemp_exponent', - DYNATEMP_RANGE: 'dynatemp_range', - ENABLE_CONTINUE_GENERATION: 'enableContinueGeneration', - EXCLUDE_REASONING_FROM_CONTEXT: 'excludeReasoningFromContext', - FREQUENCY_PENALTY: 'frequency_penalty', - FULL_HEIGHT_CODE_BLOCKS: 'fullHeightCodeBlocks', - JS_SANDBOX_ENABLED: 'jsSandboxEnabled', - MAX_IMAGE_RESOLUTION: 'maxImageMPixels', - MAX_TOKENS: 'max_tokens', - MCP_REQUEST_TIMEOUT_SECONDS: 'mcpRequestTimeoutSeconds', - // MCP - MCP_SERVERS: 'mcpServers', - MENTION_SEARCH_MAX_DEPTH: 'mentionSearchMaxDepth', - MIN_P: 'min_p', - PASTE_LONG_TEXT_TO_FILE_LEN: 'pasteLongTextToFileLen', - PDF_AS_IMAGE: 'pdfAsImage', - // Performance - PRE_ENCODE_CONVERSATION: 'preEncodeConversation', - PRESENCE_PENALTY: 'presence_penalty', - RENDER_THINKING_AS_MARKDOWN: 'renderThinkingAsMarkdown', - RENDER_USER_CONTENT_AS_MARKDOWN: 'renderUserContentAsMarkdown', - // Penalties - REPEAT_LAST_N: 'repeat_last_n', - REPEAT_PENALTY: 'repeat_penalty', - SAMPLERS: 'samplers', - SEND_ON_ENTER: 'sendOnEnter', - SHOW_AGENTIC_TURN_STATS: 'showAgenticTurnStats', - SHOW_BUILD_VERSION: 'showBuildVersion', - SHOW_FULL_PATH_IN_MENTIONS: 'showFullPathInMentions', - // Display - SHOW_MESSAGE_STATS: 'showMessageStats', - SHOW_MODEL_QUANTIZATION: 'showModelQuantization', - SHOW_MODEL_TAGS: 'showModelTags', - SHOW_RAW_MODEL_NAMES: 'showRawModelNames', - SHOW_RAW_OUTPUT_SWITCH: 'showRawOutputSwitch', - SHOW_SYSTEM_MESSAGE: 'showSystemMessage', - SHOW_THOUGHT_IN_PROGRESS: 'showThoughtInProgress', - SYMBOLIC_MATH_ENABLED: 'symbolicMathEnabled', - SYSTEM_MESSAGE: 'systemMessage', - // Sampling - TEMPERATURE: 'temperature', - // General - THEME: 'theme', - TITLE_GENERATION_PROMPT: 'titleGenerationPrompt', - TITLE_GENERATION_USE_FIRST_LINE: 'titleGenerationUseFirstLine', - TITLE_GENERATION_USE_LLM: 'titleGenerationUseLLM', - TOP_K: 'top_k', - TOP_P: 'top_p', - TYP_P: 'typ_p', - XTC_PROBABILITY: 'xtc_probability', - XTC_THRESHOLD: 'xtc_threshold' -} as const; diff --git a/tools/ui/src/lib/constants/settings-registry.constants.ts b/tools/ui/src/lib/constants/settings-registry.constants.ts new file mode 100644 index 000000000..e44459957 --- /dev/null +++ b/tools/ui/src/lib/constants/settings-registry.constants.ts @@ -0,0 +1,752 @@ +import { CLI_FLAGS } from './cli-flags.constants'; +import { DEFAULT_MCP_CONFIG } from './mcp.constants'; +import { ROUTES, SETTINGS_SECTION_SLUGS } from './routes.constants'; +import { SETTINGS_KEYS } from './settings-keys.constants'; +import { TITLE_GENERATION } from './title-generation.constants'; +import { FILE_GLOB_SEARCH_PICKERS } from './working-directory.constants'; +import { + AlertTriangle, + Code, + Database, + Funnel, + ListRestart, + Monitor, + Moon, + PencilRuler, + Sliders, + Sun +} from '@lucide/svelte'; +import { SyncableParameterType } from '$lib/enums'; +import { SettingsFieldType } from '$lib/enums/settings.enums'; +import { ColorMode } from '$lib/enums/ui.enums'; +import type { + SettingsConfigValue, + SettingsEntry, + SettingsSection, + SettingsSectionEntry, + SettingsSectionTitle, + SyncableParameter +} from '$lib/types'; +import type { Component } from 'svelte'; + +export const SETTINGS_SECTION_TITLES = { + AGENTIC: 'Agentic', + DEVELOPER: 'Developer', + DISPLAY: 'Display', + GENERAL: 'General', + IMPORT_EXPORT: 'Import/Export', + PENALTIES: 'Penalties', + SAMPLING: 'Sampling', + TOOLS: 'Tools' +} as const; + +const STANDALONE_SECTIONS: { title: SettingsSectionTitle; slug: string; icon: Component }[] = [ + { icon: PencilRuler, slug: SETTINGS_SECTION_SLUGS.TOOLS, title: SETTINGS_SECTION_TITLES.TOOLS }, + { + icon: Database, + slug: SETTINGS_SECTION_SLUGS.IMPORT_EXPORT, + title: SETTINGS_SECTION_TITLES.IMPORT_EXPORT + } +]; +const COLOR_MODE_OPTIONS: Array<{ value: string; label: string; icon: Component }> = [ + { icon: Monitor, label: 'System', value: ColorMode.SYSTEM }, + { icon: Sun, label: 'Light', value: ColorMode.LIGHT }, + { icon: Moon, label: 'Dark', value: ColorMode.DARK } +]; +// Shared options for the title-generation radio group. Both paired registry entries +// (USE_FIRST_LINE, USE_LLM) reference this list so labels stay in lockstep. +const TITLE_GENERATION_RADIO_OPTIONS: Array<{ + value: string; + label: string; + key: string; + isExperimental?: boolean; +}> = [ + { + key: SETTINGS_KEYS.TITLE_GENERATION_USE_FIRST_LINE, + label: 'Use first non-empty line for the conversation title', + value: 'firstLine' + }, + { + isExperimental: true, + key: SETTINGS_KEYS.TITLE_GENERATION_USE_LLM, + label: 'Generate title with LLM', + value: 'llm' + } +]; +// Common shape for the conversation title radio entry. +const TITLE_GENERATION_BASE = { + radioOptions: TITLE_GENERATION_RADIO_OPTIONS, + section: SETTINGS_SECTION_SLUGS.GENERAL, + type: SettingsFieldType.RADIO +} as const; +const SETTINGS_REGISTRY: Record = { + [SETTINGS_SECTION_SLUGS.AGENTIC]: { + icon: ListRestart, + settings: [ + { + defaultValue: 10, + help: 'Maximum number of tool execution cycles before stopping (prevents infinite loops).', + isPositiveInteger: true, + key: SETTINGS_KEYS.AGENTIC_MAX_TURNS, + label: 'Agentic turns', + section: SETTINGS_SECTION_SLUGS.AGENTIC, + type: SettingsFieldType.INPUT + }, + { + defaultValue: DEFAULT_MCP_CONFIG.requestTimeoutSeconds, + help: 'Timeout for individual MCP tool calls.', + isPositiveInteger: true, + key: SETTINGS_KEYS.MCP_REQUEST_TIMEOUT_SECONDS, + label: 'MCP request timeout (seconds)', + section: SETTINGS_SECTION_SLUGS.AGENTIC, + type: SettingsFieldType.INPUT + }, + { + defaultValue: FILE_GLOB_SEARCH_PICKERS.DEFAULT_SEARCH_DEPTH, + help: 'How many directory levels below the working directory the @-mention file search descends. Larger values surface deeply nested files but take longer on large trees.', + isPositiveInteger: true, + key: SETTINGS_KEYS.MENTION_SEARCH_MAX_DEPTH, + label: 'Mention search depth', + max: FILE_GLOB_SEARCH_PICKERS.MAX_SEARCH_DEPTH, + min: 1, + placeholder: `${FILE_GLOB_SEARCH_PICKERS.DEFAULT_SEARCH_DEPTH}`, + section: SETTINGS_SECTION_SLUGS.AGENTIC, + type: SettingsFieldType.INPUT + } + ], + slug: SETTINGS_SECTION_SLUGS.AGENTIC, + title: SETTINGS_SECTION_TITLES.AGENTIC + }, + [SETTINGS_SECTION_SLUGS.DEVELOPER]: { + icon: Code, + settings: [ + { + defaultValue: false, + help: 'After each response, re-submit the conversation to pre-fill the server KV cache. Makes the next turn faster since the prompt is already encoded while you read the response.', + key: SETTINGS_KEYS.PRE_ENCODE_CONVERSATION, + label: 'Pre-fill KV cache after response', + section: SETTINGS_SECTION_SLUGS.DEVELOPER, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + help: 'Send reasoning_format=none so the server returns thinking tokens inline instead of extracting them into a separate field.', + key: SETTINGS_KEYS.DISABLE_REASONING_PARSING, + label: 'Disable reasoning content parsing', + section: SETTINGS_SECTION_SLUGS.DEVELOPER, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + help: 'Strip thinking from previous messages before sending. When off, thinking is sent back via the reasoning_content field so the model sees its own chain-of-thought across turns.', + key: SETTINGS_KEYS.EXCLUDE_REASONING_FROM_CONTEXT, + label: 'Exclude reasoning from context', + section: SETTINGS_SECTION_SLUGS.DEVELOPER, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + help: 'Show toggle button to display messages as plain text instead of Markdown-formatted content', + key: SETTINGS_KEYS.SHOW_RAW_OUTPUT_SWITCH, + label: 'Enable raw output toggle', + section: SETTINGS_SECTION_SLUGS.DEVELOPER, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + help: 'Expose a run_javascript tool to the model. Code runs in a Web Worker inside a sandboxed iframe with an opaque origin, isolated from the WebUI and its API, with a hard timeout.', + key: SETTINGS_KEYS.JS_SANDBOX_ENABLED, + label: 'JavaScript sandbox tool', + section: SETTINGS_SECTION_SLUGS.DEVELOPER, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + dependsOn: SETTINGS_KEYS.JS_SANDBOX_ENABLED, + help: 'Pre-load nerdamer in the sandbox for symbolic computation: simplify, diff, integrate, solve, and more. Requires "JavaScript sandbox tool" to be enabled.', + key: SETTINGS_KEYS.SYMBOLIC_MATH_ENABLED, + label: 'Symbolic math (nerdamer)', + section: SETTINGS_SECTION_SLUGS.DEVELOPER, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: '', + help: 'Custom JSON parameters to send to the API. Must be valid JSON format.', + key: SETTINGS_KEYS.CUSTOM_JSON, + label: 'Custom JSON', + section: SETTINGS_SECTION_SLUGS.DEVELOPER, + type: SettingsFieldType.TEXTAREA + }, + { + defaultValue: '', + help: 'CSS injected into the page at runtime. Set it here, or ship it server side via the --ui-config customCss field.', + key: SETTINGS_KEYS.CUSTOM_CSS, + label: 'Custom CSS', + section: SETTINGS_SECTION_SLUGS.DEVELOPER, + type: SettingsFieldType.TEXTAREA + } + ], + slug: SETTINGS_SECTION_SLUGS.DEVELOPER, + title: SETTINGS_SECTION_TITLES.DEVELOPER + }, + [SETTINGS_SECTION_SLUGS.DISPLAY]: { + icon: Monitor, + settings: [ + { + defaultValue: true, + help: 'Display generation statistics (tokens/second, token count, duration) below each assistant message.', + key: SETTINGS_KEYS.SHOW_MESSAGE_STATS, + label: 'Show message generation statistics', + section: SETTINGS_SECTION_SLUGS.DISPLAY, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + dependsOn: SETTINGS_KEYS.SHOW_MESSAGE_STATS, + help: 'Display per-turn statistics (tokens, duration) under each turn in agentic responses. Shown only when "Show message generation statistics" is enabled.', + key: SETTINGS_KEYS.SHOW_AGENTIC_TURN_STATS, + label: 'Show statistics for individual agentic turns', + section: SETTINGS_SECTION_SLUGS.DISPLAY, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: true, + help: 'Expand thought process by default when generating messages.', + key: SETTINGS_KEYS.SHOW_THOUGHT_IN_PROGRESS, + label: 'Show thought in progress', + section: SETTINGS_SECTION_SLUGS.DISPLAY, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + help: 'Automatically expand tool call details while executing and keep them expanded after completion.', + key: SETTINGS_KEYS.ALWAYS_SHOW_TOOL_CALL_CONTENT, + label: 'Always show tool call content', + section: SETTINGS_SECTION_SLUGS.DISPLAY, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: true, + help: 'Render user messages using markdown formatting in the chat. Turn this off to keep a message exactly as typed; @-mention badges show either way.', + key: SETTINGS_KEYS.RENDER_USER_CONTENT_AS_MARKDOWN, + label: 'Render user content as Markdown', + section: SETTINGS_SECTION_SLUGS.DISPLAY, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: true, + help: 'Render the reasoning/thinking block content as formatted Markdown instead of plain text.', + key: SETTINGS_KEYS.RENDER_THINKING_AS_MARKDOWN, + label: 'Render thinking as Markdown', + section: SETTINGS_SECTION_SLUGS.DISPLAY, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + help: 'Always display code blocks at their full natural height, overriding any height limits.', + key: SETTINGS_KEYS.FULL_HEIGHT_CODE_BLOCKS, + label: 'Use full height code blocks', + section: SETTINGS_SECTION_SLUGS.DISPLAY, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + help: 'Disable automatic scrolling while messages stream so you can control the viewport position manually.', + key: SETTINGS_KEYS.DISABLE_AUTO_SCROLL, + label: 'Disable automatic scroll', + section: SETTINGS_SECTION_SLUGS.DISPLAY, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + help: 'Always keep the sidebar visible on desktop instead of auto-hiding it.', + key: SETTINGS_KEYS.ALWAYS_SHOW_SIDEBAR_ON_DESKTOP, + label: 'Always show sidebar on desktop', + section: SETTINGS_SECTION_SLUGS.DISPLAY, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + help: 'Display full raw model identifiers (e.g. "ggml-org/GLM-4.7-Flash-GGUF:Q8_0") instead of parsed names with badges.', + key: SETTINGS_KEYS.SHOW_RAW_MODEL_NAMES, + label: 'Show raw model names', + section: SETTINGS_SECTION_SLUGS.DISPLAY, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: true, + help: 'Display quantization badges (e.g. Q8_0, Q4_K_M) next to model names throughout the interface.', + key: SETTINGS_KEYS.SHOW_MODEL_QUANTIZATION, + label: 'Show model quantization information', + section: SETTINGS_SECTION_SLUGS.DISPLAY, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: true, + help: 'Display model tags (e.g. "vision", "reasoning") next to model names throughout the interface.', + key: SETTINGS_KEYS.SHOW_MODEL_TAGS, + label: 'Show model tags', + section: SETTINGS_SECTION_SLUGS.DISPLAY, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + help: 'Display the current build version in the bottom-right corner of the interface.', + key: SETTINGS_KEYS.SHOW_BUILD_VERSION, + label: 'Show build version information', + section: SETTINGS_SECTION_SLUGS.DISPLAY, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + help: 'Display the full file system path inside file and folder @-mention badges instead of just the file or folder name.', + key: SETTINGS_KEYS.SHOW_FULL_PATH_IN_MENTIONS, + label: 'Show full path in mentions', + section: SETTINGS_SECTION_SLUGS.DISPLAY, + type: SettingsFieldType.CHECKBOX + } + ], + slug: SETTINGS_SECTION_SLUGS.DISPLAY, + title: SETTINGS_SECTION_TITLES.DISPLAY + }, + [SETTINGS_SECTION_SLUGS.GENERAL]: { + icon: Sliders, + settings: [ + { + defaultValue: ColorMode.SYSTEM, + help: 'Choose the color theme for the interface. You can choose between System (follows your device settings), Light, or Dark.', + key: SETTINGS_KEYS.THEME, + label: 'Theme', + options: COLOR_MODE_OPTIONS, + section: SETTINGS_SECTION_SLUGS.GENERAL, + type: SettingsFieldType.SELECT + }, + { + defaultValue: '', + help: `Set the API Key if you are using ${CLI_FLAGS.API_KEY} option for the server.`, + key: SETTINGS_KEYS.API_KEY, + label: 'API Key', + section: SETTINGS_SECTION_SLUGS.GENERAL, + type: SettingsFieldType.INPUT + }, + { + defaultValue: '', + help: 'The starting message that defines how model should behave.', + key: SETTINGS_KEYS.SYSTEM_MESSAGE, + label: 'System Message', + section: SETTINGS_SECTION_SLUGS.GENERAL, + type: SettingsFieldType.TEXTAREA + }, + { + defaultValue: 2500, + help: 'On pasting long text, it will be converted to a file. You can control the file length by setting the value of this parameter. Value 0 means disable.', + key: SETTINGS_KEYS.PASTE_LONG_TEXT_TO_FILE_LEN, + label: 'Paste long text to file length', + section: SETTINGS_SECTION_SLUGS.GENERAL, + type: SettingsFieldType.INPUT + }, + { + defaultValue: true, + help: 'Use Enter to send messages and Shift + Enter for new lines. When disabled, use Ctrl/Cmd + Enter.', + key: SETTINGS_KEYS.SEND_ON_ENTER, + label: 'Send message on Enter', + section: SETTINGS_SECTION_SLUGS.GENERAL, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + help: 'Automatically show microphone button instead of send button when textarea is empty for models with audio modality support.', + isExperimental: true, + key: SETTINGS_KEYS.AUTO_MIC_ON_EMPTY, + label: 'Show microphone on empty input', + section: SETTINGS_SECTION_SLUGS.GENERAL, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + help: 'Enable "Continue" button for assistant messages, including reasoning models.', + isExperimental: true, + key: SETTINGS_KEYS.ENABLE_CONTINUE_GENERATION, + label: 'Enable "Continue" button', + section: SETTINGS_SECTION_SLUGS.GENERAL, + type: SettingsFieldType.CHECKBOX + }, + { + ...TITLE_GENERATION_BASE, + defaultValue: true, + help: 'Choose how conversation titles are generated. The first non-empty line uses a fast deterministic rule; the LLM option uses a model-generated title from the first message exchange.', + key: SETTINGS_KEYS.TITLE_GENERATION_USE_FIRST_LINE, + label: 'Conversation title' + }, + { + defaultValue: TITLE_GENERATION.DEFAULT_PROMPT, + dependsOn: SETTINGS_KEYS.TITLE_GENERATION_USE_LLM, + help: 'Optional template for the title generation prompt. Use {{USER}} for the user message and {{ASSISTANT}} for the assistant message.', + key: SETTINGS_KEYS.TITLE_GENERATION_PROMPT, + label: 'LLM title generation prompt', + section: SETTINGS_SECTION_SLUGS.GENERAL, + type: SettingsFieldType.TEXTAREA + }, + { + defaultValue: false, + help: 'When copying a message with text attachments, combine them into a single plain text string instead of a special format that can be pasted back as attachments.', + key: SETTINGS_KEYS.COPY_TEXT_ATTACHMENTS_AS_PLAIN_TEXT, + label: 'Copy text attachments as plain text', + section: SETTINGS_SECTION_SLUGS.GENERAL, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: false, + help: 'Parse PDF as image instead of text. Automatically falls back to text processing for non-vision models.', + key: SETTINGS_KEYS.PDF_AS_IMAGE, + label: 'Parse PDF as image', + section: SETTINGS_SECTION_SLUGS.GENERAL, + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: 0, + help: 'Images larger than this will be resized before sending to server. Set to 0 to disable.', + key: SETTINGS_KEYS.MAX_IMAGE_RESOLUTION, + label: 'Maximum image resolution (megapixels)', + section: SETTINGS_SECTION_SLUGS.GENERAL, + type: SettingsFieldType.INPUT + } + ], + slug: SETTINGS_SECTION_SLUGS.GENERAL, + title: SETTINGS_SECTION_TITLES.GENERAL + }, + [SETTINGS_SECTION_SLUGS.PENALTIES]: { + icon: AlertTriangle, + settings: [ + { + defaultValue: undefined, + help: 'Last n tokens to consider for penalizing repetition', + key: SETTINGS_KEYS.REPEAT_LAST_N, + label: 'Repeat last N', + section: SETTINGS_SECTION_SLUGS.PENALTIES, + sync: { + paramType: SyncableParameterType.NUMBER, + serverKey: SETTINGS_KEYS.REPEAT_LAST_N + }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'Controls the repetition of token sequences in the generated text', + key: SETTINGS_KEYS.REPEAT_PENALTY, + label: 'Repeat penalty', + section: SETTINGS_SECTION_SLUGS.PENALTIES, + sync: { + paramType: SyncableParameterType.NUMBER, + serverKey: SETTINGS_KEYS.REPEAT_PENALTY + }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'Limits tokens based on whether they appear in the output or not.', + key: SETTINGS_KEYS.PRESENCE_PENALTY, + label: 'Presence penalty', + section: SETTINGS_SECTION_SLUGS.PENALTIES, + sync: { + paramType: SyncableParameterType.NUMBER, + serverKey: SETTINGS_KEYS.PRESENCE_PENALTY + }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'Limits tokens based on how often they appear in the output.', + key: SETTINGS_KEYS.FREQUENCY_PENALTY, + label: 'Frequency penalty', + section: SETTINGS_SECTION_SLUGS.PENALTIES, + sync: { + paramType: SyncableParameterType.NUMBER, + serverKey: SETTINGS_KEYS.FREQUENCY_PENALTY + }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the DRY sampling multiplier.', + key: SETTINGS_KEYS.DRY_MULTIPLIER, + label: 'DRY multiplier', + section: SETTINGS_SECTION_SLUGS.PENALTIES, + sync: { + paramType: SyncableParameterType.NUMBER, + serverKey: SETTINGS_KEYS.DRY_MULTIPLIER + }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the DRY sampling base value.', + key: SETTINGS_KEYS.DRY_BASE, + label: 'DRY base', + section: SETTINGS_SECTION_SLUGS.PENALTIES, + sync: { paramType: SyncableParameterType.NUMBER, serverKey: SETTINGS_KEYS.DRY_BASE }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the allowed length for DRY sampling.', + key: SETTINGS_KEYS.DRY_ALLOWED_LENGTH, + label: 'DRY allowed length', + section: SETTINGS_SECTION_SLUGS.PENALTIES, + sync: { + paramType: SyncableParameterType.NUMBER, + serverKey: SETTINGS_KEYS.DRY_ALLOWED_LENGTH + }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets DRY penalty for the last n tokens.', + key: SETTINGS_KEYS.DRY_PENALTY_LAST_N, + label: 'DRY penalty last N', + section: SETTINGS_SECTION_SLUGS.PENALTIES, + sync: { + paramType: SyncableParameterType.NUMBER, + serverKey: SETTINGS_KEYS.DRY_PENALTY_LAST_N + }, + type: SettingsFieldType.INPUT + } + ], + slug: SETTINGS_SECTION_SLUGS.PENALTIES, + title: SETTINGS_SECTION_TITLES.PENALTIES + }, + [SETTINGS_SECTION_SLUGS.SAMPLING]: { + icon: Funnel, + settings: [ + { + defaultValue: undefined, + help: 'Controls the randomness of the generated text by affecting the probability distribution of the output tokens. Higher = more random, lower = more focused.', + key: SETTINGS_KEYS.TEMPERATURE, + label: 'Temperature', + section: SETTINGS_SECTION_SLUGS.SAMPLING, + sync: { + paramType: SyncableParameterType.NUMBER, + serverKey: SETTINGS_KEYS.TEMPERATURE + }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'Addon for the temperature sampler. The added value to the range of dynamic temperature, which adjusts probabilities by entropy of tokens.', + key: SETTINGS_KEYS.DYNATEMP_RANGE, + label: 'Dynamic temperature range', + section: SETTINGS_SECTION_SLUGS.SAMPLING, + sync: { + paramType: SyncableParameterType.NUMBER, + serverKey: SETTINGS_KEYS.DYNATEMP_RANGE + }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'Addon for the temperature sampler. Smoothes out the probability redistribution based on the most probable token.', + key: SETTINGS_KEYS.DYNATEMP_EXPONENT, + label: 'Dynamic temperature exponent', + section: SETTINGS_SECTION_SLUGS.SAMPLING, + sync: { + paramType: SyncableParameterType.NUMBER, + serverKey: SETTINGS_KEYS.DYNATEMP_EXPONENT + }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'Keeps only k top tokens.', + key: SETTINGS_KEYS.TOP_K, + label: 'Top K', + section: SETTINGS_SECTION_SLUGS.SAMPLING, + sync: { paramType: SyncableParameterType.NUMBER, serverKey: SETTINGS_KEYS.TOP_K }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'Limits tokens to those that together have a cumulative probability of at least p', + key: SETTINGS_KEYS.TOP_P, + label: 'Top P', + section: SETTINGS_SECTION_SLUGS.SAMPLING, + sync: { paramType: SyncableParameterType.NUMBER, serverKey: SETTINGS_KEYS.TOP_P }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'Limits tokens based on the minimum probability for a token to be considered, relative to the probability of the most likely token.', + key: SETTINGS_KEYS.MIN_P, + label: 'Min P', + section: SETTINGS_SECTION_SLUGS.SAMPLING, + sync: { paramType: SyncableParameterType.NUMBER, serverKey: SETTINGS_KEYS.MIN_P }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'XTC sampler cuts out top tokens; this parameter controls the chance of cutting tokens at all. 0 disables XTC.', + key: SETTINGS_KEYS.XTC_PROBABILITY, + label: 'XTC probability', + section: SETTINGS_SECTION_SLUGS.SAMPLING, + sync: { + paramType: SyncableParameterType.NUMBER, + serverKey: SETTINGS_KEYS.XTC_PROBABILITY + }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'XTC sampler cuts out top tokens; this parameter controls the token probability that is required to cut that token.', + key: SETTINGS_KEYS.XTC_THRESHOLD, + label: 'XTC threshold', + section: SETTINGS_SECTION_SLUGS.SAMPLING, + sync: { + paramType: SyncableParameterType.NUMBER, + serverKey: SETTINGS_KEYS.XTC_THRESHOLD + }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'Sorts and limits tokens based on the difference between log-probability and entropy.', + key: SETTINGS_KEYS.TYP_P, + label: 'Typical P', + section: SETTINGS_SECTION_SLUGS.SAMPLING, + sync: { paramType: SyncableParameterType.NUMBER, serverKey: SETTINGS_KEYS.TYP_P }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: undefined, + help: 'The maximum number of token per output. Use -1 for infinite (no limit).', + key: SETTINGS_KEYS.MAX_TOKENS, + label: 'Max tokens', + section: SETTINGS_SECTION_SLUGS.SAMPLING, + sync: { + paramType: SyncableParameterType.NUMBER, + serverKey: SETTINGS_KEYS.MAX_TOKENS + }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: '', + help: 'The order at which samplers are applied, in simplified way. Default is "top_k;typ_p;top_p;min_p;temperature": top_k->typ_p->top_p->min_p->temperature', + key: SETTINGS_KEYS.SAMPLERS, + label: 'Samplers', + section: SETTINGS_SECTION_SLUGS.SAMPLING, + sync: { paramType: SyncableParameterType.STRING, serverKey: SETTINGS_KEYS.SAMPLERS }, + type: SettingsFieldType.INPUT + }, + { + defaultValue: false, + help: 'Enable backend-based samplers. When enabled, supported samplers run on the accelerator backend for faster sampling.', + key: SETTINGS_KEYS.BACKEND_SAMPLING, + label: 'Backend sampling', + section: SETTINGS_SECTION_SLUGS.SAMPLING, + type: SettingsFieldType.CHECKBOX + } + ], + slug: SETTINGS_SECTION_SLUGS.SAMPLING, + title: SETTINGS_SECTION_TITLES.SAMPLING + } +} as const; +const NON_UI_SETTINGS: SettingsEntry[] = [ + { + defaultValue: true, + help: 'Display the system message at the top of each conversation.', + key: SETTINGS_KEYS.SHOW_SYSTEM_MESSAGE, + label: 'Show system message', + type: SettingsFieldType.CHECKBOX + }, + { + defaultValue: '[]', + help: 'Configure MCP servers as a JSON list. Use the form in the MCP Client settings section to edit.', + key: SETTINGS_KEYS.MCP_SERVERS, + label: 'MCP servers', + type: SettingsFieldType.INPUT + }, + { + defaultValue: false, + help: 'Counterpart of the conversation title radio; stored and synced without a dedicated UI field.', + key: SETTINGS_KEYS.TITLE_GENERATION_USE_LLM, + label: 'Generate title with LLM', + type: SettingsFieldType.CHECKBOX + } + // { + // key: SETTINGS_KEYS.PY_INTERPRETER_ENABLED, + // label: 'Python interpreter enabled', + // help: 'Enable Python interpreter using Pyodide. Allows running Python code in markdown code blocks.', + // defaultValue: false, + // type: SettingsFieldType.CHECKBOX, + // isExperimental: true, + // + // } +]; + +function getAllSettings(): SettingsEntry[] { + const result: SettingsEntry[] = []; + + for (const section of Object.values(SETTINGS_REGISTRY)) { + result.push(...section.settings); + } + result.push(...NON_UI_SETTINGS); + + return result; +} + +/** Flat config object stored in localStorage. */ +export const SETTING_CONFIG_DEFAULT: Record = Object.fromEntries( + getAllSettings().map((s) => [s.key, s.defaultValue]) +) as Record; + +/** Help text for every setting (including non-UI). */ +export const SETTING_CONFIG_INFO: Record = Object.fromEntries( + getAllSettings().map((s) => [s.key, s.help]) +) as Record; + +/** Theme select options. */ +export const SETTINGS_COLOR_MODES_CONFIG = COLOR_MODE_OPTIONS; + +/** Sidebar sections + field configs (as consumed by UI). */ +export const SETTINGS_CHAT_SECTIONS: SettingsSection[] = [ + ...Object.values(SETTINGS_REGISTRY).map((section) => ({ + fields: section.settings.map((s) => ({ + dependsOn: s.dependsOn, + help: s.help, + isExperimental: s.isExperimental, + isPositiveInteger: s.isPositiveInteger, + key: s.key, + label: s.label, + max: s.max, + min: s.min, + options: s.options, + placeholder: s.placeholder, + radioOptions: s.radioOptions, + type: s.type + })), + icon: section.icon, + slug: section.slug, + title: section.title + })), + ...STANDALONE_SECTIONS +]; + +/** INPUT-type settings whose value is a number. */ +export const NUMERIC_FIELDS = getAllSettings() + .filter((s) => s.type === SettingsFieldType.INPUT && typeof s.defaultValue !== 'string') + .map((s) => s.key) as readonly string[]; + +/** Numeric fields clamped to ≥ 1 and rounded. */ +export const POSITIVE_INTEGER_FIELDS = getAllSettings() + .filter((s) => s.isPositiveInteger) + .map((s) => s.key) as readonly string[]; + +/** Derived for the parameter sync service. */ +export const SYNCABLE_PARAMETERS: SyncableParameter[] = getAllSettings() + .filter((s) => s.sync !== undefined) + .map((s) => ({ + canSync: true, + key: s.key, + serverKey: s.sync!.serverKey, + type: s.sync!.paramType + })); + +export const SETTINGS_FALLBACK_EXIT_ROUTE = ROUTES.START; diff --git a/tools/ui/src/lib/constants/settings-registry.ts b/tools/ui/src/lib/constants/settings-registry.ts deleted file mode 100644 index ada029a33..000000000 --- a/tools/ui/src/lib/constants/settings-registry.ts +++ /dev/null @@ -1,756 +0,0 @@ -import { CLI_FLAGS } from './cli-flags'; -import { DEFAULT_MCP_CONFIG } from './mcp'; -import { ROUTES, SETTINGS_SECTION_SLUGS } from './routes'; -import { SETTINGS_KEYS } from './settings-keys'; -import { TITLE_GENERATION } from './title-generation'; -import { - FILE_GLOB_SEARCH_PICKERS_DEFAULT_SEARCH_DEPTH, - FILE_GLOB_SEARCH_PICKERS_MAX_SEARCH_DEPTH -} from './working-directory'; -import { - AlertTriangle, - Code, - Database, - Funnel, - ListRestart, - Monitor, - Monitor as MonitorIcon, - Moon, - PencilRuler, - Sliders, - Sun -} from '@lucide/svelte'; -import { SyncableParameterType } from '$lib/enums'; -import { SettingsFieldType } from '$lib/enums/settings.enums'; -import { ColorMode } from '$lib/enums/ui.enums'; -import type { - SettingsConfigValue, - SettingsEntry, - SettingsSection, - SettingsSectionEntry, - SettingsSectionTitle, - SyncableParameter -} from '$lib/types'; -import type { Component } from 'svelte'; - -export const SETTINGS_SECTION_TITLES = { - AGENTIC: 'Agentic', - DEVELOPER: 'Developer', - DISPLAY: 'Display', - GENERAL: 'General', - IMPORT_EXPORT: 'Import/Export', - PENALTIES: 'Penalties', - SAMPLING: 'Sampling', - TOOLS: 'Tools' -} as const; - -const STANDALONE_SECTIONS: { title: SettingsSectionTitle; slug: string; icon: Component }[] = [ - { icon: PencilRuler, slug: SETTINGS_SECTION_SLUGS.TOOLS, title: SETTINGS_SECTION_TITLES.TOOLS }, - { - icon: Database, - slug: SETTINGS_SECTION_SLUGS.IMPORT_EXPORT, - title: SETTINGS_SECTION_TITLES.IMPORT_EXPORT - } -]; -const COLOR_MODE_OPTIONS: Array<{ value: string; label: string; icon: Component }> = [ - { icon: MonitorIcon, label: 'System', value: ColorMode.SYSTEM }, - { icon: Sun, label: 'Light', value: ColorMode.LIGHT }, - { icon: Moon, label: 'Dark', value: ColorMode.DARK } -]; -// Shared options for the title-generation radio group. Both paired registry entries -// (USE_FIRST_LINE, USE_LLM) reference this list so labels stay in lockstep. -const TITLE_GENERATION_RADIO_OPTIONS: Array<{ - value: string; - label: string; - key: string; - isExperimental?: boolean; -}> = [ - { - key: SETTINGS_KEYS.TITLE_GENERATION_USE_FIRST_LINE, - label: 'Use first non-empty line for the conversation title', - value: 'firstLine' - }, - { - isExperimental: true, - key: SETTINGS_KEYS.TITLE_GENERATION_USE_LLM, - label: 'Generate title with LLM', - value: 'llm' - } -]; -// Common shape for the conversation title radio entry. -const TITLE_GENERATION_BASE = { - radioOptions: TITLE_GENERATION_RADIO_OPTIONS, - section: SETTINGS_SECTION_SLUGS.GENERAL, - type: SettingsFieldType.RADIO -} as const; -const SETTINGS_REGISTRY: Record = { - [SETTINGS_SECTION_SLUGS.AGENTIC]: { - icon: ListRestart, - settings: [ - { - defaultValue: 10, - help: 'Maximum number of tool execution cycles before stopping (prevents infinite loops).', - isPositiveInteger: true, - key: SETTINGS_KEYS.AGENTIC_MAX_TURNS, - label: 'Agentic turns', - section: SETTINGS_SECTION_SLUGS.AGENTIC, - type: SettingsFieldType.INPUT - }, - { - defaultValue: DEFAULT_MCP_CONFIG.requestTimeoutSeconds, - help: 'Timeout for individual MCP tool calls.', - isPositiveInteger: true, - key: SETTINGS_KEYS.MCP_REQUEST_TIMEOUT_SECONDS, - label: 'MCP request timeout (seconds)', - section: SETTINGS_SECTION_SLUGS.AGENTIC, - type: SettingsFieldType.INPUT - }, - { - defaultValue: FILE_GLOB_SEARCH_PICKERS_DEFAULT_SEARCH_DEPTH, - help: 'How many directory levels below the working directory the @-mention file search descends. Larger values surface deeply nested files but take longer on large trees.', - isPositiveInteger: true, - key: SETTINGS_KEYS.MENTION_SEARCH_MAX_DEPTH, - label: 'Mention search depth', - max: FILE_GLOB_SEARCH_PICKERS_MAX_SEARCH_DEPTH, - min: 1, - placeholder: `${FILE_GLOB_SEARCH_PICKERS_DEFAULT_SEARCH_DEPTH}`, - section: SETTINGS_SECTION_SLUGS.AGENTIC, - type: SettingsFieldType.INPUT - } - ], - slug: SETTINGS_SECTION_SLUGS.AGENTIC, - title: SETTINGS_SECTION_TITLES.AGENTIC - }, - [SETTINGS_SECTION_SLUGS.DEVELOPER]: { - icon: Code, - settings: [ - { - defaultValue: false, - help: 'After each response, re-submit the conversation to pre-fill the server KV cache. Makes the next turn faster since the prompt is already encoded while you read the response.', - key: SETTINGS_KEYS.PRE_ENCODE_CONVERSATION, - label: 'Pre-fill KV cache after response', - section: SETTINGS_SECTION_SLUGS.DEVELOPER, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - help: 'Send reasoning_format=none so the server returns thinking tokens inline instead of extracting them into a separate field.', - key: SETTINGS_KEYS.DISABLE_REASONING_PARSING, - label: 'Disable reasoning content parsing', - section: SETTINGS_SECTION_SLUGS.DEVELOPER, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - help: 'Strip thinking from previous messages before sending. When off, thinking is sent back via the reasoning_content field so the model sees its own chain-of-thought across turns.', - key: SETTINGS_KEYS.EXCLUDE_REASONING_FROM_CONTEXT, - label: 'Exclude reasoning from context', - section: SETTINGS_SECTION_SLUGS.DEVELOPER, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - help: 'Show toggle button to display messages as plain text instead of Markdown-formatted content', - key: SETTINGS_KEYS.SHOW_RAW_OUTPUT_SWITCH, - label: 'Enable raw output toggle', - section: SETTINGS_SECTION_SLUGS.DEVELOPER, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - help: 'Expose a run_javascript tool to the model. Code runs in a Web Worker inside a sandboxed iframe with an opaque origin, isolated from the WebUI and its API, with a hard timeout.', - key: SETTINGS_KEYS.JS_SANDBOX_ENABLED, - label: 'JavaScript sandbox tool', - section: SETTINGS_SECTION_SLUGS.DEVELOPER, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - dependsOn: SETTINGS_KEYS.JS_SANDBOX_ENABLED, - help: 'Pre-load nerdamer in the sandbox for symbolic computation: simplify, diff, integrate, solve, and more. Requires "JavaScript sandbox tool" to be enabled.', - key: SETTINGS_KEYS.SYMBOLIC_MATH_ENABLED, - label: 'Symbolic math (nerdamer)', - section: SETTINGS_SECTION_SLUGS.DEVELOPER, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: '', - help: 'Custom JSON parameters to send to the API. Must be valid JSON format.', - key: SETTINGS_KEYS.CUSTOM_JSON, - label: 'Custom JSON', - section: SETTINGS_SECTION_SLUGS.DEVELOPER, - type: SettingsFieldType.TEXTAREA - }, - { - defaultValue: '', - help: 'CSS injected into the page at runtime. Set it here, or ship it server side via the --ui-config customCss field.', - key: SETTINGS_KEYS.CUSTOM_CSS, - label: 'Custom CSS', - section: SETTINGS_SECTION_SLUGS.DEVELOPER, - type: SettingsFieldType.TEXTAREA - } - ], - slug: SETTINGS_SECTION_SLUGS.DEVELOPER, - title: SETTINGS_SECTION_TITLES.DEVELOPER - }, - [SETTINGS_SECTION_SLUGS.DISPLAY]: { - icon: Monitor, - settings: [ - { - defaultValue: true, - help: 'Display generation statistics (tokens/second, token count, duration) below each assistant message.', - key: SETTINGS_KEYS.SHOW_MESSAGE_STATS, - label: 'Show message generation statistics', - section: SETTINGS_SECTION_SLUGS.DISPLAY, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - dependsOn: SETTINGS_KEYS.SHOW_MESSAGE_STATS, - help: 'Display per-turn statistics (tokens, duration) under each turn in agentic responses. Shown only when "Show message generation statistics" is enabled.', - key: SETTINGS_KEYS.SHOW_AGENTIC_TURN_STATS, - label: 'Show statistics for individual agentic turns', - section: SETTINGS_SECTION_SLUGS.DISPLAY, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: true, - help: 'Expand thought process by default when generating messages.', - key: SETTINGS_KEYS.SHOW_THOUGHT_IN_PROGRESS, - label: 'Show thought in progress', - section: SETTINGS_SECTION_SLUGS.DISPLAY, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - help: 'Automatically expand tool call details while executing and keep them expanded after completion.', - key: SETTINGS_KEYS.ALWAYS_SHOW_TOOL_CALL_CONTENT, - label: 'Always show tool call content', - section: SETTINGS_SECTION_SLUGS.DISPLAY, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: true, - help: 'Render user messages using markdown formatting in the chat. Turn this off to keep a message exactly as typed; @-mention badges show either way.', - key: SETTINGS_KEYS.RENDER_USER_CONTENT_AS_MARKDOWN, - label: 'Render user content as Markdown', - section: SETTINGS_SECTION_SLUGS.DISPLAY, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: true, - help: 'Render the reasoning/thinking block content as formatted Markdown instead of plain text.', - key: SETTINGS_KEYS.RENDER_THINKING_AS_MARKDOWN, - label: 'Render thinking as Markdown', - section: SETTINGS_SECTION_SLUGS.DISPLAY, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - help: 'Always display code blocks at their full natural height, overriding any height limits.', - key: SETTINGS_KEYS.FULL_HEIGHT_CODE_BLOCKS, - label: 'Use full height code blocks', - section: SETTINGS_SECTION_SLUGS.DISPLAY, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - help: 'Disable automatic scrolling while messages stream so you can control the viewport position manually.', - key: SETTINGS_KEYS.DISABLE_AUTO_SCROLL, - label: 'Disable automatic scroll', - section: SETTINGS_SECTION_SLUGS.DISPLAY, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - help: 'Always keep the sidebar visible on desktop instead of auto-hiding it.', - key: SETTINGS_KEYS.ALWAYS_SHOW_SIDEBAR_ON_DESKTOP, - label: 'Always show sidebar on desktop', - section: SETTINGS_SECTION_SLUGS.DISPLAY, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - help: 'Display full raw model identifiers (e.g. "ggml-org/GLM-4.7-Flash-GGUF:Q8_0") instead of parsed names with badges.', - key: SETTINGS_KEYS.SHOW_RAW_MODEL_NAMES, - label: 'Show raw model names', - section: SETTINGS_SECTION_SLUGS.DISPLAY, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: true, - help: 'Display quantization badges (e.g. Q8_0, Q4_K_M) next to model names throughout the interface.', - key: SETTINGS_KEYS.SHOW_MODEL_QUANTIZATION, - label: 'Show model quantization information', - section: SETTINGS_SECTION_SLUGS.DISPLAY, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: true, - help: 'Display model tags (e.g. "vision", "reasoning") next to model names throughout the interface.', - key: SETTINGS_KEYS.SHOW_MODEL_TAGS, - label: 'Show model tags', - section: SETTINGS_SECTION_SLUGS.DISPLAY, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - help: 'Display the current build version in the bottom-right corner of the interface.', - key: SETTINGS_KEYS.SHOW_BUILD_VERSION, - label: 'Show build version information', - section: SETTINGS_SECTION_SLUGS.DISPLAY, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - help: 'Display the full file system path inside file and folder @-mention badges instead of just the file or folder name.', - key: SETTINGS_KEYS.SHOW_FULL_PATH_IN_MENTIONS, - label: 'Show full path in mentions', - section: SETTINGS_SECTION_SLUGS.DISPLAY, - type: SettingsFieldType.CHECKBOX - } - ], - slug: SETTINGS_SECTION_SLUGS.DISPLAY, - title: SETTINGS_SECTION_TITLES.DISPLAY - }, - [SETTINGS_SECTION_SLUGS.GENERAL]: { - icon: Sliders, - settings: [ - { - defaultValue: ColorMode.SYSTEM, - help: 'Choose the color theme for the interface. You can choose between System (follows your device settings), Light, or Dark.', - key: SETTINGS_KEYS.THEME, - label: 'Theme', - options: COLOR_MODE_OPTIONS, - section: SETTINGS_SECTION_SLUGS.GENERAL, - type: SettingsFieldType.SELECT - }, - { - defaultValue: '', - help: `Set the API Key if you are using ${CLI_FLAGS.API_KEY} option for the server.`, - key: SETTINGS_KEYS.API_KEY, - label: 'API Key', - section: SETTINGS_SECTION_SLUGS.GENERAL, - type: SettingsFieldType.INPUT - }, - { - defaultValue: '', - help: 'The starting message that defines how model should behave.', - key: SETTINGS_KEYS.SYSTEM_MESSAGE, - label: 'System Message', - section: SETTINGS_SECTION_SLUGS.GENERAL, - type: SettingsFieldType.TEXTAREA - }, - { - defaultValue: 2500, - help: 'On pasting long text, it will be converted to a file. You can control the file length by setting the value of this parameter. Value 0 means disable.', - key: SETTINGS_KEYS.PASTE_LONG_TEXT_TO_FILE_LEN, - label: 'Paste long text to file length', - section: SETTINGS_SECTION_SLUGS.GENERAL, - type: SettingsFieldType.INPUT - }, - { - defaultValue: true, - help: 'Use Enter to send messages and Shift + Enter for new lines. When disabled, use Ctrl/Cmd + Enter.', - key: SETTINGS_KEYS.SEND_ON_ENTER, - label: 'Send message on Enter', - section: SETTINGS_SECTION_SLUGS.GENERAL, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - help: 'Automatically show microphone button instead of send button when textarea is empty for models with audio modality support.', - isExperimental: true, - key: SETTINGS_KEYS.AUTO_MIC_ON_EMPTY, - label: 'Show microphone on empty input', - section: SETTINGS_SECTION_SLUGS.GENERAL, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - help: 'Enable "Continue" button for assistant messages, including reasoning models.', - isExperimental: true, - key: SETTINGS_KEYS.ENABLE_CONTINUE_GENERATION, - label: 'Enable "Continue" button', - section: SETTINGS_SECTION_SLUGS.GENERAL, - type: SettingsFieldType.CHECKBOX - }, - { - ...TITLE_GENERATION_BASE, - defaultValue: true, - help: 'Choose how conversation titles are generated. The first non-empty line uses a fast deterministic rule; the LLM option uses a model-generated title from the first message exchange.', - key: SETTINGS_KEYS.TITLE_GENERATION_USE_FIRST_LINE, - label: 'Conversation title' - }, - { - defaultValue: TITLE_GENERATION.DEFAULT_PROMPT, - dependsOn: SETTINGS_KEYS.TITLE_GENERATION_USE_LLM, - help: 'Optional template for the title generation prompt. Use {{USER}} for the user message and {{ASSISTANT}} for the assistant message.', - key: SETTINGS_KEYS.TITLE_GENERATION_PROMPT, - label: 'LLM title generation prompt', - section: SETTINGS_SECTION_SLUGS.GENERAL, - type: SettingsFieldType.TEXTAREA - }, - { - defaultValue: false, - help: 'When copying a message with text attachments, combine them into a single plain text string instead of a special format that can be pasted back as attachments.', - key: SETTINGS_KEYS.COPY_TEXT_ATTACHMENTS_AS_PLAIN_TEXT, - label: 'Copy text attachments as plain text', - section: SETTINGS_SECTION_SLUGS.GENERAL, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: false, - help: 'Parse PDF as image instead of text. Automatically falls back to text processing for non-vision models.', - key: SETTINGS_KEYS.PDF_AS_IMAGE, - label: 'Parse PDF as image', - section: SETTINGS_SECTION_SLUGS.GENERAL, - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: 0, - help: 'Images larger than this will be resized before sending to server. Set to 0 to disable.', - key: SETTINGS_KEYS.MAX_IMAGE_RESOLUTION, - label: 'Maximum image resolution (megapixels)', - section: SETTINGS_SECTION_SLUGS.GENERAL, - type: SettingsFieldType.INPUT - } - ], - slug: SETTINGS_SECTION_SLUGS.GENERAL, - title: SETTINGS_SECTION_TITLES.GENERAL - }, - [SETTINGS_SECTION_SLUGS.PENALTIES]: { - icon: AlertTriangle, - settings: [ - { - defaultValue: undefined, - help: 'Last n tokens to consider for penalizing repetition', - key: SETTINGS_KEYS.REPEAT_LAST_N, - label: 'Repeat last N', - section: SETTINGS_SECTION_SLUGS.PENALTIES, - sync: { - paramType: SyncableParameterType.NUMBER, - serverKey: SETTINGS_KEYS.REPEAT_LAST_N - }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'Controls the repetition of token sequences in the generated text', - key: SETTINGS_KEYS.REPEAT_PENALTY, - label: 'Repeat penalty', - section: SETTINGS_SECTION_SLUGS.PENALTIES, - sync: { - paramType: SyncableParameterType.NUMBER, - serverKey: SETTINGS_KEYS.REPEAT_PENALTY - }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'Limits tokens based on whether they appear in the output or not.', - key: SETTINGS_KEYS.PRESENCE_PENALTY, - label: 'Presence penalty', - section: SETTINGS_SECTION_SLUGS.PENALTIES, - sync: { - paramType: SyncableParameterType.NUMBER, - serverKey: SETTINGS_KEYS.PRESENCE_PENALTY - }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'Limits tokens based on how often they appear in the output.', - key: SETTINGS_KEYS.FREQUENCY_PENALTY, - label: 'Frequency penalty', - section: SETTINGS_SECTION_SLUGS.PENALTIES, - sync: { - paramType: SyncableParameterType.NUMBER, - serverKey: SETTINGS_KEYS.FREQUENCY_PENALTY - }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the DRY sampling multiplier.', - key: SETTINGS_KEYS.DRY_MULTIPLIER, - label: 'DRY multiplier', - section: SETTINGS_SECTION_SLUGS.PENALTIES, - sync: { - paramType: SyncableParameterType.NUMBER, - serverKey: SETTINGS_KEYS.DRY_MULTIPLIER - }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the DRY sampling base value.', - key: SETTINGS_KEYS.DRY_BASE, - label: 'DRY base', - section: SETTINGS_SECTION_SLUGS.PENALTIES, - sync: { paramType: SyncableParameterType.NUMBER, serverKey: SETTINGS_KEYS.DRY_BASE }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets the allowed length for DRY sampling.', - key: SETTINGS_KEYS.DRY_ALLOWED_LENGTH, - label: 'DRY allowed length', - section: SETTINGS_SECTION_SLUGS.PENALTIES, - sync: { - paramType: SyncableParameterType.NUMBER, - serverKey: SETTINGS_KEYS.DRY_ALLOWED_LENGTH - }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'DRY sampling reduces repetition in generated text even across long contexts. This parameter sets DRY penalty for the last n tokens.', - key: SETTINGS_KEYS.DRY_PENALTY_LAST_N, - label: 'DRY penalty last N', - section: SETTINGS_SECTION_SLUGS.PENALTIES, - sync: { - paramType: SyncableParameterType.NUMBER, - serverKey: SETTINGS_KEYS.DRY_PENALTY_LAST_N - }, - type: SettingsFieldType.INPUT - } - ], - slug: SETTINGS_SECTION_SLUGS.PENALTIES, - title: SETTINGS_SECTION_TITLES.PENALTIES - }, - [SETTINGS_SECTION_SLUGS.SAMPLING]: { - icon: Funnel, - settings: [ - { - defaultValue: undefined, - help: 'Controls the randomness of the generated text by affecting the probability distribution of the output tokens. Higher = more random, lower = more focused.', - key: SETTINGS_KEYS.TEMPERATURE, - label: 'Temperature', - section: SETTINGS_SECTION_SLUGS.SAMPLING, - sync: { - paramType: SyncableParameterType.NUMBER, - serverKey: SETTINGS_KEYS.TEMPERATURE - }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'Addon for the temperature sampler. The added value to the range of dynamic temperature, which adjusts probabilities by entropy of tokens.', - key: SETTINGS_KEYS.DYNATEMP_RANGE, - label: 'Dynamic temperature range', - section: SETTINGS_SECTION_SLUGS.SAMPLING, - sync: { - paramType: SyncableParameterType.NUMBER, - serverKey: SETTINGS_KEYS.DYNATEMP_RANGE - }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'Addon for the temperature sampler. Smoothes out the probability redistribution based on the most probable token.', - key: SETTINGS_KEYS.DYNATEMP_EXPONENT, - label: 'Dynamic temperature exponent', - section: SETTINGS_SECTION_SLUGS.SAMPLING, - sync: { - paramType: SyncableParameterType.NUMBER, - serverKey: SETTINGS_KEYS.DYNATEMP_EXPONENT - }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'Keeps only k top tokens.', - key: SETTINGS_KEYS.TOP_K, - label: 'Top K', - section: SETTINGS_SECTION_SLUGS.SAMPLING, - sync: { paramType: SyncableParameterType.NUMBER, serverKey: SETTINGS_KEYS.TOP_K }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'Limits tokens to those that together have a cumulative probability of at least p', - key: SETTINGS_KEYS.TOP_P, - label: 'Top P', - section: SETTINGS_SECTION_SLUGS.SAMPLING, - sync: { paramType: SyncableParameterType.NUMBER, serverKey: SETTINGS_KEYS.TOP_P }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'Limits tokens based on the minimum probability for a token to be considered, relative to the probability of the most likely token.', - key: SETTINGS_KEYS.MIN_P, - label: 'Min P', - section: SETTINGS_SECTION_SLUGS.SAMPLING, - sync: { paramType: SyncableParameterType.NUMBER, serverKey: SETTINGS_KEYS.MIN_P }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'XTC sampler cuts out top tokens; this parameter controls the chance of cutting tokens at all. 0 disables XTC.', - key: SETTINGS_KEYS.XTC_PROBABILITY, - label: 'XTC probability', - section: SETTINGS_SECTION_SLUGS.SAMPLING, - sync: { - paramType: SyncableParameterType.NUMBER, - serverKey: SETTINGS_KEYS.XTC_PROBABILITY - }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'XTC sampler cuts out top tokens; this parameter controls the token probability that is required to cut that token.', - key: SETTINGS_KEYS.XTC_THRESHOLD, - label: 'XTC threshold', - section: SETTINGS_SECTION_SLUGS.SAMPLING, - sync: { - paramType: SyncableParameterType.NUMBER, - serverKey: SETTINGS_KEYS.XTC_THRESHOLD - }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'Sorts and limits tokens based on the difference between log-probability and entropy.', - key: SETTINGS_KEYS.TYP_P, - label: 'Typical P', - section: SETTINGS_SECTION_SLUGS.SAMPLING, - sync: { paramType: SyncableParameterType.NUMBER, serverKey: SETTINGS_KEYS.TYP_P }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: undefined, - help: 'The maximum number of token per output. Use -1 for infinite (no limit).', - key: SETTINGS_KEYS.MAX_TOKENS, - label: 'Max tokens', - section: SETTINGS_SECTION_SLUGS.SAMPLING, - sync: { - paramType: SyncableParameterType.NUMBER, - serverKey: SETTINGS_KEYS.MAX_TOKENS - }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: '', - help: 'The order at which samplers are applied, in simplified way. Default is "top_k;typ_p;top_p;min_p;temperature": top_k->typ_p->top_p->min_p->temperature', - key: SETTINGS_KEYS.SAMPLERS, - label: 'Samplers', - section: SETTINGS_SECTION_SLUGS.SAMPLING, - sync: { paramType: SyncableParameterType.STRING, serverKey: SETTINGS_KEYS.SAMPLERS }, - type: SettingsFieldType.INPUT - }, - { - defaultValue: false, - help: 'Enable backend-based samplers. When enabled, supported samplers run on the accelerator backend for faster sampling.', - key: SETTINGS_KEYS.BACKEND_SAMPLING, - label: 'Backend sampling', - section: SETTINGS_SECTION_SLUGS.SAMPLING, - type: SettingsFieldType.CHECKBOX - } - ], - slug: SETTINGS_SECTION_SLUGS.SAMPLING, - title: SETTINGS_SECTION_TITLES.SAMPLING - } -} as const; -const NON_UI_SETTINGS: SettingsEntry[] = [ - { - defaultValue: true, - help: 'Display the system message at the top of each conversation.', - key: SETTINGS_KEYS.SHOW_SYSTEM_MESSAGE, - label: 'Show system message', - type: SettingsFieldType.CHECKBOX - }, - { - defaultValue: '[]', - help: 'Configure MCP servers as a JSON list. Use the form in the MCP Client settings section to edit.', - key: SETTINGS_KEYS.MCP_SERVERS, - label: 'MCP servers', - type: SettingsFieldType.INPUT - }, - { - defaultValue: false, - help: 'Counterpart of the conversation title radio; stored and synced without a dedicated UI field.', - key: SETTINGS_KEYS.TITLE_GENERATION_USE_LLM, - label: 'Generate title with LLM', - type: SettingsFieldType.CHECKBOX - } - // { - // key: SETTINGS_KEYS.PY_INTERPRETER_ENABLED, - // label: 'Python interpreter enabled', - // help: 'Enable Python interpreter using Pyodide. Allows running Python code in markdown code blocks.', - // defaultValue: false, - // type: SettingsFieldType.CHECKBOX, - // isExperimental: true, - // - // } -]; - -function getAllSettings(): SettingsEntry[] { - const result: SettingsEntry[] = []; - - for (const section of Object.values(SETTINGS_REGISTRY)) { - result.push(...section.settings); - } - result.push(...NON_UI_SETTINGS); - - return result; -} - -/** Flat config object stored in localStorage. */ -export const SETTING_CONFIG_DEFAULT: Record = Object.fromEntries( - getAllSettings().map((s) => [s.key, s.defaultValue]) -) as Record; - -/** Help text for every setting (including non-UI). */ -export const SETTING_CONFIG_INFO: Record = Object.fromEntries( - getAllSettings().map((s) => [s.key, s.help]) -) as Record; - -/** Theme select options. */ -export const SETTINGS_COLOR_MODES_CONFIG = COLOR_MODE_OPTIONS; - -/** Sidebar sections + field configs (as consumed by UI). */ -export const SETTINGS_CHAT_SECTIONS: SettingsSection[] = [ - ...Object.values(SETTINGS_REGISTRY).map((section) => ({ - fields: section.settings.map((s) => ({ - dependsOn: s.dependsOn, - help: s.help, - isExperimental: s.isExperimental, - isPositiveInteger: s.isPositiveInteger, - key: s.key, - label: s.label, - max: s.max, - min: s.min, - options: s.options, - placeholder: s.placeholder, - radioOptions: s.radioOptions, - type: s.type - })), - icon: section.icon, - slug: section.slug, - title: section.title - })), - ...STANDALONE_SECTIONS -]; - -/** INPUT-type settings whose value is a number. */ -export const NUMERIC_FIELDS = getAllSettings() - .filter((s) => s.type === SettingsFieldType.INPUT && typeof s.defaultValue !== 'string') - .map((s) => s.key) as readonly string[]; - -/** Numeric fields clamped to ≥ 1 and rounded. */ -export const POSITIVE_INTEGER_FIELDS = getAllSettings() - .filter((s) => s.isPositiveInteger) - .map((s) => s.key) as readonly string[]; - -/** Derived for the parameter sync service. */ -export const SYNCABLE_PARAMETERS: SyncableParameter[] = getAllSettings() - .filter((s) => s.sync !== undefined) - .map((s) => ({ - canSync: true, - key: s.key, - serverKey: s.sync!.serverKey, - type: s.sync!.paramType - })); - -export const SETTINGS_FALLBACK_EXIT_ROUTE = ROUTES.START; diff --git a/tools/ui/src/lib/constants/special-characters.constants.ts b/tools/ui/src/lib/constants/special-characters.constants.ts new file mode 100644 index 000000000..aaeebca33 --- /dev/null +++ b/tools/ui/src/lib/constants/special-characters.constants.ts @@ -0,0 +1,16 @@ +// Control / whitespace / formatting characters that appear literally inside rendered text. + +/** Line feed. */ +export const NEWLINE = '\n'; + +/** Horizontal tab. */ +export const TAB = '\t'; + +/** Non-breaking space. */ +export const NBSP = '\u00a0'; + +/** Non-breaking spaces used to render a tab stop that whitespace collapsing would otherwise squash. */ +export const TAB_AS_SPACES = NBSP.repeat(4); + +/** Matches a CR-terminated or bare LF line break. */ +export const LINE_BREAK = /\r?\n/; diff --git a/tools/ui/src/lib/constants/sse.ts b/tools/ui/src/lib/constants/sse.ts deleted file mode 100644 index 0eb4b6ede..000000000 --- a/tools/ui/src/lib/constants/sse.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Server-sent events wire format, shared by the chat stream and the - * /models/sse status feed (text/event-stream). - */ - -// blank line between two events -export const SSE_RECORD_SEPARATOR = '\n\n'; - -// line break inside an event -export const SSE_LINE_SEPARATOR = '\n'; - -// data field prefix, the value follows after an optional space -export const SSE_DATA_PREFIX = 'data:'; - -// end-of-stream marker on the chat completion stream -export const SSE_DONE_MARKER = '[DONE]'; diff --git a/tools/ui/src/lib/constants/storage.constants.ts b/tools/ui/src/lib/constants/storage.constants.ts new file mode 100644 index 000000000..5d9acaafb --- /dev/null +++ b/tools/ui/src/lib/constants/storage.constants.ts @@ -0,0 +1,53 @@ +/** + * Storage-related constants (localStorage, IndexedDB). + * + * Centralized to ensure consistency across the app and simplify future + * name changes. + */ + +/** Name prefix for all localStorage keys */ +export const STORAGE_APP_NAME = 'LlamaUi'; + +/** Deprecated localStorage key prefix (old app name) */ +export const STORAGE_APP_NAME_DEPRECATED = 'LlamaCppWebui'; + +/** @deprecated Deprecated IndexedDB name — will be removed after all users have migrated */ +export const DB_APP_NAME_DEPRECATED = 'LlamacppWebui'; + +export const ALWAYS_ALLOWED_TOOLS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.alwaysAllowedTools`; +export const CONFIG_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.config`; +export const DISABLED_TOOLS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.disabledTools`; + +/** Disabled tools keyed by stable selection identity, no migration from the name based key */ +export const DISABLED_TOOL_KEYS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.disabledToolKeys`; +export const FAVORITE_MODELS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.favoriteModels`; +export const REASONING_EFFORT_DEFAULT_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.reasoningEffortDefault`; +export const USER_OVERRIDES_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.userOverrides`; +export const DISMISSED_RECOMMENDED_MCP_SERVERS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.dismissedRecommendedMcpServers`; + +/** Key prefix for per-conversation resumable stream state, conversationId is appended */ +export const STREAM_RESUME_LOCALSTORAGE_KEY_PREFIX = `${STORAGE_APP_NAME}.streamResume.`; + +// Deprecated old key names (kept for backward compat while users migrate) +/** @deprecated Use {@link ALWAYS_ALLOWED_TOOLS_LOCALSTORAGE_KEY} instead */ +export const DEPRECATED_ALWAYS_ALLOWED_TOOLS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME_DEPRECATED}.alwaysAllowedTools`; +/** @deprecated Use {@link CONFIG_LOCALSTORAGE_KEY} instead */ +export const DEPRECATED_CONFIG_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME_DEPRECATED}.config`; +/** @deprecated Use {@link DISABLED_TOOLS_LOCALSTORAGE_KEY} instead */ +export const DEPRECATED_DISABLED_TOOLS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME_DEPRECATED}.disabledTools`; +/** @deprecated Use {@link FAVORITE_MODELS_LOCALSTORAGE_KEY} instead */ +export const DEPRECATED_FAVORITE_MODELS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME_DEPRECATED}.favoriteModels`; +/** @deprecated Use {@link USER_OVERRIDES_LOCALSTORAGE_KEY} instead */ +export const DEPRECATED_USER_OVERRIDES_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME_DEPRECATED}.userOverrides`; + +/** Build version stored in localStorage for non-PWA update detection */ +export const BUILD_VERSION_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.buildVersion`; + +/** Maps new keys to their deprecated fallback keys */ +export const NEW_TO_DEPRECATED_MAP: Record = { + [ALWAYS_ALLOWED_TOOLS_LOCALSTORAGE_KEY]: DEPRECATED_ALWAYS_ALLOWED_TOOLS_LOCALSTORAGE_KEY, + [CONFIG_LOCALSTORAGE_KEY]: DEPRECATED_CONFIG_LOCALSTORAGE_KEY, + [DISABLED_TOOLS_LOCALSTORAGE_KEY]: DEPRECATED_DISABLED_TOOLS_LOCALSTORAGE_KEY, + [FAVORITE_MODELS_LOCALSTORAGE_KEY]: DEPRECATED_FAVORITE_MODELS_LOCALSTORAGE_KEY, + [USER_OVERRIDES_LOCALSTORAGE_KEY]: DEPRECATED_USER_OVERRIDES_LOCALSTORAGE_KEY +}; diff --git a/tools/ui/src/lib/constants/storage.ts b/tools/ui/src/lib/constants/storage.ts deleted file mode 100644 index 5d9acaafb..000000000 --- a/tools/ui/src/lib/constants/storage.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Storage-related constants (localStorage, IndexedDB). - * - * Centralized to ensure consistency across the app and simplify future - * name changes. - */ - -/** Name prefix for all localStorage keys */ -export const STORAGE_APP_NAME = 'LlamaUi'; - -/** Deprecated localStorage key prefix (old app name) */ -export const STORAGE_APP_NAME_DEPRECATED = 'LlamaCppWebui'; - -/** @deprecated Deprecated IndexedDB name — will be removed after all users have migrated */ -export const DB_APP_NAME_DEPRECATED = 'LlamacppWebui'; - -export const ALWAYS_ALLOWED_TOOLS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.alwaysAllowedTools`; -export const CONFIG_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.config`; -export const DISABLED_TOOLS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.disabledTools`; - -/** Disabled tools keyed by stable selection identity, no migration from the name based key */ -export const DISABLED_TOOL_KEYS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.disabledToolKeys`; -export const FAVORITE_MODELS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.favoriteModels`; -export const REASONING_EFFORT_DEFAULT_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.reasoningEffortDefault`; -export const USER_OVERRIDES_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.userOverrides`; -export const DISMISSED_RECOMMENDED_MCP_SERVERS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.dismissedRecommendedMcpServers`; - -/** Key prefix for per-conversation resumable stream state, conversationId is appended */ -export const STREAM_RESUME_LOCALSTORAGE_KEY_PREFIX = `${STORAGE_APP_NAME}.streamResume.`; - -// Deprecated old key names (kept for backward compat while users migrate) -/** @deprecated Use {@link ALWAYS_ALLOWED_TOOLS_LOCALSTORAGE_KEY} instead */ -export const DEPRECATED_ALWAYS_ALLOWED_TOOLS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME_DEPRECATED}.alwaysAllowedTools`; -/** @deprecated Use {@link CONFIG_LOCALSTORAGE_KEY} instead */ -export const DEPRECATED_CONFIG_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME_DEPRECATED}.config`; -/** @deprecated Use {@link DISABLED_TOOLS_LOCALSTORAGE_KEY} instead */ -export const DEPRECATED_DISABLED_TOOLS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME_DEPRECATED}.disabledTools`; -/** @deprecated Use {@link FAVORITE_MODELS_LOCALSTORAGE_KEY} instead */ -export const DEPRECATED_FAVORITE_MODELS_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME_DEPRECATED}.favoriteModels`; -/** @deprecated Use {@link USER_OVERRIDES_LOCALSTORAGE_KEY} instead */ -export const DEPRECATED_USER_OVERRIDES_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME_DEPRECATED}.userOverrides`; - -/** Build version stored in localStorage for non-PWA update detection */ -export const BUILD_VERSION_LOCALSTORAGE_KEY = `${STORAGE_APP_NAME}.buildVersion`; - -/** Maps new keys to their deprecated fallback keys */ -export const NEW_TO_DEPRECATED_MAP: Record = { - [ALWAYS_ALLOWED_TOOLS_LOCALSTORAGE_KEY]: DEPRECATED_ALWAYS_ALLOWED_TOOLS_LOCALSTORAGE_KEY, - [CONFIG_LOCALSTORAGE_KEY]: DEPRECATED_CONFIG_LOCALSTORAGE_KEY, - [DISABLED_TOOLS_LOCALSTORAGE_KEY]: DEPRECATED_DISABLED_TOOLS_LOCALSTORAGE_KEY, - [FAVORITE_MODELS_LOCALSTORAGE_KEY]: DEPRECATED_FAVORITE_MODELS_LOCALSTORAGE_KEY, - [USER_OVERRIDES_LOCALSTORAGE_KEY]: DEPRECATED_USER_OVERRIDES_LOCALSTORAGE_KEY -}; diff --git a/tools/ui/src/lib/constants/stream.constants.ts b/tools/ui/src/lib/constants/stream.constants.ts new file mode 100644 index 000000000..64f67243c --- /dev/null +++ b/tools/ui/src/lib/constants/stream.constants.ts @@ -0,0 +1,24 @@ +// grace window after a visibilitychange before we kick a reader whose socket likely died +// while the tab was hidden. covers brief background pauses without thrashing live streams +export const STREAM_VISIBILITY_KICK_MS = 3000; + +// separator joining a conversation id and its per-model stream identity +// suffix (conv::model) used by the server side replay buffer +export const CONVERSATION_ID_SEPARATOR = '::'; + +/** + * Server-sent events wire format, shared by the chat stream and the + * /models/sse status feed (text/event-stream). + */ + +// blank line between two events +export const SSE_RECORD_SEPARATOR = '\n\n'; + +// line break inside an event +export const SSE_LINE_SEPARATOR = '\n'; + +// data field prefix, the value follows after an optional space +export const SSE_DATA_PREFIX = 'data:'; + +// end-of-stream marker on the chat completion stream +export const SSE_DONE_MARKER = '[DONE]'; diff --git a/tools/ui/src/lib/constants/stream.ts b/tools/ui/src/lib/constants/stream.ts deleted file mode 100644 index 67951ee95..000000000 --- a/tools/ui/src/lib/constants/stream.ts +++ /dev/null @@ -1,3 +0,0 @@ -// grace window after a visibilitychange before we kick a reader whose socket likely died -// while the tab was hidden. covers brief background pauses without thrashing live streams -export const STREAM_VISIBILITY_KICK_MS = 3000; diff --git a/tools/ui/src/lib/constants/supported-file-types.constants.ts b/tools/ui/src/lib/constants/supported-file-types.constants.ts new file mode 100644 index 000000000..a6bcefaa1 --- /dev/null +++ b/tools/ui/src/lib/constants/supported-file-types.constants.ts @@ -0,0 +1,234 @@ +/** + * Comprehensive dictionary of all supported file types in llama-ui + * Organized by category with TypeScript enums for better type safety + */ + +import { + FileExtensionAudio, + FileExtensionImage, + FileExtensionPdf, + FileExtensionText, + FileTypeAudio, + FileTypeImage, + FileTypePdf, + FileTypeText, + MimeTypeApplication, + MimeTypeAudio, + MimeTypeImage, + MimeTypeText, + MimeTypeVideo +} from '$lib/enums'; +import { FileExtensionVideo, FileTypeVideo } from '$lib/enums/files.enums'; + +// File type configuration using enums +export const AUDIO_FILE_TYPES = { + [FileTypeAudio.MP3]: { + extensions: [FileExtensionAudio.MP3], + mimeTypes: [MimeTypeAudio.MP3_MPEG, MimeTypeAudio.MP3] + }, + [FileTypeAudio.WAV]: { + extensions: [FileExtensionAudio.WAV], + mimeTypes: [MimeTypeAudio.WAV] + } +} as const; + +export const VIDEO_FILE_TYPES = { + [FileTypeVideo.MP4]: { + extensions: [FileExtensionVideo.MP4], + mimeTypes: [MimeTypeVideo.MP4] + }, + [FileTypeVideo.OGG]: { + extensions: [FileExtensionVideo.OGG], + mimeTypes: [MimeTypeVideo.OGG] + } +} as const; + +export const IMAGE_FILE_TYPES = { + [FileTypeImage.GIF]: { + extensions: [FileExtensionImage.GIF], + mimeTypes: [MimeTypeImage.GIF] + }, + [FileTypeImage.HEIC]: { + extensions: [FileExtensionImage.HEIC, FileExtensionImage.HEIF], + mimeTypes: [MimeTypeImage.HEIC, MimeTypeImage.HEIF] + }, + [FileTypeImage.JPEG]: { + extensions: [FileExtensionImage.JPG, FileExtensionImage.JPEG], + mimeTypes: [MimeTypeImage.JPEG] + }, + [FileTypeImage.PNG]: { + extensions: [FileExtensionImage.PNG], + mimeTypes: [MimeTypeImage.PNG] + }, + [FileTypeImage.SVG]: { + extensions: [FileExtensionImage.SVG], + mimeTypes: [MimeTypeImage.SVG] + }, + [FileTypeImage.WEBP]: { + extensions: [FileExtensionImage.WEBP], + mimeTypes: [MimeTypeImage.WEBP] + } +} as const; + +export const PDF_FILE_TYPES = { + [FileTypePdf.PDF]: { + extensions: [FileExtensionPdf.PDF], + mimeTypes: [MimeTypeApplication.PDF] + } +} as const; + +export const TEXT_FILE_TYPES = { + [FileTypeText.ASCIIDOC]: { + extensions: [FileExtensionText.ADOC], + mimeTypes: [MimeTypeText.ASCIIDOC] + }, + [FileTypeText.BIBTEX]: { + extensions: [FileExtensionText.BIB], + mimeTypes: [MimeTypeText.BIBTEX] + }, + [FileTypeText.CPP]: { + extensions: [ + FileExtensionText.CPP, + FileExtensionText.C, + FileExtensionText.H, + FileExtensionText.HPP + ], + mimeTypes: [MimeTypeText.CPP_SRC, MimeTypeText.CPP_HDR, MimeTypeText.C_SRC, MimeTypeText.C_HDR] + }, + [FileTypeText.CSHARP]: { + extensions: [FileExtensionText.CS], + mimeTypes: [MimeTypeText.CSHARP] + }, + [FileTypeText.CSS]: { + extensions: [FileExtensionText.CSS], + mimeTypes: [MimeTypeText.CSS] + }, + [FileTypeText.CSV]: { + extensions: [FileExtensionText.CSV], + mimeTypes: [MimeTypeText.CSV] + }, + [FileTypeText.CUDA]: { + extensions: [FileExtensionText.CU, FileExtensionText.CUH], + mimeTypes: [MimeTypeText.CUDA] + }, + [FileTypeText.DART]: { + extensions: [FileExtensionText.DART], + mimeTypes: [MimeTypeText.DART] + }, + [FileTypeText.GO]: { + extensions: [FileExtensionText.GO], + mimeTypes: [MimeTypeText.GO] + }, + [FileTypeText.HASKELL]: { + extensions: [FileExtensionText.HS], + mimeTypes: [MimeTypeText.HASKELL] + }, + [FileTypeText.HTML]: { + extensions: [FileExtensionText.HTML, FileExtensionText.HTM], + mimeTypes: [MimeTypeText.HTML] + }, + [FileTypeText.JAVA]: { + extensions: [FileExtensionText.JAVA], + mimeTypes: [MimeTypeText.JAVA] + }, + [FileTypeText.JAVASCRIPT]: { + extensions: [FileExtensionText.JS], + mimeTypes: [MimeTypeText.JAVASCRIPT, MimeTypeText.JAVASCRIPT_APP] + }, + [FileTypeText.JSON]: { + extensions: [FileExtensionText.JSON], + mimeTypes: [MimeTypeText.JSON] + }, + [FileTypeText.JSX]: { + extensions: [FileExtensionText.JSX], + mimeTypes: [MimeTypeText.JSX] + }, + [FileTypeText.KOTLIN]: { + extensions: [FileExtensionText.KT], + mimeTypes: [MimeTypeText.KOTLIN] + }, + [FileTypeText.LATEX]: { + extensions: [FileExtensionText.TEX], + mimeTypes: [MimeTypeText.LATEX, MimeTypeText.TEX, MimeTypeText.TEX_APP] + }, + [FileTypeText.LOG]: { + extensions: [FileExtensionText.LOG], + mimeTypes: [MimeTypeText.PLAIN] + }, + [FileTypeText.MARKDOWN]: { + extensions: [FileExtensionText.MD], + mimeTypes: [MimeTypeText.MARKDOWN] + }, + [FileTypeText.PHP]: { + extensions: [FileExtensionText.PHP], + mimeTypes: [MimeTypeText.PHP] + }, + [FileTypeText.PLAIN_TEXT]: { + extensions: [FileExtensionText.TXT], + mimeTypes: [MimeTypeText.PLAIN] + }, + [FileTypeText.PROPERTIES]: { + extensions: [FileExtensionText.PROPERTIES], + mimeTypes: [MimeTypeText.PROPERTIES] + }, + [FileTypeText.PYTHON]: { + extensions: [FileExtensionText.PY], + mimeTypes: [MimeTypeText.PYTHON] + }, + [FileTypeText.R]: { + extensions: [FileExtensionText.R], + mimeTypes: [MimeTypeText.R] + }, + [FileTypeText.RUBY]: { + extensions: [FileExtensionText.RB], + mimeTypes: [MimeTypeText.RUBY] + }, + [FileTypeText.RUST]: { + extensions: [FileExtensionText.RS], + mimeTypes: [MimeTypeText.RUST] + }, + [FileTypeText.SCALA]: { + extensions: [FileExtensionText.SCALA], + mimeTypes: [MimeTypeText.SCALA] + }, + [FileTypeText.SHELL]: { + extensions: [FileExtensionText.SH, FileExtensionText.BAT], + mimeTypes: [MimeTypeText.SHELL, MimeTypeText.BAT] + }, + [FileTypeText.SQL]: { + extensions: [FileExtensionText.SQL], + mimeTypes: [MimeTypeText.SQL] + }, + [FileTypeText.SVELTE]: { + extensions: [FileExtensionText.SVELTE], + mimeTypes: [MimeTypeText.SVELTE] + }, + [FileTypeText.SWIFT]: { + extensions: [FileExtensionText.SWIFT], + mimeTypes: [MimeTypeText.SWIFT] + }, + [FileTypeText.TSX]: { + extensions: [FileExtensionText.TSX], + mimeTypes: [MimeTypeText.TSX] + }, + [FileTypeText.TYPESCRIPT]: { + extensions: [FileExtensionText.TS], + mimeTypes: [MimeTypeText.TYPESCRIPT] + }, + [FileTypeText.VUE]: { + extensions: [FileExtensionText.VUE], + mimeTypes: [MimeTypeText.VUE] + }, + [FileTypeText.VULKAN]: { + extensions: [FileExtensionText.COMP], + mimeTypes: [MimeTypeText.PLAIN] + }, + [FileTypeText.XML]: { + extensions: [FileExtensionText.XML], + mimeTypes: [MimeTypeText.XML_TEXT, MimeTypeText.XML_APP] + }, + [FileTypeText.YAML]: { + extensions: [FileExtensionText.YAML, FileExtensionText.YML], + mimeTypes: [MimeTypeText.YAML_TEXT, MimeTypeText.YAML_APP] + } +} as const; diff --git a/tools/ui/src/lib/constants/supported-file-types.ts b/tools/ui/src/lib/constants/supported-file-types.ts deleted file mode 100644 index a6bcefaa1..000000000 --- a/tools/ui/src/lib/constants/supported-file-types.ts +++ /dev/null @@ -1,234 +0,0 @@ -/** - * Comprehensive dictionary of all supported file types in llama-ui - * Organized by category with TypeScript enums for better type safety - */ - -import { - FileExtensionAudio, - FileExtensionImage, - FileExtensionPdf, - FileExtensionText, - FileTypeAudio, - FileTypeImage, - FileTypePdf, - FileTypeText, - MimeTypeApplication, - MimeTypeAudio, - MimeTypeImage, - MimeTypeText, - MimeTypeVideo -} from '$lib/enums'; -import { FileExtensionVideo, FileTypeVideo } from '$lib/enums/files.enums'; - -// File type configuration using enums -export const AUDIO_FILE_TYPES = { - [FileTypeAudio.MP3]: { - extensions: [FileExtensionAudio.MP3], - mimeTypes: [MimeTypeAudio.MP3_MPEG, MimeTypeAudio.MP3] - }, - [FileTypeAudio.WAV]: { - extensions: [FileExtensionAudio.WAV], - mimeTypes: [MimeTypeAudio.WAV] - } -} as const; - -export const VIDEO_FILE_TYPES = { - [FileTypeVideo.MP4]: { - extensions: [FileExtensionVideo.MP4], - mimeTypes: [MimeTypeVideo.MP4] - }, - [FileTypeVideo.OGG]: { - extensions: [FileExtensionVideo.OGG], - mimeTypes: [MimeTypeVideo.OGG] - } -} as const; - -export const IMAGE_FILE_TYPES = { - [FileTypeImage.GIF]: { - extensions: [FileExtensionImage.GIF], - mimeTypes: [MimeTypeImage.GIF] - }, - [FileTypeImage.HEIC]: { - extensions: [FileExtensionImage.HEIC, FileExtensionImage.HEIF], - mimeTypes: [MimeTypeImage.HEIC, MimeTypeImage.HEIF] - }, - [FileTypeImage.JPEG]: { - extensions: [FileExtensionImage.JPG, FileExtensionImage.JPEG], - mimeTypes: [MimeTypeImage.JPEG] - }, - [FileTypeImage.PNG]: { - extensions: [FileExtensionImage.PNG], - mimeTypes: [MimeTypeImage.PNG] - }, - [FileTypeImage.SVG]: { - extensions: [FileExtensionImage.SVG], - mimeTypes: [MimeTypeImage.SVG] - }, - [FileTypeImage.WEBP]: { - extensions: [FileExtensionImage.WEBP], - mimeTypes: [MimeTypeImage.WEBP] - } -} as const; - -export const PDF_FILE_TYPES = { - [FileTypePdf.PDF]: { - extensions: [FileExtensionPdf.PDF], - mimeTypes: [MimeTypeApplication.PDF] - } -} as const; - -export const TEXT_FILE_TYPES = { - [FileTypeText.ASCIIDOC]: { - extensions: [FileExtensionText.ADOC], - mimeTypes: [MimeTypeText.ASCIIDOC] - }, - [FileTypeText.BIBTEX]: { - extensions: [FileExtensionText.BIB], - mimeTypes: [MimeTypeText.BIBTEX] - }, - [FileTypeText.CPP]: { - extensions: [ - FileExtensionText.CPP, - FileExtensionText.C, - FileExtensionText.H, - FileExtensionText.HPP - ], - mimeTypes: [MimeTypeText.CPP_SRC, MimeTypeText.CPP_HDR, MimeTypeText.C_SRC, MimeTypeText.C_HDR] - }, - [FileTypeText.CSHARP]: { - extensions: [FileExtensionText.CS], - mimeTypes: [MimeTypeText.CSHARP] - }, - [FileTypeText.CSS]: { - extensions: [FileExtensionText.CSS], - mimeTypes: [MimeTypeText.CSS] - }, - [FileTypeText.CSV]: { - extensions: [FileExtensionText.CSV], - mimeTypes: [MimeTypeText.CSV] - }, - [FileTypeText.CUDA]: { - extensions: [FileExtensionText.CU, FileExtensionText.CUH], - mimeTypes: [MimeTypeText.CUDA] - }, - [FileTypeText.DART]: { - extensions: [FileExtensionText.DART], - mimeTypes: [MimeTypeText.DART] - }, - [FileTypeText.GO]: { - extensions: [FileExtensionText.GO], - mimeTypes: [MimeTypeText.GO] - }, - [FileTypeText.HASKELL]: { - extensions: [FileExtensionText.HS], - mimeTypes: [MimeTypeText.HASKELL] - }, - [FileTypeText.HTML]: { - extensions: [FileExtensionText.HTML, FileExtensionText.HTM], - mimeTypes: [MimeTypeText.HTML] - }, - [FileTypeText.JAVA]: { - extensions: [FileExtensionText.JAVA], - mimeTypes: [MimeTypeText.JAVA] - }, - [FileTypeText.JAVASCRIPT]: { - extensions: [FileExtensionText.JS], - mimeTypes: [MimeTypeText.JAVASCRIPT, MimeTypeText.JAVASCRIPT_APP] - }, - [FileTypeText.JSON]: { - extensions: [FileExtensionText.JSON], - mimeTypes: [MimeTypeText.JSON] - }, - [FileTypeText.JSX]: { - extensions: [FileExtensionText.JSX], - mimeTypes: [MimeTypeText.JSX] - }, - [FileTypeText.KOTLIN]: { - extensions: [FileExtensionText.KT], - mimeTypes: [MimeTypeText.KOTLIN] - }, - [FileTypeText.LATEX]: { - extensions: [FileExtensionText.TEX], - mimeTypes: [MimeTypeText.LATEX, MimeTypeText.TEX, MimeTypeText.TEX_APP] - }, - [FileTypeText.LOG]: { - extensions: [FileExtensionText.LOG], - mimeTypes: [MimeTypeText.PLAIN] - }, - [FileTypeText.MARKDOWN]: { - extensions: [FileExtensionText.MD], - mimeTypes: [MimeTypeText.MARKDOWN] - }, - [FileTypeText.PHP]: { - extensions: [FileExtensionText.PHP], - mimeTypes: [MimeTypeText.PHP] - }, - [FileTypeText.PLAIN_TEXT]: { - extensions: [FileExtensionText.TXT], - mimeTypes: [MimeTypeText.PLAIN] - }, - [FileTypeText.PROPERTIES]: { - extensions: [FileExtensionText.PROPERTIES], - mimeTypes: [MimeTypeText.PROPERTIES] - }, - [FileTypeText.PYTHON]: { - extensions: [FileExtensionText.PY], - mimeTypes: [MimeTypeText.PYTHON] - }, - [FileTypeText.R]: { - extensions: [FileExtensionText.R], - mimeTypes: [MimeTypeText.R] - }, - [FileTypeText.RUBY]: { - extensions: [FileExtensionText.RB], - mimeTypes: [MimeTypeText.RUBY] - }, - [FileTypeText.RUST]: { - extensions: [FileExtensionText.RS], - mimeTypes: [MimeTypeText.RUST] - }, - [FileTypeText.SCALA]: { - extensions: [FileExtensionText.SCALA], - mimeTypes: [MimeTypeText.SCALA] - }, - [FileTypeText.SHELL]: { - extensions: [FileExtensionText.SH, FileExtensionText.BAT], - mimeTypes: [MimeTypeText.SHELL, MimeTypeText.BAT] - }, - [FileTypeText.SQL]: { - extensions: [FileExtensionText.SQL], - mimeTypes: [MimeTypeText.SQL] - }, - [FileTypeText.SVELTE]: { - extensions: [FileExtensionText.SVELTE], - mimeTypes: [MimeTypeText.SVELTE] - }, - [FileTypeText.SWIFT]: { - extensions: [FileExtensionText.SWIFT], - mimeTypes: [MimeTypeText.SWIFT] - }, - [FileTypeText.TSX]: { - extensions: [FileExtensionText.TSX], - mimeTypes: [MimeTypeText.TSX] - }, - [FileTypeText.TYPESCRIPT]: { - extensions: [FileExtensionText.TS], - mimeTypes: [MimeTypeText.TYPESCRIPT] - }, - [FileTypeText.VUE]: { - extensions: [FileExtensionText.VUE], - mimeTypes: [MimeTypeText.VUE] - }, - [FileTypeText.VULKAN]: { - extensions: [FileExtensionText.COMP], - mimeTypes: [MimeTypeText.PLAIN] - }, - [FileTypeText.XML]: { - extensions: [FileExtensionText.XML], - mimeTypes: [MimeTypeText.XML_TEXT, MimeTypeText.XML_APP] - }, - [FileTypeText.YAML]: { - extensions: [FileExtensionText.YAML, FileExtensionText.YML], - mimeTypes: [MimeTypeText.YAML_TEXT, MimeTypeText.YAML_APP] - } -} as const; diff --git a/tools/ui/src/lib/constants/svg-blocks.constants.ts b/tools/ui/src/lib/constants/svg-blocks.constants.ts new file mode 100644 index 000000000..705800c26 --- /dev/null +++ b/tools/ui/src/lib/constants/svg-blocks.constants.ts @@ -0,0 +1,57 @@ +/** + * Constants for rendering svg code blocks inline. + */ +export const SVG = { + // CSS classes applied to the inline svg block and its chrome. + BLOCK_CLASS: 'svg-block', + /** + * Shadow root style for the zoom dialog svg. Lets the svg grow past its + * intrinsic size so pan and zoom have room to work. + */ + DIALOG_SHADOW_STYLE: + ':host{display:inline-block}svg{min-height:min(50vh,12rem);min-width:min(80vw,20rem);max-width:none;max-height:none;height:auto;width:auto;display:block}', + ID_ATTR: 'data-svg-id', + + /** + * Shadow root style for an inline svg block. Mirrors the centered, padded + * sizing the light dom used before the svg moved behind a shadow boundary. + */ + INLINE_SHADOW_STYLE: + ':host{display:block;width:100%;text-align:center}svg{display:block;margin:0 auto;width:auto;height:auto;max-width:100%;max-height:70vh;min-height:8rem;padding:3rem 1rem}', + // Languages that mark a code block as svg content. + LANGUAGE: 'svg', + /** + * Hard size ceiling for a single inline svg block. + * Above this the source is left as raw text instead of being rendered. + */ + MAX_BYTES: 256 * 1024, + + RENDERED_ATTR: 'data-svg-rendered', + /** + * DOMPurify config for untrusted svg coming from model output. + * + * foreignObject and script stay forbidden unconditionally, they are the only + * inline svg vectors that execute arbitrary html or js. Everything else is + * allowed for maximum rendering compatibility: href and xlink:href stay so + * use, image, a and animateMotion work, and DOMPurify still neutralizes + * javascript: and data: uri schemes natively. External resource refs are + * allowed by design on a local first tool, the user browser fetches them. + * + * The sanitized svg is always mounted inside a shadow root (see svg-shadow), + * so an author