@greatapps/greatagents-ui 0.3.20 → 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.
@@ -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 { AgentPromptEditor } from "./agent-prompt-editor";
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, FileText, MessageCircle, Blocks, Plug } from "lucide-react";
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="prompt">
29
+ <Tabs defaultValue="definicao">
29
30
  <TabsList>
30
- <TabsTrigger value="prompt" className="flex items-center gap-1.5">
31
- <FileText aria-hidden="true" className="h-3.5 w-3.5" />
32
- Prompt
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="prompt" className="mt-4">
53
- <AgentPromptEditor agent={agent} config={config} />
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
+ }
@@ -53,6 +53,7 @@ const INTEGRATION_FUNCTIONS: Record<string, IntegrationFunction[]> = {
53
53
 
54
54
  function buildCustomInstructions(
55
55
  integrationSlug: string,
56
+ integrationName: string,
56
57
  selectedFunctions: Set<string>,
57
58
  functionInstructions: Record<string, string>,
58
59
  ): string {
@@ -62,12 +63,16 @@ function buildCustomInstructions(
62
63
  const activeFns = fns.filter((f) => selectedFunctions.has(f.slug));
63
64
  if (activeFns.length === 0) return "";
64
65
 
65
- const lines = activeFns.map((f) => {
66
+ const lines: string[] = [];
67
+ lines.push(`## ${integrationName} (${integrationSlug})`);
68
+
69
+ for (const f of activeFns) {
66
70
  const instruction = functionInstructions[f.slug] || f.defaultInstructions;
67
- return `- ${f.slug}: ${instruction}`;
68
- });
71
+ lines.push(`### ${f.slug}`);
72
+ lines.push(instruction);
73
+ }
69
74
 
70
- return `Funções disponíveis (${integrationSlug}):\n${lines.join("\n")}`;
75
+ return lines.join("\n");
71
76
  }
72
77
 
73
78
  // ---------------------------------------------------------------------------
@@ -260,7 +265,7 @@ export function IntegrationsTab({
260
265
  if (!toolId) continue;
261
266
  }
262
267
  }
263
- const customInstructions = buildCustomInstructions(slug, local.selectedFunctions, local.functionInstructions);
268
+ const customInstructions = buildCustomInstructions(slug, card.definition.name, local.selectedFunctions, local.functionInstructions);
264
269
  await addAgentTool.mutateAsync({
265
270
  idAgent: agentId,
266
271
  body: { id_tool: toolId, enabled: true, ...(customInstructions ? { custom_instructions: customInstructions } : {}) },
@@ -280,7 +285,7 @@ export function IntegrationsTab({
280
285
  if (toolId) {
281
286
  const agentTool = agentTools.find((at) => at.id_tool === toolId);
282
287
  if (agentTool) {
283
- const customInstructions = buildCustomInstructions(slug, local.selectedFunctions, local.functionInstructions);
288
+ const customInstructions = buildCustomInstructions(slug, card.definition.name, local.selectedFunctions, local.functionInstructions);
284
289
  await updateAgentTool.mutateAsync({
285
290
  idAgent: agentId, id: agentTool.id,
286
291
  body: { custom_instructions: customInstructions },
@@ -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
- prompt?: string;
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;