@greatapps/greatagents-ui 0.3.14 → 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 +6 -2
- package/dist/index.js +330 -221
- 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/integration-card.tsx +160 -94
- package/src/components/capabilities/integration-wizard.tsx +5 -2
- package/src/components/capabilities/integrations-tab.tsx +61 -13
- package/src/hooks/use-agent-tools.ts +1 -1
- package/src/pages/integrations-management-page.tsx +86 -156
- 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"}
|
|
@@ -1,7 +1,23 @@
|
|
|
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,
|
|
@@ -9,6 +25,9 @@ import {
|
|
|
9
25
|
RefreshCw,
|
|
10
26
|
Clock,
|
|
11
27
|
Plus,
|
|
28
|
+
MoreVertical,
|
|
29
|
+
Unplug,
|
|
30
|
+
Trash2,
|
|
12
31
|
} from "lucide-react";
|
|
13
32
|
import type { LucideIcon } from "lucide-react";
|
|
14
33
|
import { cn } from "../../lib";
|
|
@@ -58,20 +77,6 @@ const STATE_BADGES: Record<IntegrationCardState, BadgeVariant> = {
|
|
|
58
77
|
},
|
|
59
78
|
};
|
|
60
79
|
|
|
61
|
-
function getActionLabel(card: IntegrationCardData): string {
|
|
62
|
-
if (card.isAddNew) return "Conectar";
|
|
63
|
-
switch (card.state) {
|
|
64
|
-
case "available":
|
|
65
|
-
return "Conectar";
|
|
66
|
-
case "connected":
|
|
67
|
-
return "Configurar";
|
|
68
|
-
case "expired":
|
|
69
|
-
return "Reconectar";
|
|
70
|
-
default:
|
|
71
|
-
return "";
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
80
|
// ---------------------------------------------------------------------------
|
|
76
81
|
// Component
|
|
77
82
|
// ---------------------------------------------------------------------------
|
|
@@ -79,25 +84,36 @@ function getActionLabel(card: IntegrationCardData): string {
|
|
|
79
84
|
export interface IntegrationCardProps {
|
|
80
85
|
card: IntegrationCardData;
|
|
81
86
|
onConnect: (card: IntegrationCardData) => void;
|
|
87
|
+
onReconnect?: (card: IntegrationCardData) => void;
|
|
88
|
+
onDisconnect?: (card: IntegrationCardData) => void;
|
|
89
|
+
onDelete?: (card: IntegrationCardData) => void;
|
|
82
90
|
}
|
|
83
91
|
|
|
84
|
-
export function IntegrationCard({
|
|
92
|
+
export function IntegrationCard({
|
|
93
|
+
card,
|
|
94
|
+
onConnect,
|
|
95
|
+
onReconnect,
|
|
96
|
+
onDisconnect,
|
|
97
|
+
onDelete,
|
|
98
|
+
}: IntegrationCardProps) {
|
|
85
99
|
const { definition, state, isAddNew, accountLabel } = card;
|
|
86
100
|
const Icon = resolveIcon(definition.icon);
|
|
87
101
|
const isComingSoon = state === "coming_soon";
|
|
88
|
-
const
|
|
102
|
+
const isConnected = state === "connected" || state === "expired";
|
|
89
103
|
|
|
90
|
-
|
|
104
|
+
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
105
|
+
|
|
106
|
+
// "Add new" card — clean outline style with single "Conectar" button
|
|
91
107
|
if (isAddNew) {
|
|
92
108
|
return (
|
|
93
109
|
<div
|
|
94
110
|
className={cn(
|
|
95
|
-
"group relative flex flex-col gap-3 rounded-xl border
|
|
96
|
-
"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",
|
|
97
113
|
)}
|
|
98
114
|
role="button"
|
|
99
115
|
tabIndex={0}
|
|
100
|
-
aria-label={`
|
|
116
|
+
aria-label={`Conectar ${definition.name}`}
|
|
101
117
|
onClick={() => onConnect(card)}
|
|
102
118
|
onKeyDown={(e) => {
|
|
103
119
|
if (e.key === "Enter" || e.key === " ") {
|
|
@@ -107,24 +123,18 @@ export function IntegrationCard({ card, onConnect }: IntegrationCardProps) {
|
|
|
107
123
|
}}
|
|
108
124
|
>
|
|
109
125
|
{/* Header row */}
|
|
110
|
-
<div className="flex items-start
|
|
126
|
+
<div className="flex items-start gap-3">
|
|
111
127
|
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/5 text-primary/60">
|
|
112
128
|
<Icon className="h-5 w-5" />
|
|
113
129
|
</div>
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
{definition.name}
|
|
123
|
-
</h3>
|
|
124
|
-
<p className="text-xs text-muted-foreground/70 leading-relaxed flex items-center gap-1">
|
|
125
|
-
<Plus className="h-3 w-3" />
|
|
126
|
-
Adicionar conta
|
|
127
|
-
</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>
|
|
128
138
|
</div>
|
|
129
139
|
|
|
130
140
|
{/* Footer */}
|
|
@@ -138,76 +148,132 @@ export function IntegrationCard({ card, onConnect }: IntegrationCardProps) {
|
|
|
138
148
|
onConnect(card);
|
|
139
149
|
}}
|
|
140
150
|
>
|
|
141
|
-
|
|
151
|
+
Conectar
|
|
142
152
|
</Button>
|
|
143
153
|
</div>
|
|
144
154
|
</div>
|
|
145
155
|
);
|
|
146
156
|
}
|
|
147
157
|
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
onClick={() => !isComingSoon && onConnect(card)}
|
|
164
|
-
onKeyDown={(e) => {
|
|
165
|
-
if (!isComingSoon && (e.key === "Enter" || e.key === " ")) {
|
|
166
|
-
e.preventDefault();
|
|
167
|
-
onConnect(card);
|
|
168
|
-
}
|
|
169
|
-
}}
|
|
170
|
-
>
|
|
171
|
-
{/* Header row */}
|
|
172
|
-
<div className="flex items-start justify-between gap-2">
|
|
173
|
-
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
174
|
-
<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>
|
|
175
173
|
</div>
|
|
176
|
-
<
|
|
177
|
-
{
|
|
178
|
-
</Badge>
|
|
179
|
-
</div>
|
|
180
|
-
|
|
181
|
-
{/* Name + account label */}
|
|
182
|
-
<div className="space-y-1">
|
|
183
|
-
<h3 className="text-sm font-semibold leading-tight">{definition.name}</h3>
|
|
184
|
-
{accountLabel ? (
|
|
185
|
-
<p className="text-xs text-muted-foreground leading-relaxed truncate" title={accountLabel}>
|
|
186
|
-
{accountLabel}
|
|
187
|
-
</p>
|
|
188
|
-
) : (
|
|
174
|
+
<div className="space-y-1">
|
|
175
|
+
<h3 className="text-sm font-semibold leading-tight">{definition.name}</h3>
|
|
189
176
|
<p className="text-xs text-muted-foreground leading-relaxed">
|
|
190
177
|
{definition.description}
|
|
191
178
|
</p>
|
|
192
|
-
|
|
179
|
+
</div>
|
|
193
180
|
</div>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
194
183
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
184
|
+
// Connected / expired card
|
|
185
|
+
const badge = STATE_BADGES[state];
|
|
186
|
+
|
|
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>
|
|
210
250
|
</div>
|
|
211
|
-
|
|
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
|
+
</>
|
|
212
278
|
);
|
|
213
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,11 +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 { useCreateTool, useTools } from "../../hooks/use-tools";
|
|
7
8
|
import { Switch, Tooltip, TooltipContent, TooltipTrigger } from "@greatapps/greatauth-ui/ui";
|
|
8
9
|
import { Plug, Loader2 } from "lucide-react";
|
|
9
10
|
import type { LucideIcon } from "lucide-react";
|
|
10
11
|
import { CalendarSync } from "lucide-react";
|
|
11
12
|
import { cn } from "../../lib";
|
|
13
|
+
import type { IntegrationCardData } from "../../hooks/use-integrations";
|
|
14
|
+
import type { Tool } from "../../types";
|
|
12
15
|
|
|
13
16
|
// ---------------------------------------------------------------------------
|
|
14
17
|
// Icon mapping
|
|
@@ -23,6 +26,14 @@ function resolveIcon(name: string): LucideIcon {
|
|
|
23
26
|
return ICON_MAP[name] ?? Plug;
|
|
24
27
|
}
|
|
25
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
|
+
|
|
26
37
|
// ---------------------------------------------------------------------------
|
|
27
38
|
// Props
|
|
28
39
|
// ---------------------------------------------------------------------------
|
|
@@ -42,10 +53,13 @@ export function IntegrationsTab({
|
|
|
42
53
|
}: IntegrationsTabProps) {
|
|
43
54
|
const { cards, isLoading } = useIntegrationState(config, agentId);
|
|
44
55
|
const { data: agentToolsData, isLoading: agentToolsLoading } = useAgentTools(config, agentId);
|
|
56
|
+
const { data: toolsData } = useTools(config);
|
|
45
57
|
const addAgentTool = useAddAgentTool(config);
|
|
46
58
|
const removeAgentTool = useRemoveAgentTool(config);
|
|
59
|
+
const createTool = useCreateTool(config);
|
|
47
60
|
|
|
48
61
|
const agentTools = agentToolsData?.data ?? [];
|
|
62
|
+
const allTools = toolsData?.data ?? [];
|
|
49
63
|
|
|
50
64
|
// Only show connected credentials (account-level)
|
|
51
65
|
const connectedCards = cards.filter(
|
|
@@ -53,22 +67,58 @@ export function IntegrationsTab({
|
|
|
53
67
|
);
|
|
54
68
|
|
|
55
69
|
const handleToggle = useCallback(
|
|
56
|
-
(
|
|
70
|
+
async (card: IntegrationCardData, checked: boolean) => {
|
|
57
71
|
if (checked) {
|
|
58
|
-
|
|
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];
|
|
59
102
|
addAgentTool.mutate({
|
|
60
103
|
idAgent: agentId,
|
|
61
|
-
body: {
|
|
104
|
+
body: {
|
|
105
|
+
id_tool: toolId,
|
|
106
|
+
enabled: true,
|
|
107
|
+
...(customInstructions ? { custom_instructions: customInstructions } : {}),
|
|
108
|
+
},
|
|
62
109
|
});
|
|
63
110
|
} else {
|
|
64
111
|
// Find the agent_tool to remove
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
67
|
-
|
|
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
|
+
}
|
|
68
118
|
}
|
|
69
119
|
}
|
|
70
120
|
},
|
|
71
|
-
[agentTools, agentId, addAgentTool, removeAgentTool],
|
|
121
|
+
[agentTools, allTools, agentId, addAgentTool, removeAgentTool, createTool],
|
|
72
122
|
);
|
|
73
123
|
|
|
74
124
|
// Loading state
|
|
@@ -104,7 +154,7 @@ export function IntegrationsTab({
|
|
|
104
154
|
{connectedCards.map((card) => {
|
|
105
155
|
const Icon = resolveIcon(card.definition.icon);
|
|
106
156
|
const isLinked = card.linkedToAgent;
|
|
107
|
-
const isMutating = addAgentTool.isPending || removeAgentTool.isPending;
|
|
157
|
+
const isMutating = addAgentTool.isPending || removeAgentTool.isPending || createTool.isPending;
|
|
108
158
|
|
|
109
159
|
return (
|
|
110
160
|
<div
|
|
@@ -139,13 +189,11 @@ export function IntegrationsTab({
|
|
|
139
189
|
)}
|
|
140
190
|
</div>
|
|
141
191
|
|
|
142
|
-
{/* Toggle */}
|
|
192
|
+
{/* Toggle — no longer blocked by missing tool record */}
|
|
143
193
|
<Switch
|
|
144
194
|
checked={isLinked}
|
|
145
|
-
disabled={isMutating
|
|
146
|
-
onCheckedChange={(checked) =>
|
|
147
|
-
card.tool && handleToggle(card.tool.id, checked)
|
|
148
|
-
}
|
|
195
|
+
disabled={isMutating}
|
|
196
|
+
onCheckedChange={(checked) => handleToggle(card, checked)}
|
|
149
197
|
aria-label={`${isLinked ? "Desativar" : "Ativar"} ${card.definition.name} para este agente`}
|
|
150
198
|
/>
|
|
151
199
|
</div>
|
|
@@ -23,7 +23,7 @@ export function useAddAgentTool(config: GagentsHookConfig) {
|
|
|
23
23
|
body,
|
|
24
24
|
}: {
|
|
25
25
|
idAgent: number;
|
|
26
|
-
body: { id_tool: number; enabled?: boolean };
|
|
26
|
+
body: { id_tool: number; enabled?: boolean; custom_instructions?: string };
|
|
27
27
|
}) => client.addAgentTool(config.accountId, idAgent, body),
|
|
28
28
|
onSuccess: () => {
|
|
29
29
|
queryClient.invalidateQueries({ queryKey: ["greatagents", "agent-tools"] });
|