summaryrefslogtreecommitdiff
path: root/llama.cpp/tools/server/webui/src/lib/markdown/literal-html.ts
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-02-12 20:57:17 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-02-12 20:57:17 +0100
commitb333b06772c89d96aacb5490d6a219fba7c09cc6 (patch)
tree211df60083a5946baa2ed61d33d8121b7e251b06 /llama.cpp/tools/server/webui/src/lib/markdown/literal-html.ts
downloadllmnpc-b333b06772c89d96aacb5490d6a219fba7c09cc6.tar.gz
Engage!
Diffstat (limited to 'llama.cpp/tools/server/webui/src/lib/markdown/literal-html.ts')
-rw-r--r--llama.cpp/tools/server/webui/src/lib/markdown/literal-html.ts121
1 files changed, 121 insertions, 0 deletions
diff --git a/llama.cpp/tools/server/webui/src/lib/markdown/literal-html.ts b/llama.cpp/tools/server/webui/src/lib/markdown/literal-html.ts
new file mode 100644
index 0000000..d4ace01
--- /dev/null
+++ b/llama.cpp/tools/server/webui/src/lib/markdown/literal-html.ts
@@ -0,0 +1,121 @@
+import type { Plugin } from 'unified';
+import { visit } from 'unist-util-visit';
+import type { Break, Content, Paragraph, PhrasingContent, Root, Text } from 'mdast';
+import { LINE_BREAK, NBSP, PHRASE_PARENTS, TAB_AS_SPACES } from '$lib/constants/literal-html';
+
+/**
+ * remark plugin that rewrites raw HTML nodes into plain-text equivalents.
+ *
+ * remark parses inline HTML into `html` nodes even when we do not want to render
+ * them. We turn each of those nodes into regular text (plus `<br>` break markers)
+ * so the downstream rehype pipeline escapes the characters instead of executing
+ * them. Leading spaces and tab characters are converted to non‑breaking spaces to
+ * keep indentation identical to the original author input.
+ */
+
+function preserveIndent(line: string): string {
+ let index = 0;
+ let output = '';
+
+ while (index < line.length) {
+ const char = line[index];
+
+ if (char === ' ') {
+ output += NBSP;
+ index += 1;
+ continue;
+ }
+
+ if (char === '\t') {
+ output += TAB_AS_SPACES;
+ index += 1;
+ continue;
+ }
+
+ break;
+ }
+
+ return output + line.slice(index);
+}
+
+function createLiteralChildren(value: string): PhrasingContent[] {
+ const lines = value.split(LINE_BREAK);
+ const nodes: PhrasingContent[] = [];
+
+ for (const [lineIndex, rawLine] of lines.entries()) {
+ if (lineIndex > 0) {
+ nodes.push({ type: 'break' } as Break as unknown as PhrasingContent);
+ }
+
+ nodes.push({
+ type: 'text',
+ value: preserveIndent(rawLine)
+ } as Text as unknown as PhrasingContent);
+ }
+
+ if (!nodes.length) {
+ nodes.push({ type: 'text', value: '' } as Text as unknown as PhrasingContent);
+ }
+
+ return nodes;
+}
+
+export const remarkLiteralHtml: Plugin<[], Root> = () => {
+ return (tree) => {
+ visit(tree, 'html', (node, index, parent) => {
+ if (!parent || typeof index !== 'number') {
+ return;
+ }
+
+ const replacement = createLiteralChildren(node.value);
+
+ if (!PHRASE_PARENTS.has(parent.type as string)) {
+ const paragraph: Paragraph = {
+ type: 'paragraph',
+ children: replacement as Paragraph['children'],
+ data: { literalHtml: true }
+ };
+
+ const siblings = parent.children as unknown as Content[];
+ siblings.splice(index, 1, paragraph as unknown as Content);
+
+ if (index > 0) {
+ const previous = siblings[index - 1] as Paragraph | undefined;
+
+ if (
+ previous?.type === 'paragraph' &&
+ (previous.data as { literalHtml?: boolean } | undefined)?.literalHtml
+ ) {
+ const prevChildren = previous.children as unknown as PhrasingContent[];
+
+ if (prevChildren.length) {
+ const lastChild = prevChildren[prevChildren.length - 1];
+
+ if (lastChild.type !== 'break') {
+ prevChildren.push({
+ type: 'break'
+ } as Break as unknown as PhrasingContent);
+ }
+ }
+
+ prevChildren.push(...(paragraph.children as unknown as PhrasingContent[]));
+
+ siblings.splice(index, 1);
+
+ return index;
+ }
+ }
+
+ return index + 1;
+ }
+
+ (parent.children as unknown as PhrasingContent[]).splice(
+ index,
+ 1,
+ ...(replacement as unknown as PhrasingContent[])
+ );
+
+ return index + replacement.length;
+ });
+ };
+};