refactor: Cleanup
This commit is contained in:
parent
70efc41eb1
commit
16f333e4ec
|
|
@ -0,0 +1,7 @@
|
|||
export const NEWLINE = '\n';
|
||||
export const DEFAULT_LANGUAGE = 'text';
|
||||
export const LANG_PATTERN = /^(\w*)\n?/;
|
||||
export const AMPERSAND_REGEX = /&/g;
|
||||
export const LT_REGEX = /</g;
|
||||
export const GT_REGEX = />/g;
|
||||
export const FENCE_PATTERN = /^```|\n```/g;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export const GOOGLE_FAVICON_BASE_URL = 'https://www.google.com/s2/favicons';
|
||||
export const DEFAULT_FAVICON_SIZE = 32;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
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;
|
||||
|
|
@ -9,3 +9,5 @@ export const DEFAULT_MCP_CONFIG = {
|
|||
} as const;
|
||||
|
||||
export const MCP_SERVER_ID_PREFIX = 'LlamaCpp-WebUI-MCP-Server-';
|
||||
export const DEFAULT_CLIENT_VERSION = '1.0.0';
|
||||
export const DEFAULT_IMAGE_MIME_TYPE = 'image/png';
|
||||
|
|
|
|||
|
|
@ -28,7 +28,14 @@ export {
|
|||
SpecialFileType
|
||||
} from './files';
|
||||
|
||||
export { MCPConnectionPhase, MCPLogLevel, MCPTransportType, HealthCheckStatus } from './mcp';
|
||||
export {
|
||||
MCPConnectionPhase,
|
||||
MCPLogLevel,
|
||||
MCPTransportType,
|
||||
HealthCheckStatus,
|
||||
MCPContentType,
|
||||
MCPRefType
|
||||
} from './mcp';
|
||||
|
||||
export { ModelModality } from './model';
|
||||
|
||||
|
|
|
|||
|
|
@ -40,3 +40,20 @@ export enum HealthCheckStatus {
|
|||
SUCCESS = 'success',
|
||||
ERROR = 'error'
|
||||
}
|
||||
|
||||
/**
|
||||
* Content types for MCP tool results
|
||||
*/
|
||||
export enum MCPContentType {
|
||||
TEXT = 'text',
|
||||
IMAGE = 'image',
|
||||
RESOURCE = 'resource'
|
||||
}
|
||||
|
||||
/**
|
||||
* Reference types for MCP completions
|
||||
*/
|
||||
export enum MCPRefType {
|
||||
PROMPT = 'ref/prompt',
|
||||
RESOURCE = 'ref/resource'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,8 +39,18 @@ import type {
|
|||
MCPResourceContent,
|
||||
MCPReadResourceResult
|
||||
} from '$lib/types';
|
||||
import { MCPConnectionPhase, MCPLogLevel, MCPTransportType } from '$lib/enums';
|
||||
import { DEFAULT_MCP_CONFIG } from '$lib/constants/mcp';
|
||||
import {
|
||||
MCPConnectionPhase,
|
||||
MCPLogLevel,
|
||||
MCPTransportType,
|
||||
MCPContentType,
|
||||
MCPRefType
|
||||
} from '$lib/enums';
|
||||
import {
|
||||
DEFAULT_MCP_CONFIG,
|
||||
DEFAULT_CLIENT_VERSION,
|
||||
DEFAULT_IMAGE_MIME_TYPE
|
||||
} from '$lib/constants/mcp';
|
||||
import { throwIfAborted, isAbortError } from '$lib/utils';
|
||||
import { buildProxiedUrl } from '$lib/utils/cors-proxy';
|
||||
|
||||
|
|
@ -102,7 +112,7 @@ export class MCPService {
|
|||
requestInit.credentials = config.credentials;
|
||||
}
|
||||
|
||||
if (config.transport === 'websocket') {
|
||||
if (config.transport === MCPTransportType.WEBSOCKET) {
|
||||
if (useProxy) {
|
||||
throw new Error(
|
||||
'WebSocket transport is not supported when using CORS proxy. Use HTTP transport instead.'
|
||||
|
|
@ -215,7 +225,7 @@ export class MCPService {
|
|||
const client = new Client(
|
||||
{
|
||||
name: effectiveClientInfo.name,
|
||||
version: effectiveClientInfo.version ?? '1.0.0'
|
||||
version: effectiveClientInfo.version ?? DEFAULT_CLIENT_VERSION
|
||||
},
|
||||
{
|
||||
capabilities: effectiveCapabilities,
|
||||
|
|
@ -406,15 +416,15 @@ export class MCPService {
|
|||
}
|
||||
|
||||
private static formatSingleContent(content: ToolResultContentItem): string {
|
||||
if (content.type === 'text' && content.text) {
|
||||
if (content.type === MCPContentType.TEXT && content.text) {
|
||||
return content.text;
|
||||
}
|
||||
|
||||
if (content.type === 'image' && content.data) {
|
||||
return `data:${content.mimeType ?? 'image/png'};base64,${content.data}`;
|
||||
if (content.type === MCPContentType.IMAGE && content.data) {
|
||||
return `data:${content.mimeType ?? DEFAULT_IMAGE_MIME_TYPE};base64,${content.data}`;
|
||||
}
|
||||
|
||||
if (content.type === 'resource' && content.resource) {
|
||||
if (content.type === MCPContentType.RESOURCE && content.resource) {
|
||||
const resource = content.resource;
|
||||
|
||||
if (resource.text) return resource.text;
|
||||
|
|
@ -449,7 +459,7 @@ export class MCPService {
|
|||
*/
|
||||
static async complete(
|
||||
connection: MCPConnection,
|
||||
ref: { type: 'ref/prompt'; name: string } | { type: 'ref/resource'; uri: string },
|
||||
ref: { type: MCPRefType.PROMPT; name: string } | { type: MCPRefType.RESOURCE; uri: string },
|
||||
argument: { name: string; value: string }
|
||||
): Promise<{ values: string[]; total?: number; hasMore?: boolean } | null> {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { MCPService } from '$lib/services/mcp.service';
|
|||
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
||||
import { mcpResourceStore } from '$lib/stores/mcp-resources.svelte';
|
||||
import { parseMcpServerSettings, detectMcpTransportFromUrl } from '$lib/utils';
|
||||
import { MCPConnectionPhase, MCPLogLevel, HealthCheckStatus } from '$lib/enums';
|
||||
import { MCPConnectionPhase, MCPLogLevel, HealthCheckStatus, MCPRefType } from '$lib/enums';
|
||||
import { DEFAULT_MCP_CONFIG, MCP_SERVER_ID_PREFIX } from '$lib/constants/mcp';
|
||||
import type {
|
||||
MCPToolCall,
|
||||
|
|
@ -732,7 +732,7 @@ class MCPStore {
|
|||
if (!connection.serverCapabilities?.completions) return null;
|
||||
return MCPService.complete(
|
||||
connection,
|
||||
{ type: 'ref/prompt', name: promptName },
|
||||
{ type: MCPRefType.PROMPT, name: promptName },
|
||||
{ name: argumentName, value: argumentValue }
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { base } from '$app/paths';
|
||||
import { getJsonHeaders, getAuthHeaders } from './api-headers';
|
||||
import { UrlPrefix } from '$lib/enums';
|
||||
|
||||
/**
|
||||
* API Fetch Utilities
|
||||
|
|
@ -48,7 +49,8 @@ export async function apiFetch<T>(path: string, options: ApiFetchOptions = {}):
|
|||
const baseHeaders = authOnly ? getAuthHeaders() : getJsonHeaders();
|
||||
const headers = { ...baseHeaders, ...customHeaders };
|
||||
|
||||
const url = path.startsWith('http') ? path : `${base}${path}`;
|
||||
const url =
|
||||
path.startsWith(UrlPrefix.HTTP) || path.startsWith(UrlPrefix.HTTPS) ? path : `${base}${path}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
...fetchOptions,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,13 @@
|
|||
import hljs from 'highlight.js';
|
||||
import {
|
||||
NEWLINE,
|
||||
DEFAULT_LANGUAGE,
|
||||
LANG_PATTERN,
|
||||
AMPERSAND_REGEX,
|
||||
LT_REGEX,
|
||||
GT_REGEX,
|
||||
FENCE_PATTERN
|
||||
} from '$lib/constants/code';
|
||||
|
||||
export interface IncompleteCodeBlock {
|
||||
language: string;
|
||||
|
|
@ -26,7 +35,10 @@ export function highlightCode(code: string, language: string): string {
|
|||
}
|
||||
} catch {
|
||||
// Fallback to escaped plain text
|
||||
return code.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
return code
|
||||
.replace(AMPERSAND_REGEX, '&')
|
||||
.replace(LT_REGEX, '<')
|
||||
.replace(GT_REGEX, '>');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -39,13 +51,13 @@ export function highlightCode(code: string, language: string): string {
|
|||
export function detectIncompleteCodeBlock(markdown: string): IncompleteCodeBlock | null {
|
||||
// Count all code fences in the markdown
|
||||
// A code block is incomplete if there's an odd number of ``` fences
|
||||
const fencePattern = /^```|\n```/g;
|
||||
const fencePattern = new RegExp(FENCE_PATTERN.source, FENCE_PATTERN.flags);
|
||||
const fences: number[] = [];
|
||||
let fenceMatch;
|
||||
|
||||
while ((fenceMatch = fencePattern.exec(markdown)) !== null) {
|
||||
// Store the position after the ```
|
||||
const pos = fenceMatch[0].startsWith('\n') ? fenceMatch.index + 1 : fenceMatch.index;
|
||||
const pos = fenceMatch[0].startsWith(NEWLINE) ? fenceMatch.index + 1 : fenceMatch.index;
|
||||
fences.push(pos);
|
||||
}
|
||||
|
||||
|
|
@ -60,8 +72,8 @@ export function detectIncompleteCodeBlock(markdown: string): IncompleteCodeBlock
|
|||
const afterOpening = markdown.slice(openingIndex + 3);
|
||||
|
||||
// Extract language and code content
|
||||
const langMatch = afterOpening.match(/^(\w*)\n?/);
|
||||
const language = langMatch?.[1] || 'text';
|
||||
const langMatch = afterOpening.match(LANG_PATTERN);
|
||||
const language = langMatch?.[1] || DEFAULT_LANGUAGE;
|
||||
const codeStartIndex = openingIndex + 3 + (langMatch?.[0]?.length ?? 0);
|
||||
const code = markdown.slice(codeStartIndex);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
|
||||
import { getProxiedUrlString } from './cors-proxy';
|
||||
import { GOOGLE_FAVICON_BASE_URL, DEFAULT_FAVICON_SIZE } from '$lib/constants/favicon';
|
||||
|
||||
/**
|
||||
* Gets a favicon URL for a given URL using Google's favicon service.
|
||||
|
|
@ -17,7 +18,7 @@ export function getFaviconUrl(urlString: string): string | null {
|
|||
const hostnameParts = url.hostname.split('.');
|
||||
const rootDomain = hostnameParts.length >= 2 ? hostnameParts.slice(-2).join('.') : url.hostname;
|
||||
|
||||
const googleFaviconUrl = `https://www.google.com/s2/favicons?domain=${rootDomain}&sz=32`;
|
||||
const googleFaviconUrl = `${GOOGLE_FAVICON_BASE_URL}?domain=${rootDomain}&sz=${DEFAULT_FAVICON_SIZE}`;
|
||||
return getProxiedUrlString(googleFaviconUrl);
|
||||
} catch {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
import {
|
||||
MS_PER_SECOND,
|
||||
SECONDS_PER_MINUTE,
|
||||
SECONDS_PER_HOUR,
|
||||
SHORT_DURATION_THRESHOLD,
|
||||
MEDIUM_DURATION_THRESHOLD
|
||||
} from '$lib/constants/formatters';
|
||||
|
||||
/**
|
||||
* Formats file size in bytes to human readable format
|
||||
* Supports Bytes, KB, MB, and GB
|
||||
|
|
@ -93,19 +101,19 @@ export function formatTime(date: Date): string {
|
|||
export function formatPerformanceTime(ms: number): string {
|
||||
if (ms < 0) return '0s';
|
||||
|
||||
const totalSeconds = ms / 1000;
|
||||
const totalSeconds = ms / MS_PER_SECOND;
|
||||
|
||||
if (totalSeconds < 1) {
|
||||
if (totalSeconds < SHORT_DURATION_THRESHOLD) {
|
||||
return `${totalSeconds.toFixed(1)}s`;
|
||||
}
|
||||
|
||||
if (totalSeconds < 10) {
|
||||
if (totalSeconds < MEDIUM_DURATION_THRESHOLD) {
|
||||
return `${totalSeconds.toFixed(1)}s`;
|
||||
}
|
||||
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
const seconds = Math.floor(totalSeconds % 60);
|
||||
const hours = Math.floor(totalSeconds / SECONDS_PER_HOUR);
|
||||
const minutes = Math.floor((totalSeconds % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE);
|
||||
const seconds = Math.floor(totalSeconds % SECONDS_PER_MINUTE);
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue