@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
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { useState, useMemo } from "react";
|
|
2
|
+
import type { Agent, PromptVersion, ConversationFlowStep } from "../../types";
|
|
3
|
+
import type { GagentsHookConfig } from "../../hooks/types";
|
|
4
|
+
import { usePromptVersions } from "../../hooks";
|
|
5
|
+
import {
|
|
6
|
+
Button,
|
|
7
|
+
Badge,
|
|
8
|
+
Skeleton,
|
|
9
|
+
Dialog,
|
|
10
|
+
DialogContent,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
} from "@greatapps/greatauth-ui/ui";
|
|
14
|
+
import { FileText, RotateCcw, X, AlertTriangle } from "lucide-react";
|
|
15
|
+
import { toast } from "sonner";
|
|
16
|
+
|
|
17
|
+
interface AgentRevisionTabProps {
|
|
18
|
+
agent: Agent;
|
|
19
|
+
config: GagentsHookConfig;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const STRUCTURED_MARKERS = ["[IDENTIDADE]", "[MISSÃO]", "[TOM, ESTILO & FORMATO]"];
|
|
23
|
+
|
|
24
|
+
function formatDate(dateStr: string): string {
|
|
25
|
+
const date = new Date(dateStr);
|
|
26
|
+
return date.toLocaleDateString("pt-BR", {
|
|
27
|
+
day: "2-digit",
|
|
28
|
+
month: "2-digit",
|
|
29
|
+
year: "numeric",
|
|
30
|
+
hour: "2-digit",
|
|
31
|
+
minute: "2-digit",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function computeDiff(
|
|
36
|
+
oldText: string,
|
|
37
|
+
newText: string,
|
|
38
|
+
): { type: "added" | "removed" | "equal"; line: string }[] {
|
|
39
|
+
const oldLines = oldText.split("\n");
|
|
40
|
+
const newLines = newText.split("\n");
|
|
41
|
+
const result: { type: "added" | "removed" | "equal"; line: string }[] = [];
|
|
42
|
+
const maxLen = Math.max(oldLines.length, newLines.length);
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < maxLen; i++) {
|
|
45
|
+
const oldLine = i < oldLines.length ? oldLines[i] : undefined;
|
|
46
|
+
const newLine = i < newLines.length ? newLines[i] : undefined;
|
|
47
|
+
|
|
48
|
+
if (oldLine === newLine) {
|
|
49
|
+
result.push({ type: "equal", line: newLine! });
|
|
50
|
+
} else {
|
|
51
|
+
if (oldLine !== undefined) {
|
|
52
|
+
result.push({ type: "removed", line: oldLine });
|
|
53
|
+
}
|
|
54
|
+
if (newLine !== undefined) {
|
|
55
|
+
result.push({ type: "added", line: newLine });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function formatConversationFlow(raw: string | null): string {
|
|
64
|
+
if (!raw) return "";
|
|
65
|
+
try {
|
|
66
|
+
const steps: ConversationFlowStep[] = JSON.parse(raw);
|
|
67
|
+
if (!Array.isArray(steps) || steps.length === 0) return "";
|
|
68
|
+
return steps
|
|
69
|
+
.sort((a, b) => a.order - b.order)
|
|
70
|
+
.map((s) => {
|
|
71
|
+
let line = `${s.order}. ${s.instruction}`;
|
|
72
|
+
if (s.example) line += `\n Exemplo: ${s.example}`;
|
|
73
|
+
return line;
|
|
74
|
+
})
|
|
75
|
+
.join("\n");
|
|
76
|
+
} catch {
|
|
77
|
+
// If it's not valid JSON, return raw text
|
|
78
|
+
return raw;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildAssembledPrompt(agent: Agent): string {
|
|
83
|
+
const sections: string[] = [];
|
|
84
|
+
|
|
85
|
+
if (agent.identity?.trim()) {
|
|
86
|
+
sections.push(`[IDENTIDADE]\n${agent.identity.trim()}`);
|
|
87
|
+
}
|
|
88
|
+
if (agent.mission?.trim()) {
|
|
89
|
+
sections.push(`[MISSÃO]\n${agent.mission.trim()}`);
|
|
90
|
+
}
|
|
91
|
+
if (agent.tone_style_format?.trim()) {
|
|
92
|
+
sections.push(`[TOM, ESTILO & FORMATO]\n${agent.tone_style_format.trim()}`);
|
|
93
|
+
}
|
|
94
|
+
if (agent.rules?.trim()) {
|
|
95
|
+
sections.push(`[REGRAS]\n${agent.rules.trim()}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const flowFormatted = formatConversationFlow(agent.conversation_flow);
|
|
99
|
+
if (flowFormatted) {
|
|
100
|
+
sections.push(`[FLUXO DE CONVERSA]\n${flowFormatted}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (agent.context?.trim()) {
|
|
104
|
+
sections.push(`[CONTEXTO]\n${agent.context.trim()}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return sections.join("\n\n");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isLegacyVersion(version: PromptVersion): boolean {
|
|
111
|
+
const content = version.prompt_content ?? "";
|
|
112
|
+
return !STRUCTURED_MARKERS.some((marker) => content.includes(marker));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function AgentRevisionTab({ agent, config }: AgentRevisionTabProps) {
|
|
116
|
+
const { data: versionsData, isLoading } = usePromptVersions(config, agent.id);
|
|
117
|
+
|
|
118
|
+
const [compareVersionId, setCompareVersionId] = useState<number | null>(null);
|
|
119
|
+
const [legacyModalVersion, setLegacyModalVersion] = useState<PromptVersion | null>(null);
|
|
120
|
+
|
|
121
|
+
const versions = (versionsData?.data || []) as PromptVersion[];
|
|
122
|
+
const sortedVersions = [...versions].sort(
|
|
123
|
+
(a, b) => new Date(b.datetime_add).getTime() - new Date(a.datetime_add).getTime(),
|
|
124
|
+
);
|
|
125
|
+
const currentVersion = sortedVersions.find((v) => v.is_current) || sortedVersions[0] || null;
|
|
126
|
+
|
|
127
|
+
const assembledPrompt = useMemo(() => buildAssembledPrompt(agent), [
|
|
128
|
+
agent.identity,
|
|
129
|
+
agent.mission,
|
|
130
|
+
agent.tone_style_format,
|
|
131
|
+
agent.rules,
|
|
132
|
+
agent.conversation_flow,
|
|
133
|
+
agent.context,
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
const charCount = assembledPrompt.length;
|
|
137
|
+
const tokenEstimate = Math.ceil(charCount / 4);
|
|
138
|
+
|
|
139
|
+
const compareVersion = sortedVersions.find((v) => v.id === compareVersionId);
|
|
140
|
+
|
|
141
|
+
const diffLines =
|
|
142
|
+
currentVersion && compareVersion && compareVersion.id !== currentVersion.id
|
|
143
|
+
? computeDiff(compareVersion.prompt_content ?? "", currentVersion.prompt_content ?? "")
|
|
144
|
+
: null;
|
|
145
|
+
|
|
146
|
+
function handleRestore(version: PromptVersion) {
|
|
147
|
+
if (isLegacyVersion(version)) {
|
|
148
|
+
setLegacyModalVersion(version);
|
|
149
|
+
} else {
|
|
150
|
+
toast.info("Restaurar versão estruturada — funcionalidade em desenvolvimento");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (isLoading) {
|
|
155
|
+
return (
|
|
156
|
+
<div className="space-y-3 p-4">
|
|
157
|
+
{Array.from({ length: 3 }).map((_, i) => (
|
|
158
|
+
<Skeleton key={i} className="h-14 w-full" />
|
|
159
|
+
))}
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<div className="flex flex-col gap-4 p-4 lg:flex-row">
|
|
166
|
+
{/* Left: Preview + Diff */}
|
|
167
|
+
<div className="min-w-0 flex-1 space-y-4">
|
|
168
|
+
{/* Assembled prompt preview */}
|
|
169
|
+
<div className="space-y-2">
|
|
170
|
+
<h3 className="text-sm font-medium text-muted-foreground">
|
|
171
|
+
Preview do Prompt Montado
|
|
172
|
+
</h3>
|
|
173
|
+
<div className="rounded-lg border">
|
|
174
|
+
<pre className="max-h-[32rem] overflow-auto whitespace-pre-wrap p-4 font-mono text-sm leading-relaxed">
|
|
175
|
+
{assembledPrompt ? (
|
|
176
|
+
assembledPrompt.split("\n").map((line, i) => {
|
|
177
|
+
const isSectionHeader = /^\[.+\]$/.test(line.trim());
|
|
178
|
+
return (
|
|
179
|
+
<span
|
|
180
|
+
key={i}
|
|
181
|
+
className={isSectionHeader ? "font-bold text-foreground" : ""}
|
|
182
|
+
>
|
|
183
|
+
{line}
|
|
184
|
+
{"\n"}
|
|
185
|
+
</span>
|
|
186
|
+
);
|
|
187
|
+
})
|
|
188
|
+
) : (
|
|
189
|
+
<span className="italic text-muted-foreground">
|
|
190
|
+
Nenhum campo estruturado preenchido.
|
|
191
|
+
</span>
|
|
192
|
+
)}
|
|
193
|
+
</pre>
|
|
194
|
+
</div>
|
|
195
|
+
<div className="flex items-center gap-3 text-xs text-muted-foreground">
|
|
196
|
+
<span className="tabular-nums">{charCount.toLocaleString("pt-BR")} caracteres</span>
|
|
197
|
+
<span>·</span>
|
|
198
|
+
<span className="tabular-nums">~{tokenEstimate.toLocaleString("pt-BR")} tokens</span>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
{/* Diff panel (conditional) */}
|
|
203
|
+
{diffLines && compareVersion && currentVersion && (
|
|
204
|
+
<div>
|
|
205
|
+
<div className="mb-2 flex items-center justify-between">
|
|
206
|
+
<h3 className="text-sm font-medium text-muted-foreground">
|
|
207
|
+
Diferenças: v{compareVersion.version_number} → v{currentVersion.version_number} (actual)
|
|
208
|
+
</h3>
|
|
209
|
+
<Button
|
|
210
|
+
variant="ghost"
|
|
211
|
+
size="sm"
|
|
212
|
+
onClick={() => setCompareVersionId(null)}
|
|
213
|
+
className="text-xs"
|
|
214
|
+
>
|
|
215
|
+
<X className="mr-1 h-3 w-3" />
|
|
216
|
+
Fechar
|
|
217
|
+
</Button>
|
|
218
|
+
</div>
|
|
219
|
+
<div className="max-h-64 overflow-auto rounded-lg border font-mono text-sm">
|
|
220
|
+
{diffLines.map((line, i) => (
|
|
221
|
+
<div
|
|
222
|
+
key={i}
|
|
223
|
+
className={`whitespace-pre-wrap px-3 py-0.5 ${
|
|
224
|
+
line.type === "added"
|
|
225
|
+
? "bg-green-500/10 text-green-700 dark:text-green-400"
|
|
226
|
+
: line.type === "removed"
|
|
227
|
+
? "bg-red-500/10 text-red-700 dark:text-red-400"
|
|
228
|
+
: ""
|
|
229
|
+
}`}
|
|
230
|
+
>
|
|
231
|
+
<span className="mr-2 inline-block w-4 select-none text-muted-foreground">
|
|
232
|
+
{line.type === "added" ? "+" : line.type === "removed" ? "-" : " "}
|
|
233
|
+
</span>
|
|
234
|
+
{line.line || " "}
|
|
235
|
+
</div>
|
|
236
|
+
))}
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
)}
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
{/* Right: Version history timeline */}
|
|
243
|
+
<div className="w-full space-y-2 lg:w-80 lg:shrink-0">
|
|
244
|
+
<h3 className="text-sm font-medium text-muted-foreground">
|
|
245
|
+
Histórico de Versões
|
|
246
|
+
</h3>
|
|
247
|
+
{sortedVersions.length === 0 ? (
|
|
248
|
+
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed p-8 text-center">
|
|
249
|
+
<FileText className="mb-2 h-8 w-8 text-muted-foreground" />
|
|
250
|
+
<p className="text-sm text-muted-foreground">
|
|
251
|
+
Nenhuma versão encontrada.
|
|
252
|
+
</p>
|
|
253
|
+
</div>
|
|
254
|
+
) : (
|
|
255
|
+
<div className="space-y-1">
|
|
256
|
+
{sortedVersions.map((version) => {
|
|
257
|
+
const isCurrent = currentVersion?.id === version.id;
|
|
258
|
+
const isComparing = version.id === compareVersionId;
|
|
259
|
+
return (
|
|
260
|
+
<div
|
|
261
|
+
key={version.id}
|
|
262
|
+
className={`rounded-lg border p-3 transition-colors ${
|
|
263
|
+
isCurrent
|
|
264
|
+
? "border-primary bg-primary/5"
|
|
265
|
+
: isComparing
|
|
266
|
+
? "border-muted-foreground/30 bg-muted/50"
|
|
267
|
+
: ""
|
|
268
|
+
}`}
|
|
269
|
+
>
|
|
270
|
+
<div className="flex items-center justify-between gap-2">
|
|
271
|
+
<span className="text-sm font-medium">
|
|
272
|
+
v{version.version_number}
|
|
273
|
+
</span>
|
|
274
|
+
{isCurrent && (
|
|
275
|
+
<Badge variant="default" className="text-[10px] px-1.5 py-0">
|
|
276
|
+
Actual
|
|
277
|
+
</Badge>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
<div className="mt-1 text-xs text-muted-foreground">
|
|
281
|
+
{formatDate(version.datetime_add)}
|
|
282
|
+
</div>
|
|
283
|
+
<div className="mt-1 flex items-center gap-2 text-xs text-muted-foreground">
|
|
284
|
+
<span>{(version.prompt_content ?? "").length} chars</span>
|
|
285
|
+
<span>·</span>
|
|
286
|
+
<span className="truncate font-mono">
|
|
287
|
+
{(version.prompt_hash ?? "").slice(0, 8)}
|
|
288
|
+
</span>
|
|
289
|
+
</div>
|
|
290
|
+
{version.change_notes && (
|
|
291
|
+
<div className="mt-1.5 text-xs italic text-muted-foreground">
|
|
292
|
+
{version.change_notes}
|
|
293
|
+
</div>
|
|
294
|
+
)}
|
|
295
|
+
{!isCurrent && (
|
|
296
|
+
<div className="mt-2 flex items-center gap-3">
|
|
297
|
+
<button
|
|
298
|
+
type="button"
|
|
299
|
+
onClick={() => setCompareVersionId(isComparing ? null : version.id)}
|
|
300
|
+
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
|
|
301
|
+
>
|
|
302
|
+
<FileText aria-hidden="true" className="h-3 w-3" />
|
|
303
|
+
{isComparing ? "Ocultar diff" : "Comparar"}
|
|
304
|
+
</button>
|
|
305
|
+
<button
|
|
306
|
+
type="button"
|
|
307
|
+
onClick={() => handleRestore(version)}
|
|
308
|
+
className="flex items-center gap-1 text-xs text-primary hover:underline"
|
|
309
|
+
>
|
|
310
|
+
<RotateCcw aria-hidden="true" className="h-3 w-3" />
|
|
311
|
+
Restaurar
|
|
312
|
+
</button>
|
|
313
|
+
</div>
|
|
314
|
+
)}
|
|
315
|
+
</div>
|
|
316
|
+
);
|
|
317
|
+
})}
|
|
318
|
+
</div>
|
|
319
|
+
)}
|
|
320
|
+
</div>
|
|
321
|
+
|
|
322
|
+
{/* Legacy version modal */}
|
|
323
|
+
<Dialog
|
|
324
|
+
open={!!legacyModalVersion}
|
|
325
|
+
onOpenChange={(open) => {
|
|
326
|
+
if (!open) setLegacyModalVersion(null);
|
|
327
|
+
}}
|
|
328
|
+
>
|
|
329
|
+
<DialogContent className="max-w-2xl">
|
|
330
|
+
<DialogHeader>
|
|
331
|
+
<DialogTitle className="flex items-center gap-2">
|
|
332
|
+
<AlertTriangle className="h-5 w-5 text-amber-500" />
|
|
333
|
+
Versão Legada — v{legacyModalVersion?.version_number}
|
|
334
|
+
</DialogTitle>
|
|
335
|
+
</DialogHeader>
|
|
336
|
+
<div className="space-y-3">
|
|
337
|
+
<div className="rounded-lg border border-amber-500/30 bg-amber-500/5 p-3">
|
|
338
|
+
<p className="text-sm text-amber-700 dark:text-amber-400">
|
|
339
|
+
Esta versão foi criada antes da reestruturação e não pode ser restaurada nos campos estruturados.
|
|
340
|
+
</p>
|
|
341
|
+
</div>
|
|
342
|
+
<div className="max-h-96 overflow-auto rounded-lg border p-4">
|
|
343
|
+
<pre className="whitespace-pre-wrap font-mono text-sm leading-relaxed">
|
|
344
|
+
{legacyModalVersion?.prompt_content ?? ""}
|
|
345
|
+
</pre>
|
|
346
|
+
</div>
|
|
347
|
+
<div className="flex items-center gap-3 text-xs text-muted-foreground">
|
|
348
|
+
<span>
|
|
349
|
+
{(legacyModalVersion?.prompt_content ?? "").length.toLocaleString("pt-BR")} caracteres
|
|
350
|
+
</span>
|
|
351
|
+
{legacyModalVersion?.change_notes && (
|
|
352
|
+
<>
|
|
353
|
+
<span>·</span>
|
|
354
|
+
<span className="italic">{legacyModalVersion.change_notes}</span>
|
|
355
|
+
</>
|
|
356
|
+
)}
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
<div className="flex justify-end pt-2">
|
|
360
|
+
<Button variant="outline" onClick={() => setLegacyModalVersion(null)}>
|
|
361
|
+
Fechar
|
|
362
|
+
</Button>
|
|
363
|
+
</div>
|
|
364
|
+
</DialogContent>
|
|
365
|
+
</Dialog>
|
|
366
|
+
</div>
|
|
367
|
+
);
|
|
368
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Agent } from "../../types";
|
|
2
2
|
import type { GagentsHookConfig } from "../../hooks/types";
|
|
3
3
|
import { AgentObjectivesList } from "./agent-objectives-list";
|
|
4
|
-
import {
|
|
4
|
+
import { AgentDefinitionEditor } from "./agent-definition-editor";
|
|
5
|
+
import { AgentRevisionTab } from "./agent-revision-tab";
|
|
5
6
|
import { AgentConversationsPanel } from "../conversations/agent-conversations-panel";
|
|
6
7
|
import { CapabilitiesTab } from "../capabilities/capabilities-tab";
|
|
7
8
|
import { IntegrationsTab } from "../capabilities/integrations-tab";
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
TabsTrigger,
|
|
12
13
|
TabsContent,
|
|
13
14
|
} from "@greatapps/greatauth-ui/ui";
|
|
14
|
-
import { Target,
|
|
15
|
+
import { Target, Settings2, MessageCircle, Blocks, Plug, History } from "lucide-react";
|
|
15
16
|
|
|
16
17
|
interface AgentTabsProps {
|
|
17
18
|
agent: Agent;
|
|
@@ -25,11 +26,11 @@ export function AgentTabs({
|
|
|
25
26
|
renderChatLink,
|
|
26
27
|
}: AgentTabsProps) {
|
|
27
28
|
return (
|
|
28
|
-
<Tabs defaultValue="
|
|
29
|
+
<Tabs defaultValue="definicao">
|
|
29
30
|
<TabsList>
|
|
30
|
-
<TabsTrigger value="
|
|
31
|
-
<
|
|
32
|
-
|
|
31
|
+
<TabsTrigger value="definicao" className="flex items-center gap-1.5">
|
|
32
|
+
<Settings2 aria-hidden="true" className="h-3.5 w-3.5" />
|
|
33
|
+
Definição
|
|
33
34
|
</TabsTrigger>
|
|
34
35
|
<TabsTrigger value="objetivos" className="flex items-center gap-1.5">
|
|
35
36
|
<Target aria-hidden="true" className="h-3.5 w-3.5" />
|
|
@@ -47,10 +48,14 @@ export function AgentTabs({
|
|
|
47
48
|
<MessageCircle aria-hidden="true" className="h-3.5 w-3.5" />
|
|
48
49
|
Conversas
|
|
49
50
|
</TabsTrigger>
|
|
51
|
+
<TabsTrigger value="revisao" className="flex items-center gap-1.5">
|
|
52
|
+
<History aria-hidden="true" className="h-3.5 w-3.5" />
|
|
53
|
+
Revisão
|
|
54
|
+
</TabsTrigger>
|
|
50
55
|
</TabsList>
|
|
51
56
|
|
|
52
|
-
<TabsContent value="
|
|
53
|
-
<
|
|
57
|
+
<TabsContent value="definicao" className="mt-4">
|
|
58
|
+
<AgentDefinitionEditor agent={agent} config={config} />
|
|
54
59
|
</TabsContent>
|
|
55
60
|
|
|
56
61
|
<TabsContent value="objetivos" className="mt-4">
|
|
@@ -72,6 +77,10 @@ export function AgentTabs({
|
|
|
72
77
|
renderChatLink={renderChatLink}
|
|
73
78
|
/>
|
|
74
79
|
</TabsContent>
|
|
80
|
+
|
|
81
|
+
<TabsContent value="revisao" className="mt-4">
|
|
82
|
+
<AgentRevisionTab agent={agent} config={config} />
|
|
83
|
+
</TabsContent>
|
|
75
84
|
</Tabs>
|
|
76
85
|
);
|
|
77
86
|
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from "react";
|
|
2
|
+
import type { ConversationFlowStep } from "../../types";
|
|
3
|
+
import { Button, Input } from "@greatapps/greatauth-ui/ui";
|
|
4
|
+
import {
|
|
5
|
+
Sortable,
|
|
6
|
+
SortableContent,
|
|
7
|
+
SortableItem,
|
|
8
|
+
SortableItemHandle,
|
|
9
|
+
SortableOverlay,
|
|
10
|
+
} from "../ui/sortable";
|
|
11
|
+
import { GripVertical, Plus, Trash2 } from "lucide-react";
|
|
12
|
+
import { cn } from "../../lib";
|
|
13
|
+
|
|
14
|
+
interface StepWithKey extends ConversationFlowStep {
|
|
15
|
+
_key: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ConversationFlowEditorProps {
|
|
19
|
+
steps: ConversationFlowStep[];
|
|
20
|
+
onChange: (steps: ConversationFlowStep[]) => void;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function renumber(steps: ConversationFlowStep[]): ConversationFlowStep[] {
|
|
25
|
+
return steps.map((s, i) => ({ ...s, order: i + 1 }));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function stripKeys(steps: StepWithKey[]): ConversationFlowStep[] {
|
|
29
|
+
return steps.map(({ _key, ...rest }) => rest);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function ConversationFlowEditor({
|
|
33
|
+
steps,
|
|
34
|
+
onChange,
|
|
35
|
+
}: ConversationFlowEditorProps) {
|
|
36
|
+
const nextKey = useRef(steps.length + 1);
|
|
37
|
+
|
|
38
|
+
const [internal, setInternal] = useState<StepWithKey[]>(() =>
|
|
39
|
+
steps.map((s, i) => ({ ...s, _key: i + 1 })),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const sync = useCallback(
|
|
43
|
+
(next: StepWithKey[]) => {
|
|
44
|
+
const renumbered = renumber(next).map((s, i) => ({
|
|
45
|
+
...s,
|
|
46
|
+
_key: next[i]._key,
|
|
47
|
+
})) as StepWithKey[];
|
|
48
|
+
setInternal(renumbered);
|
|
49
|
+
onChange(stripKeys(renumbered));
|
|
50
|
+
},
|
|
51
|
+
[onChange],
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
function handleAdd() {
|
|
55
|
+
nextKey.current += 1;
|
|
56
|
+
const newStep: StepWithKey = {
|
|
57
|
+
order: internal.length + 1,
|
|
58
|
+
instruction: "",
|
|
59
|
+
example: null,
|
|
60
|
+
_key: nextKey.current,
|
|
61
|
+
};
|
|
62
|
+
sync([...internal, newStep]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function handleRemove(key: number) {
|
|
66
|
+
sync(internal.filter((s) => s._key !== key));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function handleFieldChange(
|
|
70
|
+
key: number,
|
|
71
|
+
field: "instruction" | "example",
|
|
72
|
+
value: string,
|
|
73
|
+
) {
|
|
74
|
+
const next = internal.map((s) => {
|
|
75
|
+
if (s._key !== key) return s;
|
|
76
|
+
if (field === "example") {
|
|
77
|
+
return { ...s, example: value || null };
|
|
78
|
+
}
|
|
79
|
+
return { ...s, [field]: value };
|
|
80
|
+
});
|
|
81
|
+
sync(next);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function handleReorder(newItems: StepWithKey[]) {
|
|
85
|
+
sync(newItems);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className="space-y-3">
|
|
90
|
+
{internal.length === 0 ? (
|
|
91
|
+
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed p-8 text-center">
|
|
92
|
+
<p className="text-sm text-muted-foreground">
|
|
93
|
+
Nenhuma etapa definida. Adicione a primeira etapa do fluxo de
|
|
94
|
+
conversa.
|
|
95
|
+
</p>
|
|
96
|
+
</div>
|
|
97
|
+
) : (
|
|
98
|
+
<Sortable
|
|
99
|
+
value={internal}
|
|
100
|
+
onValueChange={handleReorder}
|
|
101
|
+
getItemValue={(item) => item._key}
|
|
102
|
+
>
|
|
103
|
+
<SortableContent className="space-y-2">
|
|
104
|
+
{internal.map((step) => (
|
|
105
|
+
<SortableItem
|
|
106
|
+
key={step._key}
|
|
107
|
+
value={step._key}
|
|
108
|
+
className="flex items-center gap-2 rounded-lg border bg-card p-2"
|
|
109
|
+
>
|
|
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
|
+
|
|
114
|
+
<span className="shrink-0 text-xs font-medium text-muted-foreground tabular-nums w-6 text-right">
|
|
115
|
+
{step.order}.
|
|
116
|
+
</span>
|
|
117
|
+
|
|
118
|
+
<Input
|
|
119
|
+
value={step.instruction}
|
|
120
|
+
onChange={(e) =>
|
|
121
|
+
handleFieldChange(step._key, "instruction", e.target.value)
|
|
122
|
+
}
|
|
123
|
+
placeholder="Instrução (obrigatório)"
|
|
124
|
+
className="flex-1 min-w-0"
|
|
125
|
+
required
|
|
126
|
+
/>
|
|
127
|
+
|
|
128
|
+
<Input
|
|
129
|
+
value={step.example ?? ""}
|
|
130
|
+
onChange={(e) =>
|
|
131
|
+
handleFieldChange(step._key, "example", e.target.value)
|
|
132
|
+
}
|
|
133
|
+
placeholder="Exemplo (opcional)"
|
|
134
|
+
className="flex-1 min-w-0"
|
|
135
|
+
/>
|
|
136
|
+
|
|
137
|
+
<Button
|
|
138
|
+
type="button"
|
|
139
|
+
variant="ghost"
|
|
140
|
+
size="icon"
|
|
141
|
+
aria-label="Remover etapa"
|
|
142
|
+
className={cn(
|
|
143
|
+
"shrink-0 text-muted-foreground hover:text-destructive",
|
|
144
|
+
)}
|
|
145
|
+
onClick={() => handleRemove(step._key)}
|
|
146
|
+
>
|
|
147
|
+
<Trash2 className="h-4 w-4" />
|
|
148
|
+
</Button>
|
|
149
|
+
</SortableItem>
|
|
150
|
+
))}
|
|
151
|
+
</SortableContent>
|
|
152
|
+
<SortableOverlay>
|
|
153
|
+
{({ value }) => {
|
|
154
|
+
const step = internal.find((s) => s._key === value);
|
|
155
|
+
return (
|
|
156
|
+
<div className="flex items-center gap-2 rounded-lg border bg-card p-2 shadow-lg">
|
|
157
|
+
<GripVertical
|
|
158
|
+
aria-hidden="true"
|
|
159
|
+
className="h-4 w-4 text-muted-foreground"
|
|
160
|
+
/>
|
|
161
|
+
<span className="text-xs font-medium text-muted-foreground">
|
|
162
|
+
{step?.order}.
|
|
163
|
+
</span>
|
|
164
|
+
<span className="text-sm truncate">
|
|
165
|
+
{step?.instruction || "Etapa sem instrução"}
|
|
166
|
+
</span>
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
}}
|
|
170
|
+
</SortableOverlay>
|
|
171
|
+
</Sortable>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
<Button type="button" variant="outline" size="sm" onClick={handleAdd}>
|
|
175
|
+
<Plus className="mr-2 h-4 w-4" />
|
|
176
|
+
Adicionar etapa
|
|
177
|
+
</Button>
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
}
|
package/src/hooks/use-agents.ts
CHANGED
|
@@ -34,7 +34,6 @@ export function useCreateAgent(config: GagentsHookConfig) {
|
|
|
34
34
|
return useMutation({
|
|
35
35
|
mutationFn: (body: {
|
|
36
36
|
title: string;
|
|
37
|
-
prompt?: string;
|
|
38
37
|
photo?: string;
|
|
39
38
|
delay_typing?: number;
|
|
40
39
|
waiting_time?: number;
|
|
@@ -57,7 +56,13 @@ export function useUpdateAgent(config: GagentsHookConfig) {
|
|
|
57
56
|
id: number;
|
|
58
57
|
body: {
|
|
59
58
|
title?: string;
|
|
60
|
-
|
|
59
|
+
identity?: string | null;
|
|
60
|
+
mission?: string | null;
|
|
61
|
+
tone_style_format?: string | null;
|
|
62
|
+
rules?: string | null;
|
|
63
|
+
conversation_flow?: string | null;
|
|
64
|
+
context?: string | null;
|
|
65
|
+
change_notes?: string;
|
|
61
66
|
photo?: string;
|
|
62
67
|
delay_typing?: number;
|
|
63
68
|
waiting_time?: number;
|
|
@@ -26,7 +26,10 @@ export function useCreateObjective(config: GagentsHookConfig) {
|
|
|
26
26
|
body: {
|
|
27
27
|
title: string;
|
|
28
28
|
slug?: string;
|
|
29
|
-
|
|
29
|
+
instruction?: string | null;
|
|
30
|
+
description?: string | null;
|
|
31
|
+
conversation_flow?: string | null;
|
|
32
|
+
rules?: string | null;
|
|
30
33
|
order?: number;
|
|
31
34
|
active?: boolean;
|
|
32
35
|
};
|
|
@@ -52,7 +55,10 @@ export function useUpdateObjective(config: GagentsHookConfig) {
|
|
|
52
55
|
body: Partial<{
|
|
53
56
|
title: string;
|
|
54
57
|
slug: string;
|
|
55
|
-
|
|
58
|
+
instruction: string | null;
|
|
59
|
+
description: string | null;
|
|
60
|
+
conversation_flow: string | null;
|
|
61
|
+
rules: string | null;
|
|
56
62
|
order: number;
|
|
57
63
|
active: boolean;
|
|
58
64
|
}>;
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export type {
|
|
|
19
19
|
CapabilitiesResponse,
|
|
20
20
|
AgentCapability,
|
|
21
21
|
AgentCapabilitiesPayload,
|
|
22
|
+
ConversationFlowStep,
|
|
22
23
|
} from "./types";
|
|
23
24
|
|
|
24
25
|
// Client
|
|
@@ -36,7 +37,9 @@ export { AgentsTable } from "./components/agents/agents-table";
|
|
|
36
37
|
export { AgentFormDialog } from "./components/agents/agent-form-dialog";
|
|
37
38
|
export { AgentEditForm } from "./components/agents/agent-edit-form";
|
|
38
39
|
export { AgentTabs } from "./components/agents/agent-tabs";
|
|
39
|
-
export {
|
|
40
|
+
export { AgentDefinitionEditor } from "./components/agents/agent-definition-editor";
|
|
41
|
+
export { AgentRevisionTab } from "./components/agents/agent-revision-tab";
|
|
42
|
+
export { ConversationFlowEditor } from "./components/agents/conversation-flow-editor";
|
|
40
43
|
export { AgentObjectivesList } from "./components/agents/agent-objectives-list";
|
|
41
44
|
export { AgentToolsList } from "./components/agents/agent-tools-list";
|
|
42
45
|
export { ToolsTable } from "./components/tools/tools-table";
|