@greatapps/greatagents-ui 0.3.3 → 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 +214 -3
- package/dist/index.js +2532 -850
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +14 -0
- package/src/components/agents/agent-form-dialog.tsx +109 -29
- package/src/components/agents/agent-tabs.tsx +39 -2
- package/src/components/capabilities/advanced-tab.tsx +82 -0
- package/src/components/capabilities/capabilities-tab.tsx +475 -0
- package/src/components/capabilities/integration-card.tsx +162 -0
- package/src/components/capabilities/integration-wizard.tsx +537 -0
- package/src/components/capabilities/integrations-tab.tsx +61 -0
- package/src/components/capabilities/types.ts +48 -0
- package/src/components/capabilities/wizard-steps/config-step.tsx +117 -0
- package/src/components/capabilities/wizard-steps/confirm-step.tsx +123 -0
- package/src/components/capabilities/wizard-steps/credentials-step.tsx +205 -0
- package/src/components/capabilities/wizard-steps/info-step.tsx +78 -0
- package/src/data/integrations-registry.ts +23 -0
- package/src/hooks/index.ts +10 -0
- package/src/hooks/use-capabilities.ts +50 -0
- package/src/hooks/use-integrations.ts +114 -0
- package/src/index.ts +34 -0
- package/src/pages/agent-capabilities-page.tsx +159 -0
- package/src/pages/index.ts +2 -0
- package/src/pages/integrations-management-page.tsx +166 -0
- package/src/types/capabilities.ts +32 -0
- package/src/types/index.ts +10 -0
package/package.json
CHANGED
package/src/client/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
Tool,
|
|
13
13
|
ToolCredential,
|
|
14
14
|
} from "../types";
|
|
15
|
+
import type { AgentCapability, AgentCapabilitiesPayload, CapabilitiesResponse } from "../types/capabilities";
|
|
15
16
|
|
|
16
17
|
export interface GagentsClientConfig {
|
|
17
18
|
baseUrl: string;
|
|
@@ -248,6 +249,19 @@ export function createGagentsClient(config: GagentsClientConfig) {
|
|
|
248
249
|
undefined,
|
|
249
250
|
{ provider },
|
|
250
251
|
),
|
|
252
|
+
|
|
253
|
+
// --- Capabilities ---
|
|
254
|
+
getCapabilities: (idAccount: number) =>
|
|
255
|
+
request<CapabilitiesResponse>("GET", idAccount, "capabilities"),
|
|
256
|
+
|
|
257
|
+
getAgentCapabilities: (idAccount: number, idAgent: number) =>
|
|
258
|
+
request<AgentCapability[]>("GET", idAccount, `agents/${idAgent}/capabilities`),
|
|
259
|
+
|
|
260
|
+
updateAgentCapabilities: (
|
|
261
|
+
idAccount: number,
|
|
262
|
+
idAgent: number,
|
|
263
|
+
body: AgentCapabilitiesPayload,
|
|
264
|
+
) => request<AgentCapability[]>("PUT", idAccount, `agents/${idAgent}/capabilities`, body),
|
|
251
265
|
};
|
|
252
266
|
}
|
|
253
267
|
|
|
@@ -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}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import type { GagentsHookConfig } from "../../hooks/types";
|
|
5
|
+
import { useToolCredentials } from "../../hooks";
|
|
6
|
+
import { ToolsTable } from "../tools/tools-table";
|
|
7
|
+
import { ToolCredentialsForm } from "../tools/tool-credentials-form";
|
|
8
|
+
import { ToolFormDialog } from "../tools/tool-form-dialog";
|
|
9
|
+
import type { Tool } from "../../types";
|
|
10
|
+
import { Info } from "lucide-react";
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Props
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
export interface AdvancedTabProps {
|
|
17
|
+
config: GagentsHookConfig;
|
|
18
|
+
agentId: number;
|
|
19
|
+
gagentsApiUrl: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Component
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
export function AdvancedTab({ config, agentId, gagentsApiUrl }: AdvancedTabProps) {
|
|
27
|
+
const { data: credentialsData, isLoading: isLoadingCredentials } =
|
|
28
|
+
useToolCredentials(config);
|
|
29
|
+
const credentials = credentialsData?.data ?? [];
|
|
30
|
+
|
|
31
|
+
const [editingTool, setEditingTool] = useState<Tool | null>(null);
|
|
32
|
+
const [showToolForm, setShowToolForm] = useState(false);
|
|
33
|
+
|
|
34
|
+
function handleEditTool(tool: Tool) {
|
|
35
|
+
setEditingTool(tool);
|
|
36
|
+
setShowToolForm(true);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function handleToolFormOpenChange(open: boolean) {
|
|
40
|
+
setShowToolForm(open);
|
|
41
|
+
if (!open) setEditingTool(null);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="space-y-8">
|
|
46
|
+
{/* Info banner */}
|
|
47
|
+
<div className="flex items-start gap-3 rounded-lg border border-blue-200 bg-blue-50 p-4 dark:border-blue-900 dark:bg-blue-950/30">
|
|
48
|
+
<Info className="mt-0.5 h-4 w-4 shrink-0 text-blue-600 dark:text-blue-400" />
|
|
49
|
+
<p className="text-sm text-blue-800 dark:text-blue-300">
|
|
50
|
+
Use as abas <strong>Capacidades</strong> e <strong>Integrações</strong> para
|
|
51
|
+
configuração simplificada. Esta aba oferece controlo manual avançado sobre
|
|
52
|
+
ferramentas e credenciais.
|
|
53
|
+
</p>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{/* Ferramentas section */}
|
|
57
|
+
<section className="space-y-3">
|
|
58
|
+
<h3 className="text-sm font-medium">Ferramentas</h3>
|
|
59
|
+
<ToolsTable onEdit={handleEditTool} config={config} />
|
|
60
|
+
</section>
|
|
61
|
+
|
|
62
|
+
{/* Credenciais section */}
|
|
63
|
+
<section className="space-y-3">
|
|
64
|
+
<h3 className="text-sm font-medium">Credenciais</h3>
|
|
65
|
+
<ToolCredentialsForm
|
|
66
|
+
credentials={credentials}
|
|
67
|
+
isLoading={isLoadingCredentials}
|
|
68
|
+
config={config}
|
|
69
|
+
gagentsApiUrl={gagentsApiUrl}
|
|
70
|
+
/>
|
|
71
|
+
</section>
|
|
72
|
+
|
|
73
|
+
{/* Tool edit dialog */}
|
|
74
|
+
<ToolFormDialog
|
|
75
|
+
open={showToolForm}
|
|
76
|
+
onOpenChange={handleToolFormOpenChange}
|
|
77
|
+
tool={editingTool ?? undefined}
|
|
78
|
+
config={config}
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|