mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
Merge pull request #39130 from BerriAI/litellm_dark_mode_skill_detail
fix(ui): render the skill detail page with theme tokens
This commit is contained in:
commit
75f0a22fc6
1 changed files with 78 additions and 232 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState } from "react";
|
||||
import { ArrowLeft, Check, Copy, Link2 } from "lucide-react";
|
||||
import { cn } from "@/lib/cva.config";
|
||||
import { buildMarketplaceSettingsSnippet, formatInstallCommand } from "./helpers";
|
||||
import { Plugin } from "./types";
|
||||
|
||||
|
|
@ -50,48 +51,37 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
];
|
||||
|
||||
return (
|
||||
<div style={{ padding: "24px 32px 24px 0" }}>
|
||||
<div className="py-6 pl-0 pr-8">
|
||||
{/* Back link */}
|
||||
<div
|
||||
onClick={onBack}
|
||||
style={{
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
color: "#5f6368",
|
||||
cursor: "pointer",
|
||||
fontSize: 14,
|
||||
marginBottom: 24,
|
||||
}}
|
||||
className="mb-6 inline-flex cursor-pointer items-center gap-1.5 text-sm text-muted-foreground"
|
||||
>
|
||||
<ArrowLeft className="size-3" />
|
||||
<span>Skills</span>
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<h1 style={{ fontSize: 28, fontWeight: 400, color: "#202124", margin: 0, lineHeight: 1.2 }}>{skill.name}</h1>
|
||||
<div className="mb-2">
|
||||
<h1 className="m-0 text-[28px] font-normal leading-tight text-foreground">{skill.name}</h1>
|
||||
{skill.description && (
|
||||
<p style={{ fontSize: 14, color: "#5f6368", margin: "8px 0 0 0", lineHeight: 1.6 }}>{skill.description}</p>
|
||||
<p className="mb-0 ml-0 mr-0 mt-2 text-sm leading-relaxed text-muted-foreground">{skill.description}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Tab bar */}
|
||||
<div style={{ borderBottom: "1px solid #dadce0", marginBottom: 28, marginTop: 24 }}>
|
||||
<div style={{ display: "flex", gap: 0 }}>
|
||||
<div className="mb-7 mt-6 border-b border-border">
|
||||
<div className="flex">
|
||||
{tabs.map((tab) => (
|
||||
<div
|
||||
key={tab.key}
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
style={{
|
||||
padding: "12px 20px",
|
||||
fontSize: 14,
|
||||
color: activeTab === tab.key ? "#1a73e8" : "#5f6368",
|
||||
borderBottom: activeTab === tab.key ? "3px solid #1a73e8" : "3px solid transparent",
|
||||
cursor: "pointer",
|
||||
fontWeight: activeTab === tab.key ? 500 : 400,
|
||||
marginBottom: -1,
|
||||
}}
|
||||
className={cn(
|
||||
"-mb-px cursor-pointer border-b-[3px] px-5 py-3 text-sm",
|
||||
activeTab === tab.key
|
||||
? "border-info font-medium text-info"
|
||||
: "border-transparent font-normal text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{tab.label}
|
||||
</div>
|
||||
|
|
@ -101,27 +91,23 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
|
||||
{/* Overview tab */}
|
||||
{activeTab === "overview" && (
|
||||
<div style={{ display: "flex", gap: 64 }}>
|
||||
<div className="flex gap-16">
|
||||
{/* Left column */}
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<h2 style={{ fontSize: 18, fontWeight: 400, color: "#202124", margin: "0 0 4px 0" }}>Skill Details</h2>
|
||||
<p style={{ fontSize: 13, color: "#5f6368", margin: "0 0 16px 0" }}>Metadata registered with this skill</p>
|
||||
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 14 }}>
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 className="m-0 mb-1 text-lg font-normal text-foreground">Skill Details</h2>
|
||||
<p className="m-0 mb-4 text-[13px] text-muted-foreground">Metadata registered with this skill</p>
|
||||
<table className="w-full border-collapse text-sm">
|
||||
<thead>
|
||||
<tr style={{ borderBottom: "1px solid #dadce0" }}>
|
||||
<th style={{ textAlign: "left", padding: "12px 0", color: "#5f6368", fontWeight: 500, width: 160 }}>
|
||||
Property
|
||||
</th>
|
||||
<th style={{ textAlign: "left", padding: "12px 0", color: "#5f6368", fontWeight: 500 }}>
|
||||
{skill.name}
|
||||
</th>
|
||||
<tr className="border-b border-border">
|
||||
<th className="w-40 py-3 text-left font-medium text-muted-foreground">Property</th>
|
||||
<th className="py-3 text-left font-medium text-muted-foreground">{skill.name}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{detailRows.map((row, i) => (
|
||||
<tr key={i} style={{ borderBottom: "1px solid #f1f3f4" }}>
|
||||
<td style={{ padding: "12px 0", color: "#3c4043" }}>{row.property}</td>
|
||||
<td style={{ padding: "12px 0", color: "#202124" }}>{row.value}</td>
|
||||
<tr key={i} className="border-b border-border">
|
||||
<td className="py-3 text-foreground">{row.property}</td>
|
||||
<td className="py-3 text-foreground">{row.value}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
|
@ -129,38 +115,27 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
</div>
|
||||
|
||||
{/* Right sidebar */}
|
||||
<div style={{ width: 240, flexShrink: 0 }}>
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<div style={{ fontSize: 12, color: "#5f6368", marginBottom: 4 }}>Status</div>
|
||||
<div className="w-60 shrink-0">
|
||||
<div className="mb-6">
|
||||
<div className="mb-1 text-xs text-muted-foreground">Status</div>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 12,
|
||||
padding: "3px 10px",
|
||||
borderRadius: 12,
|
||||
backgroundColor: skill.enabled ? "#e6f4ea" : "#f1f3f4",
|
||||
color: skill.enabled ? "#137333" : "#5f6368",
|
||||
fontWeight: 500,
|
||||
}}
|
||||
className={cn(
|
||||
"rounded-xl px-2.5 py-[3px] text-xs font-medium",
|
||||
skill.enabled ? "bg-success/10 text-success" : "bg-muted text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{skill.enabled ? "Public" : "Draft"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{sourceUrl && (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<div style={{ fontSize: 12, color: "#5f6368", marginBottom: 4 }}>Source</div>
|
||||
<div className="mb-6">
|
||||
<div className="mb-1 text-xs text-muted-foreground">Source</div>
|
||||
<a
|
||||
href={sourceUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{
|
||||
fontSize: 13,
|
||||
color: "#1a73e8",
|
||||
wordBreak: "break-all",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 4,
|
||||
}}
|
||||
className="flex items-center gap-1 break-all text-[13px] text-info"
|
||||
>
|
||||
{sourceUrl.replace("https://", "")}
|
||||
<Link2 className="size-3 shrink-0" />
|
||||
|
|
@ -169,20 +144,13 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
)}
|
||||
|
||||
{skill.keywords && skill.keywords.length > 0 && (
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<div style={{ fontSize: 12, color: "#5f6368", marginBottom: 8 }}>Tags</div>
|
||||
<div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
|
||||
<div className="mb-6">
|
||||
<div className="mb-2 text-xs text-muted-foreground">Tags</div>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{skill.keywords.map((kw) => (
|
||||
<span
|
||||
key={kw}
|
||||
style={{
|
||||
fontSize: 12,
|
||||
padding: "4px 12px",
|
||||
borderRadius: 16,
|
||||
border: "1px solid #dadce0",
|
||||
color: "#3c4043",
|
||||
backgroundColor: "#fff",
|
||||
}}
|
||||
className="rounded-2xl border border-border bg-card px-3 py-1 text-xs text-foreground"
|
||||
>
|
||||
{kw}
|
||||
</span>
|
||||
|
|
@ -192,10 +160,8 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
)}
|
||||
|
||||
<div>
|
||||
<div style={{ fontSize: 12, color: "#5f6368", marginBottom: 4 }}>Skill ID</div>
|
||||
<div style={{ fontSize: 12, fontFamily: "monospace", color: "#3c4043", wordBreak: "break-all" }}>
|
||||
{skill.id}
|
||||
</div>
|
||||
<div className="mb-1 text-xs text-muted-foreground">Skill ID</div>
|
||||
<div className="break-all font-mono text-xs text-foreground">{skill.id}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -203,93 +169,43 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
|
||||
{/* How to Use tab */}
|
||||
{activeTab === "usage" && (
|
||||
<div style={{ maxWidth: 640 }}>
|
||||
<h2 style={{ fontSize: 18, fontWeight: 400, color: "#202124", margin: "0 0 8px 0" }}>Using this skill</h2>
|
||||
<p style={{ fontSize: 14, color: "#5f6368", margin: "0 0 24px 0", lineHeight: 1.6 }}>
|
||||
<div className="max-w-[640px]">
|
||||
<h2 className="m-0 mb-2 text-lg font-normal text-foreground">Using this skill</h2>
|
||||
<p className="m-0 mb-6 text-sm leading-relaxed text-muted-foreground">
|
||||
Once your proxy is set as a marketplace, enable this skill in Claude Code with one command:
|
||||
</p>
|
||||
|
||||
{/* Install command */}
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid #dadce0",
|
||||
borderRadius: 8,
|
||||
overflow: "hidden",
|
||||
marginBottom: 24,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "10px 16px",
|
||||
backgroundColor: "#f8f9fa",
|
||||
borderBottom: "1px solid #dadce0",
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 13, color: "#3c4043", fontWeight: 500 }}>Run in Claude Code</span>
|
||||
<div className="mb-6 overflow-hidden rounded-lg border border-border">
|
||||
<div className="flex items-center justify-between border-b border-border bg-muted px-4 py-2.5">
|
||||
<span className="text-[13px] font-medium text-foreground">Run in Claude Code</span>
|
||||
<button
|
||||
onClick={() => copyToClipboard(installCommand, "install")}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 4,
|
||||
fontSize: 12,
|
||||
color: copiedKey === "install" ? "#137333" : "#1a73e8",
|
||||
background: "none",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
padding: 0,
|
||||
}}
|
||||
className={cn(
|
||||
"flex cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-xs",
|
||||
copiedKey === "install" ? "text-success" : "text-info",
|
||||
)}
|
||||
>
|
||||
{copiedKey === "install" ? <Check className="size-3" /> : <Copy className="size-3" />}
|
||||
{copiedKey === "install" ? "Copied" : "Copy"}
|
||||
</button>
|
||||
</div>
|
||||
<pre
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: "14px 16px",
|
||||
fontSize: 14,
|
||||
fontFamily: "monospace",
|
||||
color: "#202124",
|
||||
backgroundColor: "#fff",
|
||||
}}
|
||||
>
|
||||
{installCommand}
|
||||
</pre>
|
||||
<pre className="m-0 bg-card px-4 py-3.5 font-mono text-sm text-foreground">{installCommand}</pre>
|
||||
</div>
|
||||
|
||||
{/* Shown when the marketplace catalog is stale and the plugin isn't found yet */}
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid #fce8b2",
|
||||
borderRadius: 8,
|
||||
padding: "12px 16px",
|
||||
backgroundColor: "#fefce8",
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
<p style={{ fontSize: 13, color: "#5f6368", lineHeight: 1.6, margin: "0 0 8px 0" }}>
|
||||
<div className="mb-4 rounded-lg border border-warning/30 bg-warning/10 px-4 py-3">
|
||||
<p className="m-0 mb-2 text-[13px] leading-relaxed text-muted-foreground">
|
||||
If you see "Plugin {skill.name} not found in marketplace", update the catalog first:
|
||||
</p>
|
||||
<pre
|
||||
style={{
|
||||
margin: 0,
|
||||
fontSize: 13,
|
||||
fontFamily: "monospace",
|
||||
color: "#202124",
|
||||
backgroundColor: "transparent",
|
||||
}}
|
||||
>
|
||||
<pre className="m-0 bg-transparent font-mono text-[13px] text-foreground">
|
||||
/plugin marketplace update litellm
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<p style={{ fontSize: 13, color: "#5f6368", lineHeight: 1.6, margin: 0 }}>
|
||||
<p className="m-0 text-[13px] leading-relaxed text-muted-foreground">
|
||||
Don't have the marketplace configured yet?{" "}
|
||||
<span onClick={() => setActiveTab("setup")} style={{ color: "#1a73e8", cursor: "pointer" }}>
|
||||
<span onClick={() => setActiveTab("setup")} className="cursor-pointer text-info">
|
||||
See one-time setup →
|
||||
</span>
|
||||
</p>
|
||||
|
|
@ -298,126 +214,56 @@ const SkillDetail: React.FC<SkillDetailProps> = ({ skill, onBack }) => {
|
|||
|
||||
{/* Setup tab (linked from usage) */}
|
||||
{activeTab === "setup" && (
|
||||
<div style={{ maxWidth: 640 }}>
|
||||
<h2 style={{ fontSize: 18, fontWeight: 400, color: "#202124", margin: "0 0 8px 0" }}>
|
||||
One-time marketplace setup
|
||||
</h2>
|
||||
<div className="max-w-[640px]">
|
||||
<h2 className="m-0 mb-2 text-lg font-normal text-foreground">One-time marketplace setup</h2>
|
||||
|
||||
{/* Option 1: single command — fastest path for most users */}
|
||||
<p style={{ fontSize: 14, color: "#5f6368", margin: "0 0 12px 0", lineHeight: 1.6 }}>
|
||||
<p className="m-0 mb-3 text-sm leading-relaxed text-muted-foreground">
|
||||
Run this command in Claude Code to register the marketplace:
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid #dadce0",
|
||||
borderRadius: 8,
|
||||
overflow: "hidden",
|
||||
marginBottom: 24,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "10px 16px",
|
||||
backgroundColor: "#f8f9fa",
|
||||
borderBottom: "1px solid #dadce0",
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 13, color: "#3c4043", fontWeight: 500 }}>Run in Claude Code</span>
|
||||
<div className="mb-6 overflow-hidden rounded-lg border border-border">
|
||||
<div className="flex items-center justify-between border-b border-border bg-muted px-4 py-2.5">
|
||||
<span className="text-[13px] font-medium text-foreground">Run in Claude Code</span>
|
||||
<button
|
||||
onClick={() => {
|
||||
const origin = typeof window !== "undefined" ? window.location.origin : "";
|
||||
copyToClipboard(`/plugin marketplace add ${origin}/claude-code/marketplace.json`, "marketplace-cmd");
|
||||
}}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 4,
|
||||
fontSize: 12,
|
||||
color: copiedKey === "marketplace-cmd" ? "#137333" : "#1a73e8",
|
||||
background: "none",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
padding: 0,
|
||||
}}
|
||||
className={cn(
|
||||
"flex cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-xs",
|
||||
copiedKey === "marketplace-cmd" ? "text-success" : "text-info",
|
||||
)}
|
||||
>
|
||||
{copiedKey === "marketplace-cmd" ? <Check className="size-3" /> : <Copy className="size-3" />}
|
||||
{copiedKey === "marketplace-cmd" ? "Copied" : "Copy"}
|
||||
</button>
|
||||
</div>
|
||||
<pre
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: "14px 16px",
|
||||
fontSize: 13,
|
||||
fontFamily: "monospace",
|
||||
color: "#202124",
|
||||
backgroundColor: "#fff",
|
||||
}}
|
||||
>
|
||||
<pre className="m-0 bg-card px-4 py-3.5 font-mono text-[13px] text-foreground">
|
||||
{`/plugin marketplace add ${typeof window !== "undefined" ? window.location.origin : "<proxy-url>"}/claude-code/marketplace.json`}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
{/* Option 2: settings.json — for persistent config or managed deployments.
|
||||
extraKnownMarketplaces requires source to be a nested object, not a flat string. */}
|
||||
<p style={{ fontSize: 14, color: "#5f6368", margin: "0 0 12px 0", lineHeight: 1.6 }}>
|
||||
Or add this to{" "}
|
||||
<code style={{ fontSize: 13, backgroundColor: "#f1f3f4", padding: "1px 6px", borderRadius: 4 }}>
|
||||
~/.claude/settings.json
|
||||
</code>{" "}
|
||||
<p className="m-0 mb-3 text-sm leading-relaxed text-muted-foreground">
|
||||
Or add this to <code className="rounded bg-muted px-1.5 py-px text-[13px]">~/.claude/settings.json</code>{" "}
|
||||
for a persistent configuration:
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid #dadce0",
|
||||
borderRadius: 8,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "10px 16px",
|
||||
backgroundColor: "#f8f9fa",
|
||||
borderBottom: "1px solid #dadce0",
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: 13, color: "#3c4043", fontWeight: 500 }}>~/.claude/settings.json</span>
|
||||
<div className="overflow-hidden rounded-lg border border-border">
|
||||
<div className="flex items-center justify-between border-b border-border bg-muted px-4 py-2.5">
|
||||
<span className="text-[13px] font-medium text-foreground">~/.claude/settings.json</span>
|
||||
<button
|
||||
onClick={() => copyToClipboard(settingsSnippet, "settings")}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 4,
|
||||
fontSize: 12,
|
||||
color: copiedKey === "settings" ? "#137333" : "#1a73e8",
|
||||
background: "none",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
padding: 0,
|
||||
}}
|
||||
className={cn(
|
||||
"flex cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-xs",
|
||||
copiedKey === "settings" ? "text-success" : "text-info",
|
||||
)}
|
||||
>
|
||||
{copiedKey === "settings" ? <Check className="size-3" /> : <Copy className="size-3" />}
|
||||
{copiedKey === "settings" ? "Copied" : "Copy"}
|
||||
</button>
|
||||
</div>
|
||||
<pre
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: "14px 16px",
|
||||
fontSize: 13,
|
||||
fontFamily: "monospace",
|
||||
color: "#202124",
|
||||
backgroundColor: "#fff",
|
||||
}}
|
||||
>
|
||||
{settingsSnippet}
|
||||
</pre>
|
||||
<pre className="m-0 bg-card px-4 py-3.5 font-mono text-[13px] text-foreground">{settingsSnippet}</pre>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue