mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
Change useful_links to include index for UI
This commit is contained in:
parent
7ea407b239
commit
d1e53365a8
7 changed files with 80 additions and 31 deletions
|
|
@ -399,7 +399,10 @@ disable_copilot_system_to_assistant: bool = (
|
|||
public_mcp_servers: Optional[List[str]] = None
|
||||
public_model_groups: Optional[List[str]] = None
|
||||
public_agent_groups: Optional[List[str]] = None
|
||||
public_model_groups_links: Dict[str, str] = {}
|
||||
# Supports both old format (Dict[str, str]) and new format (Dict[str, Dict[str, Any]])
|
||||
# New format: { "displayName": { "url": "...", "index": 0 } }
|
||||
# Old format: { "displayName": "url" } (for backward compatibility)
|
||||
public_model_groups_links: Dict[str, Union[str, Dict[str, Any]]] = {}
|
||||
#### REQUEST PRIORITIZATION #######
|
||||
priority_reservation: Optional[Dict[str, Union[float, PriorityReservationDict]]] = None
|
||||
priority_reservation_settings: "PriorityReservationSettings" = (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Dict, List
|
||||
from typing import Dict, List, Union, Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
|
@ -10,7 +10,10 @@ class ModelGroupInfoProxy(ModelGroupInfo):
|
|||
|
||||
|
||||
class UpdateUsefulLinksRequest(BaseModel):
|
||||
useful_links: Dict[str, str]
|
||||
# Supports both old format (Dict[str, str]) and new format (Dict[str, Dict[str, Any]])
|
||||
# New format: { "displayName": { "url": "...", "index": 0 } }
|
||||
# Old format: { "displayName": "url" } (for backward compatibility)
|
||||
useful_links: Dict[str, Union[str, Dict[str, Any]]]
|
||||
|
||||
|
||||
class NewModelGroupRequest(BaseModel):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Dict, List, Literal, Optional
|
||||
from typing import Dict, List, Literal, Optional, Union, Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
|
@ -7,7 +7,10 @@ class PublicModelHubInfo(BaseModel):
|
|||
docs_title: str
|
||||
custom_docs_description: Optional[str]
|
||||
litellm_version: str
|
||||
useful_links: Optional[Dict[str, str]]
|
||||
# Supports both old format (Dict[str, str]) and new format (Dict[str, Dict[str, Any]])
|
||||
# New format: { "displayName": { "url": "...", "index": 0 } }
|
||||
# Old format: { "displayName": "url" } (for backward compatibility)
|
||||
useful_links: Optional[Dict[str, Union[str, Dict[str, Any]]]]
|
||||
|
||||
|
||||
class ProviderCredentialField(BaseModel):
|
||||
|
|
|
|||
|
|
@ -221,7 +221,8 @@ export interface PublicModelHubInfo {
|
|||
docs_title: string;
|
||||
custom_docs_description: string | null;
|
||||
litellm_version: string;
|
||||
useful_links: Record<string, string>;
|
||||
// Supports both old format (Record<string, string>) and new format (Record<string, {url: string, index: number}>)
|
||||
useful_links: Record<string, string | { url: string; index: number }>;
|
||||
}
|
||||
|
||||
export interface LiteLLMWellKnownUiConfig {
|
||||
|
|
@ -2362,7 +2363,10 @@ export const modelExceptionsCall = async (
|
|||
}
|
||||
};
|
||||
|
||||
export const updateUsefulLinksCall = async (accessToken: string, useful_links: Record<string, string>) => {
|
||||
export const updateUsefulLinksCall = async (
|
||||
accessToken: string,
|
||||
useful_links: Record<string, string | { url: string; index: number }>,
|
||||
) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/model_hub/update_useful_links` : `/model_hub/update_useful_links`;
|
||||
const response = await fetch(url, {
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
|
|||
const [pageTitle, setPageTitle] = useState<string>("LiteLLM Gateway");
|
||||
const [customDocsDescription, setCustomDocsDescription] = useState<string | null>(null);
|
||||
const [litellmVersion, setLitellmVersion] = useState<string>("");
|
||||
const [usefulLinks, setUsefulLinks] = useState<Record<string, string>>({});
|
||||
const [usefulLinks, setUsefulLinks] = useState<Record<string, string | { url: string; index: number }>>({});
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [agentLoading, setAgentLoading] = useState<boolean>(true);
|
||||
const [mcpLoading, setMcpLoading] = useState<boolean>(true);
|
||||
|
|
@ -976,16 +976,24 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
|
|||
<Card className="mb-10 p-8 bg-white border border-gray-200 rounded-lg shadow-sm">
|
||||
<Title className="text-2xl font-semibold mb-6 text-gray-900">Useful Links</Title>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{Object.entries(usefulLinks || {}).map(([title, url]) => (
|
||||
<button
|
||||
key={title}
|
||||
onClick={() => window.open(url, "_blank")}
|
||||
className="flex items-center space-x-3 text-blue-600 hover:text-blue-800 transition-colors p-3 rounded-lg hover:bg-blue-50 border border-gray-200"
|
||||
>
|
||||
<ExternalLinkIcon className="w-4 h-4" />
|
||||
<Text className="text-sm font-medium">{title}</Text>
|
||||
</button>
|
||||
))}
|
||||
{Object.entries(usefulLinks || {})
|
||||
.map(([title, value]) => {
|
||||
// Handle both old format (string) and new format ({url, index})
|
||||
const url = typeof value === "string" ? value : value.url;
|
||||
const index = typeof value === "string" ? 0 : value.index ?? 0;
|
||||
return { title, url, index };
|
||||
})
|
||||
.sort((a, b) => a.index - b.index)
|
||||
.map(({ title, url }) => (
|
||||
<button
|
||||
key={title}
|
||||
onClick={() => window.open(url, "_blank")}
|
||||
className="flex items-center space-x-3 text-blue-600 hover:text-blue-800 transition-colors p-3 rounded-lg hover:bg-blue-50 border border-gray-200"
|
||||
>
|
||||
<ExternalLinkIcon className="w-4 h-4" />
|
||||
<Text className="text-sm font-medium">{title}</Text>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,9 @@ describe("UsefulLinksManagement", () => {
|
|||
await user.click(screen.getByRole("button", { name: /add link/i }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedUpdateUsefulLinksCall).toHaveBeenCalledWith("token", { Docs: "https://docs.example.com" }),
|
||||
expect(mockedUpdateUsefulLinksCall).toHaveBeenCalledWith("token", {
|
||||
Docs: { url: "https://docs.example.com", index: 0 },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(await screen.findByText("Docs")).toBeInTheDocument();
|
||||
|
|
@ -98,9 +100,9 @@ describe("UsefulLinksManagement", () => {
|
|||
|
||||
await waitFor(() =>
|
||||
expect(mockedUpdateUsefulLinksCall).toHaveBeenCalledWith("token", {
|
||||
"Second Link": "https://second.example.com",
|
||||
"First Link": "https://first.example.com",
|
||||
"Third Link": "https://third.example.com",
|
||||
"Second Link": { url: "https://second.example.com", index: 0 },
|
||||
"First Link": { url: "https://first.example.com", index: 1 },
|
||||
"Third Link": { url: "https://third.example.com", index: 2 },
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ interface Link {
|
|||
id: string;
|
||||
displayName: string;
|
||||
url: string;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
const UsefulLinksManagement: React.FC<UsefulLinksManagementProps> = ({ accessToken, userRole }) => {
|
||||
|
|
@ -38,11 +39,32 @@ const UsefulLinksManagement: React.FC<UsefulLinksManagementProps> = ({ accessTok
|
|||
const usefulLinks = response.useful_links || {};
|
||||
|
||||
// Convert object to array of links with ids
|
||||
const linksArray = Object.entries(usefulLinks).map(([displayName, url], index) => ({
|
||||
id: `${index}-${displayName}`,
|
||||
displayName,
|
||||
url: url as string,
|
||||
}));
|
||||
// Handle both old format (Dict[str, str]) and new format (Dict[str, {url, index}])
|
||||
const linksArray = Object.entries(usefulLinks)
|
||||
.map(([displayName, value]) => {
|
||||
// Check if it's the new format with {url, index}
|
||||
if (typeof value === "object" && value !== null && "url" in value) {
|
||||
return {
|
||||
id: `${(value as any).index ?? 0}-${displayName}`,
|
||||
displayName,
|
||||
url: (value as any).url as string,
|
||||
index: (value as any).index ?? 0,
|
||||
};
|
||||
} else {
|
||||
// Old format: just a string URL
|
||||
return {
|
||||
id: `0-${displayName}`,
|
||||
displayName,
|
||||
url: value as string,
|
||||
index: 0,
|
||||
};
|
||||
}
|
||||
})
|
||||
.sort((a, b) => (a.index ?? 0) - (b.index ?? 0))
|
||||
.map((link, index) => ({
|
||||
...link,
|
||||
id: `${index}-${link.displayName}`,
|
||||
}));
|
||||
|
||||
setLinks(linksArray);
|
||||
} else {
|
||||
|
|
@ -69,10 +91,14 @@ const UsefulLinksManagement: React.FC<UsefulLinksManagementProps> = ({ accessTok
|
|||
if (!accessToken) return false;
|
||||
|
||||
try {
|
||||
// Convert array back to object format
|
||||
const linksObject: Record<string, string> = {};
|
||||
updatedLinks.forEach((link) => {
|
||||
linksObject[link.displayName] = link.url;
|
||||
// Convert array back to object format with index for ordering
|
||||
// New format: { "displayName": { "url": "...", "index": 0 } }
|
||||
const linksObject: Record<string, { url: string; index: number }> = {};
|
||||
updatedLinks.forEach((link, index) => {
|
||||
linksObject[link.displayName] = {
|
||||
url: link.url,
|
||||
index: index,
|
||||
};
|
||||
});
|
||||
|
||||
await updateUsefulLinksCall(accessToken, linksObject);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue