@greatapps/greatagents-ui 0.3.3 → 0.3.4

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.
@@ -0,0 +1,50 @@
1
+ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
2
+ import type { GagentsHookConfig } from "./types";
3
+ import { useGagentsClient } from "./types";
4
+ import type { CapabilitiesResponse, AgentCapability, AgentCapabilitiesPayload } from "../types/capabilities";
5
+
6
+ export function useCapabilities(config: GagentsHookConfig) {
7
+ const client = useGagentsClient(config);
8
+
9
+ return useQuery({
10
+ queryKey: ["greatagents", "capabilities", config.accountId],
11
+ queryFn: async (): Promise<CapabilitiesResponse> => {
12
+ const res = await client.getCapabilities(config.accountId);
13
+ return (res.data as unknown as CapabilitiesResponse) ?? { product: null, categories: [] };
14
+ },
15
+ enabled: !!config.token && !!config.accountId,
16
+ });
17
+ }
18
+
19
+ export function useAgentCapabilities(config: GagentsHookConfig, agentId: number | null) {
20
+ const client = useGagentsClient(config);
21
+
22
+ return useQuery({
23
+ queryKey: ["greatagents", "agent-capabilities", config.accountId, agentId],
24
+ queryFn: async (): Promise<AgentCapability[]> => {
25
+ const res = await client.getAgentCapabilities(config.accountId, agentId!);
26
+ const d = res.data;
27
+ return (Array.isArray(d) ? d : []) as AgentCapability[];
28
+ },
29
+ enabled: !!config.token && !!config.accountId && !!agentId,
30
+ });
31
+ }
32
+
33
+ export function useUpdateAgentCapabilities(config: GagentsHookConfig) {
34
+ const client = useGagentsClient(config);
35
+ const queryClient = useQueryClient();
36
+
37
+ return useMutation({
38
+ mutationFn: ({ agentId, payload }: { agentId: number; payload: AgentCapabilitiesPayload }) =>
39
+ client.updateAgentCapabilities(config.accountId, agentId, payload),
40
+ onSuccess: (_data, variables) => {
41
+ queryClient.invalidateQueries({
42
+ queryKey: ["greatagents", "agent-capabilities", config.accountId, variables.agentId],
43
+ });
44
+ // Also invalidate agent-tools since capabilities map to tools
45
+ queryClient.invalidateQueries({
46
+ queryKey: ["greatagents", "agent-tools"],
47
+ });
48
+ },
49
+ });
50
+ }
@@ -0,0 +1,114 @@
1
+ import { useMemo } from "react";
2
+ import type { GagentsHookConfig } from "./types";
3
+ import { useToolCredentials } from "./use-settings";
4
+ import { useAgentTools } from "./use-agent-tools";
5
+ import { useTools } from "./use-tools";
6
+ import {
7
+ INTEGRATIONS_REGISTRY,
8
+ type IntegrationDefinition,
9
+ } from "../data/integrations-registry";
10
+ import type { AgentTool, Tool, ToolCredential } from "../types";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Public types
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export type IntegrationCardState =
17
+ | "available"
18
+ | "connected"
19
+ | "expired"
20
+ | "coming_soon";
21
+
22
+ export interface IntegrationCardData {
23
+ /** Static definition from registry */
24
+ definition: IntegrationDefinition;
25
+ /** Resolved visual state */
26
+ state: IntegrationCardState;
27
+ /** Matching credential if one exists */
28
+ credential: ToolCredential | null;
29
+ /** Matching tool record if one exists */
30
+ tool: Tool | null;
31
+ /** How many agents share this credential */
32
+ sharedByAgentsCount: number;
33
+ /** Whether this agent has a linked agent_tool for this integration */
34
+ linkedToAgent: boolean;
35
+ }
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Hook
39
+ // ---------------------------------------------------------------------------
40
+
41
+ /**
42
+ * Cross-references the static integrations registry with live data
43
+ * (tools, tool_credentials, agent_tools) to produce card state for each
44
+ * integration entry.
45
+ */
46
+ export function useIntegrationState(
47
+ config: GagentsHookConfig,
48
+ agentId: number | null,
49
+ ) {
50
+ const { data: credentialsData, isLoading: loadingCredentials } =
51
+ useToolCredentials(config);
52
+ const { data: toolsData, isLoading: loadingTools } = useTools(config);
53
+ const { data: agentToolsData, isLoading: loadingAgentTools } = useAgentTools(
54
+ config,
55
+ agentId ?? 0,
56
+ );
57
+
58
+ const isLoading = loadingCredentials || loadingTools || loadingAgentTools;
59
+
60
+ const cards: IntegrationCardData[] = useMemo(() => {
61
+ const credentials: ToolCredential[] = credentialsData?.data ?? [];
62
+ const tools: Tool[] = toolsData?.data ?? [];
63
+ const agentTools: AgentTool[] = agentToolsData?.data ?? [];
64
+
65
+ return INTEGRATIONS_REGISTRY.map((def) => {
66
+ // coming_soon short-circuit
67
+ if (def.status === "coming_soon") {
68
+ return {
69
+ definition: def,
70
+ state: "coming_soon" as const,
71
+ credential: null,
72
+ tool: null,
73
+ sharedByAgentsCount: 0,
74
+ linkedToAgent: false,
75
+ };
76
+ }
77
+
78
+ // Find tool record matching registry slug
79
+ const matchedTool = tools.find((t) => t.slug === def.slug) ?? null;
80
+
81
+ // Find credential for that tool
82
+ const matchedCredential = matchedTool
83
+ ? credentials.find((c) => c.id_tool === matchedTool.id) ?? null
84
+ : null;
85
+
86
+ // Check if this agent has a linked agent_tool for this tool
87
+ const linkedToAgent = matchedTool
88
+ ? agentTools.some((at) => at.id_tool === matchedTool.id)
89
+ : false;
90
+
91
+ // Sharing indicator: credential exists at account level (available to all agents)
92
+ // When a credential is account-scoped, any agent can use it — show as "shared"
93
+ const sharedByAgentsCount = matchedCredential ? 1 : 0;
94
+
95
+ // Determine state
96
+ let state: IntegrationCardState = "available";
97
+ if (matchedCredential) {
98
+ state =
99
+ matchedCredential.status === "expired" ? "expired" : "connected";
100
+ }
101
+
102
+ return {
103
+ definition: def,
104
+ state,
105
+ credential: matchedCredential,
106
+ tool: matchedTool,
107
+ sharedByAgentsCount,
108
+ linkedToAgent,
109
+ };
110
+ });
111
+ }, [credentialsData, toolsData, agentToolsData]);
112
+
113
+ return { cards, isLoading };
114
+ }
package/src/index.ts CHANGED
@@ -13,6 +13,12 @@ export type {
13
13
  PromptVersion,
14
14
  Tool,
15
15
  ToolCredential,
16
+ CapabilityOperation,
17
+ CapabilityModule,
18
+ CapabilityCategory,
19
+ CapabilitiesResponse,
20
+ AgentCapability,
21
+ AgentCapabilitiesPayload,
16
22
  } from "./types";
17
23
 
18
24
  // Client
@@ -41,5 +47,33 @@ export { AgentConversationsPanel } from "./components/conversations/agent-conver
41
47
  export { AgentConversationsTable } from "./components/conversations/agent-conversations-table";
42
48
  export { ConversationView } from "./components/conversations/conversation-view";
43
49
 
50
+ // Capabilities
51
+ export { IntegrationCard } from "./components/capabilities/integration-card";
52
+ export type { IntegrationCardProps } from "./components/capabilities/integration-card";
53
+ export { IntegrationsTab } from "./components/capabilities/integrations-tab";
54
+ export type { IntegrationsTabProps } from "./components/capabilities/integrations-tab";
55
+ export { CapabilitiesTab } from "./components/capabilities/capabilities-tab";
56
+ export type { CapabilitiesTabProps } from "./components/capabilities/capabilities-tab";
57
+ export { AdvancedTab } from "./components/capabilities/advanced-tab";
58
+ export type { AdvancedTabProps } from "./components/capabilities/advanced-tab";
59
+ export { IntegrationWizard } from "./components/capabilities/integration-wizard";
60
+ export type { IntegrationWizardProps } from "./components/capabilities/integration-wizard";
61
+ export type {
62
+ WizardIntegrationMeta,
63
+ IntegrationCapability,
64
+ WizardStep,
65
+ OAuthStatus,
66
+ OAuthResult,
67
+ } from "./components/capabilities/types";
68
+ export type { ConfigOption } from "./components/capabilities/wizard-steps/config-step";
69
+
70
+ // Data
71
+ export { INTEGRATIONS_REGISTRY } from "./data/integrations-registry";
72
+ export type {
73
+ IntegrationDefinition,
74
+ IntegrationAuthType,
75
+ IntegrationStatus,
76
+ } from "./data/integrations-registry";
77
+
44
78
  // Page Compositions
45
79
  export * from "./pages";
@@ -0,0 +1,159 @@
1
+ 'use client';
2
+
3
+ import { useState, useCallback } from "react";
4
+ import {
5
+ Tabs,
6
+ TabsList,
7
+ TabsTrigger,
8
+ TabsContent,
9
+ } from "@greatapps/greatauth-ui/ui";
10
+ import { Blocks, Plug, Settings } from "lucide-react";
11
+
12
+ import type { GagentsHookConfig } from "../hooks/types";
13
+ import type { IntegrationCardData } from "../hooks/use-integrations";
14
+ import type { WizardIntegrationMeta } from "../components/capabilities/types";
15
+ import { CapabilitiesTab } from "../components/capabilities/capabilities-tab";
16
+ import { IntegrationsTab } from "../components/capabilities/integrations-tab";
17
+ import { IntegrationWizard } from "../components/capabilities/integration-wizard";
18
+ import { AdvancedTab } from "../components/capabilities/advanced-tab";
19
+ import type { ConfigOption } from "../components/capabilities/wizard-steps/config-step";
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Props
23
+ // ---------------------------------------------------------------------------
24
+
25
+ export interface AgentCapabilitiesPageProps {
26
+ config: GagentsHookConfig;
27
+ agentId: number;
28
+ gagentsApiUrl: string;
29
+ /**
30
+ * Resolve wizard metadata for a given integration card.
31
+ * The consuming app provides this so the wizard gets correct
32
+ * capabilities, requirements, and config step flag.
33
+ */
34
+ resolveWizardMeta?: (card: IntegrationCardData) => WizardIntegrationMeta;
35
+ /**
36
+ * Callback to load config options after OAuth completes
37
+ * (e.g. load Google Calendar list). Forwarded to IntegrationWizard.
38
+ */
39
+ loadConfigOptions?: (credentialId: number) => Promise<ConfigOption[]>;
40
+ /** Called after wizard completes successfully. */
41
+ onWizardComplete?: () => void;
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Default wizard meta resolver
46
+ // ---------------------------------------------------------------------------
47
+
48
+ function defaultResolveWizardMeta(card: IntegrationCardData): WizardIntegrationMeta {
49
+ return {
50
+ capabilities: [
51
+ { label: card.definition.name, description: card.definition.description },
52
+ ],
53
+ requirements: [],
54
+ hasConfigStep: false,
55
+ };
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Component
60
+ // ---------------------------------------------------------------------------
61
+
62
+ export function AgentCapabilitiesPage({
63
+ config,
64
+ agentId,
65
+ gagentsApiUrl,
66
+ resolveWizardMeta = defaultResolveWizardMeta,
67
+ loadConfigOptions,
68
+ onWizardComplete,
69
+ }: AgentCapabilitiesPageProps) {
70
+ // Wizard dialog state
71
+ const [wizardOpen, setWizardOpen] = useState(false);
72
+ const [activeCard, setActiveCard] = useState<IntegrationCardData | null>(null);
73
+
74
+ const handleConnect = useCallback(
75
+ (card: IntegrationCardData) => {
76
+ setActiveCard(card);
77
+ setWizardOpen(true);
78
+ },
79
+ [],
80
+ );
81
+
82
+ const handleWizardComplete = useCallback(() => {
83
+ setWizardOpen(false);
84
+ setActiveCard(null);
85
+ onWizardComplete?.();
86
+ }, [onWizardComplete]);
87
+
88
+ const handleWizardOpenChange = useCallback((open: boolean) => {
89
+ setWizardOpen(open);
90
+ if (!open) setActiveCard(null);
91
+ }, []);
92
+
93
+ // Derive wizard meta from active card
94
+ const wizardMeta = activeCard ? resolveWizardMeta(activeCard) : null;
95
+
96
+ return (
97
+ <div className="space-y-4">
98
+ <div>
99
+ <h2 className="text-lg font-semibold">Capacidades e Integrações</h2>
100
+ <p className="text-sm text-muted-foreground">
101
+ Configure o que este agente pode fazer e quais serviços externos ele utiliza.
102
+ </p>
103
+ </div>
104
+
105
+ <Tabs defaultValue="capacidades">
106
+ <TabsList>
107
+ <TabsTrigger value="capacidades" className="flex items-center gap-1.5">
108
+ <Blocks aria-hidden="true" className="h-3.5 w-3.5" />
109
+ Capacidades
110
+ </TabsTrigger>
111
+ <TabsTrigger value="integracoes" className="flex items-center gap-1.5">
112
+ <Plug aria-hidden="true" className="h-3.5 w-3.5" />
113
+ Integrações
114
+ </TabsTrigger>
115
+ <TabsTrigger value="avancado" className="flex items-center gap-1.5">
116
+ <Settings aria-hidden="true" className="h-3.5 w-3.5" />
117
+ Avançado
118
+ </TabsTrigger>
119
+ </TabsList>
120
+
121
+ <TabsContent value="capacidades" className="mt-4">
122
+ <CapabilitiesTab config={config} agentId={agentId} />
123
+ </TabsContent>
124
+
125
+ <TabsContent value="integracoes" className="mt-4">
126
+ <IntegrationsTab
127
+ config={config}
128
+ agentId={agentId}
129
+ onConnect={handleConnect}
130
+ />
131
+ </TabsContent>
132
+
133
+ <TabsContent value="avancado" className="mt-4">
134
+ <AdvancedTab
135
+ config={config}
136
+ agentId={agentId}
137
+ gagentsApiUrl={gagentsApiUrl}
138
+ />
139
+ </TabsContent>
140
+ </Tabs>
141
+
142
+ {/* Integration Wizard Dialog */}
143
+ {activeCard && wizardMeta && (
144
+ <IntegrationWizard
145
+ open={wizardOpen}
146
+ onOpenChange={handleWizardOpenChange}
147
+ integration={activeCard.definition}
148
+ meta={wizardMeta}
149
+ agentId={agentId}
150
+ config={config}
151
+ onComplete={handleWizardComplete}
152
+ gagentsApiUrl={gagentsApiUrl}
153
+ existingCredentialId={activeCard.credential?.id}
154
+ loadConfigOptions={loadConfigOptions}
155
+ />
156
+ )}
157
+ </div>
158
+ );
159
+ }
@@ -1,4 +1,6 @@
1
1
  export { AgentsPage, type AgentsPageProps } from "./agents-page";
2
2
  export { AgentDetailPage, type AgentDetailPageProps } from "./agent-detail-page";
3
+ export { AgentCapabilitiesPage, type AgentCapabilitiesPageProps } from "./agent-capabilities-page";
3
4
  export { ToolsPage, type ToolsPageProps } from "./tools-page";
4
5
  export { CredentialsPage, type CredentialsPageProps } from "./credentials-page";
6
+ export { IntegrationsManagementPage, type IntegrationsManagementPageProps } from "./integrations-management-page";
@@ -0,0 +1,166 @@
1
+ 'use client';
2
+
3
+ import { useState, useMemo } from "react";
4
+ import { useToolCredentials, useAgents, useTools } from "../hooks";
5
+ import { ToolCredentialsForm } from "../components/tools/tool-credentials-form";
6
+ import { IntegrationsTab } from "../components/capabilities/integrations-tab";
7
+ import {
8
+ Badge,
9
+ Button,
10
+ Tabs,
11
+ TabsContent,
12
+ TabsList,
13
+ TabsTrigger,
14
+ } from "@greatapps/greatauth-ui/ui";
15
+ import { Plus, Plug, KeyRound, Info } from "lucide-react";
16
+ import type { GagentsHookConfig } from "../hooks/types";
17
+ import type { IntegrationCardData } from "../hooks/use-integrations";
18
+ import type { Agent, Tool, ToolCredential } from "../types";
19
+
20
+ export interface IntegrationsManagementPageProps {
21
+ config: GagentsHookConfig;
22
+ gagentsApiUrl: string;
23
+ /** Called when user clicks connect/reconnect on an integration card. */
24
+ onConnect?: (card: IntegrationCardData) => void;
25
+ title?: string;
26
+ subtitle?: string;
27
+ }
28
+
29
+ /**
30
+ * Build a map of credential id → list of agent names that use it.
31
+ * Cross-references tools (which link credential via id) with agents
32
+ * that have agent_tools referencing those tools.
33
+ *
34
+ * Since we don't have an account-level agent_tools endpoint, we
35
+ * approximate by checking which tools are linked to credentials and
36
+ * showing credential-level stats.
37
+ */
38
+ function useCredentialAgentSummary(
39
+ credentials: ToolCredential[],
40
+ tools: Tool[],
41
+ agents: Agent[],
42
+ ) {
43
+ return useMemo(() => {
44
+ // Build a set of tool IDs that have credentials
45
+ const toolIdsWithCredentials = new Set(
46
+ credentials.map((c) => c.id_tool).filter(Boolean),
47
+ );
48
+
49
+ // Count how many credentials are linked to tools that agents could use
50
+ const linkedCount = credentials.filter(
51
+ (c) => c.id_tool && toolIdsWithCredentials.has(c.id_tool),
52
+ ).length;
53
+
54
+ return {
55
+ totalCredentials: credentials.length,
56
+ linkedToTools: linkedCount,
57
+ totalAgents: agents.length,
58
+ totalTools: tools.length,
59
+ };
60
+ }, [credentials, tools, agents]);
61
+ }
62
+
63
+ export function IntegrationsManagementPage({
64
+ config,
65
+ gagentsApiUrl,
66
+ onConnect,
67
+ title = "Integrações e Credenciais",
68
+ subtitle = "Gerencie todas as integrações e credenciais da conta.",
69
+ }: IntegrationsManagementPageProps) {
70
+ const { data: credentialsData, isLoading: credentialsLoading } =
71
+ useToolCredentials(config);
72
+ const { data: agentsData } = useAgents(config);
73
+ const { data: toolsData } = useTools(config);
74
+ const [createOpen, setCreateOpen] = useState(false);
75
+
76
+ const credentials = credentialsData?.data || [];
77
+ const agents: Agent[] = agentsData?.data || [];
78
+ const tools: Tool[] = toolsData?.data || [];
79
+
80
+ const summary = useCredentialAgentSummary(credentials, tools, agents);
81
+
82
+ return (
83
+ <div className="flex flex-col gap-4 p-4 md:p-6">
84
+ <div className="flex items-center justify-between">
85
+ <div>
86
+ <h1 className="text-xl font-semibold">{title}</h1>
87
+ <p className="text-sm text-muted-foreground">{subtitle}</p>
88
+ </div>
89
+ </div>
90
+
91
+ <Tabs defaultValue="integrations" className="w-full">
92
+ <TabsList>
93
+ <TabsTrigger value="integrations" className="gap-2">
94
+ <Plug className="h-4 w-4" />
95
+ Integrações
96
+ </TabsTrigger>
97
+ <TabsTrigger value="credentials" className="gap-2">
98
+ <KeyRound className="h-4 w-4" />
99
+ Credenciais
100
+ </TabsTrigger>
101
+ </TabsList>
102
+
103
+ <TabsContent value="integrations" className="mt-4">
104
+ <IntegrationsTab
105
+ config={config}
106
+ agentId={null}
107
+ onConnect={onConnect ?? (() => {})}
108
+ />
109
+ </TabsContent>
110
+
111
+ <TabsContent value="credentials" className="mt-4">
112
+ {/* Summary bar */}
113
+ {!credentialsLoading && (
114
+ <div className="flex items-center gap-4 rounded-lg border bg-muted/50 px-4 py-3 mb-4">
115
+ <Info className="h-4 w-4 text-muted-foreground shrink-0" />
116
+ <div className="flex flex-wrap items-center gap-3 text-sm">
117
+ <span>
118
+ <Badge variant="secondary" className="mr-1">
119
+ {summary.totalCredentials}
120
+ </Badge>
121
+ {summary.totalCredentials === 1
122
+ ? "credencial configurada"
123
+ : "credenciais configuradas"}
124
+ </span>
125
+ <span className="text-muted-foreground">|</span>
126
+ <span>
127
+ <Badge variant="secondary" className="mr-1">
128
+ {summary.linkedToTools}
129
+ </Badge>
130
+ {summary.linkedToTools === 1
131
+ ? "vinculada a ferramentas"
132
+ : "vinculadas a ferramentas"}
133
+ </span>
134
+ <span className="text-muted-foreground">|</span>
135
+ <span>
136
+ <Badge variant="secondary" className="mr-1">
137
+ {summary.totalAgents}
138
+ </Badge>
139
+ {summary.totalAgents === 1
140
+ ? "agente na conta"
141
+ : "agentes na conta"}
142
+ </span>
143
+ </div>
144
+ </div>
145
+ )}
146
+
147
+ <div className="flex items-center justify-end mb-4">
148
+ <Button onClick={() => setCreateOpen(true)} size="sm">
149
+ <Plus className="mr-2 h-4 w-4" />
150
+ Nova Credencial
151
+ </Button>
152
+ </div>
153
+
154
+ <ToolCredentialsForm
155
+ config={config}
156
+ gagentsApiUrl={gagentsApiUrl}
157
+ credentials={credentials}
158
+ isLoading={credentialsLoading}
159
+ createOpen={createOpen}
160
+ onCreateOpenChange={setCreateOpen}
161
+ />
162
+ </TabsContent>
163
+ </Tabs>
164
+ </div>
165
+ );
166
+ }
@@ -0,0 +1,32 @@
1
+ export interface CapabilityOperation {
2
+ slug: string;
3
+ label: string;
4
+ description: string;
5
+ }
6
+
7
+ export interface CapabilityModule {
8
+ slug: string;
9
+ label: string;
10
+ description: string;
11
+ operations: string[];
12
+ }
13
+
14
+ export interface CapabilityCategory {
15
+ slug: string;
16
+ label: string;
17
+ modules: CapabilityModule[];
18
+ }
19
+
20
+ export interface CapabilitiesResponse {
21
+ product: string | null;
22
+ categories: CapabilityCategory[];
23
+ }
24
+
25
+ export interface AgentCapability {
26
+ module: string;
27
+ operations: string[];
28
+ }
29
+
30
+ export interface AgentCapabilitiesPayload {
31
+ capabilities: AgentCapability[];
32
+ }
@@ -154,3 +154,13 @@ export interface AvailabilityResult {
154
154
  conflicts: AvailabilityConflict[];
155
155
  failed_providers: Array<{ provider: string; error: string }>;
156
156
  }
157
+
158
+ // Capabilities
159
+ export type {
160
+ CapabilityOperation,
161
+ CapabilityModule,
162
+ CapabilityCategory,
163
+ CapabilitiesResponse,
164
+ AgentCapability,
165
+ AgentCapabilitiesPayload,
166
+ } from "./capabilities";