mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
feat(ui): migrate small top-level components to shadcn
- query_param_input: antd Button/Space + @ant-design/icons \u2192 shadcn Button/Input + lucide MinusCircle/Plus, dashed-outline add button. - object_permissions_view: @tremor Text \u2192 plain <p> with semantic tokens. - route_preview: antd Card/Typography + @ant-design/icons ChevronRight/ InfoCircle \u2192 shadcn Card + lucide ChevronRight/Info. - skill_hub_table_columns: @tremor Badge/Text + antd Tooltip + @ant-design/ icons CopyOutlined/LinkOutlined \u2192 shadcn Badge/Tooltip + lucide Copy/Link. Added to overrides for the categorical blue/emerald badge palette. - claude_code_plugins: @tremor Button + antd Modal \u2192 shadcn Button + AlertDialog (destructive variant for the Delete action). Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
38175a15a5
commit
32392518f6
6 changed files with 223 additions and 128 deletions
|
|
@ -43,7 +43,8 @@
|
||||||
"src/components/mcp_tools/ByokCredentialModal.tsx",
|
"src/components/mcp_tools/ByokCredentialModal.tsx",
|
||||||
"src/components/shared/usage_date_picker.tsx",
|
"src/components/shared/usage_date_picker.tsx",
|
||||||
"src/components/shared/advanced_date_picker.tsx",
|
"src/components/shared/advanced_date_picker.tsx",
|
||||||
"src/components/activity_metrics.tsx"
|
"src/components/activity_metrics.tsx",
|
||||||
|
"src/components/skill_hub_table_columns.tsx"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"litellm-ui/no-banned-ui-imports": "off",
|
"litellm-ui/no-banned-ui-imports": "off",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,15 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Button } from "@tremor/react";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Modal } from "antd";
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from "@/components/ui/alert-dialog";
|
||||||
import {
|
import {
|
||||||
getClaudeCodePluginsList,
|
getClaudeCodePluginsList,
|
||||||
deleteClaudeCodePlugin,
|
deleteClaudeCodePlugin,
|
||||||
|
|
@ -40,7 +49,7 @@ const ClaudeCodePluginsPanel: React.FC<ClaudeCodePluginsPanelProps> = ({
|
||||||
try {
|
try {
|
||||||
const response: ListPluginsResponse = await getClaudeCodePluginsList(
|
const response: ListPluginsResponse = await getClaudeCodePluginsList(
|
||||||
accessToken,
|
accessToken,
|
||||||
false
|
false,
|
||||||
);
|
);
|
||||||
setPluginsList(response.plugins);
|
setPluginsList(response.plugins);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -52,6 +61,7 @@ const ClaudeCodePluginsPanel: React.FC<ClaudeCodePluginsPanelProps> = ({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchPlugins();
|
fetchPlugins();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [accessToken]);
|
}, [accessToken]);
|
||||||
|
|
||||||
const handleDeleteClick = (pluginName: string, displayName: string) => {
|
const handleDeleteClick = (pluginName: string, displayName: string) => {
|
||||||
|
|
@ -64,7 +74,9 @@ const ClaudeCodePluginsPanel: React.FC<ClaudeCodePluginsPanelProps> = ({
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteClaudeCodePlugin(accessToken, pluginToDelete.name);
|
await deleteClaudeCodePlugin(accessToken, pluginToDelete.name);
|
||||||
NotificationsManager.success(`Skill "${pluginToDelete.displayName}" deleted successfully`);
|
NotificationsManager.success(
|
||||||
|
`Skill "${pluginToDelete.displayName}" deleted successfully`,
|
||||||
|
);
|
||||||
fetchPlugins();
|
fetchPlugins();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error deleting skill:", error);
|
console.error("Error deleting skill:", error);
|
||||||
|
|
@ -89,13 +101,19 @@ const ClaudeCodePluginsPanel: React.FC<ClaudeCodePluginsPanelProps> = ({
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col gap-2 mb-4">
|
<div className="flex flex-col gap-2 mb-4">
|
||||||
<h1 className="text-2xl font-bold">Skills</h1>
|
<h1 className="text-2xl font-bold">Skills</h1>
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-muted-foreground">
|
||||||
Register Claude Code skills. Published skills appear in the Skill Hub for all users and
|
Register Claude Code skills. Published skills appear in the Skill
|
||||||
are served via{" "}
|
Hub for all users and are served via{" "}
|
||||||
<code className="bg-gray-100 px-1 rounded">/claude-code/marketplace.json</code>.
|
<code className="bg-muted px-1 rounded">
|
||||||
|
/claude-code/marketplace.json
|
||||||
|
</code>
|
||||||
|
.
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-2 flex gap-2">
|
<div className="mt-2 flex gap-2">
|
||||||
<Button onClick={() => setIsAddModalVisible(true)} disabled={!accessToken || !isAdmin}>
|
<Button
|
||||||
|
onClick={() => setIsAddModalVisible(true)}
|
||||||
|
disabled={!accessToken || !isAdmin}
|
||||||
|
>
|
||||||
+ Add Skill
|
+ Add Skill
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -122,23 +140,34 @@ const ClaudeCodePluginsPanel: React.FC<ClaudeCodePluginsPanelProps> = ({
|
||||||
onSuccess={fetchPlugins}
|
onSuccess={fetchPlugins}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{pluginToDelete && (
|
<AlertDialog
|
||||||
<Modal
|
open={pluginToDelete !== null}
|
||||||
title="Delete Skill"
|
onOpenChange={(o) => (!o ? setPluginToDelete(null) : undefined)}
|
||||||
open={pluginToDelete !== null}
|
>
|
||||||
onOk={handleDeleteConfirm}
|
<AlertDialogContent>
|
||||||
onCancel={() => setPluginToDelete(null)}
|
<AlertDialogHeader>
|
||||||
confirmLoading={isDeleting}
|
<AlertDialogTitle>Delete Skill</AlertDialogTitle>
|
||||||
okText="Delete"
|
<AlertDialogDescription>
|
||||||
okButtonProps={{ danger: true }}
|
Are you sure you want to delete skill:{" "}
|
||||||
>
|
<strong>{pluginToDelete?.displayName}</strong>? This action cannot
|
||||||
<p>
|
be undone.
|
||||||
Are you sure you want to delete skill:{" "}
|
</AlertDialogDescription>
|
||||||
<strong>{pluginToDelete.displayName}</strong>?
|
</AlertDialogHeader>
|
||||||
</p>
|
<AlertDialogFooter>
|
||||||
<p>This action cannot be undone.</p>
|
<AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>
|
||||||
</Modal>
|
<AlertDialogAction
|
||||||
)}
|
disabled={isDeleting}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleDeleteConfirm();
|
||||||
|
}}
|
||||||
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||||
|
>
|
||||||
|
{isDeleting ? "Deleting…" : "Delete"}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Text } from "@tremor/react";
|
|
||||||
import VectorStorePermissions from "./permissions/VectorStorePermissions";
|
import VectorStorePermissions from "./permissions/VectorStorePermissions";
|
||||||
import MCPServerPermissions from "./permissions/MCPServerPermissions";
|
import MCPServerPermissions from "./permissions/MCPServerPermissions";
|
||||||
import AgentPermissions from "./permissions/AgentPermissions";
|
import AgentPermissions from "./permissions/AgentPermissions";
|
||||||
|
|
@ -56,11 +55,15 @@ export function ObjectPermissionsView({
|
||||||
|
|
||||||
if (variant === "card") {
|
if (variant === "card") {
|
||||||
return (
|
return (
|
||||||
<div className={`bg-white border border-gray-200 rounded-lg p-6 ${className}`}>
|
<div
|
||||||
|
className={`bg-background border border-border rounded-lg p-6 ${className}`}
|
||||||
|
>
|
||||||
<div className="flex items-center gap-2 mb-6">
|
<div className="flex items-center gap-2 mb-6">
|
||||||
<div>
|
<div>
|
||||||
<Text className="font-semibold text-gray-900">Object Permissions</Text>
|
<p className="font-semibold text-foreground">Object Permissions</p>
|
||||||
<Text className="text-xs text-gray-500">Access control for Vector Stores and MCP Servers</Text>
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Access control for Vector Stores and MCP Servers
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{content}
|
{content}
|
||||||
|
|
@ -70,7 +73,7 @@ export function ObjectPermissionsView({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${className}`}>
|
<div className={`${className}`}>
|
||||||
<Text className="font-medium text-gray-900 mb-3">Object Permissions</Text>
|
<p className="font-medium text-foreground mb-3">Object Permissions</p>
|
||||||
{content}
|
{content}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,20 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Button, Space } from "antd";
|
import { Button } from "@/components/ui/button";
|
||||||
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
|
import { Input } from "@/components/ui/input";
|
||||||
import { TextInput } from "@tremor/react";
|
import { MinusCircle, Plus } from "lucide-react";
|
||||||
|
|
||||||
interface QueryParamInputProps {
|
interface QueryParamInputProps {
|
||||||
value?: Record<string, string>;
|
value?: Record<string, string>;
|
||||||
onChange?: (value: Record<string, string>) => void;
|
onChange?: (value: Record<string, string>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QueryParamInput: React.FC<QueryParamInputProps> = ({ value = {}, onChange }) => {
|
const QueryParamInput: React.FC<QueryParamInputProps> = ({
|
||||||
const [pairs, setPairs] = useState<[string, string][]>(Object.entries(value));
|
value = {},
|
||||||
|
onChange,
|
||||||
|
}) => {
|
||||||
|
const [pairs, setPairs] = useState<[string, string][]>(
|
||||||
|
Object.entries(value),
|
||||||
|
);
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
setPairs([...pairs, ["", ""]]);
|
setPairs([...pairs, ["", ""]]);
|
||||||
|
|
@ -29,29 +34,40 @@ const QueryParamInput: React.FC<QueryParamInputProps> = ({ value = {}, onChange
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="space-y-2">
|
||||||
{pairs.map(([key, val], index) => (
|
{pairs.map(([key, val], index) => (
|
||||||
<Space key={index} style={{ display: "flex", marginBottom: 8 }} align="center">
|
<div key={index} className="flex items-center gap-2">
|
||||||
<TextInput
|
<Input
|
||||||
placeholder="Parameter Name (e.g., version)"
|
placeholder="Parameter Name (e.g., version)"
|
||||||
value={key}
|
value={key}
|
||||||
onChange={(e) => handleChange(index, e.target.value, val)}
|
onChange={(e) => handleChange(index, e.target.value, val)}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<Input
|
||||||
placeholder="Parameter Value (e.g., v1)"
|
placeholder="Parameter Value (e.g., v1)"
|
||||||
value={val}
|
value={val}
|
||||||
onChange={(e) => handleChange(index, key, e.target.value)}
|
onChange={(e) => handleChange(index, key, e.target.value)}
|
||||||
/>
|
/>
|
||||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", height: "100%" }}>
|
<button
|
||||||
<MinusCircleOutlined onClick={() => handleRemove(index)} style={{ cursor: "pointer" }} />
|
type="button"
|
||||||
</div>
|
onClick={() => handleRemove(index)}
|
||||||
</Space>
|
className="shrink-0 text-muted-foreground hover:text-destructive"
|
||||||
|
aria-label={`Remove query parameter ${index + 1}`}
|
||||||
|
>
|
||||||
|
<MinusCircle className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
<Button type="dashed" onClick={handleAdd} icon={<PlusOutlined />}>
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleAdd}
|
||||||
|
className="border-dashed"
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
Add Query Parameter
|
Add Query Parameter
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default QueryParamInput;
|
export default QueryParamInput;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Card, Typography } from "antd";
|
import { Card } from "@/components/ui/card";
|
||||||
import { RightOutlined, InfoCircleOutlined } from "@ant-design/icons";
|
import { ChevronRight, Info } from "lucide-react";
|
||||||
|
|
||||||
const { Title, Text } = Typography;
|
|
||||||
import { getProxyBaseUrl } from "./networking";
|
import { getProxyBaseUrl } from "./networking";
|
||||||
|
|
||||||
interface RoutePreviewProps {
|
interface RoutePreviewProps {
|
||||||
|
|
@ -11,94 +9,114 @@ interface RoutePreviewProps {
|
||||||
includeSubpath: boolean;
|
includeSubpath: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoutePreview: React.FC<RoutePreviewProps> = ({ pathValue, targetValue, includeSubpath }) => {
|
const RoutePreview: React.FC<RoutePreviewProps> = ({
|
||||||
|
pathValue,
|
||||||
|
targetValue,
|
||||||
|
includeSubpath,
|
||||||
|
}) => {
|
||||||
const proxyBaseUrl = getProxyBaseUrl();
|
const proxyBaseUrl = getProxyBaseUrl();
|
||||||
|
|
||||||
const getLiteLLMProxyUrl = () => {
|
const getLiteLLMProxyUrl = () =>
|
||||||
return pathValue ? `${proxyBaseUrl}${pathValue}` : "";
|
pathValue ? `${proxyBaseUrl}${pathValue}` : "";
|
||||||
};
|
|
||||||
|
|
||||||
// Only show if both path and target are provided
|
|
||||||
if (!pathValue || !targetValue) {
|
if (!pathValue || !targetValue) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="p-5">
|
<Card className="p-5">
|
||||||
<Title level={5} className="text-lg font-semibold text-gray-900 mb-2">Route Preview</Title>
|
<h5 className="text-lg font-semibold text-foreground mb-2">
|
||||||
<Text type="secondary" className="text-gray-600 mb-5" style={{ display: "block" }}>How your requests will be routed</Text>
|
Route Preview
|
||||||
|
</h5>
|
||||||
|
<p className="text-muted-foreground mb-5 text-sm">
|
||||||
|
How your requests will be routed
|
||||||
|
</p>
|
||||||
|
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
{/* Basic routing */}
|
|
||||||
<div>
|
<div>
|
||||||
<div className="text-base font-semibold text-gray-900 mb-3">Basic routing:</div>
|
<div className="text-base font-semibold text-foreground mb-3">
|
||||||
|
Basic routing:
|
||||||
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{/* Your endpoint */}
|
<div className="flex-1 bg-muted border border-border rounded-lg p-3">
|
||||||
<div className="flex-1 bg-gray-50 border border-gray-200 rounded-lg p-3">
|
<div className="text-sm text-muted-foreground mb-2">
|
||||||
<div className="text-sm text-gray-600 mb-2">Your endpoint</div>
|
Your endpoint
|
||||||
<code className="font-mono text-sm text-gray-900">{getLiteLLMProxyUrl()}</code>
|
</div>
|
||||||
|
<code className="font-mono text-sm text-foreground">
|
||||||
|
{getLiteLLMProxyUrl()}
|
||||||
|
</code>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Arrow */}
|
<div className="text-muted-foreground">
|
||||||
<div className="text-gray-400">
|
<ChevronRight className="h-5 w-5" />
|
||||||
<RightOutlined className="text-lg" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Forwards to */}
|
<div className="flex-1 bg-muted border border-border rounded-lg p-3">
|
||||||
<div className="flex-1 bg-gray-50 border border-gray-200 rounded-lg p-3">
|
<div className="text-sm text-muted-foreground mb-2">
|
||||||
<div className="text-sm text-gray-600 mb-2">Forwards to</div>
|
Forwards to
|
||||||
<code className="font-mono text-sm text-gray-900">{targetValue}</code>
|
</div>
|
||||||
|
<code className="font-mono text-sm text-foreground">
|
||||||
|
{targetValue}
|
||||||
|
</code>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{includeSubpath && (
|
{includeSubpath && (
|
||||||
<>
|
<div>
|
||||||
{/* With subpaths */}
|
<div className="text-base font-semibold text-foreground mb-3">
|
||||||
<div>
|
With subpaths:
|
||||||
<div className="text-base font-semibold text-gray-900 mb-3">With subpaths:</div>
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{/* Your endpoint + subpath */}
|
<div className="flex-1 bg-muted border border-border rounded-lg p-3">
|
||||||
<div className="flex-1 bg-gray-50 border border-gray-200 rounded-lg p-3">
|
<div className="text-sm text-muted-foreground mb-2">
|
||||||
<div className="text-sm text-gray-600 mb-2">Your endpoint + subpath</div>
|
Your endpoint + subpath
|
||||||
<code className="font-mono text-sm text-gray-900">
|
|
||||||
{pathValue && `${proxyBaseUrl}${pathValue}`}
|
|
||||||
<span className="text-blue-600">/v1/text-to-image/base/model</span>
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Arrow */}
|
|
||||||
<div className="text-gray-400">
|
|
||||||
<RightOutlined className="text-lg" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Forwards to with subpath */}
|
|
||||||
<div className="flex-1 bg-gray-50 border border-gray-200 rounded-lg p-3">
|
|
||||||
<div className="text-sm text-gray-600 mb-2">Forwards to</div>
|
|
||||||
<code className="font-mono text-sm text-gray-900">
|
|
||||||
{targetValue}
|
|
||||||
<span className="text-blue-600">/v1/text-to-image/base/model</span>
|
|
||||||
</code>
|
|
||||||
</div>
|
</div>
|
||||||
|
<code className="font-mono text-sm text-foreground">
|
||||||
|
{pathValue && `${proxyBaseUrl}${pathValue}`}
|
||||||
|
<span className="text-primary">
|
||||||
|
/v1/text-to-image/base/model
|
||||||
|
</span>
|
||||||
|
</code>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Note */}
|
<div className="text-muted-foreground">
|
||||||
<div className="mt-3 text-sm text-gray-600">
|
<ChevronRight className="h-5 w-5" />
|
||||||
Any path after {pathValue} will be appended to the target URL
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 bg-muted border border-border rounded-lg p-3">
|
||||||
|
<div className="text-sm text-muted-foreground mb-2">
|
||||||
|
Forwards to
|
||||||
|
</div>
|
||||||
|
<code className="font-mono text-sm text-foreground">
|
||||||
|
{targetValue}
|
||||||
|
<span className="text-primary">
|
||||||
|
/v1/text-to-image/base/model
|
||||||
|
</span>
|
||||||
|
</code>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
|
<div className="mt-3 text-sm text-muted-foreground">
|
||||||
|
Any path after {pathValue} will be appended to the target URL
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!includeSubpath && (
|
{!includeSubpath && (
|
||||||
<div className="mt-4 p-3 bg-blue-50 rounded-md border border-blue-200">
|
<div className="mt-4 p-3 bg-primary/5 rounded-md border border-primary/20">
|
||||||
<div className="flex items-start">
|
<div className="flex items-start">
|
||||||
<InfoCircleOutlined className="text-blue-500 mt-0.5 mr-2 flex-shrink-0" />
|
<Info className="text-primary mt-0.5 mr-2 h-4 w-4 flex-shrink-0" />
|
||||||
<div className="text-sm text-blue-700">
|
<div className="text-sm text-primary">
|
||||||
<span className="font-medium">Not seeing the routing you wanted?</span> Try enabling - Include Subpaths
|
<span className="font-medium">
|
||||||
- above - this allows subroutes like{" "}
|
Not seeing the routing you wanted?
|
||||||
<code className="bg-blue-100 px-1 py-0.5 rounded font-mono text-xs">/api/v1/models</code> to be
|
</span>{" "}
|
||||||
forwarded automatically.
|
Try enabling - Include Subpaths - above - this allows subroutes
|
||||||
|
like{" "}
|
||||||
|
<code className="bg-primary/10 px-1 py-0.5 rounded font-mono text-xs">
|
||||||
|
/api/v1/models
|
||||||
|
</code>{" "}
|
||||||
|
to be forwarded automatically.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
import { Badge, Text } from "@tremor/react";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Tooltip } from "antd";
|
import {
|
||||||
import { CopyOutlined, LinkOutlined } from "@ant-design/icons";
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
|
import { Copy, Link as LinkIcon } from "lucide-react";
|
||||||
import { Plugin } from "./claude_code_plugins/types";
|
import { Plugin } from "./claude_code_plugins/types";
|
||||||
|
|
||||||
export const skillHubColumns = (
|
export const skillHubColumns = (
|
||||||
showModal: (skill: Plugin) => void,
|
showModal: (skill: Plugin) => void,
|
||||||
copyToClipboard: (text: string) => void,
|
copyToClipboard: (text: string) => void,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
publicPage: boolean = false,
|
publicPage: boolean = false,
|
||||||
): ColumnDef<Plugin>[] => [
|
): ColumnDef<Plugin>[] => [
|
||||||
{
|
{
|
||||||
|
|
@ -21,22 +27,31 @@ export const skillHubColumns = (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="font-medium text-sm cursor-pointer text-blue-600 hover:underline bg-transparent border-none p-0"
|
className="font-medium text-sm cursor-pointer text-primary hover:underline bg-transparent border-none p-0"
|
||||||
onClick={() => showModal(skill)}
|
onClick={() => showModal(skill)}
|
||||||
>
|
>
|
||||||
{skill.name}
|
{skill.name}
|
||||||
</button>
|
</button>
|
||||||
<Tooltip title="Copy skill name">
|
<TooltipProvider>
|
||||||
<CopyOutlined
|
<Tooltip>
|
||||||
onClick={() => copyToClipboard(skill.name)}
|
<TooltipTrigger asChild>
|
||||||
className="cursor-pointer text-gray-500 hover:text-blue-500 text-xs"
|
<button
|
||||||
/>
|
type="button"
|
||||||
</Tooltip>
|
onClick={() => copyToClipboard(skill.name)}
|
||||||
|
className="cursor-pointer text-muted-foreground hover:text-primary"
|
||||||
|
aria-label="Copy skill name"
|
||||||
|
>
|
||||||
|
<Copy className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Copy skill name</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
{skill.description && (
|
{skill.description && (
|
||||||
<Text className="text-xs text-gray-500 line-clamp-1 md:hidden">
|
<p className="text-xs text-muted-foreground line-clamp-1 md:hidden">
|
||||||
{skill.description}
|
{skill.description}
|
||||||
</Text>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -47,7 +62,9 @@ export const skillHubColumns = (
|
||||||
accessorKey: "description",
|
accessorKey: "description",
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<Text className="text-xs line-clamp-2">{row.original.description || "-"}</Text>
|
<p className="text-xs line-clamp-2">
|
||||||
|
{row.original.description || "-"}
|
||||||
|
</p>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -56,8 +73,12 @@ export const skillHubColumns = (
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const cat = row.original.category;
|
const cat = row.original.category;
|
||||||
if (!cat) return <Text className="text-xs text-gray-400">-</Text>;
|
if (!cat) return <span className="text-xs text-muted-foreground">-</span>;
|
||||||
return <Badge color="blue" size="xs">{cat}</Badge>;
|
return (
|
||||||
|
<Badge className="bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300 text-xs">
|
||||||
|
{cat}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -65,7 +86,7 @@ export const skillHubColumns = (
|
||||||
accessorKey: "domain",
|
accessorKey: "domain",
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<Text className="text-xs">{row.original.domain || "-"}</Text>
|
<span className="text-xs">{row.original.domain || "-"}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -86,17 +107,18 @@ export const skillHubColumns = (
|
||||||
url = src.url;
|
url = src.url;
|
||||||
label = src.url.replace(/^https?:\/\//, "");
|
label = src.url.replace(/^https?:\/\//, "");
|
||||||
}
|
}
|
||||||
if (!url) return <Text className="text-xs text-gray-400">-</Text>;
|
if (!url)
|
||||||
|
return <span className="text-xs text-muted-foreground">-</span>;
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
href={url}
|
href={url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-1 text-xs text-blue-600 hover:underline truncate max-w-[180px]"
|
className="flex items-center gap-1 text-xs text-primary hover:underline truncate max-w-[180px]"
|
||||||
title={label}
|
title={label}
|
||||||
>
|
>
|
||||||
<span className="truncate">{label}</span>
|
<span className="truncate">{label}</span>
|
||||||
<LinkOutlined className="shrink-0" style={{ fontSize: 10 }} />
|
<LinkIcon className="h-2.5 w-2.5 shrink-0" />
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -106,7 +128,13 @@ export const skillHubColumns = (
|
||||||
accessorKey: "enabled",
|
accessorKey: "enabled",
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<Badge color={row.original.enabled ? "green" : "gray"} size="xs">
|
<Badge
|
||||||
|
className={
|
||||||
|
row.original.enabled
|
||||||
|
? "bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300 text-xs"
|
||||||
|
: "bg-muted text-muted-foreground text-xs"
|
||||||
|
}
|
||||||
|
>
|
||||||
{row.original.enabled ? "Public" : "Draft"}
|
{row.original.enabled ? "Public" : "Draft"}
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue