refactor: Enums

This commit is contained in:
Aleksander Grygier 2026-01-25 13:37:08 +01:00
parent 7f5284d597
commit ee9efae203
20 changed files with 268 additions and 134 deletions

View File

@ -919,11 +919,11 @@ export class MCPClient {
async runHealthCheck(server: HealthCheckParams): Promise<void> { async runHealthCheck(server: HealthCheckParams): Promise<void> {
const trimmedUrl = server.url.trim(); const trimmedUrl = server.url.trim();
const logs: MCPConnectionLog[] = []; const logs: MCPConnectionLog[] = [];
let currentPhase: MCPConnectionPhase = MCPConnectionPhase.Idle; let currentPhase: MCPConnectionPhase = MCPConnectionPhase.IDLE;
if (!trimmedUrl) { if (!trimmedUrl) {
mcpStore.updateHealthCheck(server.id, { mcpStore.updateHealthCheck(server.id, {
status: HealthCheckStatus.Error, status: HealthCheckStatus.ERROR,
message: 'Please enter a server URL first.', message: 'Please enter a server URL first.',
logs: [] logs: []
}); });
@ -932,8 +932,8 @@ export class MCPClient {
// Initial connecting state // Initial connecting state
mcpStore.updateHealthCheck(server.id, { mcpStore.updateHealthCheck(server.id, {
status: HealthCheckStatus.Connecting, status: HealthCheckStatus.CONNECTING,
phase: MCPConnectionPhase.TransportCreating, phase: MCPConnectionPhase.TRANSPORT_CREATING,
logs: [] logs: []
}); });
@ -957,7 +957,7 @@ export class MCPClient {
currentPhase = phase; currentPhase = phase;
logs.push(log); logs.push(log);
mcpStore.updateHealthCheck(server.id, { mcpStore.updateHealthCheck(server.id, {
status: HealthCheckStatus.Connecting, status: HealthCheckStatus.CONNECTING,
phase, phase,
logs: [...logs] logs: [...logs]
}); });
@ -976,7 +976,7 @@ export class MCPClient {
); );
mcpStore.updateHealthCheck(server.id, { mcpStore.updateHealthCheck(server.id, {
status: HealthCheckStatus.Success, status: HealthCheckStatus.SUCCESS,
tools, tools,
serverInfo: connection.serverInfo, serverInfo: connection.serverInfo,
capabilities, capabilities,
@ -992,12 +992,12 @@ export class MCPClient {
const message = error instanceof Error ? error.message : 'Unknown error occurred'; const message = error instanceof Error ? error.message : 'Unknown error occurred';
logs.push({ logs.push({
timestamp: new Date(), timestamp: new Date(),
phase: MCPConnectionPhase.Error, phase: MCPConnectionPhase.ERROR,
message: `Connection failed: ${message}`, message: `Connection failed: ${message}`,
level: MCPLogLevel.Error level: MCPLogLevel.ERROR
}); });
mcpStore.updateHealthCheck(server.id, { mcpStore.updateHealthCheck(server.id, {
status: HealthCheckStatus.Error, status: HealthCheckStatus.ERROR,
message, message,
phase: currentPhase, phase: currentPhase,
logs logs

View File

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { ChatMessageMcpPromptContent, RemoveButton } from '$lib/components/app'; import { ChatMessageMcpPromptContent, RemoveButton } from '$lib/components/app';
import type { DatabaseMessageExtraMcpPrompt } from '$lib/types'; import type { DatabaseMessageExtraMcpPrompt } from '$lib/types';
import { McpPromptVariant } from '$lib/enums';
interface Props { interface Props {
class?: string; class?: string;
@ -22,7 +23,12 @@
</script> </script>
<div class="group relative {className}"> <div class="group relative {className}">
<ChatMessageMcpPromptContent {prompt} variant="attachment" {isLoading} {loadError} /> <ChatMessageMcpPromptContent
{prompt}
variant={McpPromptVariant.ATTACHMENT}
{isLoading}
{loadError}
/>
{#if !readonly && onRemove} {#if !readonly && onRemove}
<div <div

View File

@ -67,7 +67,10 @@
} }
type ReasoningSegment = { type ReasoningSegment = {
type: 'text' | 'reasoning' | 'reasoning_pending'; type:
| AgenticSectionType.TEXT
| AgenticSectionType.REASONING
| AgenticSectionType.REASONING_PENDING;
content: string; content: string;
}; };
@ -90,7 +93,7 @@
if (startIndex === -1) { if (startIndex === -1) {
const remainingText = rawContent.slice(cursor); const remainingText = rawContent.slice(cursor);
if (remainingText) { if (remainingText) {
segments.push({ type: 'text', content: remainingText }); segments.push({ type: AgenticSectionType.TEXT, content: remainingText });
} }
break; break;
} }
@ -98,7 +101,7 @@
if (startIndex > cursor) { if (startIndex > cursor) {
const textBefore = rawContent.slice(cursor, startIndex); const textBefore = rawContent.slice(cursor, startIndex);
if (textBefore) { if (textBefore) {
segments.push({ type: 'text', content: textBefore }); segments.push({ type: AgenticSectionType.TEXT, content: textBefore });
} }
} }
@ -108,14 +111,14 @@
if (endIndex === -1) { if (endIndex === -1) {
const pendingContent = rawContent.slice(contentStart); const pendingContent = rawContent.slice(contentStart);
segments.push({ segments.push({
type: 'reasoning_pending', type: AgenticSectionType.REASONING_PENDING,
content: stripPartialMarker(pendingContent) content: stripPartialMarker(pendingContent)
}); });
break; break;
} }
const reasoningContent = rawContent.slice(contentStart, endIndex); const reasoningContent = rawContent.slice(contentStart, endIndex);
segments.push({ type: 'reasoning', content: reasoningContent }); segments.push({ type: AgenticSectionType.REASONING, content: reasoningContent });
cursor = endIndex + REASONING_TAGS.END.length; cursor = endIndex + REASONING_TAGS.END.length;
} }

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { ChatMessageActions, ChatMessageMcpPromptContent } from '$lib/components/app'; import { ChatMessageActions, ChatMessageMcpPromptContent } from '$lib/components/app';
import { MessageRole } from '$lib/enums'; import { MessageRole, McpPromptVariant } from '$lib/enums';
import type { DatabaseMessageExtraMcpPrompt } from '$lib/types'; import type { DatabaseMessageExtraMcpPrompt } from '$lib/types';
interface Props { interface Props {
@ -44,7 +44,11 @@
class="group flex flex-col items-end gap-3 md:gap-2 {className}" class="group flex flex-col items-end gap-3 md:gap-2 {className}"
role="group" role="group"
> >
<ChatMessageMcpPromptContent prompt={mcpPrompt} variant="message" class="w-full max-w-[80%]" /> <ChatMessageMcpPromptContent
prompt={mcpPrompt}
variant={McpPromptVariant.MESSAGE}
class="w-full max-w-[80%]"
/>
{#if message.timestamp} {#if message.timestamp}
<div class="max-w-[80%]"> <div class="max-w-[80%]">

View File

@ -4,6 +4,7 @@
import { getFaviconUrl } from '$lib/utils'; import { getFaviconUrl } from '$lib/utils';
import { mcpStore } from '$lib/stores/mcp.svelte'; import { mcpStore } from '$lib/stores/mcp.svelte';
import { SvelteMap } from 'svelte/reactivity'; import { SvelteMap } from 'svelte/reactivity';
import { McpPromptVariant } from '$lib/enums';
interface ContentPart { interface ContentPart {
text: string; text: string;
@ -13,7 +14,7 @@
interface Props { interface Props {
class?: string; class?: string;
prompt: DatabaseMessageExtraMcpPrompt; prompt: DatabaseMessageExtraMcpPrompt;
variant?: 'message' | 'attachment'; variant?: McpPromptVariant;
isLoading?: boolean; isLoading?: boolean;
loadError?: string; loadError?: string;
} }
@ -21,7 +22,7 @@
let { let {
class: className = '', class: className = '',
prompt, prompt,
variant = 'message', variant = McpPromptVariant.MESSAGE,
isLoading = false, isLoading = false,
loadError loadError
}: Props = $props(); }: Props = $props();
@ -99,7 +100,7 @@
}); });
let showArgBadges = $derived(hasArguments && !isLoading && !loadError); let showArgBadges = $derived(hasArguments && !isLoading && !loadError);
let isAttachment = $derived(variant === 'attachment'); let isAttachment = $derived(variant === McpPromptVariant.ATTACHMENT);
let textSizeClass = $derived(isAttachment ? 'text-sm' : 'text-md'); let textSizeClass = $derived(isAttachment ? 'text-sm' : 'text-md');
let maxHeightStyle = $derived( let maxHeightStyle = $derived(
isAttachment isAttachment

View File

@ -20,7 +20,7 @@
let healthyEnabledMcpServers = $derived( let healthyEnabledMcpServers = $derived(
enabledMcpServersForChat.filter((s) => { enabledMcpServersForChat.filter((s) => {
const healthState = mcpStore.getHealthCheckState(s.id); const healthState = mcpStore.getHealthCheckState(s.id);
return healthState.status !== HealthCheckStatus.Error; return healthState.status !== HealthCheckStatus.ERROR;
}) })
); );
let hasEnabledMcpServers = $derived(enabledMcpServersForChat.length > 0); let hasEnabledMcpServers = $derived(enabledMcpServersForChat.length > 0);

View File

@ -27,41 +27,41 @@
let healthState = $derived<HealthCheckState>(mcpStore.getHealthCheckState(server.id)); let healthState = $derived<HealthCheckState>(mcpStore.getHealthCheckState(server.id));
let displayName = $derived(mcpStore.getServerLabel(server)); let displayName = $derived(mcpStore.getServerLabel(server));
let isIdle = $derived(healthState.status === HealthCheckStatus.Idle); let isIdle = $derived(healthState.status === HealthCheckStatus.IDLE);
let isHealthChecking = $derived(healthState.status === HealthCheckStatus.Connecting); let isHealthChecking = $derived(healthState.status === HealthCheckStatus.CONNECTING);
let isConnected = $derived(healthState.status === HealthCheckStatus.Success); let isConnected = $derived(healthState.status === HealthCheckStatus.SUCCESS);
let isError = $derived(healthState.status === HealthCheckStatus.Error); let isError = $derived(healthState.status === HealthCheckStatus.ERROR);
let showSkeleton = $derived(isIdle || isHealthChecking); let showSkeleton = $derived(isIdle || isHealthChecking);
let errorMessage = $derived( let errorMessage = $derived(
healthState.status === HealthCheckStatus.Error ? healthState.message : undefined healthState.status === HealthCheckStatus.ERROR ? healthState.message : undefined
); );
let tools = $derived(healthState.status === HealthCheckStatus.Success ? healthState.tools : []); let tools = $derived(healthState.status === HealthCheckStatus.SUCCESS ? healthState.tools : []);
let connectionLogs = $derived( let connectionLogs = $derived(
healthState.status === HealthCheckStatus.Connecting || healthState.status === HealthCheckStatus.CONNECTING ||
healthState.status === HealthCheckStatus.Success || healthState.status === HealthCheckStatus.SUCCESS ||
healthState.status === HealthCheckStatus.Error healthState.status === HealthCheckStatus.ERROR
? healthState.logs ? healthState.logs
: [] : []
); );
let serverInfo = $derived( let serverInfo = $derived(
healthState.status === HealthCheckStatus.Success ? healthState.serverInfo : undefined healthState.status === HealthCheckStatus.SUCCESS ? healthState.serverInfo : undefined
); );
let capabilities = $derived( let capabilities = $derived(
healthState.status === HealthCheckStatus.Success ? healthState.capabilities : undefined healthState.status === HealthCheckStatus.SUCCESS ? healthState.capabilities : undefined
); );
let transportType = $derived( let transportType = $derived(
healthState.status === HealthCheckStatus.Success ? healthState.transportType : undefined healthState.status === HealthCheckStatus.SUCCESS ? healthState.transportType : undefined
); );
let protocolVersion = $derived( let protocolVersion = $derived(
healthState.status === HealthCheckStatus.Success ? healthState.protocolVersion : undefined healthState.status === HealthCheckStatus.SUCCESS ? healthState.protocolVersion : undefined
); );
let connectionTimeMs = $derived( let connectionTimeMs = $derived(
healthState.status === HealthCheckStatus.Success ? healthState.connectionTimeMs : undefined healthState.status === HealthCheckStatus.SUCCESS ? healthState.connectionTimeMs : undefined
); );
let instructions = $derived( let instructions = $derived(
healthState.status === HealthCheckStatus.Success ? healthState.instructions : undefined healthState.status === HealthCheckStatus.SUCCESS ? healthState.instructions : undefined
); );
let isEditing = $state(!server.url.trim()); let isEditing = $state(!server.url.trim());

View File

@ -27,14 +27,14 @@
}: Props = $props(); }: Props = $props();
const transportLabels: Record<MCPTransportType, string> = { const transportLabels: Record<MCPTransportType, string> = {
[MCPTransportType.Websocket]: 'WebSocket', [MCPTransportType.WEBSOCKET]: 'WebSocket',
[MCPTransportType.StreamableHttp]: 'HTTP', [MCPTransportType.STREAMABLE_HTTP]: 'HTTP',
[MCPTransportType.SSE]: 'SSE' [MCPTransportType.SSE]: 'SSE'
}; };
const transportIcons: Record<MCPTransportType, typeof Cable> = { const transportIcons: Record<MCPTransportType, typeof Cable> = {
[MCPTransportType.Websocket]: Zap, [MCPTransportType.WEBSOCKET]: Zap,
[MCPTransportType.StreamableHttp]: Globe, [MCPTransportType.STREAMABLE_HTTP]: Globe,
[MCPTransportType.SSE]: Radio [MCPTransportType.SSE]: Radio
}; };
</script> </script>

View File

@ -26,3 +26,7 @@ export { MCPConnectionPhase, MCPLogLevel, MCPTransportType, HealthCheckStatus }
export { ModelModality } from './model'; export { ModelModality } from './model';
export { ServerRole, ServerModelStatus } from './server'; export { ServerRole, ServerModelStatus } from './server';
export { ParameterSource, SyncableParameterType } from './settings';
export { ColorMode, McpPromptVariant, UrlPrefix } from './ui';

View File

@ -2,32 +2,32 @@
* Connection lifecycle phases for MCP protocol * Connection lifecycle phases for MCP protocol
*/ */
export enum MCPConnectionPhase { export enum MCPConnectionPhase {
Idle = 'idle', IDLE = 'idle',
TransportCreating = 'transport_creating', TRANSPORT_CREATING = 'transport_creating',
TransportReady = 'transport_ready', TRANSPORT_READY = 'transport_ready',
Initializing = 'initializing', INITIALIZING = 'initializing',
CapabilitiesExchanged = 'capabilities_exchanged', CAPABILITIES_EXCHANGED = 'capabilities_exchanged',
ListingTools = 'listing_tools', LISTING_TOOLS = 'listing_tools',
Connected = 'connected', CONNECTED = 'connected',
Error = 'error', ERROR = 'error',
Disconnected = 'disconnected' DISCONNECTED = 'disconnected'
} }
/** /**
* Log level for connection events * Log level for connection events
*/ */
export enum MCPLogLevel { export enum MCPLogLevel {
Info = 'info', INFO = 'info',
Warn = 'warn', WARN = 'warn',
Error = 'error' ERROR = 'error'
} }
/** /**
* Transport types for MCP connections * Transport types for MCP connections
*/ */
export enum MCPTransportType { export enum MCPTransportType {
Websocket = 'websocket', WEBSOCKET = 'websocket',
StreamableHttp = 'streamable_http', STREAMABLE_HTTP = 'streamable_http',
SSE = 'sse' SSE = 'sse'
} }
@ -35,8 +35,8 @@ export enum MCPTransportType {
* Health check status for MCP servers * Health check status for MCP servers
*/ */
export enum HealthCheckStatus { export enum HealthCheckStatus {
Idle = 'idle', IDLE = 'idle',
Connecting = 'connecting', CONNECTING = 'connecting',
Success = 'success', SUCCESS = 'success',
Error = 'error' ERROR = 'error'
} }

View File

@ -0,0 +1,16 @@
/**
* Parameter source - indicates whether a parameter uses default or custom value
*/
export enum ParameterSource {
DEFAULT = 'default',
CUSTOM = 'custom'
}
/**
* Syncable parameter type - data types for parameters that can be synced with server
*/
export enum SyncableParameterType {
NUMBER = 'number',
STRING = 'string',
BOOLEAN = 'boolean'
}

View File

@ -3,3 +3,22 @@ export enum ColorMode {
DARK = 'dark', DARK = 'dark',
SYSTEM = 'system' SYSTEM = 'system'
} }
/**
* MCP prompt display variant
*/
export enum McpPromptVariant {
MESSAGE = 'message',
ATTACHMENT = 'attachment'
}
/**
* URL prefixes for protocol detection
*/
export enum UrlPrefix {
DATA = 'data:',
HTTP = 'http://',
HTTPS = 'https://',
WEBSOCKET = 'ws://',
WEBSOCKET_SECURE = 'wss://'
}

View File

@ -1,7 +1,7 @@
import type { Root as HastRoot } from 'hast'; import type { Root as HastRoot } from 'hast';
import { visit } from 'unist-util-visit'; import { visit } from 'unist-util-visit';
import type { DatabaseMessage, DatabaseMessageExtraImageFile } from '$lib/types/database'; import type { DatabaseMessage, DatabaseMessageExtraImageFile } from '$lib/types/database';
import { AttachmentType } from '$lib/enums'; import { AttachmentType, UrlPrefix } from '$lib/enums';
/** /**
* Rehype plugin to resolve attachment image sources. * Rehype plugin to resolve attachment image sources.
@ -14,7 +14,7 @@ export function rehypeResolveAttachmentImages(options: { message?: DatabaseMessa
const src = String(node.properties.src); const src = String(node.properties.src);
// Skip data URLs and external URLs // Skip data URLs and external URLs
if (src.startsWith('data:') || src.startsWith('http')) { if (src.startsWith(UrlPrefix.DATA) || src.startsWith(UrlPrefix.HTTP)) {
return; return;
} }

View File

@ -59,7 +59,7 @@ export class MCPService {
private static createLog( private static createLog(
phase: MCPConnectionPhase, phase: MCPConnectionPhase,
message: string, message: string,
level: MCPLogLevel = MCPLogLevel.Info, level: MCPLogLevel = MCPLogLevel.INFO,
details?: unknown details?: unknown
): MCPConnectionLog { ): MCPConnectionLog {
return { return {
@ -100,7 +100,7 @@ export class MCPService {
return { return {
transport: new WebSocketClientTransport(url), transport: new WebSocketClientTransport(url),
type: MCPTransportType.Websocket type: MCPTransportType.WEBSOCKET
}; };
} }
@ -112,7 +112,7 @@ export class MCPService {
requestInit, requestInit,
sessionId: config.sessionId sessionId: config.sessionId
}), }),
type: MCPTransportType.StreamableHttp type: MCPTransportType.STREAMABLE_HTTP
}; };
} catch (httpError) { } catch (httpError) {
console.warn(`[MCPService] StreamableHTTP failed, trying SSE transport...`, httpError); console.warn(`[MCPService] StreamableHTTP failed, trying SSE transport...`, httpError);
@ -169,9 +169,9 @@ export class MCPService {
// Phase: Creating transport // Phase: Creating transport
onPhase?.( onPhase?.(
MCPConnectionPhase.TransportCreating, MCPConnectionPhase.TRANSPORT_CREATING,
this.createLog( this.createLog(
MCPConnectionPhase.TransportCreating, MCPConnectionPhase.TRANSPORT_CREATING,
`Creating transport for ${serverConfig.url}` `Creating transport for ${serverConfig.url}`
) )
); );
@ -181,8 +181,8 @@ export class MCPService {
// Phase: Transport ready // Phase: Transport ready
onPhase?.( onPhase?.(
MCPConnectionPhase.TransportReady, MCPConnectionPhase.TRANSPORT_READY,
this.createLog(MCPConnectionPhase.TransportReady, `Transport ready (${transportType})`), this.createLog(MCPConnectionPhase.TRANSPORT_READY, `Transport ready (${transportType})`),
{ transportType } { transportType }
); );
@ -199,8 +199,8 @@ export class MCPService {
// Phase: Initializing // Phase: Initializing
onPhase?.( onPhase?.(
MCPConnectionPhase.Initializing, MCPConnectionPhase.INITIALIZING,
this.createLog(MCPConnectionPhase.Initializing, 'Sending initialize request...') this.createLog(MCPConnectionPhase.INITIALIZING, 'Sending initialize request...')
); );
console.log(`[MCPService][${serverName}] Connecting to server...`); console.log(`[MCPService][${serverName}] Connecting to server...`);
@ -213,11 +213,11 @@ export class MCPService {
// Phase: Capabilities exchanged // Phase: Capabilities exchanged
onPhase?.( onPhase?.(
MCPConnectionPhase.CapabilitiesExchanged, MCPConnectionPhase.CAPABILITIES_EXCHANGED,
this.createLog( this.createLog(
MCPConnectionPhase.CapabilitiesExchanged, MCPConnectionPhase.CAPABILITIES_EXCHANGED,
'Capabilities exchanged successfully', 'Capabilities exchanged successfully',
MCPLogLevel.Info, MCPLogLevel.INFO,
{ {
serverCapabilities, serverCapabilities,
serverInfo serverInfo
@ -233,8 +233,8 @@ export class MCPService {
// Phase: Listing tools // Phase: Listing tools
onPhase?.( onPhase?.(
MCPConnectionPhase.ListingTools, MCPConnectionPhase.LISTING_TOOLS,
this.createLog(MCPConnectionPhase.ListingTools, 'Listing available tools...') this.createLog(MCPConnectionPhase.LISTING_TOOLS, 'Listing available tools...')
); );
console.log(`[MCPService][${serverName}] Connected, listing tools...`); console.log(`[MCPService][${serverName}] Connected, listing tools...`);
@ -251,9 +251,9 @@ export class MCPService {
// Phase: Connected // Phase: Connected
onPhase?.( onPhase?.(
MCPConnectionPhase.Connected, MCPConnectionPhase.CONNECTED,
this.createLog( this.createLog(
MCPConnectionPhase.Connected, MCPConnectionPhase.CONNECTED,
`Connection established with ${tools.length} tools (${connectionTimeMs}ms)` `Connection established with ${tools.length} tools (${connectionTimeMs}ms)`
) )
); );

View File

@ -13,77 +13,157 @@
*/ */
import { normalizeFloatingPoint } from '$lib/utils'; import { normalizeFloatingPoint } from '$lib/utils';
import type { import type { SyncableParameter, ParameterRecord, ParameterInfo, ParameterValue } from '$lib/types';
SyncableParameter, import { SyncableParameterType, ParameterSource } from '$lib/enums';
ParameterRecord,
ParameterInfo,
ParameterValue,
ParameterSource
} from '$lib/types';
/** /**
* Mapping of webui setting keys to server parameter keys * Mapping of webui setting keys to server parameter keys
* Only parameters that should be synced from server are included * Only parameters that should be synced from server are included
*/ */
export const SYNCABLE_PARAMETERS: SyncableParameter[] = [ export const SYNCABLE_PARAMETERS: SyncableParameter[] = [
{ key: 'temperature', serverKey: 'temperature', type: 'number', canSync: true }, {
{ key: 'top_k', serverKey: 'top_k', type: 'number', canSync: true }, key: 'temperature',
{ key: 'top_p', serverKey: 'top_p', type: 'number', canSync: true }, serverKey: 'temperature',
{ key: 'min_p', serverKey: 'min_p', type: 'number', canSync: true }, type: SyncableParameterType.NUMBER,
{ key: 'dynatemp_range', serverKey: 'dynatemp_range', type: 'number', canSync: true }, canSync: true
{ key: 'dynatemp_exponent', serverKey: 'dynatemp_exponent', type: 'number', canSync: true }, },
{ key: 'xtc_probability', serverKey: 'xtc_probability', type: 'number', canSync: true }, { key: 'top_k', serverKey: 'top_k', type: SyncableParameterType.NUMBER, canSync: true },
{ key: 'xtc_threshold', serverKey: 'xtc_threshold', type: 'number', canSync: true }, { key: 'top_p', serverKey: 'top_p', type: SyncableParameterType.NUMBER, canSync: true },
{ key: 'typ_p', serverKey: 'typ_p', type: 'number', canSync: true }, { key: 'min_p', serverKey: 'min_p', type: SyncableParameterType.NUMBER, canSync: true },
{ key: 'repeat_last_n', serverKey: 'repeat_last_n', type: 'number', canSync: true }, {
{ key: 'repeat_penalty', serverKey: 'repeat_penalty', type: 'number', canSync: true }, key: 'dynatemp_range',
{ key: 'presence_penalty', serverKey: 'presence_penalty', type: 'number', canSync: true }, serverKey: 'dynatemp_range',
{ key: 'frequency_penalty', serverKey: 'frequency_penalty', type: 'number', canSync: true }, type: SyncableParameterType.NUMBER,
{ key: 'dry_multiplier', serverKey: 'dry_multiplier', type: 'number', canSync: true }, canSync: true
{ key: 'dry_base', serverKey: 'dry_base', type: 'number', canSync: true }, },
{ key: 'dry_allowed_length', serverKey: 'dry_allowed_length', type: 'number', canSync: true }, {
{ key: 'dry_penalty_last_n', serverKey: 'dry_penalty_last_n', type: 'number', canSync: true }, key: 'dynatemp_exponent',
{ key: 'max_tokens', serverKey: 'max_tokens', type: 'number', canSync: true }, serverKey: 'dynatemp_exponent',
{ key: 'samplers', serverKey: 'samplers', type: 'string', canSync: true }, type: SyncableParameterType.NUMBER,
canSync: true
},
{
key: 'xtc_probability',
serverKey: 'xtc_probability',
type: SyncableParameterType.NUMBER,
canSync: true
},
{
key: 'xtc_threshold',
serverKey: 'xtc_threshold',
type: SyncableParameterType.NUMBER,
canSync: true
},
{ key: 'typ_p', serverKey: 'typ_p', type: SyncableParameterType.NUMBER, canSync: true },
{
key: 'repeat_last_n',
serverKey: 'repeat_last_n',
type: SyncableParameterType.NUMBER,
canSync: true
},
{
key: 'repeat_penalty',
serverKey: 'repeat_penalty',
type: SyncableParameterType.NUMBER,
canSync: true
},
{
key: 'presence_penalty',
serverKey: 'presence_penalty',
type: SyncableParameterType.NUMBER,
canSync: true
},
{
key: 'frequency_penalty',
serverKey: 'frequency_penalty',
type: SyncableParameterType.NUMBER,
canSync: true
},
{
key: 'dry_multiplier',
serverKey: 'dry_multiplier',
type: SyncableParameterType.NUMBER,
canSync: true
},
{ key: 'dry_base', serverKey: 'dry_base', type: SyncableParameterType.NUMBER, canSync: true },
{
key: 'dry_allowed_length',
serverKey: 'dry_allowed_length',
type: SyncableParameterType.NUMBER,
canSync: true
},
{
key: 'dry_penalty_last_n',
serverKey: 'dry_penalty_last_n',
type: SyncableParameterType.NUMBER,
canSync: true
},
{ key: 'max_tokens', serverKey: 'max_tokens', type: SyncableParameterType.NUMBER, canSync: true },
{ key: 'samplers', serverKey: 'samplers', type: SyncableParameterType.STRING, canSync: true },
{ {
key: 'pasteLongTextToFileLen', key: 'pasteLongTextToFileLen',
serverKey: 'pasteLongTextToFileLen', serverKey: 'pasteLongTextToFileLen',
type: 'number', type: SyncableParameterType.NUMBER,
canSync: true
},
{
key: 'pdfAsImage',
serverKey: 'pdfAsImage',
type: SyncableParameterType.BOOLEAN,
canSync: true canSync: true
}, },
{ key: 'pdfAsImage', serverKey: 'pdfAsImage', type: 'boolean', canSync: true },
{ {
key: 'showThoughtInProgress', key: 'showThoughtInProgress',
serverKey: 'showThoughtInProgress', serverKey: 'showThoughtInProgress',
type: 'boolean', type: SyncableParameterType.BOOLEAN,
canSync: true
},
{
key: 'keepStatsVisible',
serverKey: 'keepStatsVisible',
type: SyncableParameterType.BOOLEAN,
canSync: true
},
{
key: 'showMessageStats',
serverKey: 'showMessageStats',
type: SyncableParameterType.BOOLEAN,
canSync: true canSync: true
}, },
{ key: 'keepStatsVisible', serverKey: 'keepStatsVisible', type: 'boolean', canSync: true },
{ key: 'showMessageStats', serverKey: 'showMessageStats', type: 'boolean', canSync: true },
{ {
key: 'askForTitleConfirmation', key: 'askForTitleConfirmation',
serverKey: 'askForTitleConfirmation', serverKey: 'askForTitleConfirmation',
type: 'boolean', type: SyncableParameterType.BOOLEAN,
canSync: true
},
{
key: 'disableAutoScroll',
serverKey: 'disableAutoScroll',
type: SyncableParameterType.BOOLEAN,
canSync: true canSync: true
}, },
{ key: 'disableAutoScroll', serverKey: 'disableAutoScroll', type: 'boolean', canSync: true },
{ {
key: 'renderUserContentAsMarkdown', key: 'renderUserContentAsMarkdown',
serverKey: 'renderUserContentAsMarkdown', serverKey: 'renderUserContentAsMarkdown',
type: 'boolean', type: SyncableParameterType.BOOLEAN,
canSync: true
},
{
key: 'autoMicOnEmpty',
serverKey: 'autoMicOnEmpty',
type: SyncableParameterType.BOOLEAN,
canSync: true canSync: true
}, },
{ key: 'autoMicOnEmpty', serverKey: 'autoMicOnEmpty', type: 'boolean', canSync: true },
{ {
key: 'pyInterpreterEnabled', key: 'pyInterpreterEnabled',
serverKey: 'pyInterpreterEnabled', serverKey: 'pyInterpreterEnabled',
type: 'boolean', type: SyncableParameterType.BOOLEAN,
canSync: true canSync: true
}, },
{ {
key: 'enableContinueGeneration', key: 'enableContinueGeneration',
serverKey: 'enableContinueGeneration', serverKey: 'enableContinueGeneration',
type: 'boolean', type: SyncableParameterType.BOOLEAN,
canSync: true canSync: true
} }
]; ];
@ -196,7 +276,7 @@ export class ParameterSyncService {
const isUserOverride = userOverrides.has(key); const isUserOverride = userOverrides.has(key);
// Simple logic: either using default (from props) or custom (user override) // Simple logic: either using default (from props) or custom (user override)
const source: ParameterSource = isUserOverride ? 'custom' : 'default'; const source = isUserOverride ? ParameterSource.CUSTOM : ParameterSource.DEFAULT;
return { return {
value: currentValue, value: currentValue,
@ -228,11 +308,11 @@ export class ParameterSyncService {
if (!param) return false; if (!param) return false;
switch (param.type) { switch (param.type) {
case 'number': case SyncableParameterType.NUMBER:
return typeof value === 'number' && !isNaN(value); return typeof value === 'number' && !isNaN(value);
case 'string': case SyncableParameterType.STRING:
return typeof value === 'string'; return typeof value === 'string';
case 'boolean': case SyncableParameterType.BOOLEAN:
return typeof value === 'boolean'; return typeof value === 'boolean';
default: default:
return false; return false;

View File

@ -158,7 +158,7 @@ class MCPStore {
*/ */
getServerLabel(server: MCPServerSettingsEntry): string { getServerLabel(server: MCPServerSettingsEntry): string {
const healthState = this.getHealthCheckState(server.id); const healthState = this.getHealthCheckState(server.id);
if (healthState?.status === HealthCheckStatus.Success) { if (healthState?.status === HealthCheckStatus.SUCCESS) {
return ( return (
healthState.serverInfo?.title || healthState.serverInfo?.name || server.name || server.url healthState.serverInfo?.title || healthState.serverInfo?.name || server.name || server.url
); );
@ -175,7 +175,7 @@ class MCPStore {
return servers.some((s) => { return servers.some((s) => {
const state = this.getHealthCheckState(s.id); const state = this.getHealthCheckState(s.id);
return ( return (
state.status === HealthCheckStatus.Idle || state.status === HealthCheckStatus.Connecting state.status === HealthCheckStatus.IDLE || state.status === HealthCheckStatus.CONNECTING
); );
}); });
} }

View File

@ -76,7 +76,6 @@ export type {
SettingsFieldConfig, SettingsFieldConfig,
SettingsChatServiceOptions, SettingsChatServiceOptions,
SettingsConfigType, SettingsConfigType,
ParameterSource,
ParameterValue, ParameterValue,
ParameterRecord, ParameterRecord,
ParameterInfo, ParameterInfo,

View File

@ -136,20 +136,20 @@ export interface MCPConnection {
* Extended health check state with detailed connection info * Extended health check state with detailed connection info
*/ */
export type HealthCheckState = export type HealthCheckState =
| { status: import('$lib/enums/mcp').HealthCheckStatus.Idle } | { status: import('$lib/enums/mcp').HealthCheckStatus.IDLE }
| { | {
status: import('$lib/enums/mcp').HealthCheckStatus.Connecting; status: import('$lib/enums/mcp').HealthCheckStatus.CONNECTING;
phase: MCPConnectionPhase; phase: MCPConnectionPhase;
logs: MCPConnectionLog[]; logs: MCPConnectionLog[];
} }
| { | {
status: import('$lib/enums/mcp').HealthCheckStatus.Error; status: import('$lib/enums/mcp').HealthCheckStatus.ERROR;
message: string; message: string;
phase?: MCPConnectionPhase; phase?: MCPConnectionPhase;
logs: MCPConnectionLog[]; logs: MCPConnectionLog[];
} }
| { | {
status: import('$lib/enums/mcp').HealthCheckStatus.Success; status: import('$lib/enums/mcp').HealthCheckStatus.SUCCESS;
tools: MCPToolInfo[]; tools: MCPToolInfo[];
serverInfo?: MCPServerInfo; serverInfo?: MCPServerInfo;
capabilities?: MCPCapabilitiesInfo; capabilities?: MCPCapabilitiesInfo;

View File

@ -2,6 +2,7 @@ import type { SETTING_CONFIG_DEFAULT } from '$lib/constants/settings-config';
import type { ChatMessagePromptProgress, ChatMessageTimings } from './chat'; import type { ChatMessagePromptProgress, ChatMessageTimings } from './chat';
import type { OpenAIToolDefinition } from './mcp'; import type { OpenAIToolDefinition } from './mcp';
import type { DatabaseMessageExtra } from './database'; import type { DatabaseMessageExtra } from './database';
import type { ParameterSource, SyncableParameterType } from '$lib/enums';
export type SettingsConfigValue = string | number | boolean; export type SettingsConfigValue = string | number | boolean;
@ -72,8 +73,8 @@ export type SettingsConfigType = typeof SETTING_CONFIG_DEFAULT & {
/** /**
* Parameter synchronization types for server defaults and user overrides * Parameter synchronization types for server defaults and user overrides
* Note: ParameterSource and SyncableParameterType enums are imported from '$lib/enums'
*/ */
export type ParameterSource = 'default' | 'custom';
export type ParameterValue = string | number | boolean; export type ParameterValue = string | number | boolean;
export type ParameterRecord = Record<string, ParameterValue>; export type ParameterRecord = Record<string, ParameterValue>;
@ -87,6 +88,6 @@ export interface ParameterInfo {
export interface SyncableParameter { export interface SyncableParameter {
key: string; key: string;
serverKey: string; serverKey: string;
type: 'number' | 'string' | 'boolean'; type: SyncableParameterType;
canSync: boolean; canSync: boolean;
} }

View File

@ -1,5 +1,5 @@
import type { MCPServerSettingsEntry } from '$lib/types'; import type { MCPServerSettingsEntry } from '$lib/types';
import { MCPTransportType, MCPLogLevel } from '$lib/enums'; import { MCPTransportType, MCPLogLevel, UrlPrefix } from '$lib/enums';
import { DEFAULT_MCP_CONFIG } from '$lib/constants/mcp'; import { DEFAULT_MCP_CONFIG } from '$lib/constants/mcp';
import { Info, AlertTriangle, XCircle } from '@lucide/svelte'; import { Info, AlertTriangle, XCircle } from '@lucide/svelte';
import type { Component } from 'svelte'; import type { Component } from 'svelte';
@ -11,9 +11,10 @@ import type { Component } from 'svelte';
export function detectMcpTransportFromUrl(url: string): MCPTransportType { export function detectMcpTransportFromUrl(url: string): MCPTransportType {
const normalized = url.trim().toLowerCase(); const normalized = url.trim().toLowerCase();
return normalized.startsWith('ws://') || normalized.startsWith('wss://') return normalized.startsWith(UrlPrefix.WEBSOCKET) ||
? MCPTransportType.Websocket normalized.startsWith(UrlPrefix.WEBSOCKET_SECURE)
: MCPTransportType.StreamableHttp; ? MCPTransportType.WEBSOCKET
: MCPTransportType.STREAMABLE_HTTP;
} }
/** /**
@ -70,9 +71,9 @@ export function parseMcpServerSettings(rawServers: unknown): MCPServerSettingsEn
*/ */
export function getMcpLogLevelIcon(level: MCPLogLevel): Component { export function getMcpLogLevelIcon(level: MCPLogLevel): Component {
switch (level) { switch (level) {
case MCPLogLevel.Error: case MCPLogLevel.ERROR:
return XCircle; return XCircle;
case MCPLogLevel.Warn: case MCPLogLevel.WARN:
return AlertTriangle; return AlertTriangle;
default: default:
return Info; return Info;
@ -87,9 +88,9 @@ export function getMcpLogLevelIcon(level: MCPLogLevel): Component {
*/ */
export function getMcpLogLevelClass(level: MCPLogLevel): string { export function getMcpLogLevelClass(level: MCPLogLevel): string {
switch (level) { switch (level) {
case MCPLogLevel.Error: case MCPLogLevel.ERROR:
return 'text-destructive'; return 'text-destructive';
case MCPLogLevel.Warn: case MCPLogLevel.WARN:
return 'text-yellow-600 dark:text-yellow-500'; return 'text-yellow-600 dark:text-yellow-500';
default: default:
return 'text-muted-foreground'; return 'text-muted-foreground';