@vertesia/tools-admin-ui 1.3.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.
Files changed (77) hide show
  1. package/LICENSE +198 -10
  2. package/lib/AdminApp.d.ts +1 -1
  3. package/lib/AdminApp.d.ts.map +1 -1
  4. package/lib/components/AdminTopBar.d.ts +1 -1
  5. package/lib/components/AdminTopBar.d.ts.map +1 -1
  6. package/lib/components/CollectionCard.d.ts +1 -1
  7. package/lib/components/CollectionCard.d.ts.map +1 -1
  8. package/lib/components/DetailPage.d.ts +1 -1
  9. package/lib/components/DetailPage.d.ts.map +1 -1
  10. package/lib/components/EndpointPanel.d.ts +1 -1
  11. package/lib/components/EndpointPanel.d.ts.map +1 -1
  12. package/lib/components/HeroSection.d.ts +1 -1
  13. package/lib/components/HeroSection.d.ts.map +1 -1
  14. package/lib/components/ResourceCard.d.ts +1 -1
  15. package/lib/components/ResourceCard.d.ts.map +1 -1
  16. package/lib/components/ResourceSection.d.ts +1 -1
  17. package/lib/components/ResourceSection.d.ts.map +1 -1
  18. package/lib/components/SearchBar.d.ts +1 -1
  19. package/lib/components/SearchBar.d.ts.map +1 -1
  20. package/lib/components/SummaryBadge.d.ts +1 -1
  21. package/lib/components/SummaryBadge.d.ts.map +1 -1
  22. package/lib/hooks.d.ts +2 -2
  23. package/lib/hooks.d.ts.map +1 -1
  24. package/lib/index.d.ts +4 -4
  25. package/lib/index.d.ts.map +1 -1
  26. package/lib/pages/ActivityCollection.d.ts +1 -1
  27. package/lib/pages/ActivityCollection.d.ts.map +1 -1
  28. package/lib/pages/HomePage.d.ts +1 -1
  29. package/lib/pages/HomePage.d.ts.map +1 -1
  30. package/lib/pages/InteractionCollection.d.ts +1 -1
  31. package/lib/pages/InteractionCollection.d.ts.map +1 -1
  32. package/lib/pages/InteractionDetail.d.ts +1 -1
  33. package/lib/pages/InteractionDetail.d.ts.map +1 -1
  34. package/lib/pages/SkillCollection.d.ts +1 -1
  35. package/lib/pages/SkillCollection.d.ts.map +1 -1
  36. package/lib/pages/SkillDetail.d.ts +1 -1
  37. package/lib/pages/SkillDetail.d.ts.map +1 -1
  38. package/lib/pages/TemplateCollection.d.ts +1 -1
  39. package/lib/pages/TemplateCollection.d.ts.map +1 -1
  40. package/lib/pages/TemplateDetail.d.ts +1 -1
  41. package/lib/pages/TemplateDetail.d.ts.map +1 -1
  42. package/lib/pages/ToolCollection.d.ts +1 -1
  43. package/lib/pages/ToolCollection.d.ts.map +1 -1
  44. package/lib/pages/TypeCollection.d.ts +1 -1
  45. package/lib/pages/TypeCollection.d.ts.map +1 -1
  46. package/lib/pages/TypeDetail.d.ts +1 -1
  47. package/lib/pages/TypeDetail.d.ts.map +1 -1
  48. package/lib/tools-admin-ui.js +1484 -969
  49. package/lib/tools-admin-ui.js.map +1 -1
  50. package/lib/types.d.ts.map +1 -1
  51. package/package.json +16 -14
  52. package/src/AdminApp.tsx +14 -15
  53. package/src/components/AdminTopBar.tsx +7 -7
  54. package/src/components/CollectionCard.tsx +3 -1
  55. package/src/components/DetailPage.tsx +10 -4
  56. package/src/components/EndpointPanel.tsx +2 -9
  57. package/src/components/HeroSection.tsx +12 -3
  58. package/src/components/ResourceCard.tsx +8 -6
  59. package/src/components/ResourceSection.tsx +1 -1
  60. package/src/components/SearchBar.tsx +1 -5
  61. package/src/components/SummaryBadge.tsx +2 -1
  62. package/src/dev/env.ts +8 -8
  63. package/src/dev/main.tsx +20 -15
  64. package/src/hooks.ts +19 -12
  65. package/src/index.ts +4 -4
  66. package/src/pages/ActivityCollection.tsx +30 -12
  67. package/src/pages/HomePage.tsx +65 -57
  68. package/src/pages/InteractionCollection.tsx +26 -11
  69. package/src/pages/InteractionDetail.tsx +28 -13
  70. package/src/pages/SkillCollection.tsx +38 -18
  71. package/src/pages/SkillDetail.tsx +37 -13
  72. package/src/pages/TemplateCollection.tsx +28 -17
  73. package/src/pages/TemplateDetail.tsx +13 -7
  74. package/src/pages/ToolCollection.tsx +21 -10
  75. package/src/pages/TypeCollection.tsx +26 -17
  76. package/src/pages/TypeDetail.tsx +12 -6
  77. package/src/types.ts +16 -35
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertesia/tools-admin-ui",
3
- "version": "1.3.0",
3
+ "version": "1.4.0-dev.20260615.042549Z",
4
4
  "description": "Shared admin UI for Vertesia tool servers",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -16,24 +16,25 @@
16
16
  "./theme.css": "./src/theme.css"
17
17
  },
18
18
  "peerDependencies": {
19
- "react": "^19.0.0",
20
- "react-dom": "^19.0.0"
19
+ "react": "^19.2.7",
20
+ "react-dom": "^19.2.7"
21
21
  },
22
22
  "devDependencies": {
23
- "@tailwindcss/vite": "^4.1.17",
24
- "@types/react": "^19.2.3",
23
+ "@tailwindcss/vite": "^4.3.0",
24
+ "@types/react": "^19.2.16",
25
25
  "@types/react-dom": "^19.2.3",
26
- "@vitejs/plugin-react": "^5.1.2",
27
- "react": "^19.2.3",
28
- "react-dom": "^19.2.3",
29
- "tailwindcss": "^4.1.17",
30
- "typescript": "^6.0.2",
31
- "vite": "^7.3.0"
26
+ "@vitejs/plugin-react": "^6.0.2",
27
+ "react": "^19.2.7",
28
+ "react-dom": "^19.2.7",
29
+ "tailwindcss": "^4.3.0",
30
+ "typescript": "^6.0.3",
31
+ "vite": "^8.0.16",
32
+ "@vertesia/tsconfig": "0.1.0"
32
33
  },
33
34
  "dependencies": {
34
- "lucide-react": "^0.562.0",
35
- "@vertesia/common": "1.3.0",
36
- "@vertesia/ui": "1.3.0"
35
+ "lucide-react": "^1.17.0",
36
+ "@vertesia/common": "1.4.0-dev.20260615.042549Z",
37
+ "@vertesia/ui": "1.4.0-dev.20260615.042549Z"
37
38
  },
38
39
  "repository": {
39
40
  "type": "git",
@@ -48,6 +49,7 @@
48
49
  "react"
49
50
  ],
50
51
  "scripts": {
52
+ "lint": "biome lint src",
51
53
  "build": "rm -rf lib tsconfig.tsbuildinfo && tsc --build && vite build --mode lib",
52
54
  "dev": "vite dev"
53
55
  }
package/src/AdminApp.tsx CHANGED
@@ -47,10 +47,11 @@ export interface AdminAppProps {
47
47
  */
48
48
  export function AdminApp({ baseUrl = '/api' }: AdminAppProps) {
49
49
  const { data: serverInfo, isLoading: loadingInfo, error: infoError } = useServerInfo(baseUrl);
50
- const { data: resourceData, isLoading: loadingData, error: dataError } = useResourceData(
51
- baseUrl,
52
- serverInfo?.endpoints.mcp,
53
- );
50
+ const {
51
+ data: resourceData,
52
+ isLoading: loadingData,
53
+ error: dataError,
54
+ } = useResourceData(baseUrl, serverInfo?.endpoints?.mcp);
54
55
 
55
56
  const isLoading = loadingInfo || loadingData;
56
57
  const error = infoError || dataError;
@@ -64,11 +65,7 @@ export function AdminApp({ baseUrl = '/api' }: AdminAppProps) {
64
65
  }
65
66
 
66
67
  if (error) {
67
- return (
68
- <div className="p-6 text-destructive">
69
- Failed to load server info. Is the API running?
70
- </div>
71
- );
68
+ return <div className="p-6 text-destructive">Failed to load server info. Is the API running?</div>;
72
69
  }
73
70
 
74
71
  if (!serverInfo || !resourceData) return null;
@@ -78,12 +75,14 @@ export function AdminApp({ baseUrl = '/api' }: AdminAppProps) {
78
75
  return (
79
76
  <div className="min-h-screen bg-background text-foreground">
80
77
  <AdminTopBar title={title} />
81
- <AdminContext.Provider value={{
82
- serverInfo,
83
- collections: resourceData.collections,
84
- resources: resourceData.resources,
85
- baseUrl,
86
- }}>
78
+ <AdminContext.Provider
79
+ value={{
80
+ serverInfo,
81
+ collections: resourceData.collections,
82
+ resources: resourceData.resources,
83
+ baseUrl,
84
+ }}
85
+ >
87
86
  <NestedRouterProvider routes={routes}>
88
87
  <RouteComponent />
89
88
  </NestedRouterProvider>
@@ -13,7 +13,12 @@ export function AdminTopBar({ title }: AdminTopBarProps) {
13
13
  <header className="sticky top-0 z-40 flex h-14 items-center justify-between border-b border-border bg-background px-6">
14
14
  <div className="flex items-center gap-3">
15
15
  <div className="flex size-8 items-center justify-center rounded-lg bg-primary text-xs font-semibold uppercase tracking-wider text-primary-foreground">
16
- {title.split(/\s+/).map(w => w[0]).filter(Boolean).slice(0, 2).join('')}
16
+ {title
17
+ .split(/\s+/)
18
+ .map((w) => w[0])
19
+ .filter(Boolean)
20
+ .slice(0, 2)
21
+ .join('')}
17
22
  </div>
18
23
  <span className="text-sm font-semibold text-foreground">{title}</span>
19
24
  </div>
@@ -23,12 +28,7 @@ export function AdminTopBar({ title }: AdminTopBarProps) {
23
28
  {user && (
24
29
  <>
25
30
  <Avatar size="sm" name={user.name} color="bg-primary" />
26
- <Button
27
- variant="outline"
28
- size="sm"
29
- onClick={() => logout()}
30
- alt="Sign out"
31
- >
31
+ <Button variant="outline" size="sm" onClick={() => logout()} alt="Sign out">
32
32
  <LogOut className="size-4" />
33
33
  </Button>
34
34
  </>
@@ -12,7 +12,9 @@ export function CollectionCard({ collection }: { collection: CollectionInfo }) {
12
12
  <NavLink href={href} className="block no-underline">
13
13
  <Card className="cursor-pointer transition-all hover:-translate-y-0.5 hover:shadow-md">
14
14
  <CardContent className="p-5">
15
- <span className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS[collection.type] ?? ''}`}>
15
+ <span
16
+ className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS[collection.type] ?? ''}`}
17
+ >
16
18
  {collection.type}
17
19
  </span>
18
20
  <div className="font-semibold text-card-foreground">{collection.title}</div>
@@ -20,7 +20,9 @@ export function DetailPage({ type, title, description, tags, backHref = '/', chi
20
20
  <div className="mx-auto max-w-5xl px-7 py-10">
21
21
  <nav className="mb-5 flex items-center gap-4">
22
22
  {backHref !== '/' && (
23
- <NavLink href="/" className="text-sm text-primary hover:opacity-75">Home</NavLink>
23
+ <NavLink href="/" className="text-sm text-primary hover:opacity-75">
24
+ Home
25
+ </NavLink>
24
26
  )}
25
27
  <NavLink href={backHref} className="flex items-center gap-1 text-sm text-primary hover:opacity-75">
26
28
  <ArrowLeft className="size-3.5" />
@@ -29,15 +31,19 @@ export function DetailPage({ type, title, description, tags, backHref = '/', chi
29
31
  </nav>
30
32
 
31
33
  <div className="mb-8">
32
- <span className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS[type] ?? ''}`}>
34
+ <span
35
+ className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS[type] ?? ''}`}
36
+ >
33
37
  {type}
34
38
  </span>
35
39
  <h1 className="-tracking-wide text-3xl font-bold text-foreground">{title}</h1>
36
40
  {description && <p className="mt-1 text-sm leading-relaxed text-muted-foreground">{description}</p>}
37
41
  {tags && tags.length > 0 && (
38
42
  <div className="mt-3 flex flex-wrap gap-1.5">
39
- {tags.map(tag => (
40
- <Badge key={tag} variant="default">{tag}</Badge>
43
+ {tags.map((tag) => (
44
+ <Badge key={tag} variant="default">
45
+ {tag}
46
+ </Badge>
41
47
  ))}
42
48
  </div>
43
49
  )}
@@ -14,17 +14,10 @@ export function EndpointPanel({ label, path }: { label: string; path: string })
14
14
 
15
15
  return (
16
16
  <div className="mb-3">
17
- <div className="mb-1 text-[0.7rem] font-medium uppercase tracking-widest text-primary">
18
- {label}
19
- </div>
17
+ <div className="mb-1 text-[0.7rem] font-medium uppercase tracking-widest text-primary">{label}</div>
20
18
  <div className="flex items-center gap-2 rounded-lg border border-border bg-muted-background px-3 py-2">
21
19
  <code className="flex-1 font-mono text-sm text-foreground">{path}</code>
22
- <Button
23
- variant="ghost"
24
- size="xs"
25
- onClick={handleCopy}
26
- aria-label="Copy full URL"
27
- >
20
+ <Button variant="ghost" size="xs" onClick={handleCopy} aria-label="Copy full URL">
28
21
  {copied ? <Check className="size-3.5" /> : <Copy className="size-3.5" />}
29
22
  </Button>
30
23
  </div>
@@ -12,7 +12,13 @@ interface HeroSectionProps {
12
12
  }
13
13
 
14
14
  function getInitials(title: string): string {
15
- return title.split(/\s+/).map(w => w[0]).filter(Boolean).slice(0, 2).join('').toUpperCase();
15
+ return title
16
+ .split(/\s+/)
17
+ .map((w) => w[0])
18
+ .filter(Boolean)
19
+ .slice(0, 2)
20
+ .join('')
21
+ .toUpperCase();
16
22
  }
17
23
 
18
24
  function countByType(resources: ResourceItem[]): Record<string, number> {
@@ -45,7 +51,9 @@ export function HeroSection({ title, version, resources }: HeroSectionProps) {
45
51
  {getInitials(title)}
46
52
  </div>
47
53
  <div>
48
- <p className="text-xs font-medium uppercase tracking-widest text-muted-foreground">Tools Server</p>
54
+ <p className="text-xs font-medium uppercase tracking-widest text-muted-foreground">
55
+ Tools Server
56
+ </p>
49
57
  <h1 className="-tracking-wide text-2xl font-bold text-foreground">{title}</h1>
50
58
  </div>
51
59
  </div>
@@ -85,7 +93,8 @@ export function HeroSection({ title, version, resources }: HeroSectionProps) {
85
93
  <EndpointPanel label="Package endpoint" path="/api/package" />
86
94
  <p className="mt-2 text-xs leading-relaxed text-muted-foreground">
87
95
  Use <strong className="text-foreground">POST /api/tools/&lt;collection&gt;</strong> or{' '}
88
- <strong className="text-foreground">POST /api/skills/&lt;collection&gt;</strong> to call these from your apps or agents.
96
+ <strong className="text-foreground">POST /api/skills/&lt;collection&gt;</strong> to call these
97
+ from your apps or agents.
89
98
  </p>
90
99
  <p className="mt-1 text-xs text-muted-foreground">v{version}</p>
91
100
  </aside>
@@ -7,17 +7,19 @@ export function ResourceCard({ resource }: { resource: ResourceItem }) {
7
7
  return (
8
8
  <Card className="transition-all hover:-translate-y-0.5 hover:shadow-md">
9
9
  <CardContent className="p-5">
10
- <span className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS[resource.type] ?? ''}`}>
10
+ <span
11
+ className={`mb-2 inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS[resource.type] ?? ''}`}
12
+ >
11
13
  {resource.type}
12
14
  </span>
13
15
  <div className="font-semibold text-card-foreground">{resource.title}</div>
14
- <div className="mt-1 text-sm text-muted-foreground">
15
- {resource.description || 'No description'}
16
- </div>
16
+ <div className="mt-1 text-sm text-muted-foreground">{resource.description || 'No description'}</div>
17
17
  {resource.tags && resource.tags.length > 0 && (
18
18
  <div className="mt-2 flex flex-wrap gap-1">
19
- {resource.tags.map(tag => (
20
- <Badge key={tag} variant="default">{tag}</Badge>
19
+ {resource.tags.map((tag) => (
20
+ <Badge key={tag} variant="default">
21
+ {tag}
22
+ </Badge>
21
23
  ))}
22
24
  </div>
23
25
  )}
@@ -24,7 +24,7 @@ export function ResourceSection({ title, subtitle, resources, showDivider }: Res
24
24
  <p className="mb-4 text-sm text-muted-foreground">{subtitle}</p>
25
25
  </div>
26
26
  <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
27
- {resources.map(r => (
27
+ {resources.map((r) => (
28
28
  <ResourceCard key={`${r.type}:${r.name}`} resource={r} />
29
29
  ))}
30
30
  </div>
@@ -27,11 +27,7 @@ export function SearchBar({ value, onChange, placeholder, resultCount, totalCoun
27
27
  Showing {resultCount} of {totalCount} resources
28
28
  </p>
29
29
  )}
30
- {noResults && (
31
- <p className="mt-2 text-sm text-destructive">
32
- No resources match this search.
33
- </p>
34
- )}
30
+ {noResults && <p className="mt-2 text-sm text-destructive">No resources match this search.</p>}
35
31
  </div>
36
32
  );
37
33
  }
@@ -4,7 +4,8 @@ export function SummaryBadge({ count, label }: { count: number; label: string })
4
4
  if (count === 0) return null;
5
5
  return (
6
6
  <DotBadge variant="success">
7
- {count} {label}{count !== 1 ? 's' : ''}
7
+ {count} {label}
8
+ {count !== 1 ? 's' : ''}
8
9
  </DotBadge>
9
10
  );
10
11
  }
package/src/dev/env.ts CHANGED
@@ -1,14 +1,14 @@
1
- import { Env } from "@vertesia/ui/env";
1
+ import { Env } from '@vertesia/ui/env';
2
2
 
3
3
  Env.init({
4
- name: "Tools Admin UI",
5
- version: "1.0.0",
4
+ name: 'Tools Admin UI',
5
+ version: '1.0.0',
6
6
  isLocalDev: true,
7
7
  isDocker: true,
8
- type: "development",
8
+ type: 'development',
9
9
  endpoints: {
10
- studio: "https://api.us1.vertesia.io",
11
- zeno: "https://api.us1.vertesia.io",
12
- sts: "https://sts.us1.vertesia.io",
13
- }
10
+ studio: 'https://api.us1.vertesia.io',
11
+ zeno: 'https://api.us1.vertesia.io',
12
+ sts: 'https://sts.us1.vertesia.io',
13
+ },
14
14
  });
package/src/dev/main.tsx CHANGED
@@ -1,10 +1,9 @@
1
1
  /// <reference types="vite/client" />
2
2
  import './index.css';
3
- import "./env.js"
3
+ import './env.js';
4
4
 
5
- import { I18nProvider } from '@vertesia/ui/i18n';
6
- import { VertesiaShell } from '@vertesia/ui/shell';
7
5
  import { RouterProvider } from '@vertesia/ui/router';
6
+ import { VertesiaShell } from '@vertesia/ui/shell';
8
7
  import { StrictMode } from 'react';
9
8
  import { createRoot } from 'react-dom/client';
10
9
 
@@ -12,6 +11,7 @@ import { AdminApp } from '../AdminApp.js';
12
11
 
13
12
  const baseUrl = import.meta.env.VITE_API_BASE_URL;
14
13
 
14
+ // biome-ignore lint/style/noNonNullAssertion: intentional non-null assertion; TS can't prove narrowing here
15
15
  const root = createRoot(document.getElementById('root')!);
16
16
 
17
17
  /**
@@ -22,22 +22,27 @@ const devRoutes = [{ path: '/*', Component: () => <AdminApp baseUrl={baseUrl} />
22
22
 
23
23
  if (!baseUrl) {
24
24
  root.render(
25
- <div style={{ padding: '2rem', fontFamily: 'system-ui, sans-serif', color: '#ef4444' }}>
26
- <h2>Missing environment variable</h2>
27
- <p><code>VITE_API_BASE_URL</code> is not defined.</p>
28
- <p>Create a <code>.env.local</code> file in this package with:</p>
29
- <pre style={{ background: '#f3f4f6', padding: '1rem', borderRadius: '8px', color: '#111827' }}>
30
- VITE_API_BASE_URL=http://localhost:3000/api</pre>
31
- </div>,
25
+ <StrictMode>
26
+ <div style={{ padding: '2rem', fontFamily: 'system-ui, sans-serif', color: '#ef4444' }}>
27
+ <h2>Missing environment variable</h2>
28
+ <p>
29
+ <code>VITE_API_BASE_URL</code> is not defined.
30
+ </p>
31
+ <p>
32
+ Create a <code>.env.local</code> file in this package with:
33
+ </p>
34
+ <pre style={{ background: '#f3f4f6', padding: '1rem', borderRadius: '8px', color: '#111827' }}>
35
+ VITE_API_BASE_URL=http://localhost:3000/api
36
+ </pre>
37
+ </div>
38
+ </StrictMode>,
32
39
  );
33
40
  } else {
34
41
  root.render(
35
42
  <StrictMode>
36
- <I18nProvider lng="en">
37
- <VertesiaShell>
38
- <RouterProvider routes={devRoutes} />
39
- </VertesiaShell>
40
- </I18nProvider>
43
+ <VertesiaShell>
44
+ <RouterProvider routes={devRoutes} />
45
+ </VertesiaShell>
41
46
  </StrictMode>,
42
47
  );
43
48
  }
package/src/hooks.ts CHANGED
@@ -7,14 +7,21 @@ import { useFetch } from '@vertesia/ui/core';
7
7
  import type { ResourceData, ServerInfo } from './types.js';
8
8
  import { buildResourceData } from './types.js';
9
9
 
10
+ type ResourceDataArgs = Parameters<typeof buildResourceData>;
11
+
12
+ async function fetchJson<T>(url: string): Promise<T> {
13
+ const response = await fetch(url);
14
+ if (!response.ok) {
15
+ throw new Error(`Request failed: ${response.status} ${response.statusText}`);
16
+ }
17
+ return response.json() as Promise<T>;
18
+ }
19
+
10
20
  /**
11
21
  * Fetches the tool server info (message, version, endpoints).
12
22
  */
13
23
  export function useServerInfo(baseUrl: string) {
14
- return useFetch<ServerInfo>(() =>
15
- fetch(baseUrl).then(r => r.json()),
16
- [baseUrl]
17
- );
24
+ return useFetch<ServerInfo>(() => fetchJson<ServerInfo>(baseUrl), [baseUrl]);
18
25
  }
19
26
 
20
27
  /**
@@ -23,16 +30,16 @@ export function useServerInfo(baseUrl: string) {
23
30
  */
24
31
  export function useResourceData(baseUrl: string, mcpEndpoints?: string[]) {
25
32
  return useFetch<ResourceData>(() => {
26
- const fetchJson = (path: string) => fetch(`${baseUrl}/${path}`).then(r => r.json());
33
+ const fetchResource = <T>(path: string) => fetchJson<T>(`${baseUrl}/${path}`);
27
34
  return Promise.all([
28
- fetchJson('interactions'),
29
- fetchJson('tools'),
30
- fetchJson('skills'),
31
- fetchJson('activities'),
32
- fetchJson('types'),
33
- fetchJson('templates'),
35
+ fetchResource<ResourceDataArgs[0]>('interactions'),
36
+ fetchResource<ResourceDataArgs[1]>('tools'),
37
+ fetchResource<ResourceDataArgs[2]>('skills'),
38
+ fetchResource<ResourceDataArgs[3]>('activities'),
39
+ fetchResource<ResourceDataArgs[4]>('types'),
40
+ fetchResource<ResourceDataArgs[5]>('templates'),
34
41
  ]).then(([interactions, tools, skills, activities, types, templates]) =>
35
- buildResourceData(interactions, tools, skills, activities, types, templates, mcpEndpoints)
42
+ buildResourceData(interactions, tools, skills, activities, types, templates, mcpEndpoints),
36
43
  );
37
44
  }, [baseUrl, mcpEndpoints]);
38
45
  }
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { AdminApp } from './AdminApp.js';
2
1
  export type { AdminAppProps } from './AdminApp.js';
3
- export type { ServerInfo, ResourceType, ResourceItem, CollectionInfo } from './types.js';
4
- export { buildResourceData, filterResources } from './types.js';
5
- export { AdminContext, useAdminContext } from './AdminContext.js';
2
+ export { AdminApp } from './AdminApp.js';
6
3
  export type { AdminContextValue } from './AdminContext.js';
4
+ export { AdminContext, useAdminContext } from './AdminContext.js';
5
+ export type { CollectionInfo, ResourceItem, ResourceType, ServerInfo } from './types.js';
6
+ export { buildResourceData, filterResources } from './types.js';
@@ -17,27 +17,41 @@ export function ActivityCollection() {
17
17
  const { baseUrl } = useAdminContext();
18
18
 
19
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]
20
+ () =>
21
+ fetch(`${baseUrl}/activities/${collection}`).then((r) => {
22
+ if (!r.ok) throw new Error(`Failed to load collection: ${r.statusText}`);
23
+ return r.json();
24
+ }),
25
+ [baseUrl, collection],
25
26
  );
26
27
 
27
- if (error) return <div className="p-6 text-destructive">Failed to load activity collection &ldquo;{collection}&rdquo;.</div>;
28
- if (!data) return <div className="flex h-64 items-center justify-center text-muted-foreground"><Spinner /></div>;
28
+ if (error)
29
+ return (
30
+ <div className="p-6 text-destructive">Failed to load activity collection &ldquo;{collection}&rdquo;.</div>
31
+ );
32
+ if (!data)
33
+ return (
34
+ <div className="flex h-64 items-center justify-center text-muted-foreground">
35
+ <Spinner />
36
+ </div>
37
+ );
29
38
 
30
39
  return (
31
40
  <DetailPage
32
41
  type="activity"
33
42
  title={data.title || collection}
34
- description={data.description || `${data.activities.length} activit${data.activities.length !== 1 ? 'ies' : 'y'} in this collection.`}
43
+ description={
44
+ data.description ||
45
+ `${data.activities.length} activit${data.activities.length !== 1 ? 'ies' : 'y'} in this collection.`
46
+ }
35
47
  >
36
- {data.activities.map(activity => (
48
+ {data.activities.map((activity) => (
37
49
  <Card key={activity.name} className="mb-4">
38
50
  <CardContent className="p-5">
39
51
  <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}`}>
52
+ <span
53
+ className={`inline-block rounded-full px-2 py-0.5 text-[0.7rem] font-semibold uppercase tracking-wide ${TYPE_VARIANTS.activity}`}
54
+ >
41
55
  activity
42
56
  </span>
43
57
  <span className="font-semibold text-card-foreground">{activity.name}</span>
@@ -45,7 +59,9 @@ export function ActivityCollection() {
45
59
  <div className="text-sm text-muted-foreground">{activity.description || 'No description'}</div>
46
60
  {activity.input_schema && (
47
61
  <div className="mt-3">
48
- <p className="mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">Input Schema</p>
62
+ <p className="mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
63
+ Input Schema
64
+ </p>
49
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">
50
66
  {JSON.stringify(activity.input_schema, null, 2)}
51
67
  </pre>
@@ -53,7 +69,9 @@ export function ActivityCollection() {
53
69
  )}
54
70
  {activity.output_schema && (
55
71
  <div className="mt-3">
56
- <p className="mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">Output Schema</p>
72
+ <p className="mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
73
+ Output Schema
74
+ </p>
57
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">
58
76
  {JSON.stringify(activity.output_schema, null, 2)}
59
77
  </pre>