@greatapps/greatagents-ui 0.3.4 → 0.3.5
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 +47 -39
- package/dist/index.js +2393 -2310
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/agents/agent-form-dialog.tsx +109 -29
- package/src/components/agents/agent-tabs.tsx +39 -2
package/package.json
CHANGED
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
DialogFooter,
|
|
11
11
|
Button,
|
|
12
12
|
Input,
|
|
13
|
-
|
|
14
13
|
Label,
|
|
14
|
+
Switch,
|
|
15
15
|
} from "@greatapps/greatauth-ui/ui";
|
|
16
16
|
import { Loader2 } from "lucide-react";
|
|
17
17
|
import { toast } from "sonner";
|
|
@@ -23,6 +23,46 @@ interface AgentFormDialogProps {
|
|
|
23
23
|
agent?: Agent;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
interface FormState {
|
|
27
|
+
title: string;
|
|
28
|
+
photo: string;
|
|
29
|
+
active: boolean;
|
|
30
|
+
delayTyping: string;
|
|
31
|
+
waitingTime: string;
|
|
32
|
+
titleError: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function msToSeconds(ms: number | null | undefined): string {
|
|
36
|
+
if (ms == null || ms === 0) return "";
|
|
37
|
+
return String(Math.round(ms / 1000));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function secondsToMs(seconds: string): number | undefined {
|
|
41
|
+
const val = parseFloat(seconds);
|
|
42
|
+
if (isNaN(val) || val <= 0) return undefined;
|
|
43
|
+
return Math.round(val * 1000);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function agentToFormState(agent: Agent): FormState {
|
|
47
|
+
return {
|
|
48
|
+
title: agent.title,
|
|
49
|
+
photo: agent.photo || "",
|
|
50
|
+
active: agent.active,
|
|
51
|
+
delayTyping: msToSeconds(agent.delay_typing),
|
|
52
|
+
waitingTime: msToSeconds(agent.waiting_time),
|
|
53
|
+
titleError: false,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const emptyFormState: FormState = {
|
|
58
|
+
title: "",
|
|
59
|
+
photo: "",
|
|
60
|
+
active: true,
|
|
61
|
+
delayTyping: "",
|
|
62
|
+
waitingTime: "",
|
|
63
|
+
titleError: false,
|
|
64
|
+
};
|
|
65
|
+
|
|
26
66
|
export function AgentFormDialog({
|
|
27
67
|
config,
|
|
28
68
|
open,
|
|
@@ -33,39 +73,46 @@ export function AgentFormDialog({
|
|
|
33
73
|
const createAgent = useCreateAgent(config);
|
|
34
74
|
const updateAgent = useUpdateAgent(config);
|
|
35
75
|
|
|
36
|
-
const [
|
|
37
|
-
const [photo, setPhoto] = useState("");
|
|
38
|
-
const [delayTyping, setDelayTyping] = useState("");
|
|
39
|
-
const [waitingTime, setWaitingTime] = useState("");
|
|
76
|
+
const [form, setForm] = useState<FormState>(emptyFormState);
|
|
40
77
|
|
|
41
78
|
/* eslint-disable react-hooks/set-state-in-effect -- form state sync from props */
|
|
42
79
|
useEffect(() => {
|
|
43
80
|
if (agent) {
|
|
44
|
-
|
|
45
|
-
setPhoto(agent.photo || "");
|
|
46
|
-
setDelayTyping(agent.delay_typing != null ? String(agent.delay_typing) : "");
|
|
47
|
-
setWaitingTime(agent.waiting_time != null ? String(agent.waiting_time) : "");
|
|
81
|
+
setForm(agentToFormState(agent));
|
|
48
82
|
} else {
|
|
49
|
-
|
|
50
|
-
setPhoto("");
|
|
51
|
-
setDelayTyping("");
|
|
52
|
-
setWaitingTime("");
|
|
83
|
+
setForm(emptyFormState);
|
|
53
84
|
}
|
|
54
85
|
}, [agent, open]);
|
|
55
86
|
/* eslint-enable react-hooks/set-state-in-effect */
|
|
56
87
|
|
|
88
|
+
function updateField<K extends keyof FormState>(key: K, value: FormState[K]) {
|
|
89
|
+
setForm((prev) => ({ ...prev, [key]: value }));
|
|
90
|
+
}
|
|
91
|
+
|
|
57
92
|
const isPending = createAgent.isPending || updateAgent.isPending;
|
|
58
93
|
|
|
59
94
|
async function handleSubmit(e: React.FormEvent) {
|
|
60
95
|
e.preventDefault();
|
|
61
|
-
|
|
96
|
+
|
|
97
|
+
if (!form.title.trim()) {
|
|
98
|
+
updateField("titleError", true);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
62
101
|
|
|
63
102
|
const body: Record<string, unknown> = {
|
|
64
|
-
title: title.trim(),
|
|
103
|
+
title: form.title.trim(),
|
|
104
|
+
active: form.active,
|
|
65
105
|
};
|
|
66
|
-
if (photo.trim()) body.photo = photo.trim();
|
|
67
|
-
if (
|
|
68
|
-
|
|
106
|
+
if (form.photo.trim()) body.photo = form.photo.trim();
|
|
107
|
+
else if (isEditing) body.photo = "";
|
|
108
|
+
|
|
109
|
+
const delayMs = secondsToMs(form.delayTyping);
|
|
110
|
+
if (delayMs !== undefined) body.delay_typing = delayMs;
|
|
111
|
+
else if (isEditing) body.delay_typing = 0;
|
|
112
|
+
|
|
113
|
+
const waitingMs = secondsToMs(form.waitingTime);
|
|
114
|
+
if (waitingMs !== undefined) body.waiting_time = waitingMs;
|
|
115
|
+
else if (isEditing) body.waiting_time = 0;
|
|
69
116
|
|
|
70
117
|
try {
|
|
71
118
|
if (isEditing) {
|
|
@@ -97,50 +144,83 @@ export function AgentFormDialog({
|
|
|
97
144
|
<Input
|
|
98
145
|
id="agent-photo"
|
|
99
146
|
name="photo"
|
|
100
|
-
value={photo}
|
|
101
|
-
onChange={(e) =>
|
|
147
|
+
value={form.photo}
|
|
148
|
+
onChange={(e) => updateField("photo", e.target.value)}
|
|
102
149
|
placeholder="https://exemplo.com/foto.jpg"
|
|
103
150
|
disabled={isPending}
|
|
104
151
|
/>
|
|
152
|
+
<p className="text-xs text-muted-foreground">
|
|
153
|
+
URL da imagem de avatar do agente
|
|
154
|
+
</p>
|
|
105
155
|
</div>
|
|
106
156
|
<div className="space-y-2">
|
|
107
157
|
<Label htmlFor="agent-title">Nome do Agente *</Label>
|
|
108
158
|
<Input
|
|
109
159
|
id="agent-title"
|
|
110
160
|
name="title"
|
|
111
|
-
value={title}
|
|
112
|
-
onChange={(e) =>
|
|
161
|
+
value={form.title}
|
|
162
|
+
onChange={(e) => {
|
|
163
|
+
setForm((prev) => ({
|
|
164
|
+
...prev,
|
|
165
|
+
title: e.target.value,
|
|
166
|
+
titleError: e.target.value.trim() ? false : prev.titleError,
|
|
167
|
+
}));
|
|
168
|
+
}}
|
|
113
169
|
placeholder="Ex: Assistente de Agendamento"
|
|
114
170
|
required
|
|
115
171
|
disabled={isPending}
|
|
116
172
|
/>
|
|
173
|
+
{form.titleError && (
|
|
174
|
+
<p className="text-sm text-destructive">Nome é obrigatório</p>
|
|
175
|
+
)}
|
|
117
176
|
</div>
|
|
177
|
+
|
|
178
|
+
<div className="flex items-center gap-3">
|
|
179
|
+
<Switch
|
|
180
|
+
id="agent-active"
|
|
181
|
+
checked={form.active}
|
|
182
|
+
onCheckedChange={(checked) => updateField("active", checked)}
|
|
183
|
+
disabled={isPending}
|
|
184
|
+
/>
|
|
185
|
+
<Label htmlFor="agent-active" className="cursor-pointer">
|
|
186
|
+
{form.active ? "Ativo" : "Inativo"}
|
|
187
|
+
</Label>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
118
190
|
<div className="grid grid-cols-2 gap-4">
|
|
119
191
|
<div className="space-y-2">
|
|
120
|
-
<Label htmlFor="agent-delay">Delay de Digitação (
|
|
192
|
+
<Label htmlFor="agent-delay">Delay de Digitação (s)</Label>
|
|
121
193
|
<Input
|
|
122
194
|
id="agent-delay"
|
|
123
195
|
name="delay"
|
|
124
196
|
type="number"
|
|
125
|
-
value={delayTyping}
|
|
126
|
-
onChange={(e) =>
|
|
197
|
+
value={form.delayTyping}
|
|
198
|
+
onChange={(e) => updateField("delayTyping", e.target.value)}
|
|
127
199
|
placeholder="0"
|
|
128
200
|
min="0"
|
|
201
|
+
step="0.5"
|
|
129
202
|
disabled={isPending}
|
|
130
203
|
/>
|
|
204
|
+
<p className="text-xs text-muted-foreground">
|
|
205
|
+
Tempo de simulação de digitação
|
|
206
|
+
</p>
|
|
131
207
|
</div>
|
|
132
208
|
<div className="space-y-2">
|
|
133
|
-
<Label htmlFor="agent-waiting">Tempo de Espera (
|
|
209
|
+
<Label htmlFor="agent-waiting">Tempo de Espera (s)</Label>
|
|
134
210
|
<Input
|
|
135
211
|
id="agent-waiting"
|
|
136
212
|
name="waiting"
|
|
137
213
|
type="number"
|
|
138
|
-
value={waitingTime}
|
|
139
|
-
onChange={(e) =>
|
|
214
|
+
value={form.waitingTime}
|
|
215
|
+
onChange={(e) => updateField("waitingTime", e.target.value)}
|
|
140
216
|
placeholder="0"
|
|
141
217
|
min="0"
|
|
218
|
+
step="0.5"
|
|
142
219
|
disabled={isPending}
|
|
143
220
|
/>
|
|
221
|
+
<p className="text-xs text-muted-foreground">
|
|
222
|
+
Espera por mensagens agrupadas
|
|
223
|
+
</p>
|
|
144
224
|
</div>
|
|
145
225
|
</div>
|
|
146
226
|
<DialogFooter>
|
|
@@ -152,7 +232,7 @@ export function AgentFormDialog({
|
|
|
152
232
|
>
|
|
153
233
|
Cancelar
|
|
154
234
|
</Button>
|
|
155
|
-
<Button type="submit" disabled={isPending || !title.trim()}>
|
|
235
|
+
<Button type="submit" disabled={isPending || !form.title.trim()}>
|
|
156
236
|
{isPending ? (
|
|
157
237
|
<Loader2 aria-hidden="true" className="mr-2 h-4 w-4 animate-spin" />
|
|
158
238
|
) : null}
|
|
@@ -1,24 +1,46 @@
|
|
|
1
1
|
import type { Agent } from "../../types";
|
|
2
2
|
import type { GagentsHookConfig } from "../../hooks/types";
|
|
3
|
+
import type { IntegrationCardData } from "../../hooks/use-integrations";
|
|
4
|
+
import type { WizardIntegrationMeta } from "../capabilities/types";
|
|
5
|
+
import type { ConfigOption } from "../capabilities/wizard-steps/config-step";
|
|
3
6
|
import { AgentToolsList } from "./agent-tools-list";
|
|
4
7
|
import { AgentObjectivesList } from "./agent-objectives-list";
|
|
5
8
|
import { AgentPromptEditor } from "./agent-prompt-editor";
|
|
6
9
|
import { AgentConversationsPanel } from "../conversations/agent-conversations-panel";
|
|
10
|
+
import { AgentCapabilitiesPage } from "../../pages/agent-capabilities-page";
|
|
7
11
|
import {
|
|
8
12
|
Tabs,
|
|
9
13
|
TabsList,
|
|
10
14
|
TabsTrigger,
|
|
11
15
|
TabsContent,
|
|
12
16
|
} from "@greatapps/greatauth-ui/ui";
|
|
13
|
-
import { Wrench, Target, FileText, MessageCircle } from "lucide-react";
|
|
17
|
+
import { Wrench, Target, FileText, MessageCircle, Blocks } from "lucide-react";
|
|
14
18
|
|
|
15
19
|
interface AgentTabsProps {
|
|
16
20
|
agent: Agent;
|
|
17
21
|
config: GagentsHookConfig;
|
|
18
22
|
renderChatLink?: (inboxId: number) => React.ReactNode;
|
|
23
|
+
/** Required for the Capacidades tab — gagents API URL for OAuth flows and advanced features. Falls back to config.baseUrl. */
|
|
24
|
+
gagentsApiUrl?: string;
|
|
25
|
+
/** Resolve wizard metadata for a given integration card. */
|
|
26
|
+
resolveWizardMeta?: (card: IntegrationCardData) => WizardIntegrationMeta;
|
|
27
|
+
/** Callback to load config options after OAuth completes. */
|
|
28
|
+
loadConfigOptions?: (credentialId: number) => Promise<ConfigOption[]>;
|
|
29
|
+
/** Called after wizard completes successfully. */
|
|
30
|
+
onWizardComplete?: () => void;
|
|
19
31
|
}
|
|
20
32
|
|
|
21
|
-
export function AgentTabs({
|
|
33
|
+
export function AgentTabs({
|
|
34
|
+
agent,
|
|
35
|
+
config,
|
|
36
|
+
renderChatLink,
|
|
37
|
+
gagentsApiUrl,
|
|
38
|
+
resolveWizardMeta,
|
|
39
|
+
loadConfigOptions,
|
|
40
|
+
onWizardComplete,
|
|
41
|
+
}: AgentTabsProps) {
|
|
42
|
+
const apiUrl = gagentsApiUrl || config.baseUrl;
|
|
43
|
+
|
|
22
44
|
return (
|
|
23
45
|
<Tabs defaultValue="prompt">
|
|
24
46
|
<TabsList>
|
|
@@ -34,6 +56,10 @@ export function AgentTabs({ agent, config, renderChatLink }: AgentTabsProps) {
|
|
|
34
56
|
<Wrench aria-hidden="true" className="h-3.5 w-3.5" />
|
|
35
57
|
Ferramentas
|
|
36
58
|
</TabsTrigger>
|
|
59
|
+
<TabsTrigger value="capacidades" className="flex items-center gap-1.5">
|
|
60
|
+
<Blocks aria-hidden="true" className="h-3.5 w-3.5" />
|
|
61
|
+
Capacidades
|
|
62
|
+
</TabsTrigger>
|
|
37
63
|
<TabsTrigger value="conversas" className="flex items-center gap-1.5">
|
|
38
64
|
<MessageCircle aria-hidden="true" className="h-3.5 w-3.5" />
|
|
39
65
|
Conversas
|
|
@@ -52,6 +78,17 @@ export function AgentTabs({ agent, config, renderChatLink }: AgentTabsProps) {
|
|
|
52
78
|
<AgentToolsList agent={agent} config={config} />
|
|
53
79
|
</TabsContent>
|
|
54
80
|
|
|
81
|
+
<TabsContent value="capacidades" className="mt-4">
|
|
82
|
+
<AgentCapabilitiesPage
|
|
83
|
+
config={config}
|
|
84
|
+
agentId={agent.id}
|
|
85
|
+
gagentsApiUrl={apiUrl}
|
|
86
|
+
resolveWizardMeta={resolveWizardMeta}
|
|
87
|
+
loadConfigOptions={loadConfigOptions}
|
|
88
|
+
onWizardComplete={onWizardComplete}
|
|
89
|
+
/>
|
|
90
|
+
</TabsContent>
|
|
91
|
+
|
|
55
92
|
<TabsContent value="conversas" className="mt-4">
|
|
56
93
|
<AgentConversationsPanel
|
|
57
94
|
agent={agent}
|