Files
LLM-Labs/src/lib/courseware-runtime.ts
T

277 lines
7.2 KiB
TypeScript

export const COURSEWARE_RUNTIME_CONFIG_PATH = "/courseware-runtime.json";
export const LAB1_DEFAULT_NETRON_URL = "http://127.0.0.1:8338";
export const LAB2_DEFAULT_OLLAMA_URL = "http://127.0.0.1:11434";
export const LAB2_DEFAULT_OLLAMA_MODELS = [
{
label: "Gemma 4 E2B Q4",
value: "batiai/gemma4-e2b:q4",
},
{
label: "Gemma 4 E2B Q6",
value: "batiai/gemma4-e2b:q6",
},
] as const;
export const LAB3_DEFAULT_TERMINAL_PATH = "/wetty";
export const COURSEWARE_SERVICE_IDS = [
"open-webui",
"promptfoo",
"chunkviz",
"embedding-atlas",
"unsloth",
"ssh",
] as const;
export type CoursewareRuntimeModelOption = {
label: string;
value: string;
};
export type CoursewareServiceId = (typeof COURSEWARE_SERVICE_IDS)[number];
export type CoursewareRuntimeServices = Partial<
Record<CoursewareServiceId, string>
>;
export type ResolvedCoursewareRuntimeServices = Record<
CoursewareServiceId,
string
>;
export const COURSEWARE_SERVICE_DEFAULTS: ResolvedCoursewareRuntimeServices = {
"chunkviz": "http://127.0.0.1:3000",
"embedding-atlas": "http://127.0.0.1:5055",
"open-webui": "http://127.0.0.1:8080",
"promptfoo": "http://127.0.0.1:15500",
"ssh": "ssh://127.0.0.1:22",
"unsloth": "http://127.0.0.1:8888",
};
export type CoursewareRuntimeConfig = {
lab1NetronUrl?: string;
lab2OllamaModels?: CoursewareRuntimeModelOption[];
lab2OllamaUrl?: string;
lab3TerminalUrl?: string;
services?: CoursewareRuntimeServices;
};
export type ResolvedCoursewareRuntimeConfig = {
lab1NetronUrl: string;
lab2OllamaModels: CoursewareRuntimeModelOption[];
lab2OllamaUrl: string;
lab3TerminalUrl: string;
services: ResolvedCoursewareRuntimeServices;
};
const loopbackHosts = new Set(["127.0.0.1", "localhost", "::1"]);
function rewriteLoopbackHost(urlValue: string, currentHostname?: string) {
try {
const url = new URL(urlValue);
if (!currentHostname || !loopbackHosts.has(url.hostname)) {
return url.toString();
}
url.hostname = currentHostname;
return url.toString();
} catch {
return urlValue;
}
}
function getCurrentHostname() {
if (typeof window === "undefined") {
return undefined;
}
const hostname = window.location.hostname?.trim();
return hostname || undefined;
}
export function isCoursewareServiceId(
value: string,
): value is CoursewareServiceId {
return (COURSEWARE_SERVICE_IDS as readonly string[]).includes(value);
}
export function getCoursewareServiceBaseUrl(
serviceId: CoursewareServiceId,
envValue?: string,
currentHostname = getCurrentHostname(),
) {
const trimmedValue = envValue?.trim();
const baseUrl = trimmedValue || COURSEWARE_SERVICE_DEFAULTS[serviceId];
return rewriteLoopbackHost(baseUrl, currentHostname);
}
export function getCoursewareServices(
envValue?: CoursewareRuntimeServices,
currentHostname = getCurrentHostname(),
): ResolvedCoursewareRuntimeServices {
const services = {} as ResolvedCoursewareRuntimeServices;
for (const serviceId of COURSEWARE_SERVICE_IDS) {
services[serviceId] = getCoursewareServiceBaseUrl(
serviceId,
envValue?.[serviceId],
currentHostname,
);
}
return services;
}
function appendUrlPath(urlValue: string, pathSuffix: string) {
const trimmedSuffix = pathSuffix.trim();
if (!trimmedSuffix) {
return urlValue;
}
try {
const url = new URL(urlValue);
const normalizedSuffix = trimmedSuffix.startsWith("/")
? trimmedSuffix
: `/${trimmedSuffix}`;
const basePath = url.pathname === "/" ? "" : url.pathname.replace(/\/$/, "");
url.pathname = `${basePath}${normalizedSuffix}`;
return url.toString();
} catch {
return urlValue;
}
}
export function resolveCoursewareServiceBaseUrl(
runtimeConfig: Pick<ResolvedCoursewareRuntimeConfig, "services">,
serviceId: CoursewareServiceId,
) {
return runtimeConfig.services[serviceId];
}
export function resolveCoursewareServiceUrl(
runtimeConfig: Pick<ResolvedCoursewareRuntimeConfig, "services">,
serviceId: CoursewareServiceId,
pathSuffix?: string,
) {
const baseUrl = resolveCoursewareServiceBaseUrl(runtimeConfig, serviceId);
if (!pathSuffix?.trim()) {
return baseUrl;
}
return appendUrlPath(baseUrl, pathSuffix);
}
export function resolveCoursewareServiceAddress(
runtimeConfig: Pick<ResolvedCoursewareRuntimeConfig, "services">,
serviceId: CoursewareServiceId,
) {
const baseUrl = resolveCoursewareServiceBaseUrl(runtimeConfig, serviceId);
try {
const url = new URL(baseUrl);
return url.port ? `${url.hostname}:${url.port}` : url.hostname;
} catch {
return baseUrl;
}
}
export function getLab1NetronUrl(
envValue?: string,
currentHostname = getCurrentHostname(),
) {
const trimmedValue = envValue?.trim();
if (!trimmedValue) {
return rewriteLoopbackHost(LAB1_DEFAULT_NETRON_URL, currentHostname);
}
return rewriteLoopbackHost(trimmedValue, currentHostname);
}
export function getLab2OllamaUrl(
envValue?: string,
currentHostname = getCurrentHostname(),
) {
const trimmedValue = envValue?.trim();
if (!trimmedValue) {
return rewriteLoopbackHost(LAB2_DEFAULT_OLLAMA_URL, currentHostname);
}
return rewriteLoopbackHost(trimmedValue, currentHostname);
}
export function getLab2OllamaModels(
envValue?: CoursewareRuntimeModelOption[],
) {
if (!Array.isArray(envValue) || envValue.length === 0) {
return LAB2_DEFAULT_OLLAMA_MODELS.map((model) => ({ ...model }));
}
const normalizedModels = envValue
.map((model) => {
const label = model?.label?.trim();
const value = model?.value?.trim();
if (!label || !value) {
return null;
}
return { label, value } satisfies CoursewareRuntimeModelOption;
})
.filter(
(model): model is CoursewareRuntimeModelOption => model !== null,
);
if (normalizedModels.length === 0) {
return LAB2_DEFAULT_OLLAMA_MODELS.map((model) => ({ ...model }));
}
return normalizedModels;
}
export function getLab3TerminalPath(
envValue?: string,
currentHostname = getCurrentHostname(),
) {
const trimmedValue = envValue?.trim();
if (!trimmedValue) {
return LAB3_DEFAULT_TERMINAL_PATH;
}
if (/^https?:\/\//i.test(trimmedValue)) {
return rewriteLoopbackHost(trimmedValue, currentHostname);
}
return trimmedValue.startsWith("/") ? trimmedValue : `/${trimmedValue}`;
}
export function normalizeCoursewareRuntimeConfig(
config?: CoursewareRuntimeConfig,
currentHostname = getCurrentHostname(),
): ResolvedCoursewareRuntimeConfig {
return {
lab1NetronUrl: getLab1NetronUrl(config?.lab1NetronUrl, currentHostname),
lab2OllamaModels: getLab2OllamaModels(config?.lab2OllamaModels),
lab2OllamaUrl: getLab2OllamaUrl(config?.lab2OllamaUrl, currentHostname),
lab3TerminalUrl: getLab3TerminalPath(
config?.lab3TerminalUrl,
currentHostname,
),
services: getCoursewareServices(config?.services, currentHostname),
};
}
export async function fetchCoursewareRuntimeConfig() {
const response = await fetch(COURSEWARE_RUNTIME_CONFIG_PATH, {
cache: "no-store",
});
if (!response.ok) {
throw new Error(`Runtime config request failed: ${response.status}`);
}
const config = (await response.json()) as CoursewareRuntimeConfig;
return normalizeCoursewareRuntimeConfig(config);
}