mirror of
https://github.com/LostRuins/koboldcpp.git
synced 2026-09-19 17:25:07 +02:00
Merge branch 'upstream' into concedo_experimental
# Conflicts: # .devops/openvino.Dockerfile # .github/workflows/build-cache.yml # .github/workflows/build-openvino.yml # .github/workflows/build-self-hosted.yml # .github/workflows/release.yml # docs/backend/OPENVINO.md # docs/backend/SYCL.md # docs/ops.md # docs/ops/SYCL.csv # ggml/src/ggml-opencl/ggml-opencl.cpp # ggml/src/ggml-opencl/kernels/mul_mv_f16_f32_l4.cl # ggml/src/ggml-openvino/.clang-format # ggml/src/ggml-openvino/CMakeLists.txt # ggml/src/ggml-openvino/ggml-decoder.cpp # ggml/src/ggml-openvino/ggml-decoder.h # ggml/src/ggml-openvino/ggml-openvino-extra.cpp # ggml/src/ggml-openvino/ggml-openvino-extra.h # ggml/src/ggml-openvino/ggml-openvino.cpp # ggml/src/ggml-openvino/ggml-quants.cpp # ggml/src/ggml-openvino/ggml-quants.h # ggml/src/ggml-openvino/openvino/decoder.h # ggml/src/ggml-openvino/openvino/frontend.h # ggml/src/ggml-openvino/openvino/input_model.h # ggml/src/ggml-openvino/openvino/node_context.h # ggml/src/ggml-openvino/openvino/op/cont.cpp # ggml/src/ggml-openvino/openvino/op/cpy.cpp # ggml/src/ggml-openvino/openvino/op/flash_attn_ext.cpp # ggml/src/ggml-openvino/openvino/op/get_rows.cpp # ggml/src/ggml-openvino/openvino/op/glu_geglu.cpp # ggml/src/ggml-openvino/openvino/op/glu_swiglu.cpp # ggml/src/ggml-openvino/openvino/op/mulmat.cpp # ggml/src/ggml-openvino/openvino/op/permute.cpp # ggml/src/ggml-openvino/openvino/op/reshape.cpp # ggml/src/ggml-openvino/openvino/op/rms_norm.cpp # ggml/src/ggml-openvino/openvino/op/rope.cpp # ggml/src/ggml-openvino/openvino/op/set_rows.cpp # ggml/src/ggml-openvino/openvino/op/softmax.cpp # ggml/src/ggml-openvino/openvino/op/transpose.cpp # ggml/src/ggml-openvino/openvino/op/unary_silu.cpp # ggml/src/ggml-openvino/openvino/op/view.cpp # ggml/src/ggml-openvino/openvino/op_table.cpp # ggml/src/ggml-openvino/openvino/op_table.h # ggml/src/ggml-openvino/openvino/pass/mark_decompression_convert_constant_folding.h # ggml/src/ggml-openvino/openvino/translate_session.cpp # ggml/src/ggml-openvino/openvino/translate_session.h # ggml/src/ggml-openvino/openvino/utils.cpp # ggml/src/ggml-openvino/openvino/utils.h # ggml/src/ggml-openvino/utils.cpp # ggml/src/ggml-openvino/utils.h # ggml/src/ggml-sycl/common.hpp # ggml/src/ggml-sycl/ggml-sycl.cpp
This commit is contained in:
@@ -41,6 +41,7 @@
|
||||
DATA_ERROR_HANDLED_ATTR,
|
||||
BOOL_TRUE_STRING,
|
||||
SETTINGS_KEYS,
|
||||
CODE_BLOCK_HEADER_CLASS,
|
||||
MERMAID_WRAPPER_CLASS,
|
||||
MERMAID_BLOCK_CLASS,
|
||||
MERMAID_LANGUAGE,
|
||||
@@ -53,7 +54,11 @@
|
||||
SVG_TAG_PREFIX,
|
||||
SVG_SOURCE_ATTR,
|
||||
SVG_RENDERED_ATTR,
|
||||
SVG_INLINE_SHADOW_STYLE
|
||||
SVG_INLINE_SHADOW_STYLE,
|
||||
TOGGLE_SOURCE_BTN_CLASS,
|
||||
DIAGRAM_VIEW_MODE_ATTR,
|
||||
DIAGRAM_VIEW_RENDERED,
|
||||
DIAGRAM_VIEW_SOURCE
|
||||
} from '$lib/constants';
|
||||
import { ColorMode, UrlProtocol } from '$lib/enums';
|
||||
import { FileTypeText } from '$lib/enums/files.enums';
|
||||
@@ -501,6 +506,23 @@
|
||||
async function handleMermaidClick(event: MouseEvent) {
|
||||
const target = event.target as HTMLElement;
|
||||
|
||||
// Toggle a diagram block between its rendered view and its source view.
|
||||
// Shared by mermaid and svg, css drives the visibility from the wrapper mode.
|
||||
const toggleBtn = target.closest(`.${TOGGLE_SOURCE_BTN_CLASS}`);
|
||||
if (toggleBtn) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const wrapper = toggleBtn.closest(`.${MERMAID_WRAPPER_CLASS}, .${SVG_WRAPPER_CLASS}`);
|
||||
if (!wrapper) return;
|
||||
|
||||
const isSource = wrapper.getAttribute(DIAGRAM_VIEW_MODE_ATTR) === DIAGRAM_VIEW_SOURCE;
|
||||
const next = isSource ? DIAGRAM_VIEW_RENDERED : DIAGRAM_VIEW_SOURCE;
|
||||
wrapper.setAttribute(DIAGRAM_VIEW_MODE_ATTR, next);
|
||||
toggleBtn.setAttribute('aria-pressed', String(!isSource));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if clicking on copy or preview button in mermaid block
|
||||
const copyBtn = target.closest(`.${MERMAID_WRAPPER_CLASS} .copy-code-btn`);
|
||||
const previewBtn = target.closest(`.${MERMAID_WRAPPER_CLASS} .preview-code-btn`);
|
||||
@@ -573,6 +595,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// A click on the header chrome targets the action buttons, never the
|
||||
// diagram. Guard so a header click can not fall through to the click to
|
||||
// zoom branches below, whatever the scroll position or stacking.
|
||||
if (target.closest(`.${CODE_BLOCK_HEADER_CLASS}`)) return;
|
||||
|
||||
// Open preview when clicking the svg block itself. A final block carries its
|
||||
// source, a streaming block does not and is mirrored live into the dialog.
|
||||
const svgEl = target.closest(`.${SVG_BLOCK_CLASS}`);
|
||||
|
||||
@@ -300,7 +300,8 @@ div.markdown-user-content :global(.table-wrapper) {
|
||||
}
|
||||
|
||||
.markdown-content :global(.copy-code-btn),
|
||||
.markdown-content :global(.preview-code-btn) {
|
||||
.markdown-content :global(.preview-code-btn),
|
||||
.markdown-content :global(.toggle-source-btn) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -312,15 +313,22 @@ div.markdown-user-content :global(.table-wrapper) {
|
||||
}
|
||||
|
||||
.markdown-content :global(.copy-code-btn:hover),
|
||||
.markdown-content :global(.preview-code-btn:hover) {
|
||||
.markdown-content :global(.preview-code-btn:hover),
|
||||
.markdown-content :global(.toggle-source-btn:hover) {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.markdown-content :global(.copy-code-btn:active),
|
||||
.markdown-content :global(.preview-code-btn:active) {
|
||||
.markdown-content :global(.preview-code-btn:active),
|
||||
.markdown-content :global(.toggle-source-btn:active) {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* Pressed state marks the source view as active */
|
||||
.markdown-content :global(.toggle-source-btn[aria-pressed='true']) {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.markdown-content :global(.code-block-wrapper pre) {
|
||||
background: transparent;
|
||||
margin: 0;
|
||||
@@ -629,8 +637,8 @@ div.markdown-user-content :global(.table-wrapper) {
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-items: safe center;
|
||||
justify-content: safe center;
|
||||
padding: 3rem 1rem 1rem;
|
||||
}
|
||||
|
||||
@@ -645,7 +653,9 @@ div.markdown-user-content :global(.table-wrapper) {
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
/* Diagram block uses same header styling as code blocks */
|
||||
/* Diagram block uses same header styling as code blocks. The header floats over
|
||||
scrollable diagram content and stays transparent, so the overflow shows up to
|
||||
the box edge. It keeps a z-index so it stays the click target above content. */
|
||||
.markdown-content :global(.mermaid-block-wrapper .code-block-header),
|
||||
.markdown-content :global(.svg-block-wrapper .code-block-header) {
|
||||
display: flex;
|
||||
@@ -657,6 +667,7 @@ div.markdown-user-content :global(.table-wrapper) {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.markdown-content :global(.mermaid-block-wrapper .code-block-actions),
|
||||
@@ -683,6 +694,31 @@ div.markdown-user-content :global(.table-wrapper) {
|
||||
padding: 3rem 1rem;
|
||||
}
|
||||
|
||||
/* Source view stays hidden while the block renders, css swaps the two views
|
||||
from the wrapper mode so the click handler only flips one attribute. The view
|
||||
reuses the code block scroll container, so it matches the app code blocks. */
|
||||
.markdown-content :global(.diagram-source) {
|
||||
display: none;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown-content :global(.diagram-source pre) {
|
||||
background: transparent;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.markdown-content :global([data-view-mode='source'] .mermaid-scroll-container),
|
||||
.markdown-content :global([data-view-mode='source'] .svg-scroll-container) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.markdown-content :global([data-view-mode='source'] .diagram-source) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Streaming mermaid block - empty preview box */
|
||||
.mermaid-streaming-block {
|
||||
min-height: 300px;
|
||||
|
||||
+63
-7
@@ -7,12 +7,16 @@ import type { Element, ElementContent } from 'hast';
|
||||
import {
|
||||
CODE_BLOCK_HEADER_CLASS,
|
||||
CODE_BLOCK_ACTIONS_CLASS,
|
||||
CODE_BLOCK_SCROLL_CONTAINER_CLASS,
|
||||
CODE_LANGUAGE_CLASS,
|
||||
COPY_CODE_BTN_CLASS,
|
||||
PREVIEW_CODE_BTN_CLASS,
|
||||
TOGGLE_SOURCE_BTN_CLASS,
|
||||
DIAGRAM_SOURCE_CLASS,
|
||||
RELATIVE_CLASS,
|
||||
COPY_ICON_SVG,
|
||||
PREVIEW_ICON_SVG
|
||||
PREVIEW_ICON_SVG,
|
||||
CODE_ICON_SVG
|
||||
} from '$lib/constants';
|
||||
|
||||
export interface BlockIdGenerator {
|
||||
@@ -32,14 +36,16 @@ export function createIconElement(svg: string): Element {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a button element with icon.
|
||||
* Creates a button element with icon. Extra properties merge onto the button,
|
||||
* which lets a stateful button carry attributes like aria-pressed.
|
||||
*/
|
||||
export function createButton(
|
||||
className: string,
|
||||
title: string,
|
||||
iconSvg: string,
|
||||
id: string,
|
||||
idAttribute: string
|
||||
idAttribute: string,
|
||||
extraProperties: Record<string, string> = {}
|
||||
): Element {
|
||||
return {
|
||||
type: 'element',
|
||||
@@ -48,7 +54,8 @@ export function createButton(
|
||||
className: [className],
|
||||
[idAttribute]: id,
|
||||
title,
|
||||
type: 'button'
|
||||
type: 'button',
|
||||
...extraProperties
|
||||
},
|
||||
children: [createIconElement(iconSvg)]
|
||||
};
|
||||
@@ -72,6 +79,52 @@ export function createPreviewButton(
|
||||
return createButton(PREVIEW_CODE_BTN_CLASS, title, PREVIEW_ICON_SVG, id, idAttribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a button that toggles a diagram block between its rendered view and
|
||||
* its source view. aria-pressed starts false, the rendered view is the default.
|
||||
*/
|
||||
export function createToggleSourceButton(
|
||||
id: string,
|
||||
idAttribute: string,
|
||||
title: string = 'Toggle source'
|
||||
): Element {
|
||||
return createButton(TOGGLE_SOURCE_BTN_CLASS, title, CODE_ICON_SVG, id, idAttribute, {
|
||||
'aria-pressed': 'false'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a source view for a diagram block. It reuses the code block scroll
|
||||
* container so it matches the app code blocks, and wraps the highlighted code
|
||||
* element captured at transform time. A missing code element falls back to a
|
||||
* plain code node built from the raw source.
|
||||
*/
|
||||
export function createSourceView(
|
||||
codeElement: Element | undefined,
|
||||
source: string,
|
||||
language: string
|
||||
): Element {
|
||||
const code: Element = codeElement ?? {
|
||||
type: 'element',
|
||||
tagName: 'code',
|
||||
properties: { className: ['hljs', `language-${language}`] },
|
||||
children: [{ type: 'text', value: source }]
|
||||
};
|
||||
return {
|
||||
type: 'element',
|
||||
tagName: 'div',
|
||||
properties: { className: [DIAGRAM_SOURCE_CLASS, CODE_BLOCK_SCROLL_CONTAINER_CLASS] },
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
tagName: 'pre',
|
||||
properties: {},
|
||||
children: [code]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a block header with language label and action buttons.
|
||||
*/
|
||||
@@ -116,14 +169,17 @@ export function createScrollContainer(preElement: Element, scrollContainerClass:
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a wrapper element with header and scroll container.
|
||||
* Creates a wrapper element with header and scroll container. Extra children
|
||||
* append after the scroll container, which lets a block carry a source view
|
||||
* alongside its rendered output.
|
||||
*/
|
||||
export function createWrapper(
|
||||
header: Element,
|
||||
preElement: Element,
|
||||
wrapperClass: string,
|
||||
scrollContainerClass: string,
|
||||
additionalAttributes?: Record<string, string>
|
||||
additionalAttributes?: Record<string, string>,
|
||||
extraChildren: Element[] = []
|
||||
): Element {
|
||||
return {
|
||||
type: 'element',
|
||||
@@ -132,7 +188,7 @@ export function createWrapper(
|
||||
className: [wrapperClass, RELATIVE_CLASS],
|
||||
...additionalAttributes
|
||||
} as Element['properties'],
|
||||
children: [header, createScrollContainer(preElement, scrollContainerClass)]
|
||||
children: [header, createScrollContainer(preElement, scrollContainerClass), ...extraChildren]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+14
-2
@@ -19,12 +19,17 @@ import {
|
||||
MERMAID_BLOCK_CLASS,
|
||||
MERMAID_LANGUAGE,
|
||||
MERMAID_SYNTAX_ATTR,
|
||||
MERMAID_ID_ATTR
|
||||
MERMAID_ID_ATTR,
|
||||
DIAGRAM_VIEW_MODE_ATTR,
|
||||
DIAGRAM_VIEW_RENDERED
|
||||
} from '$lib/constants';
|
||||
import type { DiagramPreData } from './pre-transform';
|
||||
import {
|
||||
createBlockHeader,
|
||||
createCopyButton,
|
||||
createPreviewButton,
|
||||
createToggleSourceButton,
|
||||
createSourceView,
|
||||
createWrapper,
|
||||
generateBlockId
|
||||
} from './code-block-utils';
|
||||
@@ -75,16 +80,23 @@ export const rehypeEnhanceMermaidBlocks: Plugin<[], Root> = () => {
|
||||
|
||||
const actions = [
|
||||
createCopyButton(mermaidId, MERMAID_ID_ATTR, 'Copy mermaid syntax'),
|
||||
createToggleSourceButton(mermaidId, MERMAID_ID_ATTR, 'Toggle mermaid source'),
|
||||
createPreviewButton(mermaidId, MERMAID_ID_ATTR, 'Preview diagram')
|
||||
];
|
||||
|
||||
const header = createBlockHeader(MERMAID_LANGUAGE, mermaidId, MERMAID_ID_ATTR, actions);
|
||||
const preservedCode = (node.data as DiagramPreData | undefined)?.sourceCode;
|
||||
const sourceView = createSourceView(preservedCode, diagramText, MERMAID_LANGUAGE);
|
||||
const wrapper = createWrapper(
|
||||
header,
|
||||
node,
|
||||
MERMAID_WRAPPER_CLASS,
|
||||
MERMAID_SCROLL_CONTAINER_CLASS,
|
||||
{ [MERMAID_ID_ATTR]: mermaidId }
|
||||
{
|
||||
[MERMAID_ID_ATTR]: mermaidId,
|
||||
[DIAGRAM_VIEW_MODE_ATTR]: DIAGRAM_VIEW_RENDERED
|
||||
},
|
||||
[sourceView]
|
||||
);
|
||||
|
||||
// Replace pre with wrapper in parent
|
||||
|
||||
+20
-4
@@ -18,12 +18,17 @@ import {
|
||||
SVG_BLOCK_CLASS,
|
||||
SVG_LANGUAGE,
|
||||
SVG_SOURCE_ATTR,
|
||||
SVG_ID_ATTR
|
||||
SVG_ID_ATTR,
|
||||
DIAGRAM_VIEW_MODE_ATTR,
|
||||
DIAGRAM_VIEW_RENDERED
|
||||
} from '$lib/constants';
|
||||
import type { DiagramPreData } from './pre-transform';
|
||||
import {
|
||||
createBlockHeader,
|
||||
createCopyButton,
|
||||
createPreviewButton,
|
||||
createToggleSourceButton,
|
||||
createSourceView,
|
||||
createWrapper,
|
||||
generateBlockId
|
||||
} from './code-block-utils';
|
||||
@@ -65,13 +70,24 @@ export const rehypeEnhanceSvgBlocks: Plugin<[], Root> = () => {
|
||||
|
||||
const actions = [
|
||||
createCopyButton(svgId, SVG_ID_ATTR, 'Copy svg source'),
|
||||
createToggleSourceButton(svgId, SVG_ID_ATTR, 'Toggle svg source'),
|
||||
createPreviewButton(svgId, SVG_ID_ATTR, 'Preview svg')
|
||||
];
|
||||
|
||||
const header = createBlockHeader(SVG_LANGUAGE, svgId, SVG_ID_ATTR, actions);
|
||||
const wrapper = createWrapper(header, node, SVG_WRAPPER_CLASS, SVG_SCROLL_CONTAINER_CLASS, {
|
||||
[SVG_ID_ATTR]: svgId
|
||||
});
|
||||
const preservedCode = (node.data as DiagramPreData | undefined)?.sourceCode;
|
||||
const sourceView = createSourceView(preservedCode, svgSource, SVG_LANGUAGE);
|
||||
const wrapper = createWrapper(
|
||||
header,
|
||||
node,
|
||||
SVG_WRAPPER_CLASS,
|
||||
SVG_SCROLL_CONTAINER_CLASS,
|
||||
{
|
||||
[SVG_ID_ATTR]: svgId,
|
||||
[DIAGRAM_VIEW_MODE_ATTR]: DIAGRAM_VIEW_RENDERED
|
||||
},
|
||||
[sourceView]
|
||||
);
|
||||
|
||||
// Replace pre with wrapper in parent
|
||||
(parent.children as ElementContent[])[index] = wrapper;
|
||||
|
||||
+13
-1
@@ -2,6 +2,15 @@ import type { Plugin } from 'unified';
|
||||
import type { Root, Element, ElementContent, Text } from 'hast';
|
||||
import { visit } from 'unist-util-visit';
|
||||
|
||||
/**
|
||||
* Metadata a diagram pre carries on its unist data field. The source code holds
|
||||
* the highlighted code element captured before the pre became a render target,
|
||||
* which the enhancer reuses to build a matching source view.
|
||||
*/
|
||||
export interface DiagramPreData {
|
||||
sourceCode: Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively extracts all text content from a HAST node.
|
||||
* Handles nested elements (e.g., span wrappers from syntax highlighting).
|
||||
@@ -69,7 +78,10 @@ export function createPreTransform(
|
||||
properties: {
|
||||
className: [targetClass]
|
||||
},
|
||||
children: [{ type: 'text', value: text } as Text]
|
||||
children: [{ type: 'text', value: text } as Text],
|
||||
// Keep the highlighted code element so the block can offer a source
|
||||
// view that matches the app code blocks without re highlighting.
|
||||
data: { sourceCode: codeElement } satisfies DiagramPreData
|
||||
};
|
||||
|
||||
(parent.children as ElementContent[])[index] = pre;
|
||||
|
||||
@@ -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';
|
||||
@@ -39,3 +39,5 @@ export const MODALITY_LABELS = {
|
||||
export const COPY_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy-icon lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>`;
|
||||
|
||||
export const PREVIEW_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye lucide-eye-icon"><path d="M2.062 12.345a1 1 0 0 1 0-.69C3.5 7.73 7.36 5 12 5s8.5 2.73 9.938 6.655a1 1 0 0 1 0 .69C20.5 16.27 16.64 19 12 19s-8.5-2.73-9.938-6.655"/><circle cx="12" cy="12" r="3"/></svg>`;
|
||||
|
||||
export const CODE_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-code lucide-code-icon"><path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/></svg>`;
|
||||
|
||||
@@ -30,6 +30,7 @@ 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';
|
||||
|
||||
@@ -84,3 +84,8 @@ export const MCP_TRANSPORT_ICONS: Record<MCPTransportType, Component> = {
|
||||
[MCPTransportType.STREAMABLE_HTTP]: Globe,
|
||||
[MCPTransportType.SSE]: Radio
|
||||
};
|
||||
|
||||
/** 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?';
|
||||
|
||||
@@ -16,7 +16,8 @@ import {
|
||||
DEFAULT_MCP_CONFIG,
|
||||
DEFAULT_CLIENT_VERSION,
|
||||
DEFAULT_IMAGE_MIME_TYPE,
|
||||
MCP_PARTIAL_REDACT_HEADERS
|
||||
MCP_PARTIAL_REDACT_HEADERS,
|
||||
CORS_PROXY_ENDPOINT
|
||||
} from '$lib/constants';
|
||||
import {
|
||||
MCPConnectionPhase,
|
||||
@@ -236,6 +237,36 @@ export class MCPService {
|
||||
|
||||
return {
|
||||
fetch: async (input, init) => {
|
||||
if (useProxy && typeof window !== 'undefined') {
|
||||
let requestUrlStr = '';
|
||||
if (typeof input === 'string') {
|
||||
requestUrlStr = input;
|
||||
} else if (input instanceof URL) {
|
||||
requestUrlStr = input.href;
|
||||
}
|
||||
|
||||
if (requestUrlStr) {
|
||||
const parsedRequestUrl = new URL(requestUrlStr, window.location.origin);
|
||||
if (
|
||||
parsedRequestUrl.origin === window.location.origin &&
|
||||
!parsedRequestUrl.pathname.includes(CORS_PROXY_ENDPOINT)
|
||||
) {
|
||||
const originalConfigUrl = new URL(config.url);
|
||||
const realTargetUrl = new URL(
|
||||
parsedRequestUrl.pathname + parsedRequestUrl.search,
|
||||
originalConfigUrl.origin
|
||||
);
|
||||
const proxiedUrl = buildProxiedUrl(realTargetUrl.href);
|
||||
|
||||
if (typeof input === 'string') {
|
||||
input = proxiedUrl.href;
|
||||
} else if (input instanceof URL) {
|
||||
input = proxiedUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const startedAt = performance.now();
|
||||
const requestHeaders = new Headers(baseInit.headers);
|
||||
|
||||
@@ -403,6 +434,32 @@ export class MCPService {
|
||||
};
|
||||
}
|
||||
|
||||
if (config.transport === MCPTransportType.SSE) {
|
||||
const url = useProxy ? buildProxiedUrl(config.url) : new URL(config.url);
|
||||
const { fetch: diagnosticFetch, disable: stopPhaseLogging } = this.createDiagnosticFetch(
|
||||
serverName,
|
||||
config,
|
||||
requestInit,
|
||||
url,
|
||||
useProxy,
|
||||
onLog
|
||||
);
|
||||
|
||||
if (import.meta.env.DEV && import.meta.env.VITE_DEBUG) {
|
||||
console.log(`[MCPService] Creating SSE transport for ${url.href}`);
|
||||
}
|
||||
|
||||
return {
|
||||
transport: new SSEClientTransport(url, {
|
||||
requestInit,
|
||||
fetch: diagnosticFetch,
|
||||
eventSourceInit: { fetch: diagnosticFetch }
|
||||
}),
|
||||
type: MCPTransportType.SSE,
|
||||
stopPhaseLogging
|
||||
};
|
||||
}
|
||||
|
||||
const url = useProxy ? buildProxiedUrl(config.url) : new URL(config.url);
|
||||
const { fetch: diagnosticFetch, disable: stopPhaseLogging } = this.createDiagnosticFetch(
|
||||
serverName,
|
||||
|
||||
@@ -19,7 +19,10 @@ import {
|
||||
DISPLAY_NAME_SEPARATOR_REGEX,
|
||||
PATH_SEPARATOR,
|
||||
RESOURCE_TEXT_CONTENT_SEPARATOR,
|
||||
DEFAULT_RESOURCE_FILENAME
|
||||
DEFAULT_RESOURCE_FILENAME,
|
||||
MCP_SSE_ENDPOINT,
|
||||
MCP_SSE_ENDPOINT_SLASH,
|
||||
MCP_SSE_ENDPOINT_QUERY
|
||||
} from '$lib/constants';
|
||||
import {
|
||||
Database,
|
||||
@@ -41,10 +44,22 @@ import type { MimeTypeUnion } from '$lib/types/common';
|
||||
export function detectMcpTransportFromUrl(url: string): MCPTransportType {
|
||||
const normalized = url.trim().toLowerCase();
|
||||
|
||||
return normalized.startsWith(UrlProtocol.WEBSOCKET) ||
|
||||
if (
|
||||
normalized.startsWith(UrlProtocol.WEBSOCKET) ||
|
||||
normalized.startsWith(UrlProtocol.WEBSOCKET_SECURE)
|
||||
? MCPTransportType.WEBSOCKET
|
||||
: MCPTransportType.STREAMABLE_HTTP;
|
||||
) {
|
||||
return MCPTransportType.WEBSOCKET;
|
||||
}
|
||||
|
||||
if (
|
||||
normalized.endsWith(MCP_SSE_ENDPOINT) ||
|
||||
normalized.endsWith(MCP_SSE_ENDPOINT_SLASH) ||
|
||||
normalized.includes(MCP_SSE_ENDPOINT_QUERY)
|
||||
) {
|
||||
return MCPTransportType.SSE;
|
||||
}
|
||||
|
||||
return MCPTransportType.STREAMABLE_HTTP;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user