mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-12 08:42:18 +00:00
refactor(ui): remove the deprecated API Reference page (#51246)
The API Reference page was already behind a deprecation banner. This deletes the /ui/api-reference route and its components, the Developer Tools sidebar entry and page description, the legacy ?page=api_ref redirects, and the i18n strings only that page used. Old ?page=api_ref and ?page=api-reference bookmarks now fall through to the dashboard like any other unknown page id, since there is no replacement page. Tests that listed the page are updated: the sidebar and legacy-route vitest files, the uiHref examples, the e2e sidebar fixtures and Internal Viewer nav check, and the UI path detection unit test.
This commit is contained in:
parent
534a60d28d
commit
63d2f4fef7
18 changed files with 9 additions and 337 deletions
|
|
@ -14,7 +14,6 @@ export const menuLabelToPage: Record<string, Page> = {
|
|||
"Internal Users": Page.Users,
|
||||
"Internal User": Page.Users, // Legacy label support
|
||||
Organizations: Page.Organizations,
|
||||
"API Reference": Page.ApiRef,
|
||||
"AI Hub": Page.ModelHubTable,
|
||||
"Model Hub": Page.ModelHubTable,
|
||||
Logs: Page.Logs,
|
||||
|
|
|
|||
|
|
@ -13,11 +13,6 @@ export const MIGRATED_E2E_PAGES: Readonly<Record<string, MigratedPage>> = {
|
|||
linkName: "Models + Endpoints",
|
||||
content: { role: "heading", name: "Model Management" },
|
||||
},
|
||||
api_ref: {
|
||||
segment: "api-reference",
|
||||
linkName: "API Reference",
|
||||
content: { role: "heading", name: "OpenAI Compatible Proxy: API Reference" },
|
||||
},
|
||||
"llm-playground": { segment: "playground", linkName: "Playground", content: { role: "tab", name: "Chat" } },
|
||||
projects: {
|
||||
segment: "projects",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ export enum Page {
|
|||
Teams = "teams",
|
||||
Organizations = "organizations",
|
||||
AdminPanel = "admin-panel",
|
||||
ApiRef = "api_ref",
|
||||
LoggingAndAlerts = "logging-and-alerts",
|
||||
Budgets = "budgets",
|
||||
Guardrails = "guardrails",
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ test.describe("Internal Viewer", () => {
|
|||
"Usage",
|
||||
"Logs",
|
||||
"Teams",
|
||||
"API Reference",
|
||||
"AI Hub",
|
||||
];
|
||||
for (const label of expectedVisible) {
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ class TestUIDetectionLogic:
|
|||
|
||||
# Verify routes exist
|
||||
found_routes = 0
|
||||
expected_routes = ["login", "guardrails", "logs", "api-reference"]
|
||||
expected_routes = ["login", "guardrails", "logs"]
|
||||
for route in expected_routes:
|
||||
route_index = os.path.join(self.temp_dir, route, "index.html")
|
||||
if os.path.exists(route_index):
|
||||
|
|
|
|||
|
|
@ -1,92 +0,0 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import APIReferenceView from "./APIReferenceView";
|
||||
|
||||
vi.mock("@/components/CodeBlock", () => ({
|
||||
__esModule: true,
|
||||
default: ({ code }: { code: string }) => <pre data-testid="api-reference-code-block">{code}</pre>,
|
||||
}));
|
||||
|
||||
describe("APIReferenceView", () => {
|
||||
const codeBlockTestId = "api-reference-code-block";
|
||||
|
||||
it("uses the API doc base url when provided", () => {
|
||||
const apiDocUrl = "https://docs.litellm.test";
|
||||
render(<APIReferenceView proxySettings={{ LITELLM_UI_API_DOC_BASE_URL: apiDocUrl }} />);
|
||||
|
||||
const codeBlocks = screen.getAllByTestId(codeBlockTestId);
|
||||
expect(codeBlocks[0]).toHaveTextContent(new RegExp(apiDocUrl));
|
||||
});
|
||||
|
||||
it("falls back to the proxy base url when the docs url is missing", () => {
|
||||
const proxyUrl = "https://proxy.litellm.test";
|
||||
render(<APIReferenceView proxySettings={{ PROXY_BASE_URL: proxyUrl }} />);
|
||||
|
||||
const codeBlocks = screen.getAllByTestId(codeBlockTestId);
|
||||
expect(codeBlocks[0]).toHaveTextContent(new RegExp(proxyUrl));
|
||||
});
|
||||
|
||||
it("prefers the docs url when both urls are provided", () => {
|
||||
const apiDocUrl = "https://docs-preferred.litellm.test";
|
||||
const proxyUrl = "https://proxy-backup.litellm.test";
|
||||
|
||||
render(
|
||||
<APIReferenceView
|
||||
proxySettings={{
|
||||
LITELLM_UI_API_DOC_BASE_URL: apiDocUrl,
|
||||
PROXY_BASE_URL: proxyUrl,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const codeBlocks = screen.getAllByTestId(codeBlockTestId);
|
||||
const renderedCode = codeBlocks[0].textContent ?? "";
|
||||
expect(renderedCode).toContain(apiDocUrl);
|
||||
expect(renderedCode).not.toContain(proxyUrl);
|
||||
});
|
||||
|
||||
it("renders the page title, blurb and docs link", () => {
|
||||
render(<APIReferenceView proxySettings={{ PROXY_BASE_URL: "https://proxy.litellm.test" }} />);
|
||||
|
||||
expect(screen.getByText("OpenAI Compatible Proxy: API Reference")).toBeInTheDocument();
|
||||
expect(screen.getByText(/LiteLLM is OpenAI Compatible/)).toBeInTheDocument();
|
||||
|
||||
const docsLink = screen.getByRole("link", { name: /API Reference Docs/ });
|
||||
expect(docsLink).toHaveAttribute("href", "https://docs.litellm.ai/docs/proxy/user_keys");
|
||||
expect(docsLink).toHaveAttribute("target", "_blank");
|
||||
});
|
||||
|
||||
it("exposes the three SDK tabs with the first selected by default", () => {
|
||||
render(<APIReferenceView proxySettings={{ PROXY_BASE_URL: "https://proxy.litellm.test" }} />);
|
||||
|
||||
expect(screen.getAllByRole("tab").map((tab) => tab.textContent)).toEqual([
|
||||
"OpenAI Python SDK",
|
||||
"LlamaIndex",
|
||||
"Langchain Py",
|
||||
]);
|
||||
expect(screen.getAllByRole("tab").map((tab) => tab.getAttribute("aria-selected"))).toEqual([
|
||||
"true",
|
||||
"false",
|
||||
"false",
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["OpenAI Python SDK", "import openai"],
|
||||
["LlamaIndex", "from llama_index.llms import AzureOpenAI"],
|
||||
["Langchain Py", "from langchain.chat_models import ChatOpenAI"],
|
||||
])("selecting %s shows its snippet wired to the base url", async (tabName, marker) => {
|
||||
const proxyUrl = "https://proxy.litellm.test";
|
||||
const user = userEvent.setup();
|
||||
render(<APIReferenceView proxySettings={{ PROXY_BASE_URL: proxyUrl }} />);
|
||||
|
||||
await user.click(screen.getByRole("tab", { name: tabName }));
|
||||
|
||||
expect(screen.getByRole("tab", { name: tabName })).toHaveAttribute("aria-selected", "true");
|
||||
|
||||
const selectedPanel = screen.getByRole("tabpanel");
|
||||
expect(selectedPanel).toHaveTextContent(new RegExp(marker));
|
||||
expect(selectedPanel).toHaveTextContent(new RegExp(proxyUrl));
|
||||
});
|
||||
});
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
"use client";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CodeBlock from "@/components/CodeBlock";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import DocLink from "./DocLink";
|
||||
import { productNames } from "@/i18n/untranslated/productNames";
|
||||
|
||||
interface ApiRefProps {
|
||||
proxySettings: {
|
||||
PROXY_BASE_URL?: string;
|
||||
LITELLM_UI_API_DOC_BASE_URL?: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
const APIReferenceView: React.FC<ApiRefProps> = ({ proxySettings }) => {
|
||||
const { t } = useTranslation();
|
||||
let base_url = "<your_proxy_base_url>";
|
||||
const customDocBaseUrl = proxySettings?.LITELLM_UI_API_DOC_BASE_URL;
|
||||
if (customDocBaseUrl && customDocBaseUrl.trim()) {
|
||||
base_url = customDocBaseUrl;
|
||||
} else if (proxySettings?.PROXY_BASE_URL) {
|
||||
base_url = proxySettings.PROXY_BASE_URL;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-2 p-8 h-[80vh] w-full mt-2">
|
||||
<div className="mb-5">
|
||||
{/* Header row with Docs link on the right */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-semibold text-foreground">{t("apiReference.view.title")}</h1>
|
||||
<DocLink className="ml-3 shrink-0" href="https://docs.litellm.ai/docs/proxy/user_keys" />
|
||||
</div>
|
||||
|
||||
<p className="mt-2 mb-2 text-sm text-muted-foreground">{t("apiReference.view.description")} </p>
|
||||
|
||||
<Tabs defaultValue="openai">
|
||||
<TabsList variant="line" className="border-b rounded-none w-full justify-start h-auto p-0">
|
||||
<TabsTrigger value="openai" className="rounded-none px-4 py-2 flex-none">
|
||||
{productNames.openAIPythonSdk}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="llamaindex" className="rounded-none px-4 py-2 flex-none">
|
||||
{productNames.llamaIndex}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="langchain" className="rounded-none px-4 py-2 flex-none">
|
||||
{productNames.langchainPython}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="openai" keepMounted>
|
||||
<CodeBlock
|
||||
language="python"
|
||||
code={`import openai
|
||||
client = openai.OpenAI(
|
||||
api_key="your_api_key",
|
||||
base_url="${base_url}" # LiteLLM Proxy is OpenAI compatible, Read More: https://docs.litellm.ai/docs/proxy/user_keys
|
||||
)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-3.5-turbo", # model to send to the proxy
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "this is a test request, write a short poem"
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
print(response)`}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="llamaindex" keepMounted>
|
||||
<CodeBlock
|
||||
language="python"
|
||||
code={`import os, dotenv
|
||||
|
||||
from llama_index.llms import AzureOpenAI
|
||||
from llama_index.embeddings import AzureOpenAIEmbedding
|
||||
from llama_index import VectorStoreIndex, SimpleDirectoryReader, ServiceContext
|
||||
|
||||
llm = AzureOpenAI(
|
||||
engine="azure-gpt-3.5", # model_name on litellm proxy
|
||||
temperature=0.0,
|
||||
azure_endpoint="${base_url}", # litellm proxy endpoint
|
||||
api_key="<your-master-key>", # litellm proxy API Key
|
||||
api_version="2023-07-01-preview",
|
||||
)
|
||||
|
||||
embed_model = AzureOpenAIEmbedding(
|
||||
deployment_name="azure-embedding-model",
|
||||
azure_endpoint="${base_url}",
|
||||
api_key="<your-master-key>",
|
||||
api_version="2023-07-01-preview",
|
||||
)
|
||||
|
||||
documents = SimpleDirectoryReader("llama_index_data").load_data()
|
||||
service_context = ServiceContext.from_defaults(llm=llm, embed_model=embed_model)
|
||||
index = VectorStoreIndex.from_documents(documents, service_context=service_context)
|
||||
|
||||
query_engine = index.as_query_engine()
|
||||
response = query_engine.query("What did the author do growing up?")
|
||||
print(response)`}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="langchain" keepMounted>
|
||||
<CodeBlock
|
||||
language="python"
|
||||
code={`from langchain.chat_models import ChatOpenAI
|
||||
from langchain.prompts.chat import (
|
||||
ChatPromptTemplate,
|
||||
HumanMessagePromptTemplate,
|
||||
SystemMessagePromptTemplate,
|
||||
)
|
||||
from langchain.schema import HumanMessage, SystemMessage
|
||||
|
||||
chat = ChatOpenAI(
|
||||
openai_api_base="${base_url}",
|
||||
model = "gpt-3.5-turbo",
|
||||
temperature=0.1
|
||||
)
|
||||
|
||||
messages = [
|
||||
SystemMessage(
|
||||
content="You are a helpful assistant that im using to make a test request to."
|
||||
),
|
||||
HumanMessage(
|
||||
content="test from litellm. tell me why it's amazing in 1 sentence"
|
||||
),
|
||||
]
|
||||
response = chat(messages)
|
||||
|
||||
print(response)`}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default APIReferenceView;
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import React from "react";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { cn } from "@/lib/cva.config";
|
||||
|
||||
export type DocLinkProps = {
|
||||
href?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const DocLink = ({ href, className }: DocLinkProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={t("apiReference.docLink.title")}
|
||||
className={cn(
|
||||
"inline-flex items-center gap-2 rounded-xl border border-border bg-card/80 px-3.5 py-2 text-sm font-medium text-foreground shadow-xs",
|
||||
"hover:bg-card focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring active:translate-y-[0.5px]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<span>{t("apiReference.docLink.label")}</span>
|
||||
<ExternalLink aria-hidden className="h-4 w-4 opacity-80" />
|
||||
<span className="sr-only">{t("apiReference.docLink.newTab")}</span>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocLink;
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import APIReferenceView from "./_components/APIReferenceView";
|
||||
import { DeprecationBanner } from "@/components/DeprecationBanner";
|
||||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import useProxySettings from "@/app/(dashboard)/hooks/proxySettings/useProxySettings";
|
||||
|
||||
const APIReferencePage = () => {
|
||||
const { t } = useTranslation();
|
||||
const { accessToken } = useAuthorized();
|
||||
const proxySettings = useProxySettings(accessToken);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeprecationBanner featureName={t("apiReference.page.featureName")} />
|
||||
<APIReferenceView proxySettings={proxySettings} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default APIReferencePage;
|
||||
|
|
@ -14,8 +14,6 @@ describe("legacyPageRedirectHref", () => {
|
|||
});
|
||||
|
||||
it("keeps the older aliases for renamed pages", () => {
|
||||
expect(redirect("page=api_ref")).toBe("/ui/api-reference");
|
||||
expect(redirect("page=api-reference")).toBe("/ui/api-reference");
|
||||
expect(redirect("page=claude-code-plugins")).toBe("/ui/skills");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ const LEGACY_PAGE_ROUTES: ReadonlyMap<string, string> = new Map(
|
|||
home: "home",
|
||||
"api-keys": "api-keys",
|
||||
models: "models-and-endpoints",
|
||||
api_ref: "api-reference",
|
||||
"api-reference": "api-reference",
|
||||
"llm-playground": "playground",
|
||||
projects: "projects",
|
||||
chat: "chat",
|
||||
|
|
|
|||
|
|
@ -282,7 +282,6 @@ describe("Sidebar (leftnav)", () => {
|
|||
"Organizations",
|
||||
"Access Groups",
|
||||
"Budgets",
|
||||
"API Reference",
|
||||
"AI Hub",
|
||||
"Experimental",
|
||||
"Settings",
|
||||
|
|
@ -701,7 +700,6 @@ describe("Sidebar (leftnav)", () => {
|
|||
expectHref("Models + Endpoints", "/ui/models-and-endpoints");
|
||||
expectHref("Usage", "/ui/usage");
|
||||
expectHref("Alerts", "/ui/alerts");
|
||||
expectHref("API Reference", "/ui/api-reference");
|
||||
});
|
||||
|
||||
it("never links a leaf to the legacy ?page= switch", () => {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import {
|
|||
Building2,
|
||||
Boxes,
|
||||
ChevronRight,
|
||||
Code2,
|
||||
Database,
|
||||
ExternalLink,
|
||||
FileText,
|
||||
|
|
@ -420,13 +419,6 @@ const menuGroups: MenuGroup[] = [
|
|||
{
|
||||
groupLabel: "DEVELOPER TOOLS",
|
||||
items: [
|
||||
{
|
||||
key: "api_ref",
|
||||
page: "api_ref",
|
||||
route: "api-reference",
|
||||
label: "shell.nav.items.apiReference",
|
||||
icon: <Code2 {...ICON} />,
|
||||
},
|
||||
{
|
||||
key: "model-hub-table",
|
||||
page: "model-hub-table",
|
||||
|
|
|
|||
|
|
@ -388,7 +388,7 @@ describe("UI config and public endpoints", () => {
|
|||
await Networking.getUiConfig();
|
||||
|
||||
expect(Networking.serverRootPath).toBe("/litellm");
|
||||
expect(uiHref("api-reference")).toBe("/litellm/ui/api-reference");
|
||||
expect(uiHref("logs")).toBe("/litellm/ui/logs");
|
||||
});
|
||||
|
||||
it("sends model group connection probes to the configured gateway", async () => {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ export const pageDescriptions: Record<string, ParseKeys> = {
|
|||
projects: "shell.pageDescriptions.projects",
|
||||
"access-groups": "shell.pageDescriptions.accessGroups",
|
||||
budgets: "shell.pageDescriptions.budgets",
|
||||
api_ref: "shell.pageDescriptions.apiRef",
|
||||
"model-hub-table": "shell.pageDescriptions.modelHubTable",
|
||||
caching: "shell.pageDescriptions.caching",
|
||||
"transform-request": "shell.pageDescriptions.transformRequest",
|
||||
|
|
|
|||
|
|
@ -8035,7 +8035,6 @@
|
|||
"organizations": "Organizations",
|
||||
"accessGroups": "Access Groups",
|
||||
"budgets": "Budgets",
|
||||
"apiReference": "API Reference",
|
||||
"aiHub": "AI Hub",
|
||||
"responseCache": "Response Cache",
|
||||
"experimental": "Experimental",
|
||||
|
|
@ -8082,7 +8081,6 @@
|
|||
"projects": "Manage projects within teams",
|
||||
"accessGroups": "Manage access groups for role-based permissions",
|
||||
"budgets": "Set and monitor spending budgets",
|
||||
"apiRef": "Browse API documentation and endpoints",
|
||||
"modelHubTable": "Explore available AI models and providers",
|
||||
"caching": "Configure response caching and coordination Redis settings",
|
||||
"transformRequest": "Set up request transformation rules",
|
||||
|
|
@ -8554,20 +8552,6 @@
|
|||
"empty": "No posts yet"
|
||||
}
|
||||
},
|
||||
"apiReference": {
|
||||
"page": {
|
||||
"featureName": "The API Reference tab"
|
||||
},
|
||||
"view": {
|
||||
"title": "OpenAI Compatible Proxy: API Reference",
|
||||
"description": "LiteLLM is OpenAI Compatible. This means your API Key works with the OpenAI SDK. Just replace the base_url to point to your litellm proxy. Example Below"
|
||||
},
|
||||
"docLink": {
|
||||
"title": "Open documentation in a new tab",
|
||||
"label": "API Reference Docs",
|
||||
"newTab": "(opens in a new tab)"
|
||||
}
|
||||
},
|
||||
"uiTheme": {
|
||||
"settings": {
|
||||
"toasts": {
|
||||
|
|
|
|||
|
|
@ -9,16 +9,16 @@ afterEach(() => {
|
|||
|
||||
describe("uiHref", () => {
|
||||
it("builds a /ui-rooted path when serverRootPath is /", () => {
|
||||
expect(uiHref("api-reference")).toBe("/ui/api-reference");
|
||||
expect(uiHref("logs")).toBe("/ui/logs");
|
||||
});
|
||||
|
||||
it("prefixes a non-root serverRootPath without duplicating slashes", () => {
|
||||
setServerRootPath("/team-x/");
|
||||
expect(uiHref("api-reference")).toBe("/team-x/ui/api-reference");
|
||||
expect(uiHref("logs")).toBe("/team-x/ui/logs");
|
||||
});
|
||||
|
||||
it("tolerates a leading slash in the route segment", () => {
|
||||
expect(uiHref("/api-reference")).toBe("/ui/api-reference");
|
||||
expect(uiHref("/logs")).toBe("/ui/logs");
|
||||
});
|
||||
|
||||
it("stays root-relative under next dev, which serves the app at /", () => {
|
||||
|
|
@ -29,8 +29,8 @@ describe("uiHref", () => {
|
|||
|
||||
describe("routeSegmentForPathname", () => {
|
||||
it("strips the /ui base and any trailing slash", () => {
|
||||
expect(routeSegmentForPathname("/ui/api-reference")).toBe("api-reference");
|
||||
expect(routeSegmentForPathname("/ui/api-reference/")).toBe("api-reference");
|
||||
expect(routeSegmentForPathname("/ui/logs")).toBe("logs");
|
||||
expect(routeSegmentForPathname("/ui/logs/")).toBe("logs");
|
||||
});
|
||||
|
||||
it("returns an empty segment for the dashboard root", () => {
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ function uiBase(): string {
|
|||
return `${root}/ui`;
|
||||
}
|
||||
|
||||
/** Absolute (same-origin) href for a dashboard route segment, e.g. "api-reference" -> "/ui/api-reference". */
|
||||
/** Absolute (same-origin) href for a dashboard route segment, e.g. "logs" -> "/ui/logs". */
|
||||
export function uiHref(routeSegment: string): string {
|
||||
return `${uiBase()}/${routeSegment.replace(/^\/+/, "")}`;
|
||||
}
|
||||
|
||||
/** First route segment under the UI base, e.g. "/ui/api-reference/" -> "api-reference" and "/ui/" -> "". */
|
||||
/** First route segment under the UI base, e.g. "/ui/logs/" -> "logs" and "/ui/" -> "". */
|
||||
export function routeSegmentForPathname(pathname: string): string {
|
||||
const base = uiBase();
|
||||
const relative = pathname.startsWith(base) ? pathname.slice(base.length) : pathname;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue