@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@greatapps/greatagents-ui",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Shared agents UI components for Great platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -28,18 +28,28 @@
|
|
|
28
28
|
"lucide-react": "*",
|
|
29
29
|
"date-fns": "*",
|
|
30
30
|
"sonner": "*",
|
|
31
|
-
"@greatapps/greatauth-ui": "*"
|
|
31
|
+
"@greatapps/greatauth-ui": "*",
|
|
32
|
+
"@dnd-kit/core": "*",
|
|
33
|
+
"@dnd-kit/modifiers": "*",
|
|
34
|
+
"@dnd-kit/sortable": "*",
|
|
35
|
+
"@dnd-kit/utilities": "*",
|
|
36
|
+
"radix-ui": "*"
|
|
32
37
|
},
|
|
33
38
|
"dependencies": {
|
|
34
39
|
"clsx": "^2",
|
|
35
40
|
"tailwind-merge": "^3"
|
|
36
41
|
},
|
|
37
42
|
"devDependencies": {
|
|
38
|
-
"@
|
|
43
|
+
"@dnd-kit/core": "latest",
|
|
44
|
+
"@dnd-kit/modifiers": "latest",
|
|
45
|
+
"@dnd-kit/sortable": "latest",
|
|
46
|
+
"@dnd-kit/utilities": "latest",
|
|
47
|
+
"@greatapps/greatauth-ui": "^0.3.9",
|
|
39
48
|
"@tanstack/react-query": "latest",
|
|
40
49
|
"@tanstack/react-table": "latest",
|
|
41
50
|
"@types/react": "latest",
|
|
42
51
|
"@types/react-dom": "latest",
|
|
52
|
+
"radix-ui": "latest",
|
|
43
53
|
"react": "^19",
|
|
44
54
|
"react-dom": "^19",
|
|
45
55
|
"tsup": "latest",
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useUpdateAgent } from "../../hooks";
|
|
3
|
+
import type { Agent } from "../../types";
|
|
4
|
+
import type { GagentsHookConfig } from "../../hooks/types";
|
|
5
|
+
import {
|
|
6
|
+
Button,
|
|
7
|
+
Input,
|
|
8
|
+
Label,
|
|
9
|
+
Switch,
|
|
10
|
+
Dialog,
|
|
11
|
+
DialogContent,
|
|
12
|
+
DialogHeader,
|
|
13
|
+
DialogTitle,
|
|
14
|
+
DialogFooter,
|
|
15
|
+
} from "@greatapps/greatauth-ui/ui";
|
|
16
|
+
import { Loader2 } from "lucide-react";
|
|
17
|
+
import { toast } from "sonner";
|
|
18
|
+
import { ImageCropUpload } from "@greatapps/greatauth-ui";
|
|
19
|
+
|
|
20
|
+
interface AgentEditFormProps {
|
|
21
|
+
config: GagentsHookConfig;
|
|
22
|
+
agent: Agent;
|
|
23
|
+
idAccount: string | number | null;
|
|
24
|
+
open?: boolean;
|
|
25
|
+
onOpenChange?: (open: boolean) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface FormState {
|
|
29
|
+
title: string;
|
|
30
|
+
photo: string;
|
|
31
|
+
active: boolean;
|
|
32
|
+
delayTyping: string;
|
|
33
|
+
waitingTime: string;
|
|
34
|
+
titleError: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function msToSeconds(ms: number | null): string {
|
|
38
|
+
if (ms == null || ms === 0) return "";
|
|
39
|
+
return String(Math.round(ms / 1000));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function secondsToMs(seconds: string): number | undefined {
|
|
43
|
+
const val = parseFloat(seconds);
|
|
44
|
+
if (isNaN(val) || val <= 0) return undefined;
|
|
45
|
+
return Math.round(val * 1000);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function agentToFormState(agent: Agent): FormState {
|
|
49
|
+
return {
|
|
50
|
+
title: agent.title,
|
|
51
|
+
photo: agent.photo || "",
|
|
52
|
+
active: agent.active,
|
|
53
|
+
delayTyping: msToSeconds(agent.delay_typing),
|
|
54
|
+
waitingTime: msToSeconds(agent.waiting_time),
|
|
55
|
+
titleError: false,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function AgentEditForm({ config, agent, idAccount, open, onOpenChange }: AgentEditFormProps) {
|
|
60
|
+
const updateAgent = useUpdateAgent(config);
|
|
61
|
+
const [form, setForm] = useState<FormState>(() => agentToFormState(agent));
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
setForm(agentToFormState(agent));
|
|
65
|
+
}, [agent]);
|
|
66
|
+
|
|
67
|
+
function updateField<K extends keyof FormState>(key: K, value: FormState[K]) {
|
|
68
|
+
setForm((prev) => ({ ...prev, [key]: value }));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function handleSubmit(e: React.FormEvent) {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
|
|
74
|
+
if (!form.title.trim()) {
|
|
75
|
+
updateField("titleError", true);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const body: Record<string, unknown> = {
|
|
80
|
+
title: form.title.trim(),
|
|
81
|
+
active: form.active,
|
|
82
|
+
};
|
|
83
|
+
if (form.photo.trim()) body.photo = form.photo.trim();
|
|
84
|
+
else body.photo = "";
|
|
85
|
+
|
|
86
|
+
const delayMs = secondsToMs(form.delayTyping);
|
|
87
|
+
if (delayMs !== undefined) body.delay_typing = delayMs;
|
|
88
|
+
else body.delay_typing = 0;
|
|
89
|
+
|
|
90
|
+
const waitingMs = secondsToMs(form.waitingTime);
|
|
91
|
+
if (waitingMs !== undefined) body.waiting_time = waitingMs;
|
|
92
|
+
else body.waiting_time = 0;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
await updateAgent.mutateAsync({ id: agent.id, body });
|
|
96
|
+
toast.success("Agente atualizado");
|
|
97
|
+
onOpenChange?.(false);
|
|
98
|
+
} catch {
|
|
99
|
+
toast.error("Erro ao atualizar agente");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const formContent = (
|
|
104
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
105
|
+
<div className="flex justify-center">
|
|
106
|
+
<ImageCropUpload
|
|
107
|
+
value={form.photo || null}
|
|
108
|
+
onChange={(url) => updateField("photo", url)}
|
|
109
|
+
onRemove={() => updateField("photo", "")}
|
|
110
|
+
entityType="agents"
|
|
111
|
+
entityId={agent.id}
|
|
112
|
+
idAccount={typeof idAccount === "string" ? Number(idAccount) : (idAccount ?? 0)}
|
|
113
|
+
name={form.title || null}
|
|
114
|
+
disabled={updateAgent.isPending}
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div className="space-y-2">
|
|
119
|
+
<Label htmlFor="edit-title">Nome do Agente *</Label>
|
|
120
|
+
<Input
|
|
121
|
+
id="edit-title"
|
|
122
|
+
value={form.title}
|
|
123
|
+
onChange={(e) => {
|
|
124
|
+
setForm((prev) => ({
|
|
125
|
+
...prev,
|
|
126
|
+
title: e.target.value,
|
|
127
|
+
titleError: e.target.value.trim() ? false : prev.titleError,
|
|
128
|
+
}));
|
|
129
|
+
}}
|
|
130
|
+
placeholder="Ex: Assistente de Agendamento"
|
|
131
|
+
disabled={updateAgent.isPending}
|
|
132
|
+
/>
|
|
133
|
+
{form.titleError && (
|
|
134
|
+
<p className="text-sm text-destructive">Nome é obrigatório</p>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div className="flex items-center gap-3">
|
|
139
|
+
<Switch
|
|
140
|
+
id="edit-active"
|
|
141
|
+
checked={form.active}
|
|
142
|
+
onCheckedChange={(checked) => updateField("active", checked)}
|
|
143
|
+
disabled={updateAgent.isPending}
|
|
144
|
+
/>
|
|
145
|
+
<Label htmlFor="edit-active" className="cursor-pointer">
|
|
146
|
+
{form.active ? "Ativo" : "Inativo"}
|
|
147
|
+
</Label>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<div className="grid grid-cols-2 gap-4">
|
|
151
|
+
<div className="space-y-2">
|
|
152
|
+
<Label htmlFor="edit-delay">Delay de Digitação (s)</Label>
|
|
153
|
+
<Input
|
|
154
|
+
id="edit-delay"
|
|
155
|
+
type="number"
|
|
156
|
+
value={form.delayTyping}
|
|
157
|
+
onChange={(e) => updateField("delayTyping", e.target.value)}
|
|
158
|
+
placeholder="0"
|
|
159
|
+
min="0"
|
|
160
|
+
step="0.5"
|
|
161
|
+
disabled={updateAgent.isPending}
|
|
162
|
+
/>
|
|
163
|
+
<p className="text-xs text-muted-foreground">
|
|
164
|
+
Tempo de simulação de digitação
|
|
165
|
+
</p>
|
|
166
|
+
</div>
|
|
167
|
+
<div className="space-y-2">
|
|
168
|
+
<Label htmlFor="edit-waiting">Tempo de Espera (s)</Label>
|
|
169
|
+
<Input
|
|
170
|
+
id="edit-waiting"
|
|
171
|
+
type="number"
|
|
172
|
+
value={form.waitingTime}
|
|
173
|
+
onChange={(e) => updateField("waitingTime", e.target.value)}
|
|
174
|
+
placeholder="0"
|
|
175
|
+
min="0"
|
|
176
|
+
step="0.5"
|
|
177
|
+
disabled={updateAgent.isPending}
|
|
178
|
+
/>
|
|
179
|
+
<p className="text-xs text-muted-foreground">
|
|
180
|
+
Espera por mensagens agrupadas
|
|
181
|
+
</p>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<DialogFooter>
|
|
186
|
+
<Button
|
|
187
|
+
type="button"
|
|
188
|
+
variant="outline"
|
|
189
|
+
onClick={() => onOpenChange?.(false)}
|
|
190
|
+
disabled={updateAgent.isPending}
|
|
191
|
+
>
|
|
192
|
+
Cancelar
|
|
193
|
+
</Button>
|
|
194
|
+
<Button type="submit" disabled={updateAgent.isPending}>
|
|
195
|
+
{updateAgent.isPending && (
|
|
196
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
197
|
+
)}
|
|
198
|
+
Salvar
|
|
199
|
+
</Button>
|
|
200
|
+
</DialogFooter>
|
|
201
|
+
</form>
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
if (open !== undefined && onOpenChange) {
|
|
205
|
+
return (
|
|
206
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
207
|
+
<DialogContent className="sm:max-w-md">
|
|
208
|
+
<DialogHeader>
|
|
209
|
+
<DialogTitle>Editar Agente</DialogTitle>
|
|
210
|
+
</DialogHeader>
|
|
211
|
+
{formContent}
|
|
212
|
+
</DialogContent>
|
|
213
|
+
</Dialog>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return <div className="max-w-lg pt-4">{formContent}</div>;
|
|
218
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { useCreateAgent, useUpdateAgent } from "../../hooks";
|
|
3
|
+
import type { Agent } from "../../types";
|
|
4
|
+
import type { GagentsHookConfig } from "../../hooks/types";
|
|
5
|
+
import {
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogHeader,
|
|
9
|
+
DialogTitle,
|
|
10
|
+
DialogFooter,
|
|
11
|
+
Button,
|
|
12
|
+
Input,
|
|
13
|
+
Textarea,
|
|
14
|
+
Label,
|
|
15
|
+
} from "@greatapps/greatauth-ui/ui";
|
|
16
|
+
import { Loader2 } from "lucide-react";
|
|
17
|
+
import { toast } from "sonner";
|
|
18
|
+
|
|
19
|
+
interface AgentFormDialogProps {
|
|
20
|
+
config: GagentsHookConfig;
|
|
21
|
+
open: boolean;
|
|
22
|
+
onOpenChange: (open: boolean) => void;
|
|
23
|
+
agent?: Agent;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function AgentFormDialog({
|
|
27
|
+
config,
|
|
28
|
+
open,
|
|
29
|
+
onOpenChange,
|
|
30
|
+
agent,
|
|
31
|
+
}: AgentFormDialogProps) {
|
|
32
|
+
const isEditing = !!agent;
|
|
33
|
+
const createAgent = useCreateAgent(config);
|
|
34
|
+
const updateAgent = useUpdateAgent(config);
|
|
35
|
+
|
|
36
|
+
const [title, setTitle] = useState("");
|
|
37
|
+
const [prompt, setPrompt] = useState("");
|
|
38
|
+
const [photo, setPhoto] = useState("");
|
|
39
|
+
const [delayTyping, setDelayTyping] = useState("");
|
|
40
|
+
const [waitingTime, setWaitingTime] = useState("");
|
|
41
|
+
|
|
42
|
+
/* eslint-disable react-hooks/set-state-in-effect -- form state sync from props */
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (agent) {
|
|
45
|
+
setTitle(agent.title);
|
|
46
|
+
setPrompt(agent.prompt || "");
|
|
47
|
+
setPhoto(agent.photo || "");
|
|
48
|
+
setDelayTyping(agent.delay_typing != null ? String(agent.delay_typing) : "");
|
|
49
|
+
setWaitingTime(agent.waiting_time != null ? String(agent.waiting_time) : "");
|
|
50
|
+
} else {
|
|
51
|
+
setTitle("");
|
|
52
|
+
setPrompt("");
|
|
53
|
+
setPhoto("");
|
|
54
|
+
setDelayTyping("");
|
|
55
|
+
setWaitingTime("");
|
|
56
|
+
}
|
|
57
|
+
}, [agent, open]);
|
|
58
|
+
/* eslint-enable react-hooks/set-state-in-effect */
|
|
59
|
+
|
|
60
|
+
const isPending = createAgent.isPending || updateAgent.isPending;
|
|
61
|
+
|
|
62
|
+
async function handleSubmit(e: React.FormEvent) {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
if (!title.trim()) return;
|
|
65
|
+
|
|
66
|
+
const body: Record<string, unknown> = {
|
|
67
|
+
title: title.trim(),
|
|
68
|
+
};
|
|
69
|
+
if (prompt.trim()) body.prompt = prompt.trim();
|
|
70
|
+
if (photo.trim()) body.photo = photo.trim();
|
|
71
|
+
if (delayTyping.trim()) body.delay_typing = Number(delayTyping);
|
|
72
|
+
if (waitingTime.trim()) body.waiting_time = Number(waitingTime);
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
if (isEditing) {
|
|
76
|
+
await updateAgent.mutateAsync({ id: agent.id, body });
|
|
77
|
+
toast.success("Agente atualizado");
|
|
78
|
+
} else {
|
|
79
|
+
await createAgent.mutateAsync(
|
|
80
|
+
body as { title: string; prompt?: string; photo?: string; delay_typing?: number; waiting_time?: number },
|
|
81
|
+
);
|
|
82
|
+
toast.success("Agente criado");
|
|
83
|
+
}
|
|
84
|
+
onOpenChange(false);
|
|
85
|
+
} catch {
|
|
86
|
+
toast.error(isEditing ? "Erro ao atualizar agente" : "Erro ao criar agente");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
92
|
+
<DialogContent className="sm:max-w-lg">
|
|
93
|
+
<DialogHeader>
|
|
94
|
+
<DialogTitle>
|
|
95
|
+
{isEditing ? "Editar Agente" : "Novo Agente"}
|
|
96
|
+
</DialogTitle>
|
|
97
|
+
</DialogHeader>
|
|
98
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
99
|
+
<div className="space-y-2">
|
|
100
|
+
<Label htmlFor="agent-photo">Foto (URL)</Label>
|
|
101
|
+
<Input
|
|
102
|
+
id="agent-photo"
|
|
103
|
+
value={photo}
|
|
104
|
+
onChange={(e) => setPhoto(e.target.value)}
|
|
105
|
+
placeholder="https://exemplo.com/foto.jpg"
|
|
106
|
+
disabled={isPending}
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
<div className="space-y-2">
|
|
110
|
+
<Label htmlFor="agent-title">Nome do Agente *</Label>
|
|
111
|
+
<Input
|
|
112
|
+
id="agent-title"
|
|
113
|
+
value={title}
|
|
114
|
+
onChange={(e) => setTitle(e.target.value)}
|
|
115
|
+
placeholder="Ex: Assistente de Agendamento"
|
|
116
|
+
required
|
|
117
|
+
disabled={isPending}
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
<div className="space-y-2">
|
|
121
|
+
<Label htmlFor="agent-prompt">Prompt do Sistema</Label>
|
|
122
|
+
<Textarea
|
|
123
|
+
id="agent-prompt"
|
|
124
|
+
value={prompt}
|
|
125
|
+
onChange={(e) => setPrompt(e.target.value)}
|
|
126
|
+
placeholder="Instruções para o agente AI..."
|
|
127
|
+
rows={6}
|
|
128
|
+
disabled={isPending}
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
<div className="grid grid-cols-2 gap-4">
|
|
132
|
+
<div className="space-y-2">
|
|
133
|
+
<Label htmlFor="agent-delay">Delay de Digitação (ms)</Label>
|
|
134
|
+
<Input
|
|
135
|
+
id="agent-delay"
|
|
136
|
+
type="number"
|
|
137
|
+
value={delayTyping}
|
|
138
|
+
onChange={(e) => setDelayTyping(e.target.value)}
|
|
139
|
+
placeholder="0"
|
|
140
|
+
min="0"
|
|
141
|
+
disabled={isPending}
|
|
142
|
+
/>
|
|
143
|
+
</div>
|
|
144
|
+
<div className="space-y-2">
|
|
145
|
+
<Label htmlFor="agent-waiting">Tempo de Espera (ms)</Label>
|
|
146
|
+
<Input
|
|
147
|
+
id="agent-waiting"
|
|
148
|
+
type="number"
|
|
149
|
+
value={waitingTime}
|
|
150
|
+
onChange={(e) => setWaitingTime(e.target.value)}
|
|
151
|
+
placeholder="0"
|
|
152
|
+
min="0"
|
|
153
|
+
disabled={isPending}
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
<DialogFooter>
|
|
158
|
+
<Button
|
|
159
|
+
type="button"
|
|
160
|
+
variant="outline"
|
|
161
|
+
onClick={() => onOpenChange(false)}
|
|
162
|
+
disabled={isPending}
|
|
163
|
+
>
|
|
164
|
+
Cancelar
|
|
165
|
+
</Button>
|
|
166
|
+
<Button type="submit" disabled={isPending || !title.trim()}>
|
|
167
|
+
{isPending ? (
|
|
168
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
169
|
+
) : null}
|
|
170
|
+
{isEditing ? "Salvar" : "Criar"}
|
|
171
|
+
</Button>
|
|
172
|
+
</DialogFooter>
|
|
173
|
+
</form>
|
|
174
|
+
</DialogContent>
|
|
175
|
+
</Dialog>
|
|
176
|
+
);
|
|
177
|
+
}
|