@greatapps/greatagents-ui 0.3.13 → 0.3.15
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/dist/index.d.ts +7 -5
- package/dist/index.js +375 -628
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/agents/agent-prompt-editor.tsx +59 -22
- package/src/components/capabilities/capabilities-tab.tsx +2 -2
- package/src/components/capabilities/integration-card.tsx +160 -112
- package/src/components/capabilities/integration-wizard.tsx +5 -2
- package/src/components/capabilities/integrations-tab.tsx +62 -25
- package/src/components/tools/tool-credentials-form.tsx +1 -374
- package/src/hooks/use-agent-tools.ts +1 -1
- package/src/pages/credentials-page.tsx +0 -10
- package/src/pages/integrations-management-page.tsx +86 -167
- package/src/types/index.ts +1 -1
package/package.json
CHANGED
|
@@ -63,29 +63,59 @@ function buildPreview(
|
|
|
63
63
|
let preview = promptText;
|
|
64
64
|
|
|
65
65
|
const activeObjectives = objectives.filter((o) => o.active);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
const enabledAgentTools = agentTools.filter((at) => at.enabled);
|
|
67
|
+
|
|
68
|
+
const toolMap = new Map(allTools.map((t) => [t.id, t]));
|
|
69
|
+
|
|
70
|
+
// Separate capabilities vs integrations
|
|
71
|
+
const capabilityTools: { at: AgentTool; tool: Tool }[] = [];
|
|
72
|
+
const integrationTools: { at: AgentTool; tool: Tool }[] = [];
|
|
73
|
+
for (const at of enabledAgentTools) {
|
|
74
|
+
const tool = toolMap.get(at.id_tool);
|
|
75
|
+
if (!tool) continue;
|
|
76
|
+
if (tool.type === "integration") {
|
|
77
|
+
integrationTools.push({ at, tool });
|
|
78
|
+
} else {
|
|
79
|
+
capabilityTools.push({ at, tool });
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
preview += "\n[
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
preview += `\n
|
|
83
|
+
const hasContent = activeObjectives.length > 0 || capabilityTools.length > 0 || integrationTools.length > 0;
|
|
84
|
+
|
|
85
|
+
if (hasContent) {
|
|
86
|
+
preview += "\n\n[CAPACIDADES E INTEGRAÇÕES]";
|
|
87
|
+
|
|
88
|
+
// Internal capabilities
|
|
89
|
+
if (activeObjectives.length > 0 || capabilityTools.length > 0) {
|
|
90
|
+
preview += "\n\n## Capacidades Internas (GClinic)";
|
|
91
|
+
|
|
92
|
+
for (const obj of activeObjectives) {
|
|
93
|
+
preview += `\n\n### ${obj.title} (${obj.slug})`;
|
|
94
|
+
if (obj.prompt) preview += `\n${obj.prompt}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const { at, tool } of capabilityTools) {
|
|
98
|
+
preview += `\n\n### ${tool.name} (${tool.slug})`;
|
|
99
|
+
if (tool.description) preview += `\n${tool.description}`;
|
|
100
|
+
if (at.custom_instructions) preview += `\n${at.custom_instructions}`;
|
|
86
101
|
}
|
|
87
|
-
preview += "\n";
|
|
88
102
|
}
|
|
103
|
+
|
|
104
|
+
// External integrations
|
|
105
|
+
if (integrationTools.length > 0) {
|
|
106
|
+
preview += "\n\n## Integrações Externas";
|
|
107
|
+
|
|
108
|
+
for (const { at, tool } of integrationTools) {
|
|
109
|
+
preview += `\n\n### ${tool.name} (${tool.slug})`;
|
|
110
|
+
if (at.custom_instructions) preview += `\n${at.custom_instructions}`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Rules
|
|
115
|
+
preview += "\n\n## Regras";
|
|
116
|
+
preview += "\n- Sempre confirme com o usuário antes de criar ou alterar registros.";
|
|
117
|
+
preview += "\n- Nunca invente dados — sempre consulte primeiro.";
|
|
118
|
+
preview += "\n- Use EXATAMENTE os nomes de função listados acima.";
|
|
89
119
|
}
|
|
90
120
|
|
|
91
121
|
return preview;
|
|
@@ -277,13 +307,20 @@ export function AgentPromptEditor({ config, agent }: AgentPromptEditorProps) {
|
|
|
277
307
|
<div className="border-t px-4 py-3">
|
|
278
308
|
<pre className="max-h-96 overflow-auto whitespace-pre-wrap font-mono text-sm leading-relaxed">
|
|
279
309
|
{previewText.split("\n").map((line, i) => {
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
310
|
+
const isTopSection = line.startsWith("[CAPACIDADES E INTEGRAÇÕES]");
|
|
311
|
+
const isH2 = line.startsWith("## ");
|
|
312
|
+
const isH3 = line.startsWith("### ");
|
|
313
|
+
const cls = isTopSection
|
|
314
|
+
? "font-bold text-foreground"
|
|
315
|
+
: isH2
|
|
316
|
+
? "font-semibold text-muted-foreground"
|
|
317
|
+
: isH3
|
|
318
|
+
? "font-medium text-muted-foreground"
|
|
319
|
+
: "";
|
|
283
320
|
return (
|
|
284
321
|
<span
|
|
285
322
|
key={i}
|
|
286
|
-
className={
|
|
323
|
+
className={cls}
|
|
287
324
|
>
|
|
288
325
|
{line}
|
|
289
326
|
{"\n"}
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
Skeleton,
|
|
26
26
|
} from "@greatapps/greatauth-ui/ui";
|
|
27
27
|
import {
|
|
28
|
-
|
|
28
|
+
CalendarCheck,
|
|
29
29
|
Users,
|
|
30
30
|
Settings,
|
|
31
31
|
HeartHandshake,
|
|
@@ -55,7 +55,7 @@ function getOperationLabel(slug: string): string {
|
|
|
55
55
|
// ---------------------------------------------------------------------------
|
|
56
56
|
|
|
57
57
|
const CATEGORY_ICONS: Record<string, React.ElementType> = {
|
|
58
|
-
agenda:
|
|
58
|
+
agenda: CalendarCheck,
|
|
59
59
|
cadastros: Users,
|
|
60
60
|
infraestrutura: Settings,
|
|
61
61
|
relacionamentos: HeartHandshake,
|
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { useState } from "react";
|
|
3
4
|
import type { IntegrationCardData, IntegrationCardState } from "../../hooks/use-integrations";
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Badge,
|
|
7
|
+
Button,
|
|
8
|
+
DropdownMenu,
|
|
9
|
+
DropdownMenuContent,
|
|
10
|
+
DropdownMenuItem,
|
|
11
|
+
DropdownMenuTrigger,
|
|
12
|
+
AlertDialog,
|
|
13
|
+
AlertDialogAction,
|
|
14
|
+
AlertDialogCancel,
|
|
15
|
+
AlertDialogContent,
|
|
16
|
+
AlertDialogDescription,
|
|
17
|
+
AlertDialogFooter,
|
|
18
|
+
AlertDialogHeader,
|
|
19
|
+
AlertDialogTitle,
|
|
20
|
+
} from "@greatapps/greatauth-ui/ui";
|
|
5
21
|
import {
|
|
6
22
|
CalendarSync,
|
|
7
23
|
Plug,
|
|
8
24
|
Settings,
|
|
9
25
|
RefreshCw,
|
|
10
|
-
Users,
|
|
11
26
|
Clock,
|
|
12
27
|
Plus,
|
|
28
|
+
MoreVertical,
|
|
29
|
+
Unplug,
|
|
30
|
+
Trash2,
|
|
13
31
|
} from "lucide-react";
|
|
14
32
|
import type { LucideIcon } from "lucide-react";
|
|
15
33
|
import { cn } from "../../lib";
|
|
@@ -23,7 +41,6 @@ const ICON_MAP: Record<string, LucideIcon> = {
|
|
|
23
41
|
Plug,
|
|
24
42
|
Settings,
|
|
25
43
|
RefreshCw,
|
|
26
|
-
Users,
|
|
27
44
|
Clock,
|
|
28
45
|
Plus,
|
|
29
46
|
};
|
|
@@ -60,20 +77,6 @@ const STATE_BADGES: Record<IntegrationCardState, BadgeVariant> = {
|
|
|
60
77
|
},
|
|
61
78
|
};
|
|
62
79
|
|
|
63
|
-
function getActionLabel(card: IntegrationCardData): string {
|
|
64
|
-
if (card.isAddNew) return "Conectar";
|
|
65
|
-
switch (card.state) {
|
|
66
|
-
case "available":
|
|
67
|
-
return "Conectar";
|
|
68
|
-
case "connected":
|
|
69
|
-
return "Configurar";
|
|
70
|
-
case "expired":
|
|
71
|
-
return "Reconectar";
|
|
72
|
-
default:
|
|
73
|
-
return "";
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
80
|
// ---------------------------------------------------------------------------
|
|
78
81
|
// Component
|
|
79
82
|
// ---------------------------------------------------------------------------
|
|
@@ -81,25 +84,36 @@ function getActionLabel(card: IntegrationCardData): string {
|
|
|
81
84
|
export interface IntegrationCardProps {
|
|
82
85
|
card: IntegrationCardData;
|
|
83
86
|
onConnect: (card: IntegrationCardData) => void;
|
|
87
|
+
onReconnect?: (card: IntegrationCardData) => void;
|
|
88
|
+
onDisconnect?: (card: IntegrationCardData) => void;
|
|
89
|
+
onDelete?: (card: IntegrationCardData) => void;
|
|
84
90
|
}
|
|
85
91
|
|
|
86
|
-
export function IntegrationCard({
|
|
87
|
-
|
|
92
|
+
export function IntegrationCard({
|
|
93
|
+
card,
|
|
94
|
+
onConnect,
|
|
95
|
+
onReconnect,
|
|
96
|
+
onDisconnect,
|
|
97
|
+
onDelete,
|
|
98
|
+
}: IntegrationCardProps) {
|
|
99
|
+
const { definition, state, isAddNew, accountLabel } = card;
|
|
88
100
|
const Icon = resolveIcon(definition.icon);
|
|
89
101
|
const isComingSoon = state === "coming_soon";
|
|
90
|
-
const
|
|
102
|
+
const isConnected = state === "connected" || state === "expired";
|
|
91
103
|
|
|
92
|
-
|
|
104
|
+
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
105
|
+
|
|
106
|
+
// "Add new" card — clean outline style with single "Conectar" button
|
|
93
107
|
if (isAddNew) {
|
|
94
108
|
return (
|
|
95
109
|
<div
|
|
96
110
|
className={cn(
|
|
97
|
-
"group relative flex flex-col gap-3 rounded-xl border
|
|
98
|
-
"hover:shadow-md hover:
|
|
111
|
+
"group relative flex flex-col gap-3 rounded-xl border bg-card/50 p-5 transition-all",
|
|
112
|
+
"hover:shadow-md hover:bg-card cursor-pointer",
|
|
99
113
|
)}
|
|
100
114
|
role="button"
|
|
101
115
|
tabIndex={0}
|
|
102
|
-
aria-label={`
|
|
116
|
+
aria-label={`Conectar ${definition.name}`}
|
|
103
117
|
onClick={() => onConnect(card)}
|
|
104
118
|
onKeyDown={(e) => {
|
|
105
119
|
if (e.key === "Enter" || e.key === " ") {
|
|
@@ -109,24 +123,18 @@ export function IntegrationCard({ card, onConnect }: IntegrationCardProps) {
|
|
|
109
123
|
}}
|
|
110
124
|
>
|
|
111
125
|
{/* Header row */}
|
|
112
|
-
<div className="flex items-start
|
|
126
|
+
<div className="flex items-start gap-3">
|
|
113
127
|
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/5 text-primary/60">
|
|
114
128
|
<Icon className="h-5 w-5" />
|
|
115
129
|
</div>
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
{definition.name}
|
|
125
|
-
</h3>
|
|
126
|
-
<p className="text-xs text-muted-foreground/70 leading-relaxed flex items-center gap-1">
|
|
127
|
-
<Plus className="h-3 w-3" />
|
|
128
|
-
Adicionar conta
|
|
129
|
-
</p>
|
|
130
|
+
<div className="flex-1 min-w-0 space-y-0.5">
|
|
131
|
+
<h3 className="text-sm font-semibold leading-tight text-foreground">
|
|
132
|
+
{definition.name}
|
|
133
|
+
</h3>
|
|
134
|
+
<p className="text-xs text-muted-foreground leading-relaxed">
|
|
135
|
+
Conectar nova conta
|
|
136
|
+
</p>
|
|
137
|
+
</div>
|
|
130
138
|
</div>
|
|
131
139
|
|
|
132
140
|
{/* Footer */}
|
|
@@ -140,92 +148,132 @@ export function IntegrationCard({ card, onConnect }: IntegrationCardProps) {
|
|
|
140
148
|
onConnect(card);
|
|
141
149
|
}}
|
|
142
150
|
>
|
|
143
|
-
|
|
151
|
+
Conectar
|
|
144
152
|
</Button>
|
|
145
153
|
</div>
|
|
146
154
|
</div>
|
|
147
155
|
);
|
|
148
156
|
}
|
|
149
157
|
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
onClick={() => !isComingSoon && onConnect(card)}
|
|
166
|
-
onKeyDown={(e) => {
|
|
167
|
-
if (!isComingSoon && (e.key === "Enter" || e.key === " ")) {
|
|
168
|
-
e.preventDefault();
|
|
169
|
-
onConnect(card);
|
|
170
|
-
}
|
|
171
|
-
}}
|
|
172
|
-
>
|
|
173
|
-
{/* Header row */}
|
|
174
|
-
<div className="flex items-start justify-between gap-2">
|
|
175
|
-
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
176
|
-
<Icon className="h-5 w-5" />
|
|
158
|
+
// Coming soon card
|
|
159
|
+
if (isComingSoon) {
|
|
160
|
+
const badge = STATE_BADGES[state];
|
|
161
|
+
return (
|
|
162
|
+
<div
|
|
163
|
+
className="group relative flex flex-col gap-3 rounded-xl border bg-card p-5 opacity-60 cursor-default"
|
|
164
|
+
aria-disabled
|
|
165
|
+
>
|
|
166
|
+
<div className="flex items-start justify-between gap-2">
|
|
167
|
+
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
168
|
+
<Icon className="h-5 w-5" />
|
|
169
|
+
</div>
|
|
170
|
+
<Badge variant="outline" className={cn("text-xs", badge.className)}>
|
|
171
|
+
{badge.label}
|
|
172
|
+
</Badge>
|
|
177
173
|
</div>
|
|
178
|
-
<
|
|
179
|
-
{
|
|
180
|
-
</Badge>
|
|
181
|
-
</div>
|
|
182
|
-
|
|
183
|
-
{/* Name + account label */}
|
|
184
|
-
<div className="space-y-1">
|
|
185
|
-
<h3 className="text-sm font-semibold leading-tight">{definition.name}</h3>
|
|
186
|
-
{accountLabel ? (
|
|
187
|
-
<p className="text-xs text-muted-foreground leading-relaxed truncate" title={accountLabel}>
|
|
188
|
-
{accountLabel}
|
|
189
|
-
</p>
|
|
190
|
-
) : (
|
|
174
|
+
<div className="space-y-1">
|
|
175
|
+
<h3 className="text-sm font-semibold leading-tight">{definition.name}</h3>
|
|
191
176
|
<p className="text-xs text-muted-foreground leading-relaxed">
|
|
192
177
|
{definition.description}
|
|
193
178
|
</p>
|
|
194
|
-
|
|
179
|
+
</div>
|
|
195
180
|
</div>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
196
183
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
{sharedByAgentsCount > 0 ? (
|
|
200
|
-
<Tooltip>
|
|
201
|
-
<TooltipTrigger asChild>
|
|
202
|
-
<span className="inline-flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400">
|
|
203
|
-
<Users className="h-3.5 w-3.5" />
|
|
204
|
-
Compartilhada
|
|
205
|
-
</span>
|
|
206
|
-
</TooltipTrigger>
|
|
207
|
-
<TooltipContent>
|
|
208
|
-
Esta credencial está disponível para todos os agentes da conta
|
|
209
|
-
</TooltipContent>
|
|
210
|
-
</Tooltip>
|
|
211
|
-
) : (
|
|
212
|
-
<span />
|
|
213
|
-
)}
|
|
184
|
+
// Connected / expired card
|
|
185
|
+
const badge = STATE_BADGES[state];
|
|
214
186
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
187
|
+
return (
|
|
188
|
+
<>
|
|
189
|
+
<div
|
|
190
|
+
className="group relative flex flex-col gap-3 rounded-xl border bg-card p-5 transition-shadow hover:shadow-md"
|
|
191
|
+
>
|
|
192
|
+
{/* Header row */}
|
|
193
|
+
<div className="flex items-start justify-between gap-2">
|
|
194
|
+
<div className="flex items-start gap-3 min-w-0 flex-1">
|
|
195
|
+
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
196
|
+
<Icon className="h-5 w-5" />
|
|
197
|
+
</div>
|
|
198
|
+
<div className="flex-1 min-w-0 space-y-0.5">
|
|
199
|
+
<h3 className="text-sm font-semibold leading-tight">{definition.name}</h3>
|
|
200
|
+
{accountLabel && (
|
|
201
|
+
<p className="text-xs text-muted-foreground leading-relaxed truncate" title={accountLabel}>
|
|
202
|
+
{accountLabel}
|
|
203
|
+
</p>
|
|
204
|
+
)}
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
<Badge variant="outline" className={cn("text-xs shrink-0", badge.className)}>
|
|
208
|
+
{badge.label}
|
|
209
|
+
</Badge>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
{/* Footer with "Configurar" dropdown */}
|
|
213
|
+
<div className="mt-auto flex items-center justify-end gap-2 pt-1">
|
|
214
|
+
<DropdownMenu>
|
|
215
|
+
<DropdownMenuTrigger asChild>
|
|
216
|
+
<Button
|
|
217
|
+
variant="outline"
|
|
218
|
+
size="sm"
|
|
219
|
+
className="text-xs gap-1.5"
|
|
220
|
+
>
|
|
221
|
+
<Settings className="h-3.5 w-3.5" />
|
|
222
|
+
Configurar
|
|
223
|
+
</Button>
|
|
224
|
+
</DropdownMenuTrigger>
|
|
225
|
+
<DropdownMenuContent align="end">
|
|
226
|
+
<DropdownMenuItem
|
|
227
|
+
onClick={() => onReconnect?.(card)}
|
|
228
|
+
className="gap-2"
|
|
229
|
+
>
|
|
230
|
+
<RefreshCw className="h-4 w-4" />
|
|
231
|
+
Reconectar
|
|
232
|
+
</DropdownMenuItem>
|
|
233
|
+
<DropdownMenuItem
|
|
234
|
+
onClick={() => onDisconnect?.(card)}
|
|
235
|
+
className="gap-2"
|
|
236
|
+
>
|
|
237
|
+
<Unplug className="h-4 w-4" />
|
|
238
|
+
Desconectar
|
|
239
|
+
</DropdownMenuItem>
|
|
240
|
+
<DropdownMenuItem
|
|
241
|
+
onClick={() => setDeleteDialogOpen(true)}
|
|
242
|
+
className="gap-2 text-destructive focus:text-destructive"
|
|
243
|
+
>
|
|
244
|
+
<Trash2 className="h-4 w-4" />
|
|
245
|
+
Remover
|
|
246
|
+
</DropdownMenuItem>
|
|
247
|
+
</DropdownMenuContent>
|
|
248
|
+
</DropdownMenu>
|
|
249
|
+
</div>
|
|
228
250
|
</div>
|
|
229
|
-
|
|
251
|
+
|
|
252
|
+
{/* Delete confirmation dialog */}
|
|
253
|
+
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
|
254
|
+
<AlertDialogContent>
|
|
255
|
+
<AlertDialogHeader>
|
|
256
|
+
<AlertDialogTitle>Remover integração?</AlertDialogTitle>
|
|
257
|
+
<AlertDialogDescription>
|
|
258
|
+
Esta ação vai remover a credencial
|
|
259
|
+
{accountLabel ? ` (${accountLabel})` : ""} de {definition.name}.
|
|
260
|
+
Esta ação não pode ser desfeita.
|
|
261
|
+
</AlertDialogDescription>
|
|
262
|
+
</AlertDialogHeader>
|
|
263
|
+
<AlertDialogFooter>
|
|
264
|
+
<AlertDialogCancel>Cancelar</AlertDialogCancel>
|
|
265
|
+
<AlertDialogAction
|
|
266
|
+
onClick={() => {
|
|
267
|
+
onDelete?.(card);
|
|
268
|
+
setDeleteDialogOpen(false);
|
|
269
|
+
}}
|
|
270
|
+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
271
|
+
>
|
|
272
|
+
Remover
|
|
273
|
+
</AlertDialogAction>
|
|
274
|
+
</AlertDialogFooter>
|
|
275
|
+
</AlertDialogContent>
|
|
276
|
+
</AlertDialog>
|
|
277
|
+
</>
|
|
230
278
|
);
|
|
231
279
|
}
|
|
@@ -347,10 +347,13 @@ export function IntegrationWizard({
|
|
|
347
347
|
//
|
|
348
348
|
// For OAuth, the credential was already created by the backend callback.
|
|
349
349
|
// The parent component handles any additional linking (agent_tool creation)
|
|
350
|
-
// via the onComplete callback.
|
|
350
|
+
// and query invalidation via the onComplete callback.
|
|
351
|
+
//
|
|
352
|
+
// Call onComplete BEFORE closing the dialog so query invalidation
|
|
353
|
+
// triggers while the dialog is still mounted (avoids stale UI).
|
|
351
354
|
|
|
352
355
|
onComplete();
|
|
353
|
-
|
|
356
|
+
|
|
354
357
|
toast.success(
|
|
355
358
|
`${integration.name} ${isReconnect ? "reconectado" : "configurado"} com sucesso!`,
|
|
356
359
|
);
|
|
@@ -4,12 +4,14 @@ import { useCallback } from "react";
|
|
|
4
4
|
import type { GagentsHookConfig } from "../../hooks/types";
|
|
5
5
|
import { useIntegrationState } from "../../hooks/use-integrations";
|
|
6
6
|
import { useAgentTools, useAddAgentTool, useRemoveAgentTool } from "../../hooks/use-agent-tools";
|
|
7
|
-
import { useTools } from "../../hooks/use-tools";
|
|
7
|
+
import { useCreateTool, useTools } from "../../hooks/use-tools";
|
|
8
8
|
import { Switch, Tooltip, TooltipContent, TooltipTrigger } from "@greatapps/greatauth-ui/ui";
|
|
9
9
|
import { Plug, Loader2 } from "lucide-react";
|
|
10
10
|
import type { LucideIcon } from "lucide-react";
|
|
11
11
|
import { CalendarSync } from "lucide-react";
|
|
12
12
|
import { cn } from "../../lib";
|
|
13
|
+
import type { IntegrationCardData } from "../../hooks/use-integrations";
|
|
14
|
+
import type { Tool } from "../../types";
|
|
13
15
|
|
|
14
16
|
// ---------------------------------------------------------------------------
|
|
15
17
|
// Icon mapping
|
|
@@ -24,6 +26,14 @@ function resolveIcon(name: string): LucideIcon {
|
|
|
24
26
|
return ICON_MAP[name] ?? Plug;
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Integration custom_instructions per slug
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
const INTEGRATION_INSTRUCTIONS: Record<string, string> = {
|
|
34
|
+
"google-calendar": `Você tem acesso ao Google Calendar através da integração google-calendar.\nFunções disponíveis:\n- google_calendar_setup_oauth: Configurar conexão OAuth\n- google_calendar_check_status: Verificar status da conexão\n- google_calendar_list_events: Listar eventos do calendário\n- google_calendar_create_event: Criar novo evento\n- google_calendar_update_event: Atualizar evento existente\n- google_calendar_delete_event: Cancelar/remover evento\n\nUse EXATAMENTE os nomes de função listados acima.`,
|
|
35
|
+
};
|
|
36
|
+
|
|
27
37
|
// ---------------------------------------------------------------------------
|
|
28
38
|
// Props
|
|
29
39
|
// ---------------------------------------------------------------------------
|
|
@@ -41,14 +51,15 @@ export function IntegrationsTab({
|
|
|
41
51
|
config,
|
|
42
52
|
agentId,
|
|
43
53
|
}: IntegrationsTabProps) {
|
|
44
|
-
const { cards, isLoading } = useIntegrationState(config,
|
|
45
|
-
const { data: toolsData } = useTools(config);
|
|
54
|
+
const { cards, isLoading } = useIntegrationState(config, agentId);
|
|
46
55
|
const { data: agentToolsData, isLoading: agentToolsLoading } = useAgentTools(config, agentId);
|
|
56
|
+
const { data: toolsData } = useTools(config);
|
|
47
57
|
const addAgentTool = useAddAgentTool(config);
|
|
48
58
|
const removeAgentTool = useRemoveAgentTool(config);
|
|
59
|
+
const createTool = useCreateTool(config);
|
|
49
60
|
|
|
50
|
-
const tools = toolsData?.data ?? [];
|
|
51
61
|
const agentTools = agentToolsData?.data ?? [];
|
|
62
|
+
const allTools = toolsData?.data ?? [];
|
|
52
63
|
|
|
53
64
|
// Only show connected credentials (account-level)
|
|
54
65
|
const connectedCards = cards.filter(
|
|
@@ -56,26 +67,58 @@ export function IntegrationsTab({
|
|
|
56
67
|
);
|
|
57
68
|
|
|
58
69
|
const handleToggle = useCallback(
|
|
59
|
-
(
|
|
60
|
-
// Find the tool record matching this integration slug
|
|
61
|
-
const tool = tools.find((t) => t.slug === toolSlug);
|
|
62
|
-
if (!tool) return;
|
|
63
|
-
|
|
70
|
+
async (card: IntegrationCardData, checked: boolean) => {
|
|
64
71
|
if (checked) {
|
|
65
|
-
|
|
72
|
+
let toolId = card.tool?.id;
|
|
73
|
+
|
|
74
|
+
// If no tool record exists for this integration, create one on-the-fly
|
|
75
|
+
if (!toolId) {
|
|
76
|
+
const existingTool = allTools.find((t) => t.slug === card.definition.slug);
|
|
77
|
+
if (existingTool) {
|
|
78
|
+
toolId = existingTool.id;
|
|
79
|
+
} else {
|
|
80
|
+
try {
|
|
81
|
+
const result = await createTool.mutateAsync({
|
|
82
|
+
name: card.definition.name,
|
|
83
|
+
slug: card.definition.slug,
|
|
84
|
+
type: "integration",
|
|
85
|
+
description: card.definition.description,
|
|
86
|
+
});
|
|
87
|
+
const d = result?.data;
|
|
88
|
+
toolId = (Array.isArray(d) ? d[0]?.id : (d as Tool | undefined)?.id) ?? undefined;
|
|
89
|
+
if (!toolId) {
|
|
90
|
+
console.error("[IntegrationsTab] Failed to create tool — no ID returned");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error("[IntegrationsTab] Error creating tool:", err);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Create agent_tool with custom_instructions for the integration
|
|
101
|
+
const customInstructions = INTEGRATION_INSTRUCTIONS[card.definition.slug];
|
|
66
102
|
addAgentTool.mutate({
|
|
67
103
|
idAgent: agentId,
|
|
68
|
-
body: {
|
|
104
|
+
body: {
|
|
105
|
+
id_tool: toolId,
|
|
106
|
+
enabled: true,
|
|
107
|
+
...(customInstructions ? { custom_instructions: customInstructions } : {}),
|
|
108
|
+
},
|
|
69
109
|
});
|
|
70
110
|
} else {
|
|
71
111
|
// Find the agent_tool to remove
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
74
|
-
|
|
112
|
+
const toolId = card.tool?.id;
|
|
113
|
+
if (toolId) {
|
|
114
|
+
const agentTool = agentTools.find((at) => at.id_tool === toolId);
|
|
115
|
+
if (agentTool) {
|
|
116
|
+
removeAgentTool.mutate({ idAgent: agentId, id: agentTool.id });
|
|
117
|
+
}
|
|
75
118
|
}
|
|
76
119
|
}
|
|
77
120
|
},
|
|
78
|
-
[
|
|
121
|
+
[agentTools, allTools, agentId, addAgentTool, removeAgentTool, createTool],
|
|
79
122
|
);
|
|
80
123
|
|
|
81
124
|
// Loading state
|
|
@@ -110,11 +153,8 @@ export function IntegrationsTab({
|
|
|
110
153
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
111
154
|
{connectedCards.map((card) => {
|
|
112
155
|
const Icon = resolveIcon(card.definition.icon);
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
? agentTools.some((at) => at.id_tool === tool.id)
|
|
116
|
-
: false;
|
|
117
|
-
const isMutating = addAgentTool.isPending || removeAgentTool.isPending;
|
|
156
|
+
const isLinked = card.linkedToAgent;
|
|
157
|
+
const isMutating = addAgentTool.isPending || removeAgentTool.isPending || createTool.isPending;
|
|
118
158
|
|
|
119
159
|
return (
|
|
120
160
|
<div
|
|
@@ -149,14 +189,11 @@ export function IntegrationsTab({
|
|
|
149
189
|
)}
|
|
150
190
|
</div>
|
|
151
191
|
|
|
152
|
-
{/* Toggle */}
|
|
192
|
+
{/* Toggle — no longer blocked by missing tool record */}
|
|
153
193
|
<Switch
|
|
154
194
|
checked={isLinked}
|
|
155
195
|
disabled={isMutating}
|
|
156
|
-
onCheckedChange={(checked) =>
|
|
157
|
-
card.credentialId &&
|
|
158
|
-
handleToggle(card.credentialId, card.definition.slug, checked)
|
|
159
|
-
}
|
|
196
|
+
onCheckedChange={(checked) => handleToggle(card, checked)}
|
|
160
197
|
aria-label={`${isLinked ? "Desativar" : "Ativar"} ${card.definition.name} para este agente`}
|
|
161
198
|
/>
|
|
162
199
|
</div>
|