Polish lab service links as action pills
This commit is contained in:
@@ -118,10 +118,12 @@ describe("LabContent", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const link = await screen.findByRole("link", {
|
const link = await screen.findByRole("link", {
|
||||||
name: "http://localhost:8080/",
|
name: "Open WebUI on port 8080",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(link).toHaveAttribute("href", "http://localhost:8080/");
|
expect(link).toHaveAttribute("href", "http://localhost:8080/");
|
||||||
|
expect(link).toHaveAttribute("title", "http://localhost:8080/");
|
||||||
|
expect(link).toHaveClass("lab-service-pill");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders inline service URL tokens inside code as plain text", async () => {
|
it("renders inline service URL tokens inside code as plain text", async () => {
|
||||||
@@ -193,8 +195,14 @@ describe("LabContent", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await screen.findByRole("link", { name: "https://lab.example/openwebui" }),
|
await screen.findByRole("link", { name: "Open WebUI" }),
|
||||||
).toHaveAttribute("href", "https://lab.example/openwebui");
|
).toHaveAttribute("href", "https://lab.example/openwebui");
|
||||||
|
expect(
|
||||||
|
screen.getByRole("link", { name: "Open WebUI" }),
|
||||||
|
).toHaveClass("lab-service-pill");
|
||||||
|
expect(
|
||||||
|
screen.getByRole("link", { name: "Open WebUI" }),
|
||||||
|
).toHaveAttribute("title", "https://lab.example/openwebui");
|
||||||
const apiMatches = await screen.findAllByText("https://lab.example/openwebui/api");
|
const apiMatches = await screen.findAllByText("https://lab.example/openwebui/api");
|
||||||
expect(apiMatches.some((node) => node.tagName === "CODE")).toBe(true);
|
expect(apiMatches.some((node) => node.tagName === "CODE")).toBe(true);
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@@ -53,6 +53,14 @@ const lab1NetronToken = "<div data-lab1-netron-panel></div>";
|
|||||||
const tokenizerPlaygroundToken = "<div data-tokenizer-playground></div>";
|
const tokenizerPlaygroundToken = "<div data-tokenizer-playground></div>";
|
||||||
const serviceTokenPattern =
|
const serviceTokenPattern =
|
||||||
/\{\{service-(url|address):([a-z0-9-]+)(?::([^}]+))?\}\}/g;
|
/\{\{service-(url|address):([a-z0-9-]+)(?::([^}]+))?\}\}/g;
|
||||||
|
const serviceLabels: Record<string, string> = {
|
||||||
|
"chunkviz": "ChunkViz",
|
||||||
|
"embedding-atlas": "Embedding Atlas",
|
||||||
|
"open-webui": "Open WebUI",
|
||||||
|
"promptfoo": "Promptfoo",
|
||||||
|
"ssh": "SSH",
|
||||||
|
"unsloth": "Unsloth",
|
||||||
|
};
|
||||||
|
|
||||||
function looksLikeCliCommand(commandText: string, className: string) {
|
function looksLikeCliCommand(commandText: string, className: string) {
|
||||||
if (cliLanguagePattern.test(className)) return true;
|
if (cliLanguagePattern.test(className)) return true;
|
||||||
@@ -380,10 +388,25 @@ function replaceServiceTokens(
|
|||||||
|
|
||||||
if (tokenType === "url") {
|
if (tokenType === "url") {
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
|
const serviceLabel = serviceLabels[serviceId] ?? replacement;
|
||||||
|
let visibleLabel = serviceLabel;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resolvedUrl = new URL(replacement);
|
||||||
|
if (resolvedUrl.port) {
|
||||||
|
visibleLabel = `${serviceLabel} on port ${resolvedUrl.port}`;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
visibleLabel = serviceLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
link.className = "lab-service-pill";
|
||||||
|
link.dataset.serviceId = serviceId;
|
||||||
link.href = replacement;
|
link.href = replacement;
|
||||||
link.rel = "noreferrer";
|
link.rel = "noreferrer";
|
||||||
link.target = "_blank";
|
link.target = "_blank";
|
||||||
link.textContent = replacement;
|
link.title = replacement;
|
||||||
|
link.textContent = visibleLabel;
|
||||||
fragment.append(link);
|
fragment.append(link);
|
||||||
} else {
|
} else {
|
||||||
fragment.append(replacement);
|
fragment.append(replacement);
|
||||||
|
|||||||
@@ -2051,6 +2051,46 @@ ol {
|
|||||||
box-shadow: 0 12px 28px -22px rgba(15, 92, 139, 0.85);
|
box-shadow: 0 12px 28px -22px rgba(15, 92, 139, 0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lab-content a.lab-service-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.45rem;
|
||||||
|
margin: 0.1rem 0.15rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid #0f5c8b;
|
||||||
|
background: #0f5c8b;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
padding: 0.48rem 0.9rem;
|
||||||
|
text-decoration: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
transition:
|
||||||
|
transform 120ms ease,
|
||||||
|
box-shadow 120ms ease,
|
||||||
|
background-color 120ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-content a.lab-service-pill::before {
|
||||||
|
content: "Open";
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.14);
|
||||||
|
padding: 0.16rem 0.48rem;
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lab-content a.lab-service-pill:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 12px 28px -22px rgba(15, 92, 139, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
.lab1-netron-panel__note,
|
.lab1-netron-panel__note,
|
||||||
.lab-iframe-embed__status,
|
.lab-iframe-embed__status,
|
||||||
.lab-iframe-embed__fallback {
|
.lab-iframe-embed__fallback {
|
||||||
|
|||||||
Reference in New Issue
Block a user