@greatapps/greatagents-ui 0.1.0 → 0.2.1
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 +178 -1
- package/dist/index.js +3378 -0
- package/dist/index.js.map +1 -1
- package/package.json +13 -3
- package/src/components/agents/agent-edit-form.tsx +218 -0
- package/src/components/agents/agent-form-dialog.tsx +177 -0
- package/src/components/agents/agent-objectives-list.tsx +406 -0
- package/src/components/agents/agent-prompt-editor.tsx +406 -0
- package/src/components/agents/agent-tabs.tsx +64 -0
- package/src/components/agents/agent-tools-list.tsx +377 -0
- package/src/components/agents/agents-table.tsx +205 -0
- package/src/components/conversations/agent-conversations-panel.tsx +44 -0
- package/src/components/conversations/agent-conversations-table.tsx +160 -0
- package/src/components/conversations/conversation-view.tsx +124 -0
- package/src/components/tools/tool-credentials-form.tsx +572 -0
- package/src/components/tools/tool-form-dialog.tsx +342 -0
- package/src/components/tools/tools-table.tsx +225 -0
- package/src/components/ui/sortable.tsx +577 -0
- package/src/index.ts +19 -0
- package/src/lib/compose-refs.ts +44 -0
- package/src/pages/agent-detail-page.tsx +111 -0
- package/src/pages/agents-page.tsx +45 -0
- package/src/pages/credentials-page.tsx +50 -0
- package/src/pages/index.ts +4 -0
- package/src/pages/tools-page.tsx +52 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { Agent, AgentTool, Tool, ToolCredential } from "../../types";
|
|
3
|
+
import type { GagentsHookConfig } from "../../hooks/types";
|
|
4
|
+
import {
|
|
5
|
+
useAgentTools,
|
|
6
|
+
useAddAgentTool,
|
|
7
|
+
useRemoveAgentTool,
|
|
8
|
+
useUpdateAgentTool,
|
|
9
|
+
} from "../../hooks";
|
|
10
|
+
import { useTools } from "../../hooks";
|
|
11
|
+
import { useToolCredentials } from "../../hooks";
|
|
12
|
+
import {
|
|
13
|
+
Switch,
|
|
14
|
+
Badge,
|
|
15
|
+
Button,
|
|
16
|
+
Skeleton,
|
|
17
|
+
AlertDialog,
|
|
18
|
+
AlertDialogAction,
|
|
19
|
+
AlertDialogCancel,
|
|
20
|
+
AlertDialogContent,
|
|
21
|
+
AlertDialogDescription,
|
|
22
|
+
AlertDialogFooter,
|
|
23
|
+
AlertDialogHeader,
|
|
24
|
+
AlertDialogTitle,
|
|
25
|
+
Popover,
|
|
26
|
+
PopoverContent,
|
|
27
|
+
PopoverTrigger,
|
|
28
|
+
Input,
|
|
29
|
+
Textarea,
|
|
30
|
+
Dialog,
|
|
31
|
+
DialogContent,
|
|
32
|
+
DialogHeader,
|
|
33
|
+
DialogTitle,
|
|
34
|
+
DialogFooter,
|
|
35
|
+
Label,
|
|
36
|
+
Select,
|
|
37
|
+
SelectContent,
|
|
38
|
+
SelectItem,
|
|
39
|
+
SelectTrigger,
|
|
40
|
+
SelectValue,
|
|
41
|
+
} from "@greatapps/greatauth-ui/ui";
|
|
42
|
+
import {
|
|
43
|
+
Trash2,
|
|
44
|
+
Plus,
|
|
45
|
+
Wrench,
|
|
46
|
+
Settings2,
|
|
47
|
+
} from "lucide-react";
|
|
48
|
+
import { toast } from "sonner";
|
|
49
|
+
|
|
50
|
+
interface AgentToolsListProps {
|
|
51
|
+
agent: Agent;
|
|
52
|
+
config: GagentsHookConfig;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function AgentToolsList({ agent, config }: AgentToolsListProps) {
|
|
56
|
+
const { data: agentToolsData, isLoading } = useAgentTools(config, agent.id);
|
|
57
|
+
const { data: allToolsData } = useTools(config);
|
|
58
|
+
const addMutation = useAddAgentTool(config);
|
|
59
|
+
const removeMutation = useRemoveAgentTool(config);
|
|
60
|
+
const updateMutation = useUpdateAgentTool(config);
|
|
61
|
+
|
|
62
|
+
const [removeTarget, setRemoveTarget] = useState<AgentTool | null>(null);
|
|
63
|
+
const [addOpen, setAddOpen] = useState(false);
|
|
64
|
+
const [search, setSearch] = useState("");
|
|
65
|
+
const [configTarget, setConfigTarget] = useState<AgentTool | null>(null);
|
|
66
|
+
const [configInstructions, setConfigInstructions] = useState("");
|
|
67
|
+
const [configCredentialId, setConfigCredentialId] = useState<string>("");
|
|
68
|
+
|
|
69
|
+
const { data: credentialsData } = useToolCredentials(config);
|
|
70
|
+
const allCredentials: ToolCredential[] = credentialsData?.data || [];
|
|
71
|
+
|
|
72
|
+
const agentTools = agentToolsData?.data || [];
|
|
73
|
+
const allTools = allToolsData?.data || [];
|
|
74
|
+
const assignedToolIds = new Set(agentTools.map((at) => at.id_tool));
|
|
75
|
+
const availableTools = allTools.filter((t) => !assignedToolIds.has(t.id));
|
|
76
|
+
const filteredAvailable = availableTools.filter((t) =>
|
|
77
|
+
t.name.toLowerCase().includes(search.toLowerCase()),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
function getToolInfo(idTool: number): Tool | undefined {
|
|
81
|
+
return allTools.find((t) => t.id === idTool);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function handleToggleEnabled(agentTool: AgentTool, checked: boolean) {
|
|
85
|
+
try {
|
|
86
|
+
await updateMutation.mutateAsync({
|
|
87
|
+
idAgent: agent.id,
|
|
88
|
+
id: agentTool.id,
|
|
89
|
+
body: { enabled: checked },
|
|
90
|
+
});
|
|
91
|
+
toast.success(checked ? "Ferramenta ativada" : "Ferramenta desativada");
|
|
92
|
+
} catch (err) {
|
|
93
|
+
toast.error(
|
|
94
|
+
err instanceof Error ? err.message : "Erro ao alterar estado da ferramenta",
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function handleAdd(tool: Tool) {
|
|
100
|
+
try {
|
|
101
|
+
await addMutation.mutateAsync({
|
|
102
|
+
idAgent: agent.id,
|
|
103
|
+
body: { id_tool: tool.id },
|
|
104
|
+
});
|
|
105
|
+
toast.success("Ferramenta adicionada");
|
|
106
|
+
setAddOpen(false);
|
|
107
|
+
setSearch("");
|
|
108
|
+
} catch (err) {
|
|
109
|
+
toast.error(
|
|
110
|
+
err instanceof Error ? err.message : "Erro ao adicionar ferramenta",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function handleRemove() {
|
|
116
|
+
if (!removeTarget) return;
|
|
117
|
+
try {
|
|
118
|
+
await removeMutation.mutateAsync({
|
|
119
|
+
idAgent: agent.id,
|
|
120
|
+
id: removeTarget.id,
|
|
121
|
+
});
|
|
122
|
+
toast.success("Ferramenta removida");
|
|
123
|
+
} catch (err) {
|
|
124
|
+
toast.error(
|
|
125
|
+
err instanceof Error ? err.message : "Erro ao remover ferramenta",
|
|
126
|
+
);
|
|
127
|
+
} finally {
|
|
128
|
+
setRemoveTarget(null);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function openConfig(agentTool: AgentTool) {
|
|
133
|
+
setConfigTarget(agentTool);
|
|
134
|
+
setConfigInstructions(agentTool.custom_instructions || "");
|
|
135
|
+
setConfigCredentialId(agentTool.id_tool_credential ? String(agentTool.id_tool_credential) : "");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function handleSaveConfig() {
|
|
139
|
+
if (!configTarget) return;
|
|
140
|
+
try {
|
|
141
|
+
const newCredentialId = configCredentialId ? parseInt(configCredentialId, 10) : null;
|
|
142
|
+
await updateMutation.mutateAsync({
|
|
143
|
+
idAgent: agent.id,
|
|
144
|
+
id: configTarget.id,
|
|
145
|
+
body: {
|
|
146
|
+
custom_instructions: configInstructions.trim() || null,
|
|
147
|
+
id_tool_credential: newCredentialId,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
toast.success("Configuração atualizada");
|
|
151
|
+
setConfigTarget(null);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
toast.error(
|
|
154
|
+
err instanceof Error ? err.message : "Erro ao atualizar configuração",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (isLoading) {
|
|
160
|
+
return (
|
|
161
|
+
<div className="space-y-3 p-4">
|
|
162
|
+
{Array.from({ length: 3 }).map((_, i) => (
|
|
163
|
+
<Skeleton key={i} className="h-14 w-full" />
|
|
164
|
+
))}
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<div className="space-y-4 p-4">
|
|
171
|
+
<div className="flex items-center justify-between">
|
|
172
|
+
<h3 className="text-sm font-medium text-muted-foreground">
|
|
173
|
+
{agentTools.length} ferramenta{agentTools.length !== 1 ? "s" : ""} associada{agentTools.length !== 1 ? "s" : ""}
|
|
174
|
+
</h3>
|
|
175
|
+
<Popover open={addOpen} onOpenChange={setAddOpen}>
|
|
176
|
+
<PopoverTrigger asChild>
|
|
177
|
+
<Button size="sm" disabled={availableTools.length === 0}>
|
|
178
|
+
<Plus className="mr-2 h-4 w-4" />
|
|
179
|
+
Adicionar Ferramenta
|
|
180
|
+
</Button>
|
|
181
|
+
</PopoverTrigger>
|
|
182
|
+
<PopoverContent className="w-72 p-0" align="end">
|
|
183
|
+
<div className="p-2">
|
|
184
|
+
<Input
|
|
185
|
+
placeholder="Buscar ferramenta..."
|
|
186
|
+
value={search}
|
|
187
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
188
|
+
className="h-8"
|
|
189
|
+
/>
|
|
190
|
+
</div>
|
|
191
|
+
<div className="max-h-48 overflow-y-auto">
|
|
192
|
+
{filteredAvailable.length === 0 ? (
|
|
193
|
+
<p className="p-3 text-center text-sm text-muted-foreground">
|
|
194
|
+
Nenhuma ferramenta disponível
|
|
195
|
+
</p>
|
|
196
|
+
) : (
|
|
197
|
+
filteredAvailable.map((tool) => (
|
|
198
|
+
<button
|
|
199
|
+
key={tool.id}
|
|
200
|
+
type="button"
|
|
201
|
+
className="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-accent"
|
|
202
|
+
onClick={() => handleAdd(tool)}
|
|
203
|
+
disabled={addMutation.isPending}
|
|
204
|
+
>
|
|
205
|
+
<Wrench className="h-4 w-4 text-muted-foreground" />
|
|
206
|
+
<span className="flex-1 font-medium">{tool.name}</span>
|
|
207
|
+
<Badge variant="secondary" className="text-xs">
|
|
208
|
+
{tool.type}
|
|
209
|
+
</Badge>
|
|
210
|
+
</button>
|
|
211
|
+
))
|
|
212
|
+
)}
|
|
213
|
+
</div>
|
|
214
|
+
</PopoverContent>
|
|
215
|
+
</Popover>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
{agentTools.length === 0 ? (
|
|
219
|
+
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed p-8 text-center">
|
|
220
|
+
<Wrench className="mb-2 h-8 w-8 text-muted-foreground" />
|
|
221
|
+
<p className="text-sm text-muted-foreground">
|
|
222
|
+
Nenhuma ferramenta associada. Clique em 'Adicionar Ferramenta' para começar.
|
|
223
|
+
</p>
|
|
224
|
+
</div>
|
|
225
|
+
) : (
|
|
226
|
+
<div className="space-y-2">
|
|
227
|
+
{agentTools.map((agentTool) => {
|
|
228
|
+
const tool = getToolInfo(agentTool.id_tool);
|
|
229
|
+
return (
|
|
230
|
+
<div
|
|
231
|
+
key={agentTool.id}
|
|
232
|
+
className="flex items-center gap-3 rounded-lg border bg-card p-3"
|
|
233
|
+
>
|
|
234
|
+
<div className="flex flex-1 flex-col gap-1 min-w-0">
|
|
235
|
+
<div className="flex items-center gap-2">
|
|
236
|
+
<span className="truncate font-medium">
|
|
237
|
+
{tool?.name || `Ferramenta #${agentTool.id_tool}`}
|
|
238
|
+
</span>
|
|
239
|
+
{tool?.type && (
|
|
240
|
+
<Badge variant="secondary" className="shrink-0 text-xs">
|
|
241
|
+
{tool.type}
|
|
242
|
+
</Badge>
|
|
243
|
+
)}
|
|
244
|
+
</div>
|
|
245
|
+
{agentTool.custom_instructions && (
|
|
246
|
+
<p className="truncate text-xs text-muted-foreground">
|
|
247
|
+
{agentTool.custom_instructions}
|
|
248
|
+
</p>
|
|
249
|
+
)}
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<Switch
|
|
253
|
+
checked={agentTool.enabled}
|
|
254
|
+
onCheckedChange={(checked) =>
|
|
255
|
+
handleToggleEnabled(agentTool, checked)
|
|
256
|
+
}
|
|
257
|
+
disabled={updateMutation.isPending}
|
|
258
|
+
/>
|
|
259
|
+
|
|
260
|
+
<Button
|
|
261
|
+
variant="ghost"
|
|
262
|
+
size="icon"
|
|
263
|
+
className="shrink-0 text-muted-foreground hover:text-foreground"
|
|
264
|
+
onClick={() => openConfig(agentTool)}
|
|
265
|
+
title="Configurar instruções"
|
|
266
|
+
>
|
|
267
|
+
<Settings2 className="h-4 w-4" />
|
|
268
|
+
</Button>
|
|
269
|
+
|
|
270
|
+
<Button
|
|
271
|
+
variant="ghost"
|
|
272
|
+
size="icon"
|
|
273
|
+
className="shrink-0 text-muted-foreground hover:text-destructive"
|
|
274
|
+
onClick={() => setRemoveTarget(agentTool)}
|
|
275
|
+
>
|
|
276
|
+
<Trash2 className="h-4 w-4" />
|
|
277
|
+
</Button>
|
|
278
|
+
</div>
|
|
279
|
+
);
|
|
280
|
+
})}
|
|
281
|
+
</div>
|
|
282
|
+
)}
|
|
283
|
+
|
|
284
|
+
{/* Config dialog for custom_instructions */}
|
|
285
|
+
<Dialog
|
|
286
|
+
open={!!configTarget}
|
|
287
|
+
onOpenChange={(open) => !open && setConfigTarget(null)}
|
|
288
|
+
>
|
|
289
|
+
<DialogContent className="sm:max-w-lg">
|
|
290
|
+
<DialogHeader>
|
|
291
|
+
<DialogTitle>
|
|
292
|
+
Instruções da Ferramenta
|
|
293
|
+
</DialogTitle>
|
|
294
|
+
</DialogHeader>
|
|
295
|
+
<div className="space-y-4">
|
|
296
|
+
{configTarget && getToolInfo(configTarget.id_tool)?.type !== "none" && (
|
|
297
|
+
<div className="space-y-2">
|
|
298
|
+
<Label>Credencial</Label>
|
|
299
|
+
<Select
|
|
300
|
+
value={configCredentialId || undefined}
|
|
301
|
+
onValueChange={(val) => setConfigCredentialId(val === "__none__" ? "" : val)}
|
|
302
|
+
>
|
|
303
|
+
<SelectTrigger>
|
|
304
|
+
<SelectValue placeholder="Selecione uma credencial (opcional)" />
|
|
305
|
+
</SelectTrigger>
|
|
306
|
+
<SelectContent>
|
|
307
|
+
<SelectItem value="__none__">Nenhuma (automático)</SelectItem>
|
|
308
|
+
{allCredentials
|
|
309
|
+
.filter((c) => configTarget && c.id_tool === configTarget.id_tool && c.status === "active")
|
|
310
|
+
.map((c) => (
|
|
311
|
+
<SelectItem key={c.id} value={String(c.id)}>
|
|
312
|
+
{c.label || `Credencial #${c.id}`}
|
|
313
|
+
</SelectItem>
|
|
314
|
+
))}
|
|
315
|
+
</SelectContent>
|
|
316
|
+
</Select>
|
|
317
|
+
<p className="text-xs text-muted-foreground">
|
|
318
|
+
Vincule uma credencial específica a esta ferramenta neste agente.
|
|
319
|
+
</p>
|
|
320
|
+
</div>
|
|
321
|
+
)}
|
|
322
|
+
<div className="space-y-2">
|
|
323
|
+
<Label>Instruções Personalizadas</Label>
|
|
324
|
+
<Textarea
|
|
325
|
+
value={configInstructions}
|
|
326
|
+
onChange={(e) => setConfigInstructions(e.target.value)}
|
|
327
|
+
placeholder="Instruções sobre como e quando o agente deve usar esta ferramenta..."
|
|
328
|
+
rows={6}
|
|
329
|
+
/>
|
|
330
|
+
<p className="text-xs text-muted-foreground">
|
|
331
|
+
Este texto é adicionado ao prompt do agente para orientar o uso da ferramenta.
|
|
332
|
+
</p>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
<DialogFooter>
|
|
336
|
+
<Button
|
|
337
|
+
variant="outline"
|
|
338
|
+
onClick={() => setConfigTarget(null)}
|
|
339
|
+
>
|
|
340
|
+
Cancelar
|
|
341
|
+
</Button>
|
|
342
|
+
<Button
|
|
343
|
+
onClick={handleSaveConfig}
|
|
344
|
+
disabled={updateMutation.isPending}
|
|
345
|
+
>
|
|
346
|
+
Salvar
|
|
347
|
+
</Button>
|
|
348
|
+
</DialogFooter>
|
|
349
|
+
</DialogContent>
|
|
350
|
+
</Dialog>
|
|
351
|
+
|
|
352
|
+
{/* Remove confirmation */}
|
|
353
|
+
<AlertDialog
|
|
354
|
+
open={!!removeTarget}
|
|
355
|
+
onOpenChange={(open) => !open && setRemoveTarget(null)}
|
|
356
|
+
>
|
|
357
|
+
<AlertDialogContent>
|
|
358
|
+
<AlertDialogHeader>
|
|
359
|
+
<AlertDialogTitle>Remover ferramenta?</AlertDialogTitle>
|
|
360
|
+
<AlertDialogDescription>
|
|
361
|
+
A ferramenta será desassociada deste agente.
|
|
362
|
+
</AlertDialogDescription>
|
|
363
|
+
</AlertDialogHeader>
|
|
364
|
+
<AlertDialogFooter>
|
|
365
|
+
<AlertDialogCancel>Cancelar</AlertDialogCancel>
|
|
366
|
+
<AlertDialogAction
|
|
367
|
+
onClick={handleRemove}
|
|
368
|
+
disabled={removeMutation.isPending}
|
|
369
|
+
>
|
|
370
|
+
Remover
|
|
371
|
+
</AlertDialogAction>
|
|
372
|
+
</AlertDialogFooter>
|
|
373
|
+
</AlertDialogContent>
|
|
374
|
+
</AlertDialog>
|
|
375
|
+
</div>
|
|
376
|
+
);
|
|
377
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { useMemo, useState } from "react";
|
|
2
|
+
import type { ColumnDef } from "@tanstack/react-table";
|
|
3
|
+
import { useAgents, useDeleteAgent } from "../../hooks";
|
|
4
|
+
import type { Agent } from "../../types";
|
|
5
|
+
import type { GagentsHookConfig } from "../../hooks/types";
|
|
6
|
+
import { DataTable } from "@greatapps/greatauth-ui";
|
|
7
|
+
import {
|
|
8
|
+
Input,
|
|
9
|
+
Badge,
|
|
10
|
+
Tooltip,
|
|
11
|
+
TooltipTrigger,
|
|
12
|
+
TooltipContent,
|
|
13
|
+
AlertDialog,
|
|
14
|
+
AlertDialogAction,
|
|
15
|
+
AlertDialogCancel,
|
|
16
|
+
AlertDialogContent,
|
|
17
|
+
AlertDialogDescription,
|
|
18
|
+
AlertDialogFooter,
|
|
19
|
+
AlertDialogHeader,
|
|
20
|
+
AlertDialogTitle,
|
|
21
|
+
Button,
|
|
22
|
+
} from "@greatapps/greatauth-ui/ui";
|
|
23
|
+
import { Pencil, Trash2, Search } from "lucide-react";
|
|
24
|
+
import { EntityAvatar } from "@greatapps/greatauth-ui";
|
|
25
|
+
import { format } from "date-fns";
|
|
26
|
+
import { ptBR } from "date-fns/locale";
|
|
27
|
+
import { toast } from "sonner";
|
|
28
|
+
|
|
29
|
+
function useColumns(
|
|
30
|
+
onEdit: (agent: Agent) => void,
|
|
31
|
+
onDelete: (id: number) => void,
|
|
32
|
+
): ColumnDef<Agent>[] {
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
accessorKey: "title",
|
|
36
|
+
header: "Nome",
|
|
37
|
+
cell: ({ row }) => (
|
|
38
|
+
<div className="flex items-center gap-2">
|
|
39
|
+
<EntityAvatar photo={row.original.photo} name={row.original.title} size="sm" />
|
|
40
|
+
<span className="font-medium">{row.original.title}</span>
|
|
41
|
+
</div>
|
|
42
|
+
),
|
|
43
|
+
sortingFn: (rowA, rowB) =>
|
|
44
|
+
rowA.original.title
|
|
45
|
+
.toLowerCase()
|
|
46
|
+
.localeCompare(rowB.original.title.toLowerCase()),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
accessorKey: "active",
|
|
50
|
+
header: "Status",
|
|
51
|
+
cell: ({ row }) =>
|
|
52
|
+
row.original.active ? (
|
|
53
|
+
<Badge variant="default">Ativo</Badge>
|
|
54
|
+
) : (
|
|
55
|
+
<Badge variant="secondary">Inativo</Badge>
|
|
56
|
+
),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
accessorKey: "datetime_add",
|
|
60
|
+
header: "Criado em",
|
|
61
|
+
cell: ({ row }) => (
|
|
62
|
+
<span className="text-muted-foreground text-sm">
|
|
63
|
+
{format(new Date(row.original.datetime_add), "dd/MM/yyyy", {
|
|
64
|
+
locale: ptBR,
|
|
65
|
+
})}
|
|
66
|
+
</span>
|
|
67
|
+
),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "actions",
|
|
71
|
+
size: 80,
|
|
72
|
+
enableSorting: false,
|
|
73
|
+
cell: ({ row }) => (
|
|
74
|
+
<div className="flex items-center gap-1">
|
|
75
|
+
<Tooltip>
|
|
76
|
+
<TooltipTrigger asChild>
|
|
77
|
+
<Button
|
|
78
|
+
variant="ghost"
|
|
79
|
+
size="icon"
|
|
80
|
+
className="h-8 w-8"
|
|
81
|
+
onClick={() => onEdit(row.original)}
|
|
82
|
+
>
|
|
83
|
+
<Pencil className="h-4 w-4" />
|
|
84
|
+
</Button>
|
|
85
|
+
</TooltipTrigger>
|
|
86
|
+
<TooltipContent>Editar</TooltipContent>
|
|
87
|
+
</Tooltip>
|
|
88
|
+
<Tooltip>
|
|
89
|
+
<TooltipTrigger asChild>
|
|
90
|
+
<Button
|
|
91
|
+
variant="ghost"
|
|
92
|
+
size="icon"
|
|
93
|
+
className="h-8 w-8 text-destructive hover:text-destructive"
|
|
94
|
+
onClick={() => onDelete(row.original.id)}
|
|
95
|
+
>
|
|
96
|
+
<Trash2 className="h-4 w-4" />
|
|
97
|
+
</Button>
|
|
98
|
+
</TooltipTrigger>
|
|
99
|
+
<TooltipContent>Excluir</TooltipContent>
|
|
100
|
+
</Tooltip>
|
|
101
|
+
</div>
|
|
102
|
+
),
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function AgentsTable({ config, onNavigateToAgent }: { config: GagentsHookConfig; onNavigateToAgent?: (agentId: number) => void }) {
|
|
108
|
+
const [search, setSearch] = useState("");
|
|
109
|
+
const [page, setPage] = useState(1);
|
|
110
|
+
|
|
111
|
+
const queryParams = useMemo(() => {
|
|
112
|
+
const params: Record<string, string> = {
|
|
113
|
+
limit: "15",
|
|
114
|
+
page: String(page),
|
|
115
|
+
};
|
|
116
|
+
if (search) {
|
|
117
|
+
params.search = search;
|
|
118
|
+
}
|
|
119
|
+
return params;
|
|
120
|
+
}, [search, page]);
|
|
121
|
+
|
|
122
|
+
const { data, isLoading } = useAgents(config, queryParams);
|
|
123
|
+
const deleteAgent = useDeleteAgent(config);
|
|
124
|
+
const [deleteId, setDeleteId] = useState<number | null>(null);
|
|
125
|
+
|
|
126
|
+
const agents = data?.data || [];
|
|
127
|
+
const total = data?.total || 0;
|
|
128
|
+
|
|
129
|
+
const columns = useColumns(
|
|
130
|
+
(agent) => onNavigateToAgent?.(agent.id),
|
|
131
|
+
(id) => setDeleteId(id),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
function handleDelete() {
|
|
135
|
+
if (!deleteId) return;
|
|
136
|
+
deleteAgent.mutate(deleteId, {
|
|
137
|
+
onSuccess: () => {
|
|
138
|
+
toast.success("Agente excluído");
|
|
139
|
+
setDeleteId(null);
|
|
140
|
+
},
|
|
141
|
+
onError: () => toast.error("Erro ao excluir agente"),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function handleSearchChange(value: string) {
|
|
146
|
+
setSearch(value);
|
|
147
|
+
setPage(1);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<>
|
|
152
|
+
<div className="flex items-center gap-3">
|
|
153
|
+
<div className="relative flex-1 max-w-md">
|
|
154
|
+
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
155
|
+
<Input
|
|
156
|
+
placeholder="Buscar agentes..."
|
|
157
|
+
value={search}
|
|
158
|
+
onChange={(e) => handleSearchChange(e.target.value)}
|
|
159
|
+
className="pl-9"
|
|
160
|
+
/>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<DataTable
|
|
165
|
+
columns={columns}
|
|
166
|
+
data={agents}
|
|
167
|
+
isLoading={isLoading}
|
|
168
|
+
emptyMessage="Nenhum agente encontrado"
|
|
169
|
+
total={total}
|
|
170
|
+
page={page}
|
|
171
|
+
onPageChange={setPage}
|
|
172
|
+
pageSize={15}
|
|
173
|
+
/>
|
|
174
|
+
|
|
175
|
+
{/* Delete confirmation */}
|
|
176
|
+
<AlertDialog
|
|
177
|
+
open={!!deleteId}
|
|
178
|
+
onOpenChange={(open) => !open && setDeleteId(null)}
|
|
179
|
+
>
|
|
180
|
+
<AlertDialogContent>
|
|
181
|
+
<AlertDialogHeader>
|
|
182
|
+
<AlertDialogTitle>Excluir agente?</AlertDialogTitle>
|
|
183
|
+
<AlertDialogDescription>
|
|
184
|
+
Esta ação não pode ser desfeita. O agente será removido
|
|
185
|
+
permanentemente.
|
|
186
|
+
</AlertDialogDescription>
|
|
187
|
+
</AlertDialogHeader>
|
|
188
|
+
<AlertDialogFooter>
|
|
189
|
+
<AlertDialogCancel variant="outline" size="default">
|
|
190
|
+
Cancelar
|
|
191
|
+
</AlertDialogCancel>
|
|
192
|
+
<AlertDialogAction
|
|
193
|
+
variant="default"
|
|
194
|
+
size="default"
|
|
195
|
+
onClick={handleDelete}
|
|
196
|
+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
197
|
+
>
|
|
198
|
+
Excluir
|
|
199
|
+
</AlertDialogAction>
|
|
200
|
+
</AlertDialogFooter>
|
|
201
|
+
</AlertDialogContent>
|
|
202
|
+
</AlertDialog>
|
|
203
|
+
</>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Agent } from "../../types";
|
|
2
|
+
import type { GagentsHookConfig } from "../../hooks/types";
|
|
3
|
+
import { useAgentConversations, useGagentsContacts, useObjectives } from "../../hooks";
|
|
4
|
+
import { AgentConversationsTable } from "./agent-conversations-table";
|
|
5
|
+
|
|
6
|
+
interface AgentConversationsPanelProps {
|
|
7
|
+
agent: Agent;
|
|
8
|
+
config: GagentsHookConfig;
|
|
9
|
+
renderChatLink?: (inboxId: number) => React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function AgentConversationsPanel({
|
|
13
|
+
agent,
|
|
14
|
+
config,
|
|
15
|
+
renderChatLink,
|
|
16
|
+
}: AgentConversationsPanelProps) {
|
|
17
|
+
const { data: conversationsData, isLoading } = useAgentConversations(
|
|
18
|
+
config,
|
|
19
|
+
agent.id,
|
|
20
|
+
);
|
|
21
|
+
const { data: contactsData } = useGagentsContacts(config);
|
|
22
|
+
const { data: objectivesData } = useObjectives(config, agent.id);
|
|
23
|
+
|
|
24
|
+
const conversations = conversationsData?.data || [];
|
|
25
|
+
const contactsMap = new Map(
|
|
26
|
+
(contactsData?.data || []).map((c) => [c.id, c.name]),
|
|
27
|
+
);
|
|
28
|
+
const objectivesMap = new Map(
|
|
29
|
+
(objectivesData?.data || []).map((o) => [o.id, o.title]),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="p-4">
|
|
34
|
+
<AgentConversationsTable
|
|
35
|
+
conversations={conversations}
|
|
36
|
+
isLoading={isLoading}
|
|
37
|
+
contactsMap={contactsMap}
|
|
38
|
+
objectivesMap={objectivesMap}
|
|
39
|
+
config={config}
|
|
40
|
+
renderChatLink={renderChatLink}
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|