@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/greatagents-ui",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Shared agents UI components for Great platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -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 [title, setTitle] = useState("");
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
- setTitle(agent.title);
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
- setTitle("");
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
- if (!title.trim()) return;
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 (delayTyping.trim()) body.delay_typing = Number(delayTyping);
68
- if (waitingTime.trim()) body.waiting_time = Number(waitingTime);
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) => setPhoto(e.target.value)}
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) => setTitle(e.target.value)}
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 (ms)</Label>
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) => setDelayTyping(e.target.value)}
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 (ms)</Label>
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) => setWaitingTime(e.target.value)}
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({ agent, config, renderChatLink }: AgentTabsProps) {
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}