@vertesia/tools-admin-ui 1.0.0-dev.20260227.112605Z → 1.0.0-dev.20260331.091034Z
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/ActivityCollection.d.ts +2 -0
- package/lib/pages/ActivityCollection.d.ts.map +1 -0
- 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 +481 -379
- package/lib/tools-admin-ui.js.map +1 -1
- package/lib/types.d.ts +8 -3
- package/lib/types.d.ts.map +1 -1
- package/package.json +8 -4
- package/src/AdminApp.tsx +23 -17
- package/src/components/AdminTopBar.tsx +39 -0
- package/src/components/CollectionCard.tsx +20 -14
- package/src/components/DetailPage.tsx +20 -11
- package/src/components/EndpointPanel.tsx +16 -7
- package/src/components/HeroSection.tsx +52 -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 +19 -0
- package/src/dev/env.ts +3 -3
- package/src/dev/index.css +13 -0
- package/src/dev/main.tsx +11 -5
- package/src/hooks.ts +5 -3
- package/src/pages/ActivityCollection.tsx +67 -0
- package/src/pages/HomePage.tsx +19 -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/types.ts +34 -1
- package/src/admin.css +0 -650
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { RemoteActivityDefinition } from '@vertesia/common';
|
|
2
|
+
import { Card, CardContent, Spinner, useFetch } from '@vertesia/ui/core';
|
|
3
|
+
import { useParams } from '@vertesia/ui/router';
|
|
4
|
+
|
|
5
|
+
import { useAdminContext } from '../AdminContext.js';
|
|
6
|
+
import { DetailPage } from '../components/DetailPage.js';
|
|
7
|
+
import { TYPE_VARIANTS } from '../components/typeVariants.js';
|
|
8
|
+
|
|
9
|
+
interface ActivityCollectionResponse {
|
|
10
|
+
title: string;
|
|
11
|
+
description: string;
|
|
12
|
+
activities: RemoteActivityDefinition[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function ActivityCollection() {
|
|
16
|
+
const collection = useParams('collection');
|
|
17
|
+
const { baseUrl } = useAdminContext();
|
|
18
|
+
|
|
19
|
+
const { data, error } = useFetch<ActivityCollectionResponse>(
|
|
20
|
+
() => fetch(`${baseUrl}/activities/${collection}`).then(r => {
|
|
21
|
+
if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
|
|
22
|
+
return r.json();
|
|
23
|
+
}),
|
|
24
|
+
[baseUrl, collection]
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
if (error) return <div className="p-6 text-destructive">Failed to load activity collection “{collection}”.</div>;
|
|
28
|
+
if (!data) return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<DetailPage
|
|
32
|
+
type="activity"
|
|
33
|
+
title={data.title || collection}
|
|
34
|
+
description={data.description || `${data.activities.length} activit${data.activities.length !== 1 ? 'ies' : 'y'} in this collection.`}
|
|
35
|
+
>
|
|
36
|
+
{data.activities.map(activity => (
|
|
37
|
+
<Card key={activity.name} className="mb-4">
|
|
38
|
+
<CardContent className="p-5">
|
|
39
|
+
<div className="mb-2 flex items-center gap-2">
|
|
40
|
+
<span className={`inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.activity}`}>
|
|
41
|
+
activity
|
|
42
|
+
</span>
|
|
43
|
+
<span className="font-semibold text-card-foreground">{activity.name}</span>
|
|
44
|
+
</div>
|
|
45
|
+
<div className="text-sm text-muted-foreground">{activity.description || 'No description'}</div>
|
|
46
|
+
{activity.input_schema && (
|
|
47
|
+
<div className="mt-3">
|
|
48
|
+
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">Input Schema</p>
|
|
49
|
+
<pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">
|
|
50
|
+
{JSON.stringify(activity.input_schema, null, 2)}
|
|
51
|
+
</pre>
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
{activity.output_schema && (
|
|
55
|
+
<div className="mt-3">
|
|
56
|
+
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">Output Schema</p>
|
|
57
|
+
<pre className="whitespace-pre-wrap wrap-break-word rounded-lg border border-border bg-muted-background p-4 font-mono text-sm text-foreground">
|
|
58
|
+
{JSON.stringify(activity.output_schema, null, 2)}
|
|
59
|
+
</pre>
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
</CardContent>
|
|
63
|
+
</Card>
|
|
64
|
+
))}
|
|
65
|
+
</DetailPage>
|
|
66
|
+
);
|
|
67
|
+
}
|
package/src/pages/HomePage.tsx
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import { Separator } from '@vertesia/ui/core';
|
|
1
2
|
import { useMemo, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { useAdminContext } from '../AdminContext.js';
|
|
5
|
+
import { CollectionCard, HeroSection, ResourceSection, SearchBar } from '../components/index.js';
|
|
6
|
+
import { TYPE_VARIANTS } from '../components/typeVariants.js';
|
|
2
7
|
import type { ResourceType } from '../types.js';
|
|
3
8
|
import { filterResources } from '../types.js';
|
|
4
|
-
import { HeroSection, SearchBar, ResourceSection, CollectionCard } from '../components/index.js';
|
|
5
|
-
import { useAdminContext } from '../AdminContext.js';
|
|
6
9
|
|
|
7
10
|
const sections: { type: ResourceType; title: string; subtitle: string }[] = [
|
|
8
11
|
{ type: 'tool', title: 'Tools', subtitle: 'Remote tools available to agents via Vertesia.' },
|
|
12
|
+
{ type: 'activity', title: 'Activities', subtitle: 'Remote activities for DSL workflows, invoked via HTTP.' },
|
|
9
13
|
{ type: 'skill', title: 'Skills', subtitle: 'Reusable instructions and scripts packaged as tools.' },
|
|
10
14
|
{ type: 'interaction', title: 'Interactions', subtitle: 'Conversation blueprints surfaced in the Vertesia UI.' },
|
|
11
15
|
{ type: 'type', title: 'Content Types', subtitle: 'Schema definitions for structured content in the data store.' },
|
|
@@ -25,7 +29,7 @@ export function HomePage() {
|
|
|
25
29
|
const isSearching = search.trim().length > 0;
|
|
26
30
|
|
|
27
31
|
return (
|
|
28
|
-
<div className="
|
|
32
|
+
<div className="mx-auto max-w-5xl px-7 py-10">
|
|
29
33
|
<HeroSection
|
|
30
34
|
title={serverInfo.message.replace('Vertesia Tools API', 'Tools Server')}
|
|
31
35
|
version={serverInfo.version}
|
|
@@ -41,7 +45,6 @@ export function HomePage() {
|
|
|
41
45
|
/>
|
|
42
46
|
|
|
43
47
|
{isSearching ? (
|
|
44
|
-
/* Search mode: show individual resource cards */
|
|
45
48
|
sections.map((section, i) => {
|
|
46
49
|
const sectionItems = filtered.filter(r => r.type === section.type);
|
|
47
50
|
return (
|
|
@@ -55,7 +58,6 @@ export function HomePage() {
|
|
|
55
58
|
);
|
|
56
59
|
})
|
|
57
60
|
) : (
|
|
58
|
-
/* Browse mode: show collection cards grouped by type */
|
|
59
61
|
sections.map((section, i) => {
|
|
60
62
|
const sectionCollections = collections.filter(c => c.type === section.type);
|
|
61
63
|
const mcpResources = section.type === 'mcp'
|
|
@@ -66,27 +68,29 @@ export function HomePage() {
|
|
|
66
68
|
|
|
67
69
|
return (
|
|
68
70
|
<section key={section.type}>
|
|
69
|
-
{i > 0 && <
|
|
71
|
+
{i > 0 && <Separator className="my-8" />}
|
|
70
72
|
<div>
|
|
71
|
-
<h2 className="
|
|
73
|
+
<h2 className="text-xl font-semibold text-foreground">
|
|
72
74
|
{section.title}
|
|
73
|
-
<span className="
|
|
75
|
+
<span className="ml-2 text-sm font-normal text-muted-foreground">
|
|
74
76
|
({sectionCollections.length}{sectionCollections.length === 1
|
|
75
77
|
? ' collection' : ' collections'})
|
|
76
78
|
</span>
|
|
77
79
|
</h2>
|
|
78
|
-
<p className="
|
|
80
|
+
<p className="mb-4 text-sm text-muted-foreground">{section.subtitle}</p>
|
|
79
81
|
</div>
|
|
80
|
-
<div className="
|
|
82
|
+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
81
83
|
{sectionCollections.map(col => (
|
|
82
84
|
<CollectionCard key={`${col.type}:${col.name}`} collection={col} />
|
|
83
85
|
))}
|
|
84
86
|
{mcpResources.map(r => (
|
|
85
|
-
<div key={r.name} className="
|
|
86
|
-
<span className=
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
<div key={r.name} className="rounded-xl border border-border bg-card p-5 shadow-sm">
|
|
88
|
+
<span className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.mcp}`}>
|
|
89
|
+
mcp
|
|
90
|
+
</span>
|
|
91
|
+
<div className="font-semibold text-card-foreground">{r.title}</div>
|
|
92
|
+
<div className="mt-1 text-sm text-muted-foreground">{r.description || 'No description'}</div>
|
|
93
|
+
{r.url && <div className="mt-2 truncate font-mono text-xs text-muted-foreground">{r.url}</div>}
|
|
90
94
|
</div>
|
|
91
95
|
))}
|
|
92
96
|
</div>
|
|
@@ -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>
|