@greatapps/greatagents-ui 0.3.22 → 0.3.24
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.js +248 -243
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/agents/agent-definition-editor.tsx +47 -42
- package/src/components/agents/agent-edit-form.tsx +59 -34
- package/src/components/agents/agent-form-dialog.tsx +18 -16
- package/src/components/agents/agent-objectives-list.tsx +17 -17
- package/src/components/agents/conversation-flow-editor.tsx +41 -42
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { useState, useRef, useCallback, useEffect } from "react";
|
|
1
|
+
import { useState, useRef, useCallback, useEffect, useMemo } from "react";
|
|
2
2
|
import type { Agent, ConversationFlowStep } from "../../types";
|
|
3
3
|
import type { GagentsHookConfig } from "../../hooks/types";
|
|
4
4
|
import { useUpdateAgent } from "../../hooks";
|
|
5
|
-
import { Button,
|
|
5
|
+
import { Button, Label } from "@greatapps/greatauth-ui/ui";
|
|
6
6
|
import { Loader2 } from "lucide-react";
|
|
7
7
|
import { toast } from "sonner";
|
|
8
8
|
import { ConversationFlowEditor } from "./conversation-flow-editor";
|
|
@@ -15,6 +15,7 @@ interface SectionDef {
|
|
|
15
15
|
key: "identity" | "mission" | "tone_style_format" | "rules" | "conversation_flow" | "context";
|
|
16
16
|
label: string;
|
|
17
17
|
helper: string;
|
|
18
|
+
placeholder: string;
|
|
18
19
|
required?: boolean;
|
|
19
20
|
field: keyof Agent;
|
|
20
21
|
type: "textarea" | "conversation_flow";
|
|
@@ -28,6 +29,7 @@ const SECTIONS: SectionDef[] = [
|
|
|
28
29
|
required: true,
|
|
29
30
|
field: "identity",
|
|
30
31
|
type: "textarea",
|
|
32
|
+
placeholder: "Você é a Ana, assistente virtual da Clínica Saúde & Bem-Estar. Você é simpática, profissional e sempre disposta a ajudar os pacientes...",
|
|
31
33
|
},
|
|
32
34
|
{
|
|
33
35
|
key: "mission",
|
|
@@ -36,6 +38,7 @@ const SECTIONS: SectionDef[] = [
|
|
|
36
38
|
required: true,
|
|
37
39
|
field: "mission",
|
|
38
40
|
type: "textarea",
|
|
41
|
+
placeholder: "Sua missão é ajudar pacientes a agendar consultas, tirar dúvidas sobre horários e especialidades, e fornecer informações sobre a clínica...",
|
|
39
42
|
},
|
|
40
43
|
{
|
|
41
44
|
key: "tone_style_format",
|
|
@@ -44,6 +47,7 @@ const SECTIONS: SectionDef[] = [
|
|
|
44
47
|
required: false,
|
|
45
48
|
field: "tone_style_format",
|
|
46
49
|
type: "textarea",
|
|
50
|
+
placeholder: "Use tom empático e acolhedor. Responda de forma clara e objetiva, em no máximo 3 parágrafos. Use listas quando houver múltiplas opções...",
|
|
47
51
|
},
|
|
48
52
|
{
|
|
49
53
|
key: "rules",
|
|
@@ -52,6 +56,7 @@ const SECTIONS: SectionDef[] = [
|
|
|
52
56
|
required: false,
|
|
53
57
|
field: "rules",
|
|
54
58
|
type: "textarea",
|
|
59
|
+
placeholder: "Nunca forneça diagnósticos médicos. Sempre recomende consulta presencial para urgências. Não agende consultas fora do horário de funcionamento (08h-18h)...",
|
|
55
60
|
},
|
|
56
61
|
{
|
|
57
62
|
key: "conversation_flow",
|
|
@@ -60,6 +65,7 @@ const SECTIONS: SectionDef[] = [
|
|
|
60
65
|
required: false,
|
|
61
66
|
field: "conversation_flow",
|
|
62
67
|
type: "conversation_flow",
|
|
68
|
+
placeholder: "",
|
|
63
69
|
},
|
|
64
70
|
{
|
|
65
71
|
key: "context",
|
|
@@ -68,6 +74,7 @@ const SECTIONS: SectionDef[] = [
|
|
|
68
74
|
required: false,
|
|
69
75
|
field: "context",
|
|
70
76
|
type: "textarea",
|
|
77
|
+
placeholder: "A clínica funciona de segunda a sexta, das 08h às 18h. Especialidades disponíveis: Cardiologia, Dermatologia, Ortopedia, Pediatria...",
|
|
71
78
|
},
|
|
72
79
|
];
|
|
73
80
|
|
|
@@ -181,8 +188,6 @@ export function AgentDefinitionEditor({ agent, config }: AgentDefinitionEditorPr
|
|
|
181
188
|
() => parseConversationFlow(agent.conversation_flow),
|
|
182
189
|
);
|
|
183
190
|
|
|
184
|
-
const [changeNotes, setChangeNotes] = useState("");
|
|
185
|
-
|
|
186
191
|
// Reset state when agent changes
|
|
187
192
|
if (trackedAgentId !== agent.id) {
|
|
188
193
|
setTrackedAgentId(agent.id);
|
|
@@ -194,7 +199,6 @@ export function AgentDefinitionEditor({ agent, config }: AgentDefinitionEditorPr
|
|
|
194
199
|
context: agent.context ?? "",
|
|
195
200
|
});
|
|
196
201
|
setConversationFlowSteps(parseConversationFlow(agent.conversation_flow));
|
|
197
|
-
setChangeNotes("");
|
|
198
202
|
}
|
|
199
203
|
|
|
200
204
|
function updateField(key: string, value: string) {
|
|
@@ -203,6 +207,28 @@ export function AgentDefinitionEditor({ agent, config }: AgentDefinitionEditorPr
|
|
|
203
207
|
|
|
204
208
|
const { chars, tokens } = computeTotals(fields, conversationFlowSteps);
|
|
205
209
|
|
|
210
|
+
const hasChanges = useMemo(() => {
|
|
211
|
+
if (fields.identity !== (agent.identity ?? "")) return true;
|
|
212
|
+
if (fields.mission !== (agent.mission ?? "")) return true;
|
|
213
|
+
if (fields.tone_style_format !== (agent.tone_style_format ?? "")) return true;
|
|
214
|
+
if (fields.rules !== (agent.rules ?? "")) return true;
|
|
215
|
+
if (fields.context !== (agent.context ?? "")) return true;
|
|
216
|
+
const originalSteps = parseConversationFlow(agent.conversation_flow);
|
|
217
|
+
if (JSON.stringify(conversationFlowSteps) !== JSON.stringify(originalSteps)) return true;
|
|
218
|
+
return false;
|
|
219
|
+
}, [fields, conversationFlowSteps, agent]);
|
|
220
|
+
|
|
221
|
+
function discard() {
|
|
222
|
+
setFields({
|
|
223
|
+
identity: agent.identity ?? "",
|
|
224
|
+
mission: agent.mission ?? "",
|
|
225
|
+
tone_style_format: agent.tone_style_format ?? "",
|
|
226
|
+
rules: agent.rules ?? "",
|
|
227
|
+
context: agent.context ?? "",
|
|
228
|
+
});
|
|
229
|
+
setConversationFlowSteps(parseConversationFlow(agent.conversation_flow));
|
|
230
|
+
}
|
|
231
|
+
|
|
206
232
|
async function handleSave() {
|
|
207
233
|
if (!fields.identity.trim() || !fields.mission.trim()) {
|
|
208
234
|
toast.error("Identidade e Missão são campos obrigatórios");
|
|
@@ -220,13 +246,8 @@ export function AgentDefinitionEditor({ agent, config }: AgentDefinitionEditorPr
|
|
|
220
246
|
context: fields.context.trim() || null,
|
|
221
247
|
};
|
|
222
248
|
|
|
223
|
-
if (changeNotes.trim()) {
|
|
224
|
-
body.change_notes = changeNotes.trim();
|
|
225
|
-
}
|
|
226
|
-
|
|
227
249
|
try {
|
|
228
250
|
await updateAgent.mutateAsync({ id: agent.id, body });
|
|
229
|
-
setChangeNotes("");
|
|
230
251
|
toast.success("Definição do agente salva com sucesso");
|
|
231
252
|
} catch {
|
|
232
253
|
toast.error("Erro ao salvar definição do agente");
|
|
@@ -249,7 +270,7 @@ export function AgentDefinitionEditor({ agent, config }: AgentDefinitionEditorPr
|
|
|
249
270
|
value={fields[section.key] ?? ""}
|
|
250
271
|
onChange={(v) => updateField(section.key, v)}
|
|
251
272
|
disabled={updateAgent.isPending}
|
|
252
|
-
placeholder={
|
|
273
|
+
placeholder={section.placeholder}
|
|
253
274
|
ariaLabel={section.label}
|
|
254
275
|
/>
|
|
255
276
|
) : (
|
|
@@ -269,37 +290,21 @@ export function AgentDefinitionEditor({ agent, config }: AgentDefinitionEditorPr
|
|
|
269
290
|
<span className="tabular-nums">~{tokens.toLocaleString("pt-BR")} tokens</span>
|
|
270
291
|
</div>
|
|
271
292
|
|
|
272
|
-
{/*
|
|
273
|
-
|
|
274
|
-
<
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}}
|
|
288
|
-
/>
|
|
289
|
-
<Button
|
|
290
|
-
onClick={handleSave}
|
|
291
|
-
disabled={
|
|
292
|
-
updateAgent.isPending ||
|
|
293
|
-
!fields.identity.trim() ||
|
|
294
|
-
!fields.mission.trim()
|
|
295
|
-
}
|
|
296
|
-
>
|
|
297
|
-
{updateAgent.isPending && (
|
|
298
|
-
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
299
|
-
)}
|
|
300
|
-
Salvar
|
|
301
|
-
</Button>
|
|
302
|
-
</div>
|
|
293
|
+
{/* Sticky save/discard bar */}
|
|
294
|
+
{hasChanges && (
|
|
295
|
+
<div className="sticky bottom-0 z-10 flex items-center justify-between gap-2 rounded-lg border bg-background p-3 shadow-sm">
|
|
296
|
+
<p className="text-sm text-muted-foreground">Você tem alterações não salvas.</p>
|
|
297
|
+
<div className="flex gap-2">
|
|
298
|
+
<Button variant="ghost" size="sm" onClick={discard} disabled={updateAgent.isPending}>
|
|
299
|
+
Descartar
|
|
300
|
+
</Button>
|
|
301
|
+
<Button size="sm" onClick={handleSave} disabled={updateAgent.isPending}>
|
|
302
|
+
{updateAgent.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
303
|
+
Salvar
|
|
304
|
+
</Button>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
)}
|
|
303
308
|
</div>
|
|
304
309
|
);
|
|
305
310
|
}
|
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
Input,
|
|
8
8
|
Label,
|
|
9
9
|
Switch,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
Sheet,
|
|
11
|
+
SheetContent,
|
|
12
|
+
SheetHeader,
|
|
13
|
+
SheetTitle,
|
|
14
|
+
SheetFooter,
|
|
15
15
|
} from "@greatapps/greatauth-ui/ui";
|
|
16
16
|
import { Loader2 } from "lucide-react";
|
|
17
17
|
import { toast } from "sonner";
|
|
@@ -100,8 +100,8 @@ export function AgentEditForm({ config, agent, idAccount, open, onOpenChange }:
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
const
|
|
104
|
-
|
|
103
|
+
const formFields = (
|
|
104
|
+
<>
|
|
105
105
|
<div className="flex justify-center">
|
|
106
106
|
<ImageCropUpload
|
|
107
107
|
value={form.photo || null}
|
|
@@ -184,38 +184,63 @@ export function AgentEditForm({ config, agent, idAccount, open, onOpenChange }:
|
|
|
184
184
|
</p>
|
|
185
185
|
</div>
|
|
186
186
|
</div>
|
|
187
|
-
|
|
188
|
-
<DialogFooter>
|
|
189
|
-
<Button
|
|
190
|
-
type="button"
|
|
191
|
-
variant="outline"
|
|
192
|
-
onClick={() => onOpenChange?.(false)}
|
|
193
|
-
disabled={updateAgent.isPending}
|
|
194
|
-
>
|
|
195
|
-
Cancelar
|
|
196
|
-
</Button>
|
|
197
|
-
<Button type="submit" disabled={updateAgent.isPending}>
|
|
198
|
-
{updateAgent.isPending && (
|
|
199
|
-
<Loader2 aria-hidden="true" className="mr-2 h-4 w-4 animate-spin" />
|
|
200
|
-
)}
|
|
201
|
-
Salvar
|
|
202
|
-
</Button>
|
|
203
|
-
</DialogFooter>
|
|
204
|
-
</form>
|
|
187
|
+
</>
|
|
205
188
|
);
|
|
206
189
|
|
|
207
190
|
if (open !== undefined && onOpenChange) {
|
|
208
191
|
return (
|
|
209
|
-
<
|
|
210
|
-
<
|
|
211
|
-
<
|
|
212
|
-
<
|
|
213
|
-
</
|
|
214
|
-
{
|
|
215
|
-
|
|
216
|
-
|
|
192
|
+
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
193
|
+
<SheetContent className="sm:max-w-md">
|
|
194
|
+
<SheetHeader>
|
|
195
|
+
<SheetTitle>Editar Agente</SheetTitle>
|
|
196
|
+
</SheetHeader>
|
|
197
|
+
<form onSubmit={handleSubmit} className="flex flex-1 flex-col overflow-hidden">
|
|
198
|
+
<div className="flex-1 overflow-y-auto px-4 space-y-4">
|
|
199
|
+
{formFields}
|
|
200
|
+
</div>
|
|
201
|
+
<SheetFooter className="flex-row justify-end border-t">
|
|
202
|
+
<Button
|
|
203
|
+
type="button"
|
|
204
|
+
variant="outline"
|
|
205
|
+
onClick={() => onOpenChange?.(false)}
|
|
206
|
+
disabled={updateAgent.isPending}
|
|
207
|
+
>
|
|
208
|
+
Cancelar
|
|
209
|
+
</Button>
|
|
210
|
+
<Button type="submit" disabled={updateAgent.isPending}>
|
|
211
|
+
{updateAgent.isPending && (
|
|
212
|
+
<Loader2 aria-hidden="true" className="mr-2 h-4 w-4 animate-spin" />
|
|
213
|
+
)}
|
|
214
|
+
Salvar
|
|
215
|
+
</Button>
|
|
216
|
+
</SheetFooter>
|
|
217
|
+
</form>
|
|
218
|
+
</SheetContent>
|
|
219
|
+
</Sheet>
|
|
217
220
|
);
|
|
218
221
|
}
|
|
219
222
|
|
|
220
|
-
return
|
|
223
|
+
return (
|
|
224
|
+
<div className="max-w-lg pt-4">
|
|
225
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
226
|
+
{formFields}
|
|
227
|
+
<div className="flex justify-end gap-2 pt-4 border-t">
|
|
228
|
+
<Button
|
|
229
|
+
type="button"
|
|
230
|
+
variant="outline"
|
|
231
|
+
onClick={() => onOpenChange?.(false)}
|
|
232
|
+
disabled={updateAgent.isPending}
|
|
233
|
+
>
|
|
234
|
+
Cancelar
|
|
235
|
+
</Button>
|
|
236
|
+
<Button type="submit" disabled={updateAgent.isPending}>
|
|
237
|
+
{updateAgent.isPending && (
|
|
238
|
+
<Loader2 aria-hidden="true" className="mr-2 h-4 w-4 animate-spin" />
|
|
239
|
+
)}
|
|
240
|
+
Salvar
|
|
241
|
+
</Button>
|
|
242
|
+
</div>
|
|
243
|
+
</form>
|
|
244
|
+
</div>
|
|
245
|
+
);
|
|
221
246
|
}
|
|
@@ -3,11 +3,11 @@ import { useCreateAgent, useUpdateAgent } from "../../hooks";
|
|
|
3
3
|
import type { Agent } from "../../types";
|
|
4
4
|
import type { GagentsHookConfig } from "../../hooks/types";
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
Sheet,
|
|
7
|
+
SheetContent,
|
|
8
|
+
SheetHeader,
|
|
9
|
+
SheetTitle,
|
|
10
|
+
SheetFooter,
|
|
11
11
|
Button,
|
|
12
12
|
Input,
|
|
13
13
|
Label,
|
|
@@ -134,14 +134,15 @@ export function AgentFormDialog({
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
return (
|
|
137
|
-
<
|
|
138
|
-
<
|
|
139
|
-
<
|
|
140
|
-
<
|
|
137
|
+
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
138
|
+
<SheetContent className="sm:max-w-md">
|
|
139
|
+
<SheetHeader>
|
|
140
|
+
<SheetTitle>
|
|
141
141
|
{isEditing ? "Editar Agente" : "Novo Agente"}
|
|
142
|
-
</
|
|
143
|
-
</
|
|
144
|
-
<form onSubmit={handleSubmit} className="
|
|
142
|
+
</SheetTitle>
|
|
143
|
+
</SheetHeader>
|
|
144
|
+
<form onSubmit={handleSubmit} className="flex flex-1 flex-col overflow-hidden">
|
|
145
|
+
<div className="flex-1 overflow-y-auto px-4 space-y-4">
|
|
145
146
|
<div className="flex justify-center">
|
|
146
147
|
<ImageCropUpload
|
|
147
148
|
value={form.photo || null}
|
|
@@ -224,7 +225,8 @@ export function AgentFormDialog({
|
|
|
224
225
|
</p>
|
|
225
226
|
</div>
|
|
226
227
|
</div>
|
|
227
|
-
|
|
228
|
+
</div>
|
|
229
|
+
<SheetFooter className="flex-row justify-end border-t">
|
|
228
230
|
<Button
|
|
229
231
|
type="button"
|
|
230
232
|
variant="outline"
|
|
@@ -239,9 +241,9 @@ export function AgentFormDialog({
|
|
|
239
241
|
) : null}
|
|
240
242
|
{isEditing ? "Salvar" : "Criar"}
|
|
241
243
|
</Button>
|
|
242
|
-
</
|
|
244
|
+
</SheetFooter>
|
|
243
245
|
</form>
|
|
244
|
-
</
|
|
245
|
-
</
|
|
246
|
+
</SheetContent>
|
|
247
|
+
</Sheet>
|
|
246
248
|
);
|
|
247
249
|
}
|
|
@@ -15,11 +15,11 @@ import {
|
|
|
15
15
|
Textarea,
|
|
16
16
|
Label,
|
|
17
17
|
Badge,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
Sheet,
|
|
19
|
+
SheetContent,
|
|
20
|
+
SheetHeader,
|
|
21
|
+
SheetTitle,
|
|
22
|
+
SheetFooter,
|
|
23
23
|
AlertDialog,
|
|
24
24
|
AlertDialogAction,
|
|
25
25
|
AlertDialogCancel,
|
|
@@ -324,15 +324,15 @@ export function AgentObjectivesList({ agent, config }: AgentObjectivesListProps)
|
|
|
324
324
|
</Sortable>
|
|
325
325
|
)}
|
|
326
326
|
|
|
327
|
-
{/* Create/Edit
|
|
328
|
-
<
|
|
329
|
-
<
|
|
330
|
-
<
|
|
331
|
-
<
|
|
327
|
+
{/* Create/Edit Sheet */}
|
|
328
|
+
<Sheet open={formOpen} onOpenChange={setFormOpen}>
|
|
329
|
+
<SheetContent className="sm:max-w-lg">
|
|
330
|
+
<SheetHeader>
|
|
331
|
+
<SheetTitle>
|
|
332
332
|
{editTarget ? "Editar Objetivo" : "Novo Objetivo"}
|
|
333
|
-
</
|
|
334
|
-
</
|
|
335
|
-
<div className="space-y-4">
|
|
333
|
+
</SheetTitle>
|
|
334
|
+
</SheetHeader>
|
|
335
|
+
<div className="flex-1 overflow-y-auto px-4 space-y-4">
|
|
336
336
|
<div className="space-y-2">
|
|
337
337
|
<Label htmlFor="objective-title">Título *</Label>
|
|
338
338
|
<Input
|
|
@@ -430,7 +430,7 @@ export function AgentObjectivesList({ agent, config }: AgentObjectivesListProps)
|
|
|
430
430
|
</p>
|
|
431
431
|
</div>
|
|
432
432
|
</div>
|
|
433
|
-
<
|
|
433
|
+
<SheetFooter className="flex-row justify-end border-t">
|
|
434
434
|
<Button
|
|
435
435
|
variant="outline"
|
|
436
436
|
onClick={() => setFormOpen(false)}
|
|
@@ -447,9 +447,9 @@ export function AgentObjectivesList({ agent, config }: AgentObjectivesListProps)
|
|
|
447
447
|
>
|
|
448
448
|
{editTarget ? "Salvar" : "Criar"}
|
|
449
449
|
</Button>
|
|
450
|
-
</
|
|
451
|
-
</
|
|
452
|
-
</
|
|
450
|
+
</SheetFooter>
|
|
451
|
+
</SheetContent>
|
|
452
|
+
</Sheet>
|
|
453
453
|
|
|
454
454
|
{/* Delete confirmation */}
|
|
455
455
|
<AlertDialog
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useRef, useState } from "react";
|
|
2
2
|
import type { ConversationFlowStep } from "../../types";
|
|
3
|
-
import { Button,
|
|
3
|
+
import { Button, Textarea } from "@greatapps/greatauth-ui/ui";
|
|
4
4
|
import {
|
|
5
5
|
Sortable,
|
|
6
6
|
SortableContent,
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
SortableOverlay,
|
|
10
10
|
} from "../ui/sortable";
|
|
11
11
|
import { GripVertical, Plus, Trash2 } from "lucide-react";
|
|
12
|
-
import { cn } from "../../lib";
|
|
13
12
|
|
|
14
13
|
interface StepWithKey extends ConversationFlowStep {
|
|
15
14
|
_key: number;
|
|
@@ -105,47 +104,47 @@ export function ConversationFlowEditor({
|
|
|
105
104
|
<SortableItem
|
|
106
105
|
key={step._key}
|
|
107
106
|
value={step._key}
|
|
108
|
-
className="flex
|
|
107
|
+
className="flex flex-col gap-2 rounded-lg border bg-card p-3"
|
|
109
108
|
>
|
|
110
|
-
<
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
</
|
|
109
|
+
<div className="flex items-center gap-2">
|
|
110
|
+
<SortableItemHandle className="shrink-0 cursor-grab text-muted-foreground hover:text-foreground">
|
|
111
|
+
<GripVertical aria-hidden="true" className="h-4 w-4" />
|
|
112
|
+
</SortableItemHandle>
|
|
113
|
+
<span className="shrink-0 text-xs font-medium text-muted-foreground tabular-nums">
|
|
114
|
+
{step.order}.
|
|
115
|
+
</span>
|
|
116
|
+
<span className="flex-1 text-sm font-medium truncate">{step.instruction || "Nova etapa"}</span>
|
|
117
|
+
<Button
|
|
118
|
+
type="button"
|
|
119
|
+
variant="ghost"
|
|
120
|
+
size="icon"
|
|
121
|
+
aria-label="Remover etapa"
|
|
122
|
+
className="shrink-0 text-muted-foreground hover:text-destructive"
|
|
123
|
+
onClick={() => handleRemove(step._key)}
|
|
124
|
+
>
|
|
125
|
+
<Trash2 className="h-4 w-4" />
|
|
126
|
+
</Button>
|
|
127
|
+
</div>
|
|
128
|
+
<div className="space-y-2 pl-8">
|
|
129
|
+
<Textarea
|
|
130
|
+
value={step.instruction}
|
|
131
|
+
onChange={(e) =>
|
|
132
|
+
handleFieldChange(step._key, "instruction", e.target.value)
|
|
133
|
+
}
|
|
134
|
+
placeholder="Descreva o que o agente deve fazer nesta etapa..."
|
|
135
|
+
className="min-h-[2.5rem] resize-none"
|
|
136
|
+
rows={2}
|
|
137
|
+
/>
|
|
138
|
+
<Textarea
|
|
139
|
+
value={step.example ?? ""}
|
|
140
|
+
onChange={(e) =>
|
|
141
|
+
handleFieldChange(step._key, "example", e.target.value)
|
|
142
|
+
}
|
|
143
|
+
placeholder="Exemplo de resposta do agente (opcional)"
|
|
144
|
+
className="min-h-[2.5rem] resize-none text-muted-foreground"
|
|
145
|
+
rows={1}
|
|
146
|
+
/>
|
|
147
|
+
</div>
|
|
149
148
|
</SortableItem>
|
|
150
149
|
))}
|
|
151
150
|
</SortableContent>
|