Files
LLM-Labs/src/components/labs/LabContent.tsx
T

378 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { Fragment, useEffect, useRef, useState } from "react";
import { Lab1ConfidenceChat } from "~/components/labs/Lab1ConfidenceChat";
import { Lab1NetronPanel } from "~/components/labs/Lab1NetronPanel";
import { Lab3TerminalFrame } from "~/components/labs/Lab3TerminalFrame";
import { Objective5Chat } from "~/components/labs/Objective5Chat";
import { QuantizationGridExplorer } from "~/components/labs/QuantizationGridExplorer";
import { QuantizationExplorer } from "~/components/labs/QuantizationExplorer";
import { TokenizerPlaygroundEmbed } from "~/components/labs/TokenizerPlaygroundEmbed";
type LabContentProps = {
className: string;
html: string;
};
const cliLanguagePattern =
/\b(language-(bash|sh|shell|zsh|console|terminal)|bash|shell|zsh)\b/i;
const cliCommandPattern =
/(^|\n)\s*(\$|sudo\s|git\s|python3?\s|pip\s|npm\s|pnpm\s|yarn\s|llama-|ollama\s|curl\s|wget\s|apt\s|cd\s|ls\s|cat\s|cp\s|mv\s|chmod\s|make\s)/i;
const promptLanguagePattern =
/\b(language-(text|plaintext|md|markdown)|text|plaintext|markdown)\b/i;
const promptSignalPattern =
/\b(you are|guidelines|follow these|example|when provided|system prompt|tasked with)\b/i;
type ParsedSetting = {
key: string;
value: string;
};
type ZoomedImageState = {
src: string;
alt: string;
};
const quantizationExplorerToken = "<div data-quantization-explorer></div>";
const quantizationGridExplorerToken =
"<div data-quantization-grid-explorer></div>";
const objective5ChatToken = "<div data-objective5-chat></div>";
const lab3TerminalToken = "<div data-lab3-terminal></div>";
const lab1ConfidenceToken = "<div data-lab1-confidence></div>";
const lab1NetronToken = "<div data-lab1-netron-panel></div>";
const tokenizerPlaygroundToken = "<div data-tokenizer-playground></div>";
function looksLikeCliCommand(commandText: string, className: string) {
if (cliLanguagePattern.test(className)) return true;
return (
cliCommandPattern.test(commandText) || /--[a-z0-9-]+/i.test(commandText)
);
}
function looksLikePromptTextBlock(text: string, className: string) {
if (looksLikeCliCommand(text, className)) return false;
const normalizedText = text.trim();
if (!normalizedText) return false;
const lineCount = normalizedText.split("\n").length;
if (promptLanguagePattern.test(className) && normalizedText.length > 80)
return true;
if (lineCount >= 4 && promptSignalPattern.test(normalizedText)) return true;
if (lineCount >= 6 && /(^|\n)\s*[*-]\s+/.test(normalizedText)) return true;
return false;
}
function escapeRegex(value: string) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function escapeHtml(value: string) {
return value
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#39;");
}
function parseSettingListItem(item: HTMLLIElement): ParsedSetting | null {
const keyElement = item.querySelector("code");
if (!keyElement) return null;
const key = (keyElement.textContent ?? "").replace(/\s+/g, " ").trim();
if (!key || key.length > 40) return null;
const text = (item.textContent ?? "").replace(/\s+/g, " ").trim();
const match = new RegExp(
`^${escapeRegex(key)}\\s*(?:-||—|:|=)\\s*(.+)$`,
).exec(text);
if (!match) return null;
const value = (match[1] ?? "").replace(/\s+/g, " ").trim();
if (!value || value.length > 36) return null;
if (/[.;]/.test(value) && value.length > 16) return null;
return { key, value };
}
function enhanceSettingsLists(root: HTMLElement) {
const lists = root.querySelectorAll<HTMLUListElement>("ul");
for (const list of lists) {
if (list.dataset.settingsEnhanced === "true") continue;
const items = Array.from(list.children).filter(
(node): node is HTMLLIElement => {
return node.tagName === "LI";
},
);
if (items.length < 2) continue;
const parsedItems = items.map((item) => parseSettingListItem(item));
if (parsedItems.some((parsedItem) => parsedItem === null)) continue;
const settings = parsedItems as ParsedSetting[];
const compactValueCount = settings.filter(
(setting) => setting.value.length <= 20,
).length;
if (compactValueCount < Math.max(2, Math.ceil(settings.length * 0.66)))
continue;
list.dataset.settingsEnhanced = "true";
list.classList.add("lab-settings-list");
for (let i = 0; i < items.length; i++) {
const item = items[i];
const setting = settings[i];
if (!item || !setting) continue;
item.classList.add("lab-settings-item");
item.innerHTML =
`<span class="lab-setting-key">${escapeHtml(setting.key)}</span>` +
`<span class="lab-setting-value">${escapeHtml(setting.value)}</span>`;
}
}
}
function ensureCopyButton(pre: HTMLPreElement, label: string) {
if (pre.dataset.copyEnhanced === "true") return;
pre.dataset.copyEnhanced = "true";
const copyButton = document.createElement("button");
copyButton.type = "button";
copyButton.className = "lab-copy-button";
copyButton.textContent = label;
copyButton.dataset.defaultLabel = label;
copyButton.setAttribute("aria-label", "Copy block to clipboard");
pre.appendChild(copyButton);
}
async function copyTextToClipboard(text: string) {
if (window.isSecureContext && navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
return;
}
const activeElement =
document.activeElement instanceof HTMLElement
? document.activeElement
: null;
const selection = document.getSelection();
const previousRanges =
selection && selection.rangeCount > 0
? Array.from({ length: selection.rangeCount }, (_, index) =>
selection.getRangeAt(index).cloneRange(),
)
: [];
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.setAttribute("readonly", "");
textarea.setAttribute("aria-hidden", "true");
textarea.style.position = "fixed";
textarea.style.top = "0";
textarea.style.left = "-9999px";
textarea.style.opacity = "0";
textarea.style.pointerEvents = "none";
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
textarea.setSelectionRange(0, textarea.value.length);
try {
const didCopy = document.execCommand("copy");
if (!didCopy) {
throw new Error("Copy command was rejected");
}
} finally {
document.body.removeChild(textarea);
if (selection) {
selection.removeAllRanges();
for (const range of previousRanges) {
selection.addRange(range);
}
}
activeElement?.focus();
}
}
export function LabContent({ className, html }: LabContentProps) {
const containerRef = useRef<HTMLElement>(null);
const [zoomedImage, setZoomedImage] = useState<ZoomedImageState | null>(null);
const renderedContent = html
.split(
new RegExp(
`(${escapeRegex(quantizationExplorerToken)}|${escapeRegex(quantizationGridExplorerToken)}|${escapeRegex(objective5ChatToken)}|${escapeRegex(lab3TerminalToken)}|${escapeRegex(lab1ConfidenceToken)}|${escapeRegex(lab1NetronToken)}|${escapeRegex(tokenizerPlaygroundToken)})`,
"g",
),
)
.filter(Boolean)
.map((part, index) => {
if (part === quantizationExplorerToken) {
return <QuantizationExplorer key={`quantization-explorer-${index}`} />;
}
if (part === quantizationGridExplorerToken) {
return (
<QuantizationGridExplorer
key={`quantization-grid-explorer-${index}`}
/>
);
}
if (part === objective5ChatToken) {
return <Objective5Chat key={`objective5-chat-${index}`} />;
}
if (part === lab3TerminalToken) {
return <Lab3TerminalFrame key={`lab3-terminal-${index}`} />;
}
if (part === lab1ConfidenceToken) {
return <Lab1ConfidenceChat key={`lab1-confidence-${index}`} />;
}
if (part === lab1NetronToken) {
return <Lab1NetronPanel key={`lab1-netron-${index}`} />;
}
if (part === tokenizerPlaygroundToken) {
return <TokenizerPlaygroundEmbed key={`tokenizer-playground-${index}`} />;
}
return (
<Fragment key={`html-segment-${index}`}>
<div dangerouslySetInnerHTML={{ __html: part }} />
</Fragment>
);
});
useEffect(() => {
const root = containerRef.current;
if (!root) return;
const preBlocks = root.querySelectorAll<HTMLPreElement>("pre");
for (const pre of preBlocks) {
const code = pre.querySelector<HTMLElement>("code");
if (!code) continue;
const blockText = code.textContent ?? "";
if (looksLikeCliCommand(blockText, code.className)) {
pre.classList.add("lab-cli-shell");
ensureCopyButton(pre, "Copy");
continue;
}
if (looksLikePromptTextBlock(blockText, code.className)) {
pre.classList.add("lab-prompt-card");
ensureCopyButton(pre, "Copy Text");
}
}
enhanceSettingsLists(root);
const handleRootClick = (event: Event) => {
const target = event.target as HTMLElement;
const button = target.closest<HTMLButtonElement>(
"button.lab-copy-button",
);
if (button) {
const pre = button.closest("pre");
const code = pre?.querySelector("code");
const commandText = code?.textContent?.trimEnd();
if (!commandText) return;
const defaultLabel = button.dataset.defaultLabel ?? "Copy";
void copyTextToClipboard(commandText)
.then(() => {
button.textContent = "Copied";
button.classList.add("is-copied");
window.setTimeout(() => {
button.textContent = defaultLabel;
button.classList.remove("is-copied");
}, 1200);
})
.catch(() => {
button.textContent = "Failed";
window.setTimeout(() => {
button.textContent = defaultLabel;
}, 1200);
});
return;
}
const image = target.closest<HTMLImageElement>("img");
if (!image || !root.contains(image)) return;
const src = image.getAttribute("src");
if (!src) return;
event.preventDefault();
event.stopPropagation();
setZoomedImage({
src,
alt: image.getAttribute("alt") ?? "",
});
};
root.addEventListener("click", handleRootClick);
return () => {
root.removeEventListener("click", handleRootClick);
};
}, [html]);
useEffect(() => {
if (!zoomedImage) return;
const previousOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
const activeElement = document.activeElement;
const previousFocusedElement =
activeElement instanceof HTMLElement ? activeElement : null;
const handleEscape = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setZoomedImage(null);
}
};
window.addEventListener("keydown", handleEscape);
return () => {
window.removeEventListener("keydown", handleEscape);
document.body.style.overflow = previousOverflow;
previousFocusedElement?.focus();
};
}, [zoomedImage]);
return (
<>
<article ref={containerRef} className={className}>
{renderedContent}
</article>
{zoomedImage ? (
<div
className="lab-image-modal"
role="presentation"
onClick={() => setZoomedImage(null)}
>
<div
className="lab-image-modal__surface"
onClick={(event) => event.stopPropagation()}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
className="lab-image-modal__image"
src={zoomedImage.src}
alt={zoomedImage.alt}
/>
</div>
</div>
) : null}
</>
);
}