@vertesia/tools-admin-ui 1.2.0 → 1.4.0-dev.20260615.042549Z
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +198 -10
- package/lib/AdminApp.d.ts +1 -1
- package/lib/AdminApp.d.ts.map +1 -1
- package/lib/components/AdminTopBar.d.ts +1 -1
- package/lib/components/AdminTopBar.d.ts.map +1 -1
- package/lib/components/CollectionCard.d.ts +1 -1
- package/lib/components/CollectionCard.d.ts.map +1 -1
- package/lib/components/DetailPage.d.ts +1 -1
- package/lib/components/DetailPage.d.ts.map +1 -1
- package/lib/components/EndpointPanel.d.ts +1 -1
- package/lib/components/EndpointPanel.d.ts.map +1 -1
- package/lib/components/HeroSection.d.ts +1 -1
- package/lib/components/HeroSection.d.ts.map +1 -1
- package/lib/components/ResourceCard.d.ts +1 -1
- package/lib/components/ResourceCard.d.ts.map +1 -1
- package/lib/components/ResourceSection.d.ts +1 -1
- package/lib/components/ResourceSection.d.ts.map +1 -1
- package/lib/components/SearchBar.d.ts +1 -1
- package/lib/components/SearchBar.d.ts.map +1 -1
- package/lib/components/SummaryBadge.d.ts +1 -1
- package/lib/components/SummaryBadge.d.ts.map +1 -1
- package/lib/hooks.d.ts +2 -2
- package/lib/hooks.d.ts.map +1 -1
- package/lib/index.d.ts +4 -4
- package/lib/index.d.ts.map +1 -1
- package/lib/pages/ActivityCollection.d.ts +1 -1
- package/lib/pages/ActivityCollection.d.ts.map +1 -1
- package/lib/pages/HomePage.d.ts +1 -1
- package/lib/pages/HomePage.d.ts.map +1 -1
- package/lib/pages/InteractionCollection.d.ts +1 -1
- package/lib/pages/InteractionCollection.d.ts.map +1 -1
- package/lib/pages/InteractionDetail.d.ts +1 -1
- package/lib/pages/InteractionDetail.d.ts.map +1 -1
- package/lib/pages/SkillCollection.d.ts +1 -1
- package/lib/pages/SkillCollection.d.ts.map +1 -1
- package/lib/pages/SkillDetail.d.ts +1 -1
- package/lib/pages/SkillDetail.d.ts.map +1 -1
- package/lib/pages/TemplateCollection.d.ts +1 -1
- package/lib/pages/TemplateCollection.d.ts.map +1 -1
- package/lib/pages/TemplateDetail.d.ts +1 -1
- package/lib/pages/TemplateDetail.d.ts.map +1 -1
- package/lib/pages/ToolCollection.d.ts +1 -1
- package/lib/pages/ToolCollection.d.ts.map +1 -1
- package/lib/pages/TypeCollection.d.ts +1 -1
- package/lib/pages/TypeCollection.d.ts.map +1 -1
- package/lib/pages/TypeDetail.d.ts +1 -1
- package/lib/pages/TypeDetail.d.ts.map +1 -1
- package/lib/tools-admin-ui.js +1484 -969
- package/lib/tools-admin-ui.js.map +1 -1
- package/lib/types.d.ts.map +1 -1
- package/package.json +16 -14
- package/src/AdminApp.tsx +14 -15
- package/src/components/AdminTopBar.tsx +7 -7
- package/src/components/CollectionCard.tsx +3 -1
- package/src/components/DetailPage.tsx +10 -4
- package/src/components/EndpointPanel.tsx +2 -9
- package/src/components/HeroSection.tsx +12 -3
- package/src/components/ResourceCard.tsx +8 -6
- package/src/components/ResourceSection.tsx +1 -1
- package/src/components/SearchBar.tsx +1 -5
- package/src/components/SummaryBadge.tsx +2 -1
- package/src/dev/env.ts +8 -8
- package/src/dev/main.tsx +20 -15
- package/src/hooks.ts +19 -12
- package/src/index.ts +4 -4
- package/src/pages/ActivityCollection.tsx +30 -12
- package/src/pages/HomePage.tsx +65 -57
- package/src/pages/InteractionCollection.tsx +26 -11
- package/src/pages/InteractionDetail.tsx +28 -13
- package/src/pages/SkillCollection.tsx +38 -18
- package/src/pages/SkillDetail.tsx +37 -13
- package/src/pages/TemplateCollection.tsx +28 -17
- package/src/pages/TemplateDetail.tsx +13 -7
- package/src/pages/ToolCollection.tsx +21 -10
- package/src/pages/TypeCollection.tsx +26 -17
- package/src/pages/TypeDetail.tsx +12 -6
- package/src/types.ts +16 -35
package/src/pages/HomePage.tsx
CHANGED
|
@@ -13,7 +13,11 @@ const sections: { type: ResourceType; title: string; subtitle: string }[] = [
|
|
|
13
13
|
{ type: 'skill', title: 'Skills', subtitle: 'Reusable instructions and scripts packaged as tools.' },
|
|
14
14
|
{ type: 'interaction', title: 'Interactions', subtitle: 'Conversation blueprints surfaced in the Vertesia UI.' },
|
|
15
15
|
{ type: 'type', title: 'Content Types', subtitle: 'Schema definitions for structured content in the data store.' },
|
|
16
|
-
{
|
|
16
|
+
{
|
|
17
|
+
type: 'template',
|
|
18
|
+
title: 'Rendering Templates',
|
|
19
|
+
subtitle: 'Document and presentation templates for content generation.',
|
|
20
|
+
},
|
|
17
21
|
{ type: 'mcp', title: 'MCP Providers', subtitle: 'Remote MCP servers available through this tools server.' },
|
|
18
22
|
];
|
|
19
23
|
|
|
@@ -21,10 +25,7 @@ export function HomePage() {
|
|
|
21
25
|
const { serverInfo, collections, resources } = useAdminContext();
|
|
22
26
|
const [search, setSearch] = useState('');
|
|
23
27
|
|
|
24
|
-
const filtered = useMemo(() =>
|
|
25
|
-
filterResources(resources, search),
|
|
26
|
-
[resources, search]
|
|
27
|
-
);
|
|
28
|
+
const filtered = useMemo(() => filterResources(resources, search), [resources, search]);
|
|
28
29
|
|
|
29
30
|
const isSearching = search.trim().length > 0;
|
|
30
31
|
|
|
@@ -44,60 +45,67 @@ export function HomePage() {
|
|
|
44
45
|
totalCount={resources.length}
|
|
45
46
|
/>
|
|
46
47
|
|
|
47
|
-
{isSearching
|
|
48
|
-
sections.map((section, i) => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const mcpResources = section.type === 'mcp'
|
|
64
|
-
? resources.filter(r => r.type === 'mcp')
|
|
65
|
-
: [];
|
|
48
|
+
{isSearching
|
|
49
|
+
? sections.map((section, i) => {
|
|
50
|
+
const sectionItems = filtered.filter((r) => r.type === section.type);
|
|
51
|
+
return (
|
|
52
|
+
<ResourceSection
|
|
53
|
+
key={section.type}
|
|
54
|
+
title={section.title}
|
|
55
|
+
subtitle={section.subtitle}
|
|
56
|
+
resources={sectionItems}
|
|
57
|
+
showDivider={i > 0}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
})
|
|
61
|
+
: sections.map((section, i) => {
|
|
62
|
+
const sectionCollections = collections.filter((c) => c.type === section.type);
|
|
63
|
+
const mcpResources = section.type === 'mcp' ? resources.filter((r) => r.type === 'mcp') : [];
|
|
66
64
|
|
|
67
|
-
|
|
65
|
+
if (sectionCollections.length === 0 && mcpResources.length === 0) return null;
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
67
|
+
return (
|
|
68
|
+
<section key={section.type}>
|
|
69
|
+
{i > 0 && <Separator className="my-8" />}
|
|
70
|
+
<div>
|
|
71
|
+
<h2 className="text-xl font-semibold text-foreground">
|
|
72
|
+
{section.title}
|
|
73
|
+
<span className="ml-2 text-sm font-normal text-muted-foreground">
|
|
74
|
+
({sectionCollections.length}
|
|
75
|
+
{sectionCollections.length === 1 ? ' collection' : ' collections'})
|
|
76
|
+
</span>
|
|
77
|
+
</h2>
|
|
78
|
+
<p className="mb-4 text-sm text-muted-foreground">{section.subtitle}</p>
|
|
79
|
+
</div>
|
|
80
|
+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
81
|
+
{sectionCollections.map((col) => (
|
|
82
|
+
<CollectionCard key={`${col.type}:${col.name}`} collection={col} />
|
|
83
|
+
))}
|
|
84
|
+
{mcpResources.map((r) => (
|
|
85
|
+
<div
|
|
86
|
+
key={r.name}
|
|
87
|
+
className="rounded-xl border border-border bg-card p-5 shadow-sm"
|
|
88
|
+
>
|
|
89
|
+
<span
|
|
90
|
+
className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.mcp}`}
|
|
91
|
+
>
|
|
92
|
+
mcp
|
|
93
|
+
</span>
|
|
94
|
+
<div className="font-semibold text-card-foreground">{r.title}</div>
|
|
95
|
+
<div className="mt-1 text-sm text-muted-foreground">
|
|
96
|
+
{r.description || 'No description'}
|
|
97
|
+
</div>
|
|
98
|
+
{r.url && (
|
|
99
|
+
<div className="mt-2 truncate font-mono text-xs text-muted-foreground">
|
|
100
|
+
{r.url}
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
))}
|
|
105
|
+
</div>
|
|
106
|
+
</section>
|
|
107
|
+
);
|
|
108
|
+
})}
|
|
101
109
|
</div>
|
|
102
110
|
);
|
|
103
111
|
}
|
|
@@ -11,11 +11,12 @@ export function InteractionCollection() {
|
|
|
11
11
|
const { baseUrl } = useAdminContext();
|
|
12
12
|
|
|
13
13
|
const { data: interactions, error } = useFetch<CatalogInteractionRef[]>(
|
|
14
|
-
() =>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
() =>
|
|
15
|
+
fetch(`${baseUrl}/interactions/${collection}`).then((r) => {
|
|
16
|
+
if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
|
|
17
|
+
return r.json();
|
|
18
|
+
}),
|
|
19
|
+
[baseUrl, collection],
|
|
19
20
|
);
|
|
20
21
|
|
|
21
22
|
if (error) {
|
|
@@ -23,7 +24,11 @@ export function InteractionCollection() {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
if (!interactions) {
|
|
26
|
-
return
|
|
27
|
+
return (
|
|
28
|
+
<div className="flex h-64 items-center justify-center text-muted-foreground">
|
|
29
|
+
<Spinner />
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
return (
|
|
@@ -33,18 +38,28 @@ export function InteractionCollection() {
|
|
|
33
38
|
description={`${interactions.length} interaction${interactions.length !== 1 ? 's' : ''} in this collection.`}
|
|
34
39
|
>
|
|
35
40
|
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
36
|
-
{interactions.map(inter => (
|
|
37
|
-
<NavLink
|
|
41
|
+
{interactions.map((inter) => (
|
|
42
|
+
<NavLink
|
|
43
|
+
key={inter.id}
|
|
44
|
+
href={`/interactions/${collection}/${inter.name}`}
|
|
45
|
+
className="block no-underline"
|
|
46
|
+
>
|
|
38
47
|
<Card className="cursor-pointer transition-all hover:-translate-y-0.5 hover:shadow-md">
|
|
39
48
|
<CardContent className="p-5">
|
|
40
|
-
<span
|
|
49
|
+
<span
|
|
50
|
+
className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.interaction}`}
|
|
51
|
+
>
|
|
41
52
|
interaction
|
|
42
53
|
</span>
|
|
43
54
|
<div className="font-semibold text-card-foreground">{inter.title || inter.name}</div>
|
|
44
|
-
<div className="mt-1 text-sm text-muted-foreground">
|
|
55
|
+
<div className="mt-1 text-sm text-muted-foreground">
|
|
56
|
+
{inter.description || 'No description'}
|
|
57
|
+
</div>
|
|
45
58
|
{inter.tags && inter.tags.length > 0 && (
|
|
46
59
|
<div className="mt-2 flex flex-wrap gap-1">
|
|
47
|
-
{inter.tags.map(tag =>
|
|
60
|
+
{inter.tags.map((tag) => (
|
|
61
|
+
<Badge key={tag}>{tag}</Badge>
|
|
62
|
+
))}
|
|
48
63
|
</div>
|
|
49
64
|
)}
|
|
50
65
|
</CardContent>
|
|
@@ -17,15 +17,21 @@ export function InteractionDetail() {
|
|
|
17
17
|
const { baseUrl } = useAdminContext();
|
|
18
18
|
|
|
19
19
|
const { data: interaction, error } = useFetch<InteractionResponse>(
|
|
20
|
-
() =>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
() =>
|
|
21
|
+
client
|
|
22
|
+
.getRawJWT()
|
|
23
|
+
.then((token) =>
|
|
24
|
+
fetch(`${baseUrl}/interactions/${collection}/${name}`, {
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${token}`,
|
|
27
|
+
},
|
|
28
|
+
}),
|
|
29
|
+
)
|
|
30
|
+
.then((r) => {
|
|
31
|
+
if (!r.ok) throw new Error(`Failed to load interaction: ${r.statusText}`);
|
|
32
|
+
return r.json();
|
|
33
|
+
}),
|
|
34
|
+
[baseUrl, collection, name],
|
|
29
35
|
);
|
|
30
36
|
|
|
31
37
|
if (error) {
|
|
@@ -33,11 +39,16 @@ export function InteractionDetail() {
|
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
if (!interaction) {
|
|
36
|
-
return
|
|
42
|
+
return (
|
|
43
|
+
<div className="flex h-64 items-center justify-center text-muted-foreground">
|
|
44
|
+
<Spinner />
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
const { agent_runner_options } = interaction;
|
|
40
|
-
const hasAgentFlags =
|
|
50
|
+
const hasAgentFlags =
|
|
51
|
+
agent_runner_options &&
|
|
41
52
|
(agent_runner_options.is_agent || agent_runner_options.is_tool || agent_runner_options.is_skill);
|
|
42
53
|
|
|
43
54
|
return (
|
|
@@ -55,14 +66,18 @@ export function InteractionDetail() {
|
|
|
55
66
|
<Card key={`${prompt.role}-${prompt.name ?? ''}`} className="mb-3">
|
|
56
67
|
<CardContent className="p-4">
|
|
57
68
|
<div className="mb-2 flex items-center gap-2">
|
|
58
|
-
<span
|
|
69
|
+
<span
|
|
70
|
+
className={`rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${ROLE_VARIANTS[prompt.role] ?? ''}`}
|
|
71
|
+
>
|
|
59
72
|
{prompt.role}
|
|
60
73
|
</span>
|
|
61
74
|
{prompt.name && (
|
|
62
75
|
<span className="text-sm italic text-muted-foreground">{prompt.name}</span>
|
|
63
76
|
)}
|
|
64
77
|
</div>
|
|
65
|
-
<pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">
|
|
78
|
+
<pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">
|
|
79
|
+
{prompt.content}
|
|
80
|
+
</pre>
|
|
66
81
|
</CardContent>
|
|
67
82
|
</Card>
|
|
68
83
|
))}
|
|
@@ -9,7 +9,7 @@ import { TYPE_VARIANTS } from '../components/typeVariants.js';
|
|
|
9
9
|
interface SkillToolDef {
|
|
10
10
|
name: string;
|
|
11
11
|
description?: string;
|
|
12
|
-
|
|
12
|
+
tools?: string[];
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
interface SkillCollectionResponse {
|
|
@@ -38,16 +38,17 @@ export function SkillCollection() {
|
|
|
38
38
|
const { baseUrl } = useAdminContext();
|
|
39
39
|
|
|
40
40
|
const { data, error } = useFetch<SkillCollectionResponse>(
|
|
41
|
-
() =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
() =>
|
|
42
|
+
fetch(`${baseUrl}/skills/${collection}`).then((r) => {
|
|
43
|
+
if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
|
|
44
|
+
return r.json();
|
|
45
|
+
}),
|
|
46
|
+
[baseUrl, collection],
|
|
46
47
|
);
|
|
47
48
|
|
|
48
49
|
const { data: widgetsData } = useFetch<WidgetsResponse>(
|
|
49
|
-
() => fetch(`${baseUrl}/package?scope=widgets`).then(r => r.ok ? r.json() : { widgets: {} }),
|
|
50
|
-
[baseUrl]
|
|
50
|
+
() => fetch(`${baseUrl}/package?scope=widgets`).then((r) => (r.ok ? r.json() : { widgets: {} })),
|
|
51
|
+
[baseUrl],
|
|
51
52
|
);
|
|
52
53
|
|
|
53
54
|
const collectionWidgets = useMemo(() => {
|
|
@@ -57,20 +58,29 @@ export function SkillCollection() {
|
|
|
57
58
|
.map(([name, w]) => ({ name, ...w }));
|
|
58
59
|
}, [widgetsData, collection]);
|
|
59
60
|
|
|
60
|
-
if (error)
|
|
61
|
-
|
|
61
|
+
if (error)
|
|
62
|
+
return <div className="p-6 text-destructive">Failed to load skill collection “{collection}”.</div>;
|
|
63
|
+
if (!data)
|
|
64
|
+
return (
|
|
65
|
+
<div className="flex h-64 items-center justify-center text-muted-foreground">
|
|
66
|
+
<Spinner />
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
62
69
|
|
|
63
70
|
return (
|
|
64
71
|
<DetailPage
|
|
65
72
|
type="skill"
|
|
66
73
|
title={data.title || collection}
|
|
67
|
-
description={
|
|
74
|
+
description={
|
|
75
|
+
data.description ||
|
|
76
|
+
`${data.tools.length} skill${data.tools.length !== 1 ? 's' : ''} in this collection.`
|
|
77
|
+
}
|
|
68
78
|
>
|
|
69
79
|
{collectionWidgets.length > 0 && (
|
|
70
80
|
<div className="mb-8">
|
|
71
81
|
<h2 className="mb-3 text-lg font-semibold text-foreground">Widgets</h2>
|
|
72
82
|
<div className="flex flex-wrap gap-2">
|
|
73
|
-
{collectionWidgets.map(w => (
|
|
83
|
+
{collectionWidgets.map((w) => (
|
|
74
84
|
<Badge key={w.name} variant="success">
|
|
75
85
|
{w.name}
|
|
76
86
|
<span className="ml-2 font-mono text-xs opacity-70">(skill: {w.skill})</span>
|
|
@@ -81,20 +91,30 @@ export function SkillCollection() {
|
|
|
81
91
|
)}
|
|
82
92
|
|
|
83
93
|
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
84
|
-
{data.tools.map(skill => {
|
|
94
|
+
{data.tools.map((skill) => {
|
|
85
95
|
const displayName = skillDisplayName(skill.name);
|
|
86
96
|
return (
|
|
87
|
-
<NavLink
|
|
97
|
+
<NavLink
|
|
98
|
+
key={skill.name}
|
|
99
|
+
href={`/skills/${collection}/${displayName}`}
|
|
100
|
+
className="block no-underline"
|
|
101
|
+
>
|
|
88
102
|
<Card className="cursor-pointer transition-all hover:-translate-y-0.5 hover:shadow-md">
|
|
89
103
|
<CardContent className="p-5">
|
|
90
|
-
<span
|
|
104
|
+
<span
|
|
105
|
+
className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.skill}`}
|
|
106
|
+
>
|
|
91
107
|
skill
|
|
92
108
|
</span>
|
|
93
109
|
<div className="font-semibold text-card-foreground">{displayName}</div>
|
|
94
|
-
<div className="mt-1 text-sm text-muted-foreground">
|
|
95
|
-
|
|
110
|
+
<div className="mt-1 text-sm text-muted-foreground">
|
|
111
|
+
{skill.description || 'No description'}
|
|
112
|
+
</div>
|
|
113
|
+
{skill.tools && skill.tools.length > 0 && (
|
|
96
114
|
<div className="mt-2 flex flex-wrap gap-1">
|
|
97
|
-
{skill.
|
|
115
|
+
{skill.tools.map((t) => (
|
|
116
|
+
<Badge key={t}>{t}</Badge>
|
|
117
|
+
))}
|
|
98
118
|
</div>
|
|
99
119
|
)}
|
|
100
120
|
</CardContent>
|
|
@@ -11,7 +11,7 @@ interface SkillDefinitionResponse {
|
|
|
11
11
|
instructions: string;
|
|
12
12
|
content_type: 'md' | 'jst';
|
|
13
13
|
input_schema?: Record<string, unknown>;
|
|
14
|
-
|
|
14
|
+
tools?: string[];
|
|
15
15
|
execution?: {
|
|
16
16
|
language: string;
|
|
17
17
|
packages?: string[];
|
|
@@ -27,15 +27,21 @@ export function SkillDetail() {
|
|
|
27
27
|
const { baseUrl } = useAdminContext();
|
|
28
28
|
|
|
29
29
|
const { data: skill, error } = useFetch<SkillDefinitionResponse>(
|
|
30
|
-
() =>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
() =>
|
|
31
|
+
fetch(`${baseUrl}/skills/${collection}/${name}`).then((r) => {
|
|
32
|
+
if (!r.ok) throw new Error(`Failed to load skill: ${r.statusText}`);
|
|
33
|
+
return r.json();
|
|
34
|
+
}),
|
|
35
|
+
[baseUrl, collection, name],
|
|
35
36
|
);
|
|
36
37
|
|
|
37
38
|
if (error) return <div className="p-6 text-destructive">Failed to load skill “{name}”.</div>;
|
|
38
|
-
if (!skill)
|
|
39
|
+
if (!skill)
|
|
40
|
+
return (
|
|
41
|
+
<div className="flex h-64 items-center justify-center text-muted-foreground">
|
|
42
|
+
<Spinner />
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
39
45
|
|
|
40
46
|
return (
|
|
41
47
|
<DetailPage
|
|
@@ -48,7 +54,11 @@ export function SkillDetail() {
|
|
|
48
54
|
<div className="mb-8">
|
|
49
55
|
<h2 className="mb-3 text-lg font-semibold text-foreground">Widgets</h2>
|
|
50
56
|
<div className="flex flex-wrap gap-2">
|
|
51
|
-
{skill.widgets.map(w =>
|
|
57
|
+
{skill.widgets.map((w) => (
|
|
58
|
+
<Badge key={w} variant="success">
|
|
59
|
+
{w}
|
|
60
|
+
</Badge>
|
|
61
|
+
))}
|
|
52
62
|
</div>
|
|
53
63
|
</div>
|
|
54
64
|
)}
|
|
@@ -57,16 +67,24 @@ export function SkillDetail() {
|
|
|
57
67
|
<div className="mb-8">
|
|
58
68
|
<h2 className="mb-3 text-lg font-semibold text-foreground">Scripts</h2>
|
|
59
69
|
<div className="flex flex-wrap gap-2">
|
|
60
|
-
{skill.scripts.map(s =>
|
|
70
|
+
{skill.scripts.map((s) => (
|
|
71
|
+
<Badge key={s} variant="success">
|
|
72
|
+
{s}
|
|
73
|
+
</Badge>
|
|
74
|
+
))}
|
|
61
75
|
</div>
|
|
62
76
|
</div>
|
|
63
77
|
)}
|
|
64
78
|
|
|
65
|
-
{skill.
|
|
79
|
+
{skill.tools && skill.tools.length > 0 && (
|
|
66
80
|
<div className="mb-8">
|
|
67
81
|
<h2 className="mb-3 text-lg font-semibold text-foreground">Related Tools</h2>
|
|
68
82
|
<div className="flex flex-wrap gap-2">
|
|
69
|
-
{skill.
|
|
83
|
+
{skill.tools.map((t) => (
|
|
84
|
+
<Badge key={t} variant="success">
|
|
85
|
+
{t}
|
|
86
|
+
</Badge>
|
|
87
|
+
))}
|
|
70
88
|
</div>
|
|
71
89
|
</div>
|
|
72
90
|
)}
|
|
@@ -76,7 +94,11 @@ export function SkillDetail() {
|
|
|
76
94
|
<h2 className="mb-3 text-lg font-semibold text-foreground">Execution</h2>
|
|
77
95
|
<div className="flex flex-wrap gap-2">
|
|
78
96
|
<Badge variant="success">{skill.execution.language}</Badge>
|
|
79
|
-
{skill.execution.packages?.map(p =>
|
|
97
|
+
{skill.execution.packages?.map((p) => (
|
|
98
|
+
<Badge key={p} variant="success">
|
|
99
|
+
{p}
|
|
100
|
+
</Badge>
|
|
101
|
+
))}
|
|
80
102
|
</div>
|
|
81
103
|
</div>
|
|
82
104
|
)}
|
|
@@ -86,7 +108,9 @@ export function SkillDetail() {
|
|
|
86
108
|
Instructions
|
|
87
109
|
{skill.content_type === 'jst' && <Badge className="ml-2">JST template</Badge>}
|
|
88
110
|
</h2>
|
|
89
|
-
<pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">
|
|
111
|
+
<pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">
|
|
112
|
+
{skill.instructions}
|
|
113
|
+
</pre>
|
|
90
114
|
</div>
|
|
91
115
|
|
|
92
116
|
{skill.input_schema && (
|
|
@@ -11,15 +11,24 @@ export function TemplateCollection() {
|
|
|
11
11
|
const { baseUrl } = useAdminContext();
|
|
12
12
|
|
|
13
13
|
const { data: templates, error } = useFetch<RenderingTemplateDefinitionRef[]>(
|
|
14
|
-
() =>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
() =>
|
|
15
|
+
fetch(`${baseUrl}/templates/${collection}`).then((r) => {
|
|
16
|
+
if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
|
|
17
|
+
return r.json();
|
|
18
|
+
}),
|
|
19
|
+
[baseUrl, collection],
|
|
19
20
|
);
|
|
20
21
|
|
|
21
|
-
if (error)
|
|
22
|
-
|
|
22
|
+
if (error)
|
|
23
|
+
return (
|
|
24
|
+
<div className="p-6 text-destructive">Failed to load template collection “{collection}”.</div>
|
|
25
|
+
);
|
|
26
|
+
if (!templates)
|
|
27
|
+
return (
|
|
28
|
+
<div className="flex h-64 items-center justify-center text-muted-foreground">
|
|
29
|
+
<Spinner />
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
23
32
|
|
|
24
33
|
return (
|
|
25
34
|
<DetailPage
|
|
@@ -28,23 +37,25 @@ export function TemplateCollection() {
|
|
|
28
37
|
description={`${templates.length} template${templates.length !== 1 ? 's' : ''} in this collection.`}
|
|
29
38
|
>
|
|
30
39
|
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
31
|
-
{templates.map(tmpl => (
|
|
32
|
-
<NavLink
|
|
33
|
-
key={tmpl.name}
|
|
34
|
-
href={`/templates/${collection}/${tmpl.name}`}
|
|
35
|
-
className="no-underline"
|
|
36
|
-
>
|
|
40
|
+
{templates.map((tmpl) => (
|
|
41
|
+
<NavLink key={tmpl.name} href={`/templates/${collection}/${tmpl.name}`} className="no-underline">
|
|
37
42
|
<Card className="h-full transition-all hover:-translate-y-0.5 hover:shadow-md">
|
|
38
43
|
<CardContent className="p-5">
|
|
39
|
-
<span
|
|
44
|
+
<span
|
|
45
|
+
className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.template}`}
|
|
46
|
+
>
|
|
40
47
|
{tmpl.type || 'template'}
|
|
41
48
|
</span>
|
|
42
49
|
<div className="font-semibold text-card-foreground">{tmpl.title || tmpl.name}</div>
|
|
43
|
-
<div className="mt-1 text-sm text-muted-foreground">
|
|
50
|
+
<div className="mt-1 text-sm text-muted-foreground">
|
|
51
|
+
{tmpl.description || 'No description'}
|
|
52
|
+
</div>
|
|
44
53
|
{tmpl.tags && tmpl.tags.length > 0 && (
|
|
45
54
|
<div className="mt-3 flex flex-wrap gap-1.5">
|
|
46
|
-
{tmpl.tags.map(tag => (
|
|
47
|
-
<Badge key={tag} variant="default">
|
|
55
|
+
{tmpl.tags.map((tag) => (
|
|
56
|
+
<Badge key={tag} variant="default">
|
|
57
|
+
{tag}
|
|
58
|
+
</Badge>
|
|
48
59
|
))}
|
|
49
60
|
</div>
|
|
50
61
|
)}
|
|
@@ -21,15 +21,21 @@ export function TemplateDetail() {
|
|
|
21
21
|
const { baseUrl } = useAdminContext();
|
|
22
22
|
|
|
23
23
|
const { data: template, error } = useFetch<TemplateDefinitionResponse>(
|
|
24
|
-
() =>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
() =>
|
|
25
|
+
fetch(`${baseUrl}/templates/${collection}/${name}`).then((r) => {
|
|
26
|
+
if (!r.ok) throw new Error(`Failed to load template: ${r.statusText}`);
|
|
27
|
+
return r.json();
|
|
28
|
+
}),
|
|
29
|
+
[baseUrl, collection, name],
|
|
29
30
|
);
|
|
30
31
|
|
|
31
32
|
if (error) return <div className="p-6 text-destructive">Failed to load template “{name}”.</div>;
|
|
32
|
-
if (!template)
|
|
33
|
+
if (!template)
|
|
34
|
+
return (
|
|
35
|
+
<div className="flex h-64 items-center justify-center text-muted-foreground">
|
|
36
|
+
<Spinner />
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
33
39
|
|
|
34
40
|
return (
|
|
35
41
|
<DetailPage
|
|
@@ -49,7 +55,7 @@ export function TemplateDetail() {
|
|
|
49
55
|
<div className="mb-8">
|
|
50
56
|
<h2 className="mb-3 text-lg font-semibold text-foreground">Assets</h2>
|
|
51
57
|
<div className="flex flex-wrap gap-2">
|
|
52
|
-
{template.assets.map(asset => (
|
|
58
|
+
{template.assets.map((asset) => (
|
|
53
59
|
<Badge key={asset} variant="success">
|
|
54
60
|
{asset.split('/').pop()}
|
|
55
61
|
</Badge>
|