@greatapps/greatagents-ui 0.3.21 → 0.3.22
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 +52 -16
- package/dist/index.js +1439 -1101
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +4 -4
- package/src/components/agents/agent-definition-editor.tsx +305 -0
- package/src/components/agents/agent-objectives-list.tsx +76 -62
- package/src/components/agents/agent-revision-tab.tsx +368 -0
- package/src/components/agents/agent-tabs.tsx +17 -8
- package/src/components/agents/conversation-flow-editor.tsx +180 -0
- package/src/hooks/use-agents.ts +7 -2
- package/src/hooks/use-objectives.ts +8 -2
- package/src/index.ts +4 -1
- package/src/types/index.ts +16 -1
- package/src/components/agents/agent-prompt-editor.tsx +0 -442
package/package.json
CHANGED
package/src/client/index.ts
CHANGED
|
@@ -89,8 +89,8 @@ export function createGagentsClient(config: GagentsClientConfig) {
|
|
|
89
89
|
idAccount: number,
|
|
90
90
|
id: number,
|
|
91
91
|
body: Partial<
|
|
92
|
-
Pick<Agent, "title" | "photo" | "delay_typing" | "waiting_time" | "active">
|
|
93
|
-
> & {
|
|
92
|
+
Pick<Agent, "title" | "photo" | "delay_typing" | "waiting_time" | "active" | "identity" | "mission" | "tone_style_format" | "rules" | "conversation_flow" | "context">
|
|
93
|
+
> & { change_notes?: string },
|
|
94
94
|
) => request<Agent>("PUT", idAccount, `agents/${id}`, body),
|
|
95
95
|
|
|
96
96
|
deleteAgent: (idAccount: number, id: number) =>
|
|
@@ -154,14 +154,14 @@ export function createGagentsClient(config: GagentsClientConfig) {
|
|
|
154
154
|
idAccount: number,
|
|
155
155
|
idAgent: number,
|
|
156
156
|
body: Pick<Objective, "title"> &
|
|
157
|
-
Partial<Pick<Objective, "slug" | "
|
|
157
|
+
Partial<Pick<Objective, "slug" | "instruction" | "description" | "conversation_flow" | "rules" | "order" | "active">>,
|
|
158
158
|
) => request<Objective>("POST", idAccount, `agents/${idAgent}/objectives`, body),
|
|
159
159
|
|
|
160
160
|
updateObjective: (
|
|
161
161
|
idAccount: number,
|
|
162
162
|
idAgent: number,
|
|
163
163
|
id: number,
|
|
164
|
-
body: Partial<Pick<Objective, "title" | "slug" | "
|
|
164
|
+
body: Partial<Pick<Objective, "title" | "slug" | "instruction" | "description" | "conversation_flow" | "rules" | "order" | "active">>,
|
|
165
165
|
) => request<Objective>("PUT", idAccount, `agents/${idAgent}/objectives/${id}`, body),
|
|
166
166
|
|
|
167
167
|
deleteObjective: (idAccount: number, idAgent: number, id: number) =>
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from "react";
|
|
2
|
+
import type { Agent, ConversationFlowStep } from "../../types";
|
|
3
|
+
import type { GagentsHookConfig } from "../../hooks/types";
|
|
4
|
+
import { useUpdateAgent } from "../../hooks";
|
|
5
|
+
import { Button, Input, Label } from "@greatapps/greatauth-ui/ui";
|
|
6
|
+
import { Loader2 } from "lucide-react";
|
|
7
|
+
import { toast } from "sonner";
|
|
8
|
+
import { ConversationFlowEditor } from "./conversation-flow-editor";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Section config
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
interface SectionDef {
|
|
15
|
+
key: "identity" | "mission" | "tone_style_format" | "rules" | "conversation_flow" | "context";
|
|
16
|
+
label: string;
|
|
17
|
+
helper: string;
|
|
18
|
+
required?: boolean;
|
|
19
|
+
field: keyof Agent;
|
|
20
|
+
type: "textarea" | "conversation_flow";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const SECTIONS: SectionDef[] = [
|
|
24
|
+
{
|
|
25
|
+
key: "identity",
|
|
26
|
+
label: "Identidade",
|
|
27
|
+
helper: "Descreva quem é o agente, o seu nome e personalidade",
|
|
28
|
+
required: true,
|
|
29
|
+
field: "identity",
|
|
30
|
+
type: "textarea",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: "mission",
|
|
34
|
+
label: "Missão",
|
|
35
|
+
helper: "Qual é a missão principal deste agente",
|
|
36
|
+
required: true,
|
|
37
|
+
field: "mission",
|
|
38
|
+
type: "textarea",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: "tone_style_format",
|
|
42
|
+
label: "Tom, Estilo & Formato",
|
|
43
|
+
helper: "Defina como o agente comunica e formata as respostas",
|
|
44
|
+
required: false,
|
|
45
|
+
field: "tone_style_format",
|
|
46
|
+
type: "textarea",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: "rules",
|
|
50
|
+
label: "Regras",
|
|
51
|
+
helper: "Limites, restrições e comportamentos obrigatórios",
|
|
52
|
+
required: false,
|
|
53
|
+
field: "rules",
|
|
54
|
+
type: "textarea",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
key: "conversation_flow",
|
|
58
|
+
label: "Fluxo de Conversa",
|
|
59
|
+
helper: "Etapas que o agente segue no início de cada conversa",
|
|
60
|
+
required: false,
|
|
61
|
+
field: "conversation_flow",
|
|
62
|
+
type: "conversation_flow",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
key: "context",
|
|
66
|
+
label: "Contexto",
|
|
67
|
+
helper: "Informações adicionais que o agente deve saber",
|
|
68
|
+
required: false,
|
|
69
|
+
field: "context",
|
|
70
|
+
type: "textarea",
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Helpers
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
function parseConversationFlow(raw: string | null | undefined): ConversationFlowStep[] {
|
|
79
|
+
if (!raw) return [];
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(raw);
|
|
82
|
+
if (Array.isArray(parsed)) return parsed;
|
|
83
|
+
return [];
|
|
84
|
+
} catch {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function computeTotals(fields: Record<string, string>, steps: ConversationFlowStep[]) {
|
|
90
|
+
const textChars = Object.values(fields).reduce((sum, v) => sum + v.length, 0);
|
|
91
|
+
const flowChars = JSON.stringify(steps).length;
|
|
92
|
+
const total = textChars + flowChars;
|
|
93
|
+
return { chars: total, tokens: Math.ceil(total / 4) };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Auto-resize textarea component
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
function AutoTextarea({
|
|
101
|
+
value,
|
|
102
|
+
onChange,
|
|
103
|
+
disabled,
|
|
104
|
+
placeholder,
|
|
105
|
+
ariaLabel,
|
|
106
|
+
}: {
|
|
107
|
+
value: string;
|
|
108
|
+
onChange: (v: string) => void;
|
|
109
|
+
disabled?: boolean;
|
|
110
|
+
placeholder?: string;
|
|
111
|
+
ariaLabel?: string;
|
|
112
|
+
}) {
|
|
113
|
+
const ref = useRef<HTMLTextAreaElement>(null);
|
|
114
|
+
|
|
115
|
+
const autoResize = useCallback(() => {
|
|
116
|
+
const el = ref.current;
|
|
117
|
+
if (!el) return;
|
|
118
|
+
el.style.height = "auto";
|
|
119
|
+
el.style.height = `${Math.max(120, el.scrollHeight)}px`;
|
|
120
|
+
}, []);
|
|
121
|
+
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
autoResize();
|
|
124
|
+
}, [value, autoResize]);
|
|
125
|
+
|
|
126
|
+
function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
|
|
127
|
+
if (e.key === "Tab") {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
const el = e.currentTarget;
|
|
130
|
+
const start = el.selectionStart;
|
|
131
|
+
const end = el.selectionEnd;
|
|
132
|
+
const newValue = el.value.substring(0, start) + " " + el.value.substring(end);
|
|
133
|
+
onChange(newValue);
|
|
134
|
+
requestAnimationFrame(() => {
|
|
135
|
+
el.selectionStart = el.selectionEnd = start + 2;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<textarea
|
|
142
|
+
ref={ref}
|
|
143
|
+
aria-label={ariaLabel}
|
|
144
|
+
value={value}
|
|
145
|
+
onChange={(e) => onChange(e.target.value)}
|
|
146
|
+
onKeyDown={handleKeyDown}
|
|
147
|
+
placeholder={placeholder}
|
|
148
|
+
disabled={disabled}
|
|
149
|
+
className="w-full resize-none rounded-lg border bg-background p-3 text-sm leading-relaxed focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50"
|
|
150
|
+
style={{ minHeight: "120px" }}
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// AgentDefinitionEditor
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
interface AgentDefinitionEditorProps {
|
|
160
|
+
agent: Agent;
|
|
161
|
+
config: GagentsHookConfig;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function AgentDefinitionEditor({ agent, config }: AgentDefinitionEditorProps) {
|
|
165
|
+
const updateAgent = useUpdateAgent(config);
|
|
166
|
+
|
|
167
|
+
// Track agent ID to reset state on agent switch
|
|
168
|
+
const [trackedAgentId, setTrackedAgentId] = useState(agent.id);
|
|
169
|
+
|
|
170
|
+
// Text field state
|
|
171
|
+
const [fields, setFields] = useState<Record<string, string>>(() => ({
|
|
172
|
+
identity: agent.identity ?? "",
|
|
173
|
+
mission: agent.mission ?? "",
|
|
174
|
+
tone_style_format: agent.tone_style_format ?? "",
|
|
175
|
+
rules: agent.rules ?? "",
|
|
176
|
+
context: agent.context ?? "",
|
|
177
|
+
}));
|
|
178
|
+
|
|
179
|
+
// Conversation flow state
|
|
180
|
+
const [conversationFlowSteps, setConversationFlowSteps] = useState<ConversationFlowStep[]>(
|
|
181
|
+
() => parseConversationFlow(agent.conversation_flow),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const [changeNotes, setChangeNotes] = useState("");
|
|
185
|
+
|
|
186
|
+
// Reset state when agent changes
|
|
187
|
+
if (trackedAgentId !== agent.id) {
|
|
188
|
+
setTrackedAgentId(agent.id);
|
|
189
|
+
setFields({
|
|
190
|
+
identity: agent.identity ?? "",
|
|
191
|
+
mission: agent.mission ?? "",
|
|
192
|
+
tone_style_format: agent.tone_style_format ?? "",
|
|
193
|
+
rules: agent.rules ?? "",
|
|
194
|
+
context: agent.context ?? "",
|
|
195
|
+
});
|
|
196
|
+
setConversationFlowSteps(parseConversationFlow(agent.conversation_flow));
|
|
197
|
+
setChangeNotes("");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function updateField(key: string, value: string) {
|
|
201
|
+
setFields((prev) => ({ ...prev, [key]: value }));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const { chars, tokens } = computeTotals(fields, conversationFlowSteps);
|
|
205
|
+
|
|
206
|
+
async function handleSave() {
|
|
207
|
+
if (!fields.identity.trim() || !fields.mission.trim()) {
|
|
208
|
+
toast.error("Identidade e Missão são campos obrigatórios");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const body: Record<string, unknown> = {
|
|
213
|
+
identity: fields.identity.trim(),
|
|
214
|
+
mission: fields.mission.trim(),
|
|
215
|
+
tone_style_format: fields.tone_style_format.trim() || null,
|
|
216
|
+
rules: fields.rules.trim() || null,
|
|
217
|
+
conversation_flow: conversationFlowSteps.length > 0
|
|
218
|
+
? JSON.stringify(conversationFlowSteps)
|
|
219
|
+
: null,
|
|
220
|
+
context: fields.context.trim() || null,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
if (changeNotes.trim()) {
|
|
224
|
+
body.change_notes = changeNotes.trim();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
await updateAgent.mutateAsync({ id: agent.id, body });
|
|
229
|
+
setChangeNotes("");
|
|
230
|
+
toast.success("Definição do agente salva com sucesso");
|
|
231
|
+
} catch {
|
|
232
|
+
toast.error("Erro ao salvar definição do agente");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<div className="space-y-6 p-4">
|
|
238
|
+
{/* Sections */}
|
|
239
|
+
{SECTIONS.map((section) => (
|
|
240
|
+
<div key={section.key} className="space-y-2">
|
|
241
|
+
<Label className="text-sm font-medium">
|
|
242
|
+
{section.label}
|
|
243
|
+
{section.required && <span className="ml-0.5 text-destructive">*</span>}
|
|
244
|
+
</Label>
|
|
245
|
+
<p className="text-xs text-muted-foreground">{section.helper}</p>
|
|
246
|
+
|
|
247
|
+
{section.type === "textarea" ? (
|
|
248
|
+
<AutoTextarea
|
|
249
|
+
value={fields[section.key] ?? ""}
|
|
250
|
+
onChange={(v) => updateField(section.key, v)}
|
|
251
|
+
disabled={updateAgent.isPending}
|
|
252
|
+
placeholder={`${section.label}...`}
|
|
253
|
+
ariaLabel={section.label}
|
|
254
|
+
/>
|
|
255
|
+
) : (
|
|
256
|
+
<ConversationFlowEditor
|
|
257
|
+
steps={conversationFlowSteps}
|
|
258
|
+
onChange={setConversationFlowSteps}
|
|
259
|
+
disabled={updateAgent.isPending}
|
|
260
|
+
/>
|
|
261
|
+
)}
|
|
262
|
+
</div>
|
|
263
|
+
))}
|
|
264
|
+
|
|
265
|
+
{/* Character count + token estimate */}
|
|
266
|
+
<div className="flex items-center gap-3 text-xs text-muted-foreground">
|
|
267
|
+
<span className="tabular-nums">{chars.toLocaleString("pt-BR")} caracteres</span>
|
|
268
|
+
<span>·</span>
|
|
269
|
+
<span className="tabular-nums">~{tokens.toLocaleString("pt-BR")} tokens</span>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
{/* Footer: change notes + save */}
|
|
273
|
+
<div className="flex items-center gap-3">
|
|
274
|
+
<Input
|
|
275
|
+
aria-label="Notas da alteração"
|
|
276
|
+
name="changeNotes"
|
|
277
|
+
value={changeNotes}
|
|
278
|
+
onChange={(e) => setChangeNotes(e.target.value)}
|
|
279
|
+
placeholder="O que mudou? (opcional)"
|
|
280
|
+
disabled={updateAgent.isPending}
|
|
281
|
+
className="flex-1"
|
|
282
|
+
onKeyDown={(e) => {
|
|
283
|
+
if (e.key === "Enter") {
|
|
284
|
+
e.preventDefault();
|
|
285
|
+
handleSave();
|
|
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>
|
|
303
|
+
</div>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
-
import type { Agent, Objective } from "../../types";
|
|
2
|
+
import type { Agent, Objective, ConversationFlowStep } from "../../types";
|
|
3
3
|
import type { GagentsHookConfig } from "../../hooks/types";
|
|
4
4
|
import {
|
|
5
5
|
useObjectives,
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
SortableItemHandle,
|
|
37
37
|
SortableOverlay,
|
|
38
38
|
} from "../ui/sortable";
|
|
39
|
+
import { ConversationFlowEditor } from "./conversation-flow-editor";
|
|
39
40
|
import { Trash2, Target, Pencil, Plus, GripVertical } from "lucide-react";
|
|
40
41
|
import { toast } from "sonner";
|
|
41
42
|
|
|
@@ -57,28 +58,12 @@ interface ObjectiveFormState {
|
|
|
57
58
|
title: string;
|
|
58
59
|
slug: string;
|
|
59
60
|
instruction: string;
|
|
60
|
-
|
|
61
|
+
description: string;
|
|
62
|
+
conversation_flow: ConversationFlowStep[];
|
|
63
|
+
rules: string;
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
const EMPTY_FORM: ObjectiveFormState = { title: "", slug: "", instruction: "",
|
|
64
|
-
|
|
65
|
-
/** Split a stored prompt into instruction (first line) and body (rest). */
|
|
66
|
-
function splitPrompt(prompt: string | null | undefined): { instruction: string; body: string } {
|
|
67
|
-
if (!prompt) return { instruction: "", body: "" };
|
|
68
|
-
const idx = prompt.indexOf("\n");
|
|
69
|
-
if (idx === -1) return { instruction: prompt.trim(), body: "" };
|
|
70
|
-
return { instruction: prompt.slice(0, idx).trim(), body: prompt.slice(idx + 1).trim() };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Merge instruction + body into a single prompt string. */
|
|
74
|
-
function mergePrompt(instruction: string, body: string): string {
|
|
75
|
-
const i = instruction.trim();
|
|
76
|
-
const b = body.trim();
|
|
77
|
-
if (!i && !b) return "";
|
|
78
|
-
if (!b) return i;
|
|
79
|
-
if (!i) return b;
|
|
80
|
-
return `${i}\n${b}`;
|
|
81
|
-
}
|
|
66
|
+
const EMPTY_FORM: ObjectiveFormState = { title: "", slug: "", instruction: "", description: "", conversation_flow: [], rules: "" };
|
|
82
67
|
|
|
83
68
|
export function AgentObjectivesList({ agent, config }: AgentObjectivesListProps) {
|
|
84
69
|
const { data: objectivesData, isLoading } = useObjectives(config, agent.id);
|
|
@@ -123,12 +108,17 @@ export function AgentObjectivesList({ agent, config }: AgentObjectivesListProps)
|
|
|
123
108
|
|
|
124
109
|
function openEdit(objective: Objective) {
|
|
125
110
|
setEditTarget(objective);
|
|
126
|
-
|
|
111
|
+
let parsedFlow: ConversationFlowStep[] = [];
|
|
112
|
+
if (objective.conversation_flow) {
|
|
113
|
+
try { parsedFlow = JSON.parse(objective.conversation_flow); } catch { /* invalid JSON */ }
|
|
114
|
+
}
|
|
127
115
|
setForm({
|
|
128
116
|
title: objective.title,
|
|
129
117
|
slug: objective.slug || "",
|
|
130
|
-
instruction,
|
|
131
|
-
|
|
118
|
+
instruction: objective.instruction || "",
|
|
119
|
+
description: objective.description || "",
|
|
120
|
+
conversation_flow: parsedFlow,
|
|
121
|
+
rules: objective.rules || "",
|
|
132
122
|
});
|
|
133
123
|
setSlugManual(true);
|
|
134
124
|
setFormOpen(true);
|
|
@@ -138,33 +128,32 @@ export function AgentObjectivesList({ agent, config }: AgentObjectivesListProps)
|
|
|
138
128
|
if (!form.title.trim()) return;
|
|
139
129
|
|
|
140
130
|
const effectiveSlug = form.slug.trim() || slugify(form.title);
|
|
141
|
-
const mergedPrompt = mergePrompt(form.instruction, form.prompt) || null;
|
|
142
131
|
const nextOrder =
|
|
143
132
|
sortedObjectives.length > 0
|
|
144
133
|
? Math.max(...sortedObjectives.map((o) => o.order)) + 1
|
|
145
134
|
: 1;
|
|
146
135
|
|
|
147
136
|
try {
|
|
137
|
+
const bodyFields = {
|
|
138
|
+
title: form.title.trim(),
|
|
139
|
+
slug: effectiveSlug,
|
|
140
|
+
instruction: form.instruction.trim() || null,
|
|
141
|
+
description: form.description.trim() || null,
|
|
142
|
+
conversation_flow: form.conversation_flow.length > 0 ? JSON.stringify(form.conversation_flow) : null,
|
|
143
|
+
rules: form.rules.trim() || null,
|
|
144
|
+
};
|
|
145
|
+
|
|
148
146
|
if (editTarget) {
|
|
149
147
|
await updateMutation.mutateAsync({
|
|
150
148
|
idAgent: agent.id,
|
|
151
149
|
id: editTarget.id,
|
|
152
|
-
body:
|
|
153
|
-
title: form.title.trim(),
|
|
154
|
-
slug: effectiveSlug,
|
|
155
|
-
prompt: mergedPrompt,
|
|
156
|
-
},
|
|
150
|
+
body: bodyFields,
|
|
157
151
|
});
|
|
158
152
|
toast.success("Objetivo atualizado");
|
|
159
153
|
} else {
|
|
160
154
|
await createMutation.mutateAsync({
|
|
161
155
|
idAgent: agent.id,
|
|
162
|
-
body: {
|
|
163
|
-
title: form.title.trim(),
|
|
164
|
-
slug: effectiveSlug,
|
|
165
|
-
prompt: mergedPrompt,
|
|
166
|
-
order: nextOrder,
|
|
167
|
-
},
|
|
156
|
+
body: { ...bodyFields, order: nextOrder },
|
|
168
157
|
});
|
|
169
158
|
toast.success("Objetivo criado");
|
|
170
159
|
}
|
|
@@ -274,23 +263,20 @@ export function AgentObjectivesList({ agent, config }: AgentObjectivesListProps)
|
|
|
274
263
|
</Badge>
|
|
275
264
|
)}
|
|
276
265
|
</div>
|
|
277
|
-
{objective.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
</div>
|
|
292
|
-
);
|
|
293
|
-
})()}
|
|
266
|
+
{(objective.instruction || objective.description) && (
|
|
267
|
+
<div className="space-y-0.5">
|
|
268
|
+
{objective.instruction && (
|
|
269
|
+
<p className="text-xs font-medium text-muted-foreground">
|
|
270
|
+
Quando: {objective.instruction}
|
|
271
|
+
</p>
|
|
272
|
+
)}
|
|
273
|
+
{objective.description && (
|
|
274
|
+
<p className="line-clamp-1 text-xs text-muted-foreground">
|
|
275
|
+
{objective.description}
|
|
276
|
+
</p>
|
|
277
|
+
)}
|
|
278
|
+
</div>
|
|
279
|
+
)}
|
|
294
280
|
</div>
|
|
295
281
|
|
|
296
282
|
<Switch
|
|
@@ -395,24 +381,52 @@ export function AgentObjectivesList({ agent, config }: AgentObjectivesListProps)
|
|
|
395
381
|
placeholder="Ex: Quando o utilizador quer agendar uma consulta"
|
|
396
382
|
/>
|
|
397
383
|
<p className="text-xs text-muted-foreground">
|
|
398
|
-
Instrução curta que diz ao agente QUANDO ativar este objetivo. Aparece na secção [
|
|
384
|
+
Instrução curta que diz ao agente QUANDO ativar este objetivo. Aparece na secção [OBJETIVOS] do prompt.
|
|
385
|
+
</p>
|
|
386
|
+
</div>
|
|
387
|
+
|
|
388
|
+
<div className="space-y-2">
|
|
389
|
+
<Label htmlFor="objective-description">Descrição</Label>
|
|
390
|
+
<Textarea
|
|
391
|
+
id="objective-description"
|
|
392
|
+
name="description"
|
|
393
|
+
value={form.description}
|
|
394
|
+
onChange={(e) =>
|
|
395
|
+
setForm((f) => ({ ...f, description: e.target.value }))
|
|
396
|
+
}
|
|
397
|
+
placeholder="Descreva o propósito deste objectivo..."
|
|
398
|
+
rows={3}
|
|
399
|
+
/>
|
|
400
|
+
<p className="text-xs text-muted-foreground">
|
|
401
|
+
Missão e propósito deste objectivo — o que o agente deve alcançar.
|
|
402
|
+
</p>
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
<div className="space-y-2">
|
|
406
|
+
<Label>Fluxo de Conversa</Label>
|
|
407
|
+
<ConversationFlowEditor
|
|
408
|
+
steps={form.conversation_flow}
|
|
409
|
+
onChange={(steps) => setForm((f) => ({ ...f, conversation_flow: steps }))}
|
|
410
|
+
/>
|
|
411
|
+
<p className="text-xs text-muted-foreground">
|
|
412
|
+
Etapas que o agente segue quando este objectivo é activado.
|
|
399
413
|
</p>
|
|
400
414
|
</div>
|
|
401
415
|
|
|
402
416
|
<div className="space-y-2">
|
|
403
|
-
<Label htmlFor="objective-
|
|
417
|
+
<Label htmlFor="objective-rules">Regras</Label>
|
|
404
418
|
<Textarea
|
|
405
|
-
id="objective-
|
|
406
|
-
name="
|
|
407
|
-
value={form.
|
|
419
|
+
id="objective-rules"
|
|
420
|
+
name="rules"
|
|
421
|
+
value={form.rules}
|
|
408
422
|
onChange={(e) =>
|
|
409
|
-
setForm((f) => ({ ...f,
|
|
423
|
+
setForm((f) => ({ ...f, rules: e.target.value }))
|
|
410
424
|
}
|
|
411
|
-
placeholder="
|
|
412
|
-
rows={
|
|
425
|
+
placeholder="Regras específicas deste objectivo..."
|
|
426
|
+
rows={3}
|
|
413
427
|
/>
|
|
414
428
|
<p className="text-xs text-muted-foreground">
|
|
415
|
-
|
|
429
|
+
Restrições e limites específicos quando este objectivo está activo.
|
|
416
430
|
</p>
|
|
417
431
|
</div>
|
|
418
432
|
</div>
|