@greatapps/greatagents-ui 0.3.5 → 0.3.7

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.5",
3
+ "version": "0.3.7",
4
4
  "description": "Shared agents UI components for Great platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,12 +15,14 @@ import {
15
15
  } from "@greatapps/greatauth-ui/ui";
16
16
  import { Loader2 } from "lucide-react";
17
17
  import { toast } from "sonner";
18
+ import { ImageCropUpload } from "@greatapps/greatauth-ui";
18
19
 
19
20
  interface AgentFormDialogProps {
20
21
  config: GagentsHookConfig;
21
22
  open: boolean;
22
23
  onOpenChange: (open: boolean) => void;
23
24
  agent?: Agent;
25
+ idAccount?: string | number | null;
24
26
  }
25
27
 
26
28
  interface FormState {
@@ -68,6 +70,7 @@ export function AgentFormDialog({
68
70
  open,
69
71
  onOpenChange,
70
72
  agent,
73
+ idAccount,
71
74
  }: AgentFormDialogProps) {
72
75
  const isEditing = !!agent;
73
76
  const createAgent = useCreateAgent(config);
@@ -139,19 +142,17 @@ export function AgentFormDialog({
139
142
  </DialogTitle>
140
143
  </DialogHeader>
141
144
  <form onSubmit={handleSubmit} className="space-y-4">
142
- <div className="space-y-2">
143
- <Label htmlFor="agent-photo">Foto (URL)</Label>
144
- <Input
145
- id="agent-photo"
146
- name="photo"
147
- value={form.photo}
148
- onChange={(e) => updateField("photo", e.target.value)}
149
- placeholder="https://exemplo.com/foto.jpg"
145
+ <div className="flex justify-center">
146
+ <ImageCropUpload
147
+ value={form.photo || null}
148
+ onChange={(url) => updateField("photo", url)}
149
+ onRemove={() => updateField("photo", "")}
150
+ entityType="agents"
151
+ entityId={agent?.id}
152
+ idAccount={typeof idAccount === "string" ? Number(idAccount) : (idAccount ?? Number(config.accountId) ?? 0)}
153
+ name={form.title || null}
150
154
  disabled={isPending}
151
155
  />
152
- <p className="text-xs text-muted-foreground">
153
- URL da imagem de avatar do agente
154
- </p>
155
156
  </div>
156
157
  <div className="space-y-2">
157
158
  <Label htmlFor="agent-title">Nome do Agente *</Label>
@@ -3,7 +3,6 @@ import type { GagentsHookConfig } from "../../hooks/types";
3
3
  import type { IntegrationCardData } from "../../hooks/use-integrations";
4
4
  import type { WizardIntegrationMeta } from "../capabilities/types";
5
5
  import type { ConfigOption } from "../capabilities/wizard-steps/config-step";
6
- import { AgentToolsList } from "./agent-tools-list";
7
6
  import { AgentObjectivesList } from "./agent-objectives-list";
8
7
  import { AgentPromptEditor } from "./agent-prompt-editor";
9
8
  import { AgentConversationsPanel } from "../conversations/agent-conversations-panel";
@@ -14,7 +13,7 @@ import {
14
13
  TabsTrigger,
15
14
  TabsContent,
16
15
  } from "@greatapps/greatauth-ui/ui";
17
- import { Wrench, Target, FileText, MessageCircle, Blocks } from "lucide-react";
16
+ import { Target, FileText, MessageCircle, Blocks } from "lucide-react";
18
17
 
19
18
  interface AgentTabsProps {
20
19
  agent: Agent;
@@ -52,10 +51,6 @@ export function AgentTabs({
52
51
  <Target aria-hidden="true" className="h-3.5 w-3.5" />
53
52
  Objetivos
54
53
  </TabsTrigger>
55
- <TabsTrigger value="ferramentas" className="flex items-center gap-1.5">
56
- <Wrench aria-hidden="true" className="h-3.5 w-3.5" />
57
- Ferramentas
58
- </TabsTrigger>
59
54
  <TabsTrigger value="capacidades" className="flex items-center gap-1.5">
60
55
  <Blocks aria-hidden="true" className="h-3.5 w-3.5" />
61
56
  Capacidades
@@ -74,10 +69,6 @@ export function AgentTabs({
74
69
  <AgentObjectivesList agent={agent} config={config} />
75
70
  </TabsContent>
76
71
 
77
- <TabsContent value="ferramentas" className="mt-4">
78
- <AgentToolsList agent={agent} config={config} />
79
- </TabsContent>
80
-
81
72
  <TabsContent value="capacidades" className="mt-4">
82
73
  <AgentCapabilitiesPage
83
74
  config={config}
@@ -70,10 +70,15 @@ export function AgentToolsList({ agent, config }: AgentToolsListProps) {
70
70
  const allCredentials: ToolCredential[] = credentialsData?.data || [];
71
71
 
72
72
  const agentTools = agentToolsData?.data || [];
73
- const allTools = allToolsData?.data || [];
73
+ const allTools = (allToolsData?.data || []).filter((t: Tool) => !t.slug?.startsWith("gclinic_"));
74
74
  const assignedToolIds = new Set(agentTools.map((at) => at.id_tool));
75
- const availableTools = allTools.filter((t) => !assignedToolIds.has(t.id));
76
- const filteredAvailable = availableTools.filter((t) =>
75
+ // Filter out internal gclinic_* tools from assigned tools display
76
+ const visibleAgentTools = agentTools.filter((at) => {
77
+ const tool = allTools.find((t: Tool) => t.id === at.id_tool);
78
+ return !tool || !tool.slug?.startsWith("gclinic_");
79
+ });
80
+ const availableTools = allTools.filter((t: Tool) => !assignedToolIds.has(t.id));
81
+ const filteredAvailable = availableTools.filter((t: Tool) =>
77
82
  t.name.toLowerCase().includes(search.toLowerCase()),
78
83
  );
79
84
 
@@ -170,7 +175,7 @@ export function AgentToolsList({ agent, config }: AgentToolsListProps) {
170
175
  <div className="space-y-4 p-4">
171
176
  <div className="flex items-center justify-between">
172
177
  <h3 className="text-sm font-medium text-muted-foreground">
173
- {agentTools.length} ferramenta{agentTools.length !== 1 ? "s" : ""} associada{agentTools.length !== 1 ? "s" : ""}
178
+ {visibleAgentTools.length} ferramenta{visibleAgentTools.length !== 1 ? "s" : ""} associada{visibleAgentTools.length !== 1 ? "s" : ""}
174
179
  </h3>
175
180
  <Popover open={addOpen} onOpenChange={setAddOpen}>
176
181
  <PopoverTrigger asChild>
@@ -217,7 +222,7 @@ export function AgentToolsList({ agent, config }: AgentToolsListProps) {
217
222
  </Popover>
218
223
  </div>
219
224
 
220
- {agentTools.length === 0 ? (
225
+ {visibleAgentTools.length === 0 ? (
221
226
  <div className="flex flex-col items-center justify-center rounded-lg border border-dashed p-8 text-center">
222
227
  <Wrench className="mb-2 h-8 w-8 text-muted-foreground" />
223
228
  <p className="text-sm text-muted-foreground">
@@ -226,7 +231,7 @@ export function AgentToolsList({ agent, config }: AgentToolsListProps) {
226
231
  </div>
227
232
  ) : (
228
233
  <div className="space-y-2">
229
- {agentTools.map((agentTool) => {
234
+ {visibleAgentTools.map((agentTool) => {
230
235
  const tool = getToolInfo(agentTool.id_tool);
231
236
  return (
232
237
  <div
@@ -337,7 +337,7 @@ export function CapabilitiesTab({ config, agentId }: CapabilitiesTabProps) {
337
337
  </Badge>
338
338
  </div>
339
339
  </AccordionTrigger>
340
- <AccordionContent className="pb-3">
340
+ <AccordionContent className="pb-3 !h-auto">
341
341
  <div className="space-y-1">
342
342
  {cat.modules.map((mod) => {
343
343
  const enabledOps = localState.get(mod.slug);
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { CheckCircle2, Loader2, AlertCircle, Shield } from "lucide-react";
4
- import { Button, Input, Label } from "@greatapps/greatauth-ui/ui";
3
+ import { CheckCircle2, Loader2, AlertCircle, Shield, Info } from "lucide-react";
4
+ import { Button, Input, Label, Tooltip, TooltipTrigger, TooltipContent } from "@greatapps/greatauth-ui/ui";
5
5
  import type { IntegrationDefinition } from "../../../data/integrations-registry";
6
6
  import type { WizardIntegrationMeta, OAuthStatus, OAuthResult } from "../types";
7
7
 
@@ -14,6 +14,8 @@ interface CredentialsStepProps {
14
14
  onApiKeyChange: (value: string) => void;
15
15
  onStartOAuth: () => void;
16
16
  isReconnect?: boolean;
17
+ /** When true, the OAuth authorize endpoint is available on the backend. */
18
+ oauthConfigured?: boolean;
17
19
  }
18
20
 
19
21
  export function CredentialsStep({
@@ -25,6 +27,7 @@ export function CredentialsStep({
25
27
  onApiKeyChange,
26
28
  onStartOAuth,
27
29
  isReconnect = false,
30
+ oauthConfigured = false,
28
31
  }: CredentialsStepProps) {
29
32
  if (integration.authType === "oauth2") {
30
33
  return (
@@ -35,6 +38,7 @@ export function CredentialsStep({
35
38
  oauthResult={oauthResult}
36
39
  onStartOAuth={onStartOAuth}
37
40
  isReconnect={isReconnect}
41
+ oauthConfigured={oauthConfigured}
38
42
  />
39
43
  );
40
44
  }
@@ -53,6 +57,7 @@ function OAuthCredentials({
53
57
  oauthResult,
54
58
  onStartOAuth,
55
59
  isReconnect,
60
+ oauthConfigured,
56
61
  }: {
57
62
  integration: IntegrationDefinition;
58
63
  meta: WizardIntegrationMeta;
@@ -60,6 +65,7 @@ function OAuthCredentials({
60
65
  oauthResult: OAuthResult | null;
61
66
  onStartOAuth: () => void;
62
67
  isReconnect: boolean;
68
+ oauthConfigured: boolean;
63
69
  }) {
64
70
  const providerLabel = meta.providerLabel || integration.name;
65
71
 
@@ -74,15 +80,51 @@ function OAuthCredentials({
74
80
  </p>
75
81
  </div>
76
82
 
83
+ {/* OAuth not configured notice */}
84
+ {!oauthConfigured && oauthStatus === "idle" && (
85
+ <div className="flex items-start gap-3 rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-900 dark:bg-amber-950/30">
86
+ <Info
87
+ aria-hidden="true"
88
+ className="mt-0.5 h-5 w-5 shrink-0 text-amber-600 dark:text-amber-400"
89
+ />
90
+ <div className="space-y-1">
91
+ <p className="text-sm font-medium text-amber-800 dark:text-amber-200">
92
+ Configuração necessária
93
+ </p>
94
+ <p className="text-xs text-amber-700 dark:text-amber-300">
95
+ A integração com {providerLabel} requer configuração pelo
96
+ administrador do sistema. Entre em contato com o suporte para
97
+ ativar esta funcionalidade.
98
+ </p>
99
+ </div>
100
+ </div>
101
+ )}
102
+
77
103
  {/* OAuth status area */}
78
104
  <div className="flex flex-col items-center gap-4 rounded-lg border p-6">
79
105
  {oauthStatus === "idle" && (
80
- <Button onClick={onStartOAuth} size="lg" className="gap-2">
81
- {meta.icon}
82
- {isReconnect
83
- ? `Reconectar com ${providerLabel}`
84
- : `Conectar com ${providerLabel}`}
85
- </Button>
106
+ <Tooltip>
107
+ <TooltipTrigger asChild>
108
+ <span tabIndex={!oauthConfigured ? 0 : undefined}>
109
+ <Button
110
+ onClick={onStartOAuth}
111
+ size="lg"
112
+ className="gap-2"
113
+ disabled={!oauthConfigured}
114
+ >
115
+ {meta.icon}
116
+ {isReconnect
117
+ ? `Reconectar com ${providerLabel}`
118
+ : `Conectar com ${providerLabel}`}
119
+ </Button>
120
+ </span>
121
+ </TooltipTrigger>
122
+ {!oauthConfigured && (
123
+ <TooltipContent>
124
+ Integração OAuth ainda não configurada no servidor
125
+ </TooltipContent>
126
+ )}
127
+ </Tooltip>
86
128
  )}
87
129
 
88
130
  {oauthStatus === "waiting" && (
@@ -185,7 +185,7 @@ export function ToolCredentialsForm({
185
185
  const updateMutation = useUpdateToolCredential(config);
186
186
  const deleteMutation = useDeleteToolCredential(config);
187
187
  const { data: toolsData } = useTools(config);
188
- const tools: Tool[] = toolsData?.data || [];
188
+ const tools: Tool[] = (toolsData?.data || []).filter((t: Tool) => !t.slug?.startsWith("gclinic_"));
189
189
 
190
190
  const [search, setSearch] = useState("");
191
191
  const [internalCreateOpen, setInternalCreateOpen] = useState(false);
@@ -209,17 +209,31 @@ export function ToolCredentialsForm({
209
209
 
210
210
  const [removeTarget, setRemoveTarget] = useState<ToolCredential | null>(null);
211
211
 
212
+ // Build a set of internal tool IDs to exclude from credentials display
213
+ const internalToolIds = useMemo(() => {
214
+ const allRawTools: Tool[] = toolsData?.data || [];
215
+ return new Set(
216
+ allRawTools
217
+ .filter((t: Tool) => t.slug?.startsWith("gclinic_"))
218
+ .map((t: Tool) => t.id),
219
+ );
220
+ }, [toolsData]);
221
+
212
222
  const filteredCredentials = useMemo(() => {
213
- if (!search) return credentials;
223
+ // Exclude credentials linked to internal gclinic_* tools
224
+ const visible = credentials.filter(
225
+ (cred) => !cred.id_tool || !internalToolIds.has(cred.id_tool),
226
+ );
227
+ if (!search) return visible;
214
228
  const term = search.toLowerCase();
215
- return credentials.filter((cred) => {
229
+ return visible.filter((cred) => {
216
230
  const toolName = tools.find((t) => t.id === cred.id_tool)?.name || "";
217
231
  return (
218
232
  (cred.label || "").toLowerCase().includes(term) ||
219
233
  toolName.toLowerCase().includes(term)
220
234
  );
221
235
  });
222
- }, [credentials, search, tools]);
236
+ }, [credentials, search, tools, internalToolIds]);
223
237
 
224
238
  const columns = useColumns(
225
239
  tools,
@@ -145,8 +145,9 @@ export function ToolsTable({ onEdit, config }: ToolsTableProps) {
145
145
  const deleteTool = useDeleteTool(config);
146
146
  const [deleteId, setDeleteId] = useState<number | null>(null);
147
147
 
148
- const tools = data?.data || [];
149
- const total = data?.total || 0;
148
+ const rawTools = data?.data || [];
149
+ const tools = rawTools.filter((t: Tool) => !t.slug?.startsWith("gclinic_"));
150
+ const total = tools.length;
150
151
 
151
152
  const columns = useColumns(
152
153
  (tool) => onEdit(tool),