@vertesia/tools-admin-ui 1.0.0-dev.20260227.112605Z → 1.0.0-dev.20260305.083323Z
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/lib/AdminApp.d.ts +3 -1
- package/lib/AdminApp.d.ts.map +1 -1
- package/lib/components/AdminTopBar.d.ts +6 -0
- package/lib/components/AdminTopBar.d.ts.map +1 -0
- package/lib/components/CollectionCard.d.ts.map +1 -1
- package/lib/components/DetailPage.d.ts.map +1 -1
- package/lib/components/EndpointPanel.d.ts.map +1 -1
- package/lib/components/HeroSection.d.ts.map +1 -1
- package/lib/components/ResourceCard.d.ts.map +1 -1
- package/lib/components/ResourceSection.d.ts.map +1 -1
- package/lib/components/SearchBar.d.ts.map +1 -1
- package/lib/components/SummaryBadge.d.ts.map +1 -1
- package/lib/components/index.d.ts +6 -5
- package/lib/components/index.d.ts.map +1 -1
- package/lib/components/typeVariants.d.ts +5 -0
- package/lib/components/typeVariants.d.ts.map +1 -0
- package/lib/hooks.d.ts +1 -1
- package/lib/hooks.d.ts.map +1 -1
- package/lib/pages/HomePage.d.ts.map +1 -1
- package/lib/pages/InteractionCollection.d.ts.map +1 -1
- package/lib/pages/InteractionDetail.d.ts.map +1 -1
- package/lib/pages/SkillCollection.d.ts.map +1 -1
- package/lib/pages/SkillDetail.d.ts.map +1 -1
- package/lib/pages/TemplateCollection.d.ts.map +1 -1
- package/lib/pages/TemplateDetail.d.ts.map +1 -1
- package/lib/pages/ToolCollection.d.ts.map +1 -1
- package/lib/pages/TypeCollection.d.ts.map +1 -1
- package/lib/pages/TypeDetail.d.ts.map +1 -1
- package/lib/tools-admin-ui.js +410 -377
- package/lib/tools-admin-ui.js.map +1 -1
- package/package.json +8 -4
- package/src/AdminApp.tsx +21 -17
- package/src/components/AdminTopBar.tsx +39 -0
- package/src/components/CollectionCard.tsx +18 -13
- package/src/components/DetailPage.tsx +20 -11
- package/src/components/EndpointPanel.tsx +16 -7
- package/src/components/HeroSection.tsx +51 -45
- package/src/components/ResourceCard.tsx +23 -18
- package/src/components/ResourceSection.tsx +7 -5
- package/src/components/SearchBar.tsx +8 -6
- package/src/components/SummaryBadge.tsx +4 -3
- package/src/components/index.ts +6 -5
- package/src/components/typeVariants.ts +18 -0
- package/src/dev/index.css +13 -0
- package/src/dev/main.tsx +5 -2
- package/src/hooks.ts +2 -1
- package/src/pages/HomePage.tsx +18 -15
- package/src/pages/InteractionCollection.tsx +25 -27
- package/src/pages/InteractionDetail.tsx +35 -34
- package/src/pages/SkillCollection.tsx +29 -32
- package/src/pages/SkillDetail.tsx +31 -41
- package/src/pages/TemplateCollection.tsx +25 -21
- package/src/pages/TemplateDetail.tsx +18 -17
- package/src/pages/ToolCollection.tsx +22 -16
- package/src/pages/TypeCollection.tsx +32 -24
- package/src/pages/TypeDetail.tsx +16 -18
- package/src/theme.css +12 -0
- package/src/admin.css +0 -650
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { useFetch } from '@vertesia/ui/core';
|
|
2
|
-
import { useParams, NavLink } from '@vertesia/ui/router';
|
|
3
1
|
import type { CatalogInteractionRef } from '@vertesia/common';
|
|
2
|
+
import { Badge, Card, CardContent, Spinner, useFetch } from '@vertesia/ui/core';
|
|
3
|
+
import { NavLink, useParams } from '@vertesia/ui/router';
|
|
4
|
+
|
|
4
5
|
import { useAdminContext } from '../AdminContext.js';
|
|
5
6
|
import { DetailPage } from '../components/DetailPage.js';
|
|
7
|
+
import { TYPE_VARIANTS } from '../components/typeVariants.js';
|
|
6
8
|
|
|
7
9
|
export function InteractionCollection() {
|
|
8
10
|
const collection = useParams('collection');
|
|
9
11
|
const { baseUrl } = useAdminContext();
|
|
10
12
|
|
|
11
|
-
const { data: interactions,
|
|
13
|
+
const { data: interactions, error } = useFetch<CatalogInteractionRef[]>(
|
|
12
14
|
() => fetch(`${baseUrl}/interactions/${collection}`).then(r => {
|
|
13
15
|
if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
|
|
14
16
|
return r.json();
|
|
@@ -16,12 +18,12 @@ export function InteractionCollection() {
|
|
|
16
18
|
[baseUrl, collection]
|
|
17
19
|
);
|
|
18
20
|
|
|
19
|
-
if (
|
|
20
|
-
return <div className="
|
|
21
|
+
if (error) {
|
|
22
|
+
return <div className="p-6 text-destructive">Failed to load collection “{collection}”.</div>;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
if (
|
|
24
|
-
return <div className="
|
|
25
|
+
if (!interactions) {
|
|
26
|
+
return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
return (
|
|
@@ -30,27 +32,23 @@ export function InteractionCollection() {
|
|
|
30
32
|
title={collection}
|
|
31
33
|
description={`${interactions.length} interaction${interactions.length !== 1 ? 's' : ''} in this collection.`}
|
|
32
34
|
>
|
|
33
|
-
<div className="
|
|
35
|
+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
34
36
|
{interactions.map(inter => (
|
|
35
|
-
<NavLink
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
))}
|
|
51
|
-
</div>
|
|
52
|
-
)}
|
|
53
|
-
</div>
|
|
37
|
+
<NavLink key={inter.id} href={`/interactions/${collection}/${inter.name}`} className="block no-underline">
|
|
38
|
+
<Card className="cursor-pointer transition-all hover:-translate-y-0.5 hover:shadow-md">
|
|
39
|
+
<CardContent className="p-5">
|
|
40
|
+
<span className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.interaction}`}>
|
|
41
|
+
interaction
|
|
42
|
+
</span>
|
|
43
|
+
<div className="font-semibold text-card-foreground">{inter.title || inter.name}</div>
|
|
44
|
+
<div className="mt-1 text-sm text-muted-foreground">{inter.description || 'No description'}</div>
|
|
45
|
+
{inter.tags && inter.tags.length > 0 && (
|
|
46
|
+
<div className="mt-2 flex flex-wrap gap-1">
|
|
47
|
+
{inter.tags.map(tag => <Badge key={tag}>{tag}</Badge>)}
|
|
48
|
+
</div>
|
|
49
|
+
)}
|
|
50
|
+
</CardContent>
|
|
51
|
+
</Card>
|
|
54
52
|
</NavLink>
|
|
55
53
|
))}
|
|
56
54
|
</div>
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { useFetch } from '@vertesia/ui/core';
|
|
2
|
-
import { useParams } from '@vertesia/ui/router';
|
|
3
1
|
import type { InteractionSpec } from '@vertesia/common';
|
|
2
|
+
import { Badge, Card, CardContent, Spinner, useFetch } from '@vertesia/ui/core';
|
|
3
|
+
import { useParams } from '@vertesia/ui/router';
|
|
4
|
+
import { useUserSession } from '@vertesia/ui/session';
|
|
5
|
+
|
|
4
6
|
import { useAdminContext } from '../AdminContext.js';
|
|
5
7
|
import { DetailPage } from '../components/DetailPage.js';
|
|
6
|
-
import {
|
|
8
|
+
import { ROLE_VARIANTS } from '../components/typeVariants.js';
|
|
7
9
|
|
|
8
10
|
type InteractionResponse = InteractionSpec & { id: string };
|
|
9
11
|
|
|
@@ -14,7 +16,7 @@ export function InteractionDetail() {
|
|
|
14
16
|
const name = params.name;
|
|
15
17
|
const { baseUrl } = useAdminContext();
|
|
16
18
|
|
|
17
|
-
const { data: interaction,
|
|
19
|
+
const { data: interaction, error } = useFetch<InteractionResponse>(
|
|
18
20
|
() => client.getRawJWT().then(token => fetch(`${baseUrl}/interactions/${collection}/${name}`, {
|
|
19
21
|
headers: {
|
|
20
22
|
Authorization: `Bearer ${token}`,
|
|
@@ -26,12 +28,12 @@ export function InteractionDetail() {
|
|
|
26
28
|
[baseUrl, collection, name]
|
|
27
29
|
);
|
|
28
30
|
|
|
29
|
-
if (
|
|
30
|
-
return <div className="
|
|
31
|
+
if (error) {
|
|
32
|
+
return <div className="p-6 text-destructive">Failed to load interaction “{name}”.</div>;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
if (
|
|
34
|
-
return <div className="
|
|
35
|
+
if (!interaction) {
|
|
36
|
+
return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
const { agent_runner_options } = interaction;
|
|
@@ -46,44 +48,43 @@ export function InteractionDetail() {
|
|
|
46
48
|
tags={interaction.tags}
|
|
47
49
|
backHref={`/interactions/${collection}`}
|
|
48
50
|
>
|
|
49
|
-
{/* Prompts */}
|
|
50
51
|
{interaction.prompts && interaction.prompts.length > 0 && (
|
|
51
|
-
<div className="
|
|
52
|
-
<h2>Prompts</h2>
|
|
53
|
-
{interaction.prompts.map((prompt
|
|
54
|
-
<
|
|
55
|
-
<
|
|
56
|
-
<
|
|
57
|
-
{prompt.role}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
52
|
+
<div className="mb-8">
|
|
53
|
+
<h2 className="mb-3 text-lg font-semibold text-foreground">Prompts</h2>
|
|
54
|
+
{interaction.prompts.map((prompt) => (
|
|
55
|
+
<Card key={`${prompt.role}-${prompt.name ?? ''}`} className="mb-3">
|
|
56
|
+
<CardContent className="p-4">
|
|
57
|
+
<div className="mb-2 flex items-center gap-2">
|
|
58
|
+
<span className={`rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${ROLE_VARIANTS[prompt.role] ?? ''}`}>
|
|
59
|
+
{prompt.role}
|
|
60
|
+
</span>
|
|
61
|
+
{prompt.name && (
|
|
62
|
+
<span className="text-sm italic text-muted-foreground">{prompt.name}</span>
|
|
63
|
+
)}
|
|
64
|
+
</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">{prompt.content}</pre>
|
|
66
|
+
</CardContent>
|
|
67
|
+
</Card>
|
|
65
68
|
))}
|
|
66
69
|
</div>
|
|
67
70
|
)}
|
|
68
71
|
|
|
69
|
-
{/* Result schema */}
|
|
70
72
|
{interaction.result_schema && (
|
|
71
|
-
<div className="
|
|
72
|
-
<h2>Result Schema</h2>
|
|
73
|
-
<pre className="
|
|
73
|
+
<div className="mb-8">
|
|
74
|
+
<h2 className="mb-3 text-lg font-semibold text-foreground">Result Schema</h2>
|
|
75
|
+
<pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">
|
|
74
76
|
{JSON.stringify(interaction.result_schema, null, 2)}
|
|
75
77
|
</pre>
|
|
76
78
|
</div>
|
|
77
79
|
)}
|
|
78
80
|
|
|
79
|
-
{/* Agent runner options */}
|
|
80
81
|
{hasAgentFlags && (
|
|
81
|
-
<div className="
|
|
82
|
-
<h2>Agent Runner</h2>
|
|
83
|
-
<div className="
|
|
84
|
-
{agent_runner_options.is_agent && <
|
|
85
|
-
{agent_runner_options.is_tool && <
|
|
86
|
-
{agent_runner_options.is_skill && <
|
|
82
|
+
<div className="mb-8">
|
|
83
|
+
<h2 className="mb-3 text-lg font-semibold text-foreground">Agent Runner</h2>
|
|
84
|
+
<div className="flex flex-wrap gap-2">
|
|
85
|
+
{agent_runner_options.is_agent && <Badge variant="success">Agent</Badge>}
|
|
86
|
+
{agent_runner_options.is_tool && <Badge variant="success">Tool</Badge>}
|
|
87
|
+
{agent_runner_options.is_skill && <Badge variant="success">Skill</Badge>}
|
|
87
88
|
</div>
|
|
88
89
|
</div>
|
|
89
90
|
)}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { Badge, Card, CardContent, Spinner, useFetch } from '@vertesia/ui/core';
|
|
2
|
+
import { NavLink, useParams } from '@vertesia/ui/router';
|
|
1
3
|
import { useMemo } from 'react';
|
|
2
|
-
|
|
3
|
-
import { useParams, NavLink } from '@vertesia/ui/router';
|
|
4
|
+
|
|
4
5
|
import { useAdminContext } from '../AdminContext.js';
|
|
5
6
|
import { DetailPage } from '../components/DetailPage.js';
|
|
7
|
+
import { TYPE_VARIANTS } from '../components/typeVariants.js';
|
|
6
8
|
|
|
7
9
|
interface SkillToolDef {
|
|
8
10
|
name: string;
|
|
@@ -35,7 +37,7 @@ export function SkillCollection() {
|
|
|
35
37
|
const collection = useParams('collection');
|
|
36
38
|
const { baseUrl } = useAdminContext();
|
|
37
39
|
|
|
38
|
-
const { data,
|
|
40
|
+
const { data, error } = useFetch<SkillCollectionResponse>(
|
|
39
41
|
() => fetch(`${baseUrl}/skills/${collection}`).then(r => {
|
|
40
42
|
if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
|
|
41
43
|
return r.json();
|
|
@@ -55,8 +57,8 @@ export function SkillCollection() {
|
|
|
55
57
|
.map(([name, w]) => ({ name, ...w }));
|
|
56
58
|
}, [widgetsData, collection]);
|
|
57
59
|
|
|
58
|
-
if (
|
|
59
|
-
if (
|
|
60
|
+
if (error) return <div className="p-6 text-destructive">Failed to load skill collection “{collection}”.</div>;
|
|
61
|
+
if (!data) return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
|
|
60
62
|
|
|
61
63
|
return (
|
|
62
64
|
<DetailPage
|
|
@@ -64,44 +66,39 @@ export function SkillCollection() {
|
|
|
64
66
|
title={data.title || collection}
|
|
65
67
|
description={data.description || `${data.tools.length} skill${data.tools.length !== 1 ? 's' : ''} in this collection.`}
|
|
66
68
|
>
|
|
67
|
-
{/* Widgets provided by this collection */}
|
|
68
69
|
{collectionWidgets.length > 0 && (
|
|
69
|
-
<div className="
|
|
70
|
-
<h2>Widgets</h2>
|
|
71
|
-
<div className="
|
|
70
|
+
<div className="mb-8">
|
|
71
|
+
<h2 className="mb-3 text-lg font-semibold text-foreground">Widgets</h2>
|
|
72
|
+
<div className="flex flex-wrap gap-2">
|
|
72
73
|
{collectionWidgets.map(w => (
|
|
73
|
-
<
|
|
74
|
+
<Badge key={w.name} variant="success">
|
|
74
75
|
{w.name}
|
|
75
|
-
<span className="
|
|
76
|
-
|
|
77
|
-
</span>
|
|
78
|
-
</span>
|
|
76
|
+
<span className="ml-2 font-mono text-xs opacity-70">(skill: {w.skill})</span>
|
|
77
|
+
</Badge>
|
|
79
78
|
))}
|
|
80
79
|
</div>
|
|
81
80
|
</div>
|
|
82
81
|
)}
|
|
83
82
|
|
|
84
|
-
<div className="
|
|
83
|
+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
85
84
|
{data.tools.map(skill => {
|
|
86
85
|
const displayName = skillDisplayName(skill.name);
|
|
87
86
|
return (
|
|
88
|
-
<NavLink
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
)}
|
|
104
|
-
</div>
|
|
87
|
+
<NavLink key={skill.name} href={`/skills/${collection}/${displayName}`} className="block no-underline">
|
|
88
|
+
<Card className="cursor-pointer transition-all hover:-translate-y-0.5 hover:shadow-md">
|
|
89
|
+
<CardContent className="p-5">
|
|
90
|
+
<span className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.skill}`}>
|
|
91
|
+
skill
|
|
92
|
+
</span>
|
|
93
|
+
<div className="font-semibold text-card-foreground">{displayName}</div>
|
|
94
|
+
<div className="mt-1 text-sm text-muted-foreground">{skill.description || 'No description'}</div>
|
|
95
|
+
{skill.related_tools && skill.related_tools.length > 0 && (
|
|
96
|
+
<div className="mt-2 flex flex-wrap gap-1">
|
|
97
|
+
{skill.related_tools.map(t => <Badge key={t}>{t}</Badge>)}
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
</CardContent>
|
|
101
|
+
</Card>
|
|
105
102
|
</NavLink>
|
|
106
103
|
);
|
|
107
104
|
})}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { useFetch } from '@vertesia/ui/core';
|
|
1
|
+
import { Badge, Spinner, useFetch } from '@vertesia/ui/core';
|
|
2
2
|
import { useParams } from '@vertesia/ui/router';
|
|
3
|
+
|
|
3
4
|
import { useAdminContext } from '../AdminContext.js';
|
|
4
5
|
import { DetailPage } from '../components/DetailPage.js';
|
|
5
6
|
|
|
@@ -25,7 +26,7 @@ export function SkillDetail() {
|
|
|
25
26
|
const name = params.name;
|
|
26
27
|
const { baseUrl } = useAdminContext();
|
|
27
28
|
|
|
28
|
-
const { data: skill,
|
|
29
|
+
const { data: skill, error } = useFetch<SkillDefinitionResponse>(
|
|
29
30
|
() => fetch(`${baseUrl}/skills/${collection}/${name}`).then(r => {
|
|
30
31
|
if (!r.ok) throw new Error(`Failed to load skill: ${r.statusText}`);
|
|
31
32
|
return r.json();
|
|
@@ -33,8 +34,8 @@ export function SkillDetail() {
|
|
|
33
34
|
[baseUrl, collection, name]
|
|
34
35
|
);
|
|
35
36
|
|
|
36
|
-
if (
|
|
37
|
-
if (
|
|
37
|
+
if (error) return <div className="p-6 text-destructive">Failed to load skill “{name}”.</div>;
|
|
38
|
+
if (!skill) return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
|
|
38
39
|
|
|
39
40
|
return (
|
|
40
41
|
<DetailPage
|
|
@@ -43,66 +44,55 @@ export function SkillDetail() {
|
|
|
43
44
|
description={skill.description}
|
|
44
45
|
backHref={`/skills/${collection}`}
|
|
45
46
|
>
|
|
46
|
-
{/* Widgets */}
|
|
47
47
|
{skill.widgets && skill.widgets.length > 0 && (
|
|
48
|
-
<div className="
|
|
49
|
-
<h2>Widgets</h2>
|
|
50
|
-
<div className="
|
|
51
|
-
{skill.widgets.map(w =>
|
|
52
|
-
<span key={w} className="vta-detail-flag">{w}</span>
|
|
53
|
-
))}
|
|
48
|
+
<div className="mb-8">
|
|
49
|
+
<h2 className="mb-3 text-lg font-semibold text-foreground">Widgets</h2>
|
|
50
|
+
<div className="flex flex-wrap gap-2">
|
|
51
|
+
{skill.widgets.map(w => <Badge key={w} variant="success">{w}</Badge>)}
|
|
54
52
|
</div>
|
|
55
53
|
</div>
|
|
56
54
|
)}
|
|
57
55
|
|
|
58
|
-
{/* Scripts */}
|
|
59
56
|
{skill.scripts && skill.scripts.length > 0 && (
|
|
60
|
-
<div className="
|
|
61
|
-
<h2>Scripts</h2>
|
|
62
|
-
<div className="
|
|
63
|
-
{skill.scripts.map(s =>
|
|
64
|
-
<span key={s} className="vta-detail-flag">{s}</span>
|
|
65
|
-
))}
|
|
57
|
+
<div className="mb-8">
|
|
58
|
+
<h2 className="mb-3 text-lg font-semibold text-foreground">Scripts</h2>
|
|
59
|
+
<div className="flex flex-wrap gap-2">
|
|
60
|
+
{skill.scripts.map(s => <Badge key={s} variant="success">{s}</Badge>)}
|
|
66
61
|
</div>
|
|
67
62
|
</div>
|
|
68
63
|
)}
|
|
69
64
|
|
|
70
|
-
{/* Related tools */}
|
|
71
65
|
{skill.related_tools && skill.related_tools.length > 0 && (
|
|
72
|
-
<div className="
|
|
73
|
-
<h2>Related Tools</h2>
|
|
74
|
-
<div className="
|
|
75
|
-
{skill.related_tools.map(t =>
|
|
76
|
-
<span key={t} className="vta-detail-flag">{t}</span>
|
|
77
|
-
))}
|
|
66
|
+
<div className="mb-8">
|
|
67
|
+
<h2 className="mb-3 text-lg font-semibold text-foreground">Related Tools</h2>
|
|
68
|
+
<div className="flex flex-wrap gap-2">
|
|
69
|
+
{skill.related_tools.map(t => <Badge key={t} variant="success">{t}</Badge>)}
|
|
78
70
|
</div>
|
|
79
71
|
</div>
|
|
80
72
|
)}
|
|
81
73
|
|
|
82
|
-
{/* Execution environment */}
|
|
83
74
|
{skill.execution && (
|
|
84
|
-
<div className="
|
|
85
|
-
<h2>Execution</h2>
|
|
86
|
-
<div className="
|
|
87
|
-
<
|
|
88
|
-
{skill.execution.packages?.map(p =>
|
|
89
|
-
<span key={p} className="vta-detail-flag">{p}</span>
|
|
90
|
-
))}
|
|
75
|
+
<div className="mb-8">
|
|
76
|
+
<h2 className="mb-3 text-lg font-semibold text-foreground">Execution</h2>
|
|
77
|
+
<div className="flex flex-wrap gap-2">
|
|
78
|
+
<Badge variant="success">{skill.execution.language}</Badge>
|
|
79
|
+
{skill.execution.packages?.map(p => <Badge key={p} variant="success">{p}</Badge>)}
|
|
91
80
|
</div>
|
|
92
81
|
</div>
|
|
93
82
|
)}
|
|
94
83
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
84
|
+
<div className="mb-8">
|
|
85
|
+
<h2 className="mb-3 text-lg font-semibold text-foreground">
|
|
86
|
+
Instructions
|
|
87
|
+
{skill.content_type === 'jst' && <Badge className="ml-2">JST template</Badge>}
|
|
88
|
+
</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">{skill.instructions}</pre>
|
|
99
90
|
</div>
|
|
100
91
|
|
|
101
|
-
{/* Input schema */}
|
|
102
92
|
{skill.input_schema && (
|
|
103
|
-
<div className="
|
|
104
|
-
<h2>Input Schema</h2>
|
|
105
|
-
<pre className="
|
|
93
|
+
<div className="mb-8">
|
|
94
|
+
<h2 className="mb-3 text-lg font-semibold text-foreground">Input Schema</h2>
|
|
95
|
+
<pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">
|
|
106
96
|
{JSON.stringify(skill.input_schema, null, 2)}
|
|
107
97
|
</pre>
|
|
108
98
|
</div>
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { useFetch } from '@vertesia/ui/core';
|
|
2
|
-
import { useParams, NavLink } from '@vertesia/ui/router';
|
|
3
1
|
import type { RenderingTemplateDefinitionRef } from '@vertesia/common';
|
|
2
|
+
import { Badge, Card, CardContent, Spinner, useFetch } from '@vertesia/ui/core';
|
|
3
|
+
import { NavLink, useParams } from '@vertesia/ui/router';
|
|
4
|
+
|
|
4
5
|
import { useAdminContext } from '../AdminContext.js';
|
|
5
6
|
import { DetailPage } from '../components/DetailPage.js';
|
|
7
|
+
import { TYPE_VARIANTS } from '../components/typeVariants.js';
|
|
6
8
|
|
|
7
9
|
export function TemplateCollection() {
|
|
8
10
|
const collection = useParams('collection');
|
|
9
11
|
const { baseUrl } = useAdminContext();
|
|
10
12
|
|
|
11
|
-
const { data: templates,
|
|
13
|
+
const { data: templates, error } = useFetch<RenderingTemplateDefinitionRef[]>(
|
|
12
14
|
() => fetch(`${baseUrl}/templates/${collection}`).then(r => {
|
|
13
15
|
if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
|
|
14
16
|
return r.json();
|
|
@@ -16,8 +18,8 @@ export function TemplateCollection() {
|
|
|
16
18
|
[baseUrl, collection]
|
|
17
19
|
);
|
|
18
20
|
|
|
19
|
-
if (
|
|
20
|
-
if (
|
|
21
|
+
if (error) return <div className="p-6 text-destructive">Failed to load template collection “{collection}”.</div>;
|
|
22
|
+
if (!templates) return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
|
|
21
23
|
|
|
22
24
|
return (
|
|
23
25
|
<DetailPage
|
|
@@ -25,27 +27,29 @@ export function TemplateCollection() {
|
|
|
25
27
|
title={collection}
|
|
26
28
|
description={`${templates.length} template${templates.length !== 1 ? 's' : ''} in this collection.`}
|
|
27
29
|
>
|
|
28
|
-
<div className="
|
|
30
|
+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
29
31
|
{templates.map(tmpl => (
|
|
30
32
|
<NavLink
|
|
31
33
|
key={tmpl.name}
|
|
32
34
|
href={`/templates/${collection}/${tmpl.name}`}
|
|
33
|
-
className="
|
|
35
|
+
className="no-underline"
|
|
34
36
|
>
|
|
35
|
-
<
|
|
36
|
-
<
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
<Card className="h-full transition-all hover:-translate-y-0.5 hover:shadow-md">
|
|
38
|
+
<CardContent className="p-5">
|
|
39
|
+
<span className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.template}`}>
|
|
40
|
+
{tmpl.type || 'template'}
|
|
41
|
+
</span>
|
|
42
|
+
<div className="font-semibold text-card-foreground">{tmpl.title || tmpl.name}</div>
|
|
43
|
+
<div className="mt-1 text-sm text-muted-foreground">{tmpl.description || 'No description'}</div>
|
|
44
|
+
{tmpl.tags && tmpl.tags.length > 0 && (
|
|
45
|
+
<div className="mt-3 flex flex-wrap gap-1.5">
|
|
46
|
+
{tmpl.tags.map(tag => (
|
|
47
|
+
<Badge key={tag} variant="default">{tag}</Badge>
|
|
48
|
+
))}
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
</CardContent>
|
|
52
|
+
</Card>
|
|
49
53
|
</NavLink>
|
|
50
54
|
))}
|
|
51
55
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { useFetch } from '@vertesia/ui/core';
|
|
1
|
+
import { Badge, Spinner, useFetch } from '@vertesia/ui/core';
|
|
2
2
|
import { useParams } from '@vertesia/ui/router';
|
|
3
|
+
|
|
3
4
|
import { useAdminContext } from '../AdminContext.js';
|
|
4
5
|
import { DetailPage } from '../components/DetailPage.js';
|
|
5
6
|
|
|
@@ -19,7 +20,7 @@ export function TemplateDetail() {
|
|
|
19
20
|
const name = params.name;
|
|
20
21
|
const { baseUrl } = useAdminContext();
|
|
21
22
|
|
|
22
|
-
const { data: template,
|
|
23
|
+
const { data: template, error } = useFetch<TemplateDefinitionResponse>(
|
|
23
24
|
() => fetch(`${baseUrl}/templates/${collection}/${name}`).then(r => {
|
|
24
25
|
if (!r.ok) throw new Error(`Failed to load template: ${r.statusText}`);
|
|
25
26
|
return r.json();
|
|
@@ -27,8 +28,8 @@ export function TemplateDetail() {
|
|
|
27
28
|
[baseUrl, collection, name]
|
|
28
29
|
);
|
|
29
30
|
|
|
30
|
-
if (
|
|
31
|
-
if (
|
|
31
|
+
if (error) return <div className="p-6 text-destructive">Failed to load template “{name}”.</div>;
|
|
32
|
+
if (!template) return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
|
|
32
33
|
|
|
33
34
|
return (
|
|
34
35
|
<DetailPage
|
|
@@ -38,30 +39,30 @@ export function TemplateDetail() {
|
|
|
38
39
|
tags={template.tags}
|
|
39
40
|
backHref={`/templates/${collection}`}
|
|
40
41
|
>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<span className="vta-detail-flag">{template.type}</span>
|
|
42
|
+
<div className="mb-8">
|
|
43
|
+
<div className="flex flex-wrap gap-2">
|
|
44
|
+
<Badge variant="success">{template.type}</Badge>
|
|
45
45
|
</div>
|
|
46
46
|
</div>
|
|
47
47
|
|
|
48
48
|
{template.assets && template.assets.length > 0 && (
|
|
49
|
-
<div className="
|
|
50
|
-
<h2>Assets</h2>
|
|
51
|
-
<div className="
|
|
49
|
+
<div className="mb-8">
|
|
50
|
+
<h2 className="mb-3 text-lg font-semibold text-foreground">Assets</h2>
|
|
51
|
+
<div className="flex flex-wrap gap-2">
|
|
52
52
|
{template.assets.map(asset => (
|
|
53
|
-
<
|
|
53
|
+
<Badge key={asset} variant="success">
|
|
54
54
|
{asset.split('/').pop()}
|
|
55
|
-
</
|
|
55
|
+
</Badge>
|
|
56
56
|
))}
|
|
57
57
|
</div>
|
|
58
58
|
</div>
|
|
59
59
|
)}
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<
|
|
64
|
-
|
|
61
|
+
<div className="mb-8">
|
|
62
|
+
<h2 className="mb-3 text-lg font-semibold text-foreground">Instructions</h2>
|
|
63
|
+
<pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">
|
|
64
|
+
{template.instructions}
|
|
65
|
+
</pre>
|
|
65
66
|
</div>
|
|
66
67
|
</DetailPage>
|
|
67
68
|
);
|