Add runtime service links for lab endpoints

This commit is contained in:
2026-04-23 18:11:16 -06:00
parent 431e667c5e
commit ea55178f9c
10 changed files with 509 additions and 46 deletions
+177 -2
View File
@@ -9,6 +9,14 @@ import { Objective5Chat } from "~/components/labs/Objective5Chat";
import { QuantizationGridExplorer } from "~/components/labs/QuantizationGridExplorer";
import { QuantizationExplorer } from "~/components/labs/QuantizationExplorer";
import { TokenizerPlaygroundEmbed } from "~/components/labs/TokenizerPlaygroundEmbed";
import {
fetchCoursewareRuntimeConfig,
isCoursewareServiceId,
normalizeCoursewareRuntimeConfig,
resolveCoursewareServiceAddress,
resolveCoursewareServiceUrl,
type ResolvedCoursewareRuntimeConfig,
} from "~/lib/courseware-runtime";
type LabContentProps = {
className: string;
@@ -43,6 +51,8 @@ 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>";
const serviceTokenPattern =
/\{\{service-(url|address):([a-z0-9-]+)(?::([^}]+))?\}\}/g;
function looksLikeCliCommand(commandText: string, className: string) {
if (cliLanguagePattern.test(className)) return true;
@@ -258,9 +268,150 @@ function enhanceHarnessSelectors(root: HTMLElement) {
};
}
function resolveServiceTokenValue(
runtimeConfig: ResolvedCoursewareRuntimeConfig,
tokenType: string,
serviceId: string,
pathSuffix?: string,
) {
if (!isCoursewareServiceId(serviceId)) {
return null;
}
if (tokenType === "url") {
return resolveCoursewareServiceUrl(runtimeConfig, serviceId, pathSuffix);
}
if (tokenType === "address") {
return resolveCoursewareServiceAddress(runtimeConfig, serviceId);
}
return null;
}
function replaceServiceTokens(
root: HTMLElement,
runtimeConfig: ResolvedCoursewareRuntimeConfig,
) {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode(node) {
if (!(node instanceof Text)) {
return NodeFilter.FILTER_REJECT;
}
if (!node.nodeValue?.includes("{{service-")) {
return NodeFilter.FILTER_REJECT;
}
const parent = node.parentElement;
if (!parent) {
return NodeFilter.FILTER_REJECT;
}
if (parent.closest("[data-widget-enhanced='true']")) {
return NodeFilter.FILTER_REJECT;
}
return NodeFilter.FILTER_ACCEPT;
},
});
const textNodes: Text[] = [];
let currentNode = walker.nextNode();
while (currentNode) {
if (currentNode instanceof Text) {
textNodes.push(currentNode);
}
currentNode = walker.nextNode();
}
for (const textNode of textNodes) {
const parent = textNode.parentElement;
const nodeValue = textNode.nodeValue;
if (!parent || !nodeValue) {
continue;
}
const allowLinks = !parent.closest("code, pre, a");
const nextTextValue = nodeValue.replace(
serviceTokenPattern,
(fullMatch, tokenType: string, serviceId: string, pathSuffix?: string) => {
const replacement = resolveServiceTokenValue(
runtimeConfig,
tokenType,
serviceId,
pathSuffix,
);
return replacement ?? fullMatch;
},
);
if (!allowLinks) {
if (nextTextValue !== nodeValue) {
textNode.nodeValue = nextTextValue;
}
continue;
}
serviceTokenPattern.lastIndex = 0;
let lastIndex = 0;
let didReplace = false;
const fragment = document.createDocumentFragment();
let match = serviceTokenPattern.exec(nodeValue);
while (match) {
const [fullMatch, tokenType, serviceId, pathSuffix] = match;
const replacement = resolveServiceTokenValue(
runtimeConfig,
tokenType,
serviceId,
pathSuffix,
);
if (replacement === null) {
match = serviceTokenPattern.exec(nodeValue);
continue;
}
didReplace = true;
if (match.index > lastIndex) {
fragment.append(nodeValue.slice(lastIndex, match.index));
}
if (tokenType === "url") {
const link = document.createElement("a");
link.href = replacement;
link.rel = "noreferrer";
link.target = "_blank";
link.textContent = replacement;
fragment.append(link);
} else {
fragment.append(replacement);
}
lastIndex = match.index + fullMatch.length;
match = serviceTokenPattern.exec(nodeValue);
}
if (!didReplace) {
continue;
}
if (lastIndex < nodeValue.length) {
fragment.append(nodeValue.slice(lastIndex));
}
textNode.replaceWith(fragment);
}
}
export function LabContent({ className, html }: LabContentProps) {
const containerRef = useRef<HTMLElement>(null);
const [zoomedImage, setZoomedImage] = useState<ZoomedImageState | null>(null);
const [runtimeConfig, setRuntimeConfig] = useState(() =>
normalizeCoursewareRuntimeConfig(),
);
const [isRuntimeResolved, setIsRuntimeResolved] = useState(false);
const renderedContent = html
.split(
@@ -314,9 +465,33 @@ export function LabContent({ className, html }: LabContentProps) {
);
});
useEffect(() => {
let isCancelled = false;
void fetchCoursewareRuntimeConfig()
.then((nextRuntimeConfig) => {
if (isCancelled) return;
setRuntimeConfig(nextRuntimeConfig);
})
.catch(() => {
if (isCancelled) return;
setRuntimeConfig(normalizeCoursewareRuntimeConfig());
})
.finally(() => {
if (isCancelled) return;
setIsRuntimeResolved(true);
});
return () => {
isCancelled = true;
};
}, []);
useEffect(() => {
const root = containerRef.current;
if (!root) return;
if (!root || !isRuntimeResolved) return;
replaceServiceTokens(root, runtimeConfig);
const preBlocks = root.querySelectorAll<HTMLPreElement>("pre");
for (const pre of preBlocks) {
@@ -388,7 +563,7 @@ export function LabContent({ className, html }: LabContentProps) {
cleanupHarnessSelectors();
root.removeEventListener("click", handleRootClick);
};
}, [html]);
}, [html, isRuntimeResolved, runtimeConfig]);
useEffect(() => {
if (!zoomedImage) return;