@greatapps/greatagents-ui 0.3.2 → 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.
Files changed (38) hide show
  1. package/dist/index.d.ts +204 -1
  2. package/dist/index.js +1797 -125
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/src/client/index.ts +14 -0
  6. package/src/components/agents/agent-edit-form.tsx +4 -1
  7. package/src/components/agents/agent-form-dialog.tsx +5 -1
  8. package/src/components/agents/agent-objectives-list.tsx +15 -6
  9. package/src/components/agents/agent-prompt-editor.tsx +9 -5
  10. package/src/components/agents/agent-tabs.tsx +4 -4
  11. package/src/components/agents/agent-tools-list.tsx +12 -5
  12. package/src/components/agents/agents-table.tsx +7 -2
  13. package/src/components/capabilities/advanced-tab.tsx +82 -0
  14. package/src/components/capabilities/capabilities-tab.tsx +475 -0
  15. package/src/components/capabilities/integration-card.tsx +162 -0
  16. package/src/components/capabilities/integration-wizard.tsx +537 -0
  17. package/src/components/capabilities/integrations-tab.tsx +61 -0
  18. package/src/components/capabilities/types.ts +48 -0
  19. package/src/components/capabilities/wizard-steps/config-step.tsx +117 -0
  20. package/src/components/capabilities/wizard-steps/confirm-step.tsx +123 -0
  21. package/src/components/capabilities/wizard-steps/credentials-step.tsx +205 -0
  22. package/src/components/capabilities/wizard-steps/info-step.tsx +78 -0
  23. package/src/components/conversations/agent-conversations-table.tsx +13 -2
  24. package/src/components/conversations/conversation-view.tsx +2 -2
  25. package/src/components/tools/tool-credentials-form.tsx +34 -14
  26. package/src/components/tools/tool-form-dialog.tsx +9 -5
  27. package/src/components/tools/tools-table.tsx +8 -3
  28. package/src/data/integrations-registry.ts +23 -0
  29. package/src/hooks/index.ts +10 -0
  30. package/src/hooks/use-capabilities.ts +50 -0
  31. package/src/hooks/use-integrations.ts +114 -0
  32. package/src/index.ts +34 -0
  33. package/src/pages/agent-capabilities-page.tsx +159 -0
  34. package/src/pages/agent-detail-page.tsx +1 -0
  35. package/src/pages/index.ts +2 -0
  36. package/src/pages/integrations-management-page.tsx +166 -0
  37. package/src/types/capabilities.ts +32 -0
  38. package/src/types/index.ts +10 -0
@@ -130,6 +130,7 @@ function useColumns(
130
130
  variant="ghost"
131
131
  size="icon"
132
132
  className="h-8 w-8"
133
+ aria-label="Vincular"
133
134
  disabled
134
135
  >
135
136
  <Link className="h-4 w-4" />
@@ -144,6 +145,7 @@ function useColumns(
144
145
  variant="ghost"
145
146
  size="icon"
146
147
  className="h-8 w-8"
148
+ aria-label="Editar"
147
149
  onClick={() => onEdit(row.original)}
148
150
  >
149
151
  <Pencil className="h-4 w-4" />
@@ -157,6 +159,7 @@ function useColumns(
157
159
  variant="ghost"
158
160
  size="icon"
159
161
  className="h-8 w-8 text-destructive hover:text-destructive"
162
+ aria-label="Excluir"
160
163
  onClick={() => onRemove(row.original)}
161
164
  >
162
165
  <Trash2 className="h-4 w-4" />
@@ -328,9 +331,12 @@ export function ToolCredentialsForm({
328
331
  <div className="space-y-4">
329
332
  <div className="flex items-center gap-3">
330
333
  <div className="relative flex-1 max-w-md">
331
- <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
334
+ <Search aria-hidden="true" className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
332
335
  <Input
333
- placeholder="Buscar credenciais..."
336
+ placeholder="Buscar credenciais\u2026"
337
+ aria-label="Buscar credenciais"
338
+ name="search"
339
+ autoComplete="off"
334
340
  value={search}
335
341
  onChange={(e) => setSearch(e.target.value)}
336
342
  className="pl-9"
@@ -353,7 +359,7 @@ export function ToolCredentialsForm({
353
359
  </DialogHeader>
354
360
  <div className="space-y-4">
355
361
  <div>
356
- <label className="mb-1 block text-sm font-medium">
362
+ <label htmlFor="cred-tool" className="mb-1 block text-sm font-medium">
357
363
  Ferramenta *
358
364
  </label>
359
365
  <Select
@@ -362,7 +368,7 @@ export function ToolCredentialsForm({
362
368
  setCreateForm((f) => ({ ...f, id_tool: val }))
363
369
  }
364
370
  >
365
- <SelectTrigger>
371
+ <SelectTrigger id="cred-tool">
366
372
  <SelectValue placeholder="Selecione a ferramenta" />
367
373
  </SelectTrigger>
368
374
  <SelectContent>
@@ -375,10 +381,12 @@ export function ToolCredentialsForm({
375
381
  </Select>
376
382
  </div>
377
383
  <div>
378
- <label className="mb-1 block text-sm font-medium">
384
+ <label htmlFor="cred-label" className="mb-1 block text-sm font-medium">
379
385
  Label *
380
386
  </label>
381
387
  <Input
388
+ id="cred-label"
389
+ name="label"
382
390
  value={createForm.label}
383
391
  onChange={(e) =>
384
392
  setCreateForm((f) => ({ ...f, label: e.target.value }))
@@ -387,10 +395,13 @@ export function ToolCredentialsForm({
387
395
  />
388
396
  </div>
389
397
  <div>
390
- <label className="mb-1 block text-sm font-medium">
398
+ <label htmlFor="cred-credential" className="mb-1 block text-sm font-medium">
391
399
  Credencial *
392
400
  </label>
393
401
  <Input
402
+ id="cred-credential"
403
+ name="credential"
404
+ autoComplete="off"
394
405
  type="password"
395
406
  value={createForm.credentials_encrypted}
396
407
  onChange={(e) =>
@@ -403,10 +414,12 @@ export function ToolCredentialsForm({
403
414
  />
404
415
  </div>
405
416
  <div>
406
- <label className="mb-1 block text-sm font-medium">
417
+ <label htmlFor="cred-expires" className="mb-1 block text-sm font-medium">
407
418
  Data de Expiração (opcional)
408
419
  </label>
409
420
  <Input
421
+ id="cred-expires"
422
+ name="expires"
410
423
  type="date"
411
424
  value={createForm.expires_at}
412
425
  onChange={(e) =>
@@ -448,7 +461,7 @@ export function ToolCredentialsForm({
448
461
  </DialogHeader>
449
462
  <div className="space-y-4">
450
463
  <div>
451
- <label className="mb-1 block text-sm font-medium">
464
+ <label htmlFor="edit-cred-tool" className="mb-1 block text-sm font-medium">
452
465
  Ferramenta *
453
466
  </label>
454
467
  <Select
@@ -457,7 +470,7 @@ export function ToolCredentialsForm({
457
470
  setEditForm((f) => ({ ...f, id_tool: val }))
458
471
  }
459
472
  >
460
- <SelectTrigger>
473
+ <SelectTrigger id="edit-cred-tool">
461
474
  <SelectValue placeholder="Selecione a ferramenta" />
462
475
  </SelectTrigger>
463
476
  <SelectContent>
@@ -470,10 +483,12 @@ export function ToolCredentialsForm({
470
483
  </Select>
471
484
  </div>
472
485
  <div>
473
- <label className="mb-1 block text-sm font-medium">
486
+ <label htmlFor="edit-cred-label" className="mb-1 block text-sm font-medium">
474
487
  Label
475
488
  </label>
476
489
  <Input
490
+ id="edit-cred-label"
491
+ name="label"
477
492
  value={editForm.label}
478
493
  onChange={(e) =>
479
494
  setEditForm((f) => ({ ...f, label: e.target.value }))
@@ -482,10 +497,13 @@ export function ToolCredentialsForm({
482
497
  />
483
498
  </div>
484
499
  <div>
485
- <label className="mb-1 block text-sm font-medium">
500
+ <label htmlFor="edit-cred-credential" className="mb-1 block text-sm font-medium">
486
501
  Nova Credencial (vazio = manter atual)
487
502
  </label>
488
503
  <Input
504
+ id="edit-cred-credential"
505
+ name="credential"
506
+ autoComplete="off"
489
507
  type="password"
490
508
  value={editForm.credentials_encrypted}
491
509
  onChange={(e) =>
@@ -498,10 +516,12 @@ export function ToolCredentialsForm({
498
516
  />
499
517
  </div>
500
518
  <div>
501
- <label className="mb-1 block text-sm font-medium">
519
+ <label htmlFor="edit-cred-expires" className="mb-1 block text-sm font-medium">
502
520
  Data de Expiração
503
521
  </label>
504
522
  <Input
523
+ id="edit-cred-expires"
524
+ name="expires"
505
525
  type="date"
506
526
  value={editForm.expires_at}
507
527
  onChange={(e) =>
@@ -510,7 +530,7 @@ export function ToolCredentialsForm({
510
530
  />
511
531
  </div>
512
532
  <div>
513
- <label className="mb-1 block text-sm font-medium">Status</label>
533
+ <label htmlFor="edit-cred-status" className="mb-1 block text-sm font-medium">Status</label>
514
534
  <Select
515
535
  value={editForm.status || undefined}
516
536
  onValueChange={(val) =>
@@ -520,7 +540,7 @@ export function ToolCredentialsForm({
520
540
  }))
521
541
  }
522
542
  >
523
- <SelectTrigger>
543
+ <SelectTrigger id="edit-cred-status">
524
544
  <SelectValue />
525
545
  </SelectTrigger>
526
546
  <SelectContent>
@@ -189,6 +189,7 @@ export function ToolFormDialog({
189
189
  <Label htmlFor="tool-name">Nome *</Label>
190
190
  <Input
191
191
  id="tool-name"
192
+ name="name"
192
193
  value={form.name}
193
194
  onChange={(e) => {
194
195
  const name = e.target.value;
@@ -213,6 +214,7 @@ export function ToolFormDialog({
213
214
  <Label htmlFor="tool-slug">Slug (identificador único) *</Label>
214
215
  <Input
215
216
  id="tool-slug"
217
+ name="slug"
216
218
  value={form.slug}
217
219
  onChange={(e) => {
218
220
  setSlugManuallyEdited(true);
@@ -269,11 +271,12 @@ export function ToolFormDialog({
269
271
  <Label htmlFor="tool-description">Descrição</Label>
270
272
  <Textarea
271
273
  id="tool-description"
274
+ name="description"
272
275
  value={form.description}
273
276
  onChange={(e) =>
274
277
  setForm((prev) => ({ ...prev, description: e.target.value }))
275
278
  }
276
- placeholder="Descrição da ferramenta..."
279
+ placeholder="Descri\u00e7\u00e3o da ferramenta\u2026"
277
280
  rows={3}
278
281
  disabled={isPending}
279
282
  />
@@ -285,6 +288,7 @@ export function ToolFormDialog({
285
288
  </Label>
286
289
  <Textarea
287
290
  id="tool-function-defs"
291
+ name="functionDefs"
288
292
  value={form.functionDefinitions}
289
293
  onChange={(e) => {
290
294
  setForm((prev) => ({
@@ -298,11 +302,11 @@ export function ToolFormDialog({
298
302
  "type": "function",
299
303
  "function": {
300
304
  "name": "nome_da_funcao",
301
- "description": "O que a função faz",
305
+ "description": "O que a fun\u00e7\u00e3o faz",
302
306
  "parameters": {
303
307
  "type": "object",
304
- "properties": { ... },
305
- "required": [...]
308
+ "properties": { \u2026 },
309
+ "required": [\u2026]
306
310
  }
307
311
  }
308
312
  }
@@ -330,7 +334,7 @@ export function ToolFormDialog({
330
334
  </Button>
331
335
  <Button type="submit" disabled={isPending}>
332
336
  {isPending ? (
333
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
337
+ <Loader2 aria-hidden="true" className="mr-2 h-4 w-4 animate-spin" />
334
338
  ) : null}
335
339
  {isEditing ? "Salvar" : "Criar"}
336
340
  </Button>
@@ -70,7 +70,7 @@ function useColumns(
70
70
  if (!desc) return <span className="text-muted-foreground text-sm">{"\u2014"}</span>;
71
71
  return (
72
72
  <span className="text-muted-foreground text-sm">
73
- {desc.length > 50 ? `${desc.slice(0, 50)}...` : desc}
73
+ {desc.length > 50 ? `${desc.slice(0, 50)}\u2026` : desc}
74
74
  </span>
75
75
  );
76
76
  },
@@ -98,6 +98,7 @@ function useColumns(
98
98
  variant="ghost"
99
99
  size="icon"
100
100
  className="h-8 w-8"
101
+ aria-label="Editar"
101
102
  onClick={() => onEdit(row.original)}
102
103
  >
103
104
  <Pencil className="h-4 w-4" />
@@ -111,6 +112,7 @@ function useColumns(
111
112
  variant="ghost"
112
113
  size="icon"
113
114
  className="h-8 w-8 text-destructive hover:text-destructive"
115
+ aria-label="Excluir"
114
116
  onClick={() => onDelete(row.original.id)}
115
117
  >
116
118
  <Trash2 className="h-4 w-4" />
@@ -171,9 +173,12 @@ export function ToolsTable({ onEdit, config }: ToolsTableProps) {
171
173
  <>
172
174
  <div className="flex items-center gap-3">
173
175
  <div className="relative flex-1 max-w-md">
174
- <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
176
+ <Search aria-hidden="true" className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
175
177
  <Input
176
- placeholder="Buscar ferramentas..."
178
+ placeholder="Buscar ferramentas\u2026"
179
+ aria-label="Buscar ferramentas"
180
+ name="search"
181
+ autoComplete="off"
177
182
  value={search}
178
183
  onChange={(e) => handleSearchChange(e.target.value)}
179
184
  className="pl-9"
@@ -0,0 +1,23 @@
1
+ export type IntegrationAuthType = "oauth2" | "api_key" | "none";
2
+ export type IntegrationStatus = "available" | "coming_soon";
3
+
4
+ export interface IntegrationDefinition {
5
+ slug: string;
6
+ name: string;
7
+ description: string;
8
+ icon: string; // Lucide icon name
9
+ authType: IntegrationAuthType;
10
+ status: IntegrationStatus;
11
+ }
12
+
13
+ export const INTEGRATIONS_REGISTRY: IntegrationDefinition[] = [
14
+ {
15
+ slug: "google_calendar",
16
+ name: "Google Agenda",
17
+ description: "Sincronize agendamentos com o Google Calendar",
18
+ icon: "CalendarSync",
19
+ authType: "oauth2",
20
+ status: "available",
21
+ },
22
+ // Future integrations — add entries here without code changes
23
+ ];
@@ -28,3 +28,13 @@ export {
28
28
 
29
29
  // Contacts
30
30
  export { useGagentsContacts } from "./use-contacts";
31
+
32
+ // Capabilities
33
+ export { useCapabilities, useAgentCapabilities, useUpdateAgentCapabilities } from "./use-capabilities";
34
+
35
+ // Integrations
36
+ export {
37
+ useIntegrationState,
38
+ type IntegrationCardState,
39
+ type IntegrationCardData,
40
+ } from "./use-integrations";
@@ -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
+ }