@inkeep/agents-manage-ui 0.1.2 → 0.1.3

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.
Files changed (72) hide show
  1. package/.env.example +2 -2
  2. package/.turbo/turbo-build.log +30 -30
  3. package/.turbo/turbo-test.log +10 -43
  4. package/.turbo/turbo-typecheck.log +1 -1
  5. package/README.md +3 -3
  6. package/biome.json +3 -0
  7. package/package.json +8 -7
  8. package/src/app/api/signoz/conversations/[conversationId]/route.ts +56 -34
  9. package/src/components/artifact-components/form/artifact-component-form.tsx +16 -7
  10. package/src/components/data-components/form/data-component-form.tsx +16 -7
  11. package/src/components/graph/configuration/node-types.tsx +2 -4
  12. package/src/components/graph/graph.tsx +4 -26
  13. package/src/components/graph/nodes/agent-node.tsx +1 -1
  14. package/src/components/graph/nodes/external-agent-node.tsx +1 -1
  15. package/src/components/graph/playground/chat-widget.tsx +2 -2
  16. package/src/components/graph/playground/ikp-message.tsx +16 -16
  17. package/src/components/graph/playground/playground.tsx +5 -5
  18. package/src/components/graph/sidepane/metadata/metadata-editor.tsx +34 -17
  19. package/src/components/graph/sidepane/nodes/agent-node-editor.tsx +34 -19
  20. package/src/components/graph/sidepane/nodes/external-agent-node-editor.tsx +31 -11
  21. package/src/components/graph/sidepane/nodes/mcp-node-editor.tsx +4 -9
  22. package/src/components/graph/sidepane/nodes/model-section.tsx +1 -1
  23. package/src/components/graph/toolbar/toolbar.tsx +1 -1
  24. package/src/components/mcp-servers/form/active-tools-selector.tsx +4 -2
  25. package/src/components/projects/form/project-form.tsx +18 -9
  26. package/src/components/projects/form/project-models-section.tsx +51 -23
  27. package/src/components/projects/form/project-stopwhen-section.tsx +25 -11
  28. package/src/components/traces/ai-calls-breakdown.tsx +1 -1
  29. package/src/components/traces/charts/area-chart-card.tsx +2 -2
  30. package/src/components/traces/charts/area-chart.tsx +2 -2
  31. package/src/components/traces/charts/chart-card.tsx +4 -4
  32. package/src/components/traces/conversation-detail.tsx +10 -8
  33. package/src/components/traces/conversation-stats/conversation-list-item.tsx +48 -37
  34. package/src/components/traces/conversation-stats/conversation-stats-card.tsx +1 -1
  35. package/src/components/traces/filters/date-picker.tsx +6 -6
  36. package/src/components/traces/filters/filter-trigger.tsx +1 -1
  37. package/src/components/traces/filters/graph-filter.tsx +3 -3
  38. package/src/components/traces/timeline/activity-details-sidepane.tsx +1 -1
  39. package/src/components/traces/timeline/activity-timeline.tsx +1 -1
  40. package/src/components/traces/timeline/blocks.tsx +2 -2
  41. package/src/components/traces/timeline/render-panel-content.tsx +11 -11
  42. package/src/components/traces/timeline/timeline-item.tsx +36 -22
  43. package/src/components/traces/timeline/timeline-wrapper.tsx +11 -12
  44. package/src/components/traces/traces-overview.tsx +3 -3
  45. package/src/components/ui/alert.tsx +17 -23
  46. package/src/components/ui/calendar.tsx +3 -4
  47. package/src/components/ui/external-link.tsx +2 -2
  48. package/src/components/ui/inheritance-indicator.tsx +20 -17
  49. package/src/components/ui/resizable.tsx +13 -18
  50. package/src/constants/page-descriptions.tsx +2 -5
  51. package/src/constants/signoz.ts +15 -18
  52. package/src/features/graph/domain/__tests__/roundtrip.test.ts +5 -5
  53. package/src/features/graph/domain/serialize.ts +8 -9
  54. package/src/hooks/use-auto-prefill-id-zustand.ts +68 -0
  55. package/src/hooks/use-auto-prefill-id.ts +36 -0
  56. package/src/hooks/use-chat-activities-polling.ts +1 -1
  57. package/src/hooks/use-graph-errors.ts +2 -1
  58. package/src/hooks/use-project-data.ts +2 -2
  59. package/src/lib/actions/graph-full.ts +6 -2
  60. package/src/lib/actions/projects.ts +1 -1
  61. package/src/lib/api/api-config.ts +6 -6
  62. package/src/lib/api/api-keys.ts +4 -1
  63. package/src/lib/api/credentials.ts +1 -1
  64. package/src/lib/api/data-components.ts +1 -1
  65. package/src/lib/api/graph-full-client.ts +6 -3
  66. package/src/lib/api/projects.ts +1 -1
  67. package/src/lib/api/signoz-sql.ts +1 -1
  68. package/src/lib/index.ts +1 -1
  69. package/src/lib/logger.ts +1 -2
  70. package/src/lib/utils/generate-id.ts +14 -0
  71. package/tsconfig.json +2 -2
  72. package/eslint.config.mjs +0 -14
@@ -16,7 +16,7 @@ import {
16
16
  } from '@xyflow/react';
17
17
  import { nanoid } from 'nanoid';
18
18
  import { useParams, useRouter } from 'next/navigation';
19
- import { useCallback, useState, useEffect, useMemo } from 'react';
19
+ import { useCallback, useEffect, useMemo, useState } from 'react';
20
20
  import { toast } from 'sonner';
21
21
  import { commandManager } from '@/features/graph/commands/command-manager';
22
22
  import { AddNodeCommand, AddPreparedEdgeCommand } from '@/features/graph/commands/commands';
@@ -31,7 +31,6 @@ import { saveGraph } from '@/lib/services/save-graph';
31
31
  import type { FullGraphDefinition } from '@/lib/types/graph-full';
32
32
  import { formatJsonField } from '@/lib/utils';
33
33
  import { getErrorSummaryMessage, parseGraphValidationErrors } from '@/lib/utils/graph-error-parser';
34
- import { Playground } from './playground/playground';
35
34
  import { EdgeType, edgeTypes, initialEdges } from './configuration/edge-types';
36
35
  import type { ContextConfig } from './configuration/graph-types';
37
36
  import {
@@ -47,10 +46,10 @@ import { GraphErrorSummary } from './error-display/graph-error-summary';
47
46
  import { DefaultMarker } from './markers/default-marker';
48
47
  import { SelectedMarker } from './markers/selected-marker';
49
48
  import NodeLibrary from './node-library/node-library';
49
+ import { Playground } from './playground/playground';
50
50
  import { SidePane } from './sidepane/sidepane';
51
51
  import { Toolbar } from './toolbar/toolbar';
52
52
 
53
-
54
53
  function getEdgeId(a: string, b: string) {
55
54
  const [low, high] = [a, b].sort();
56
55
  return `edge-${low}-${high}`;
@@ -76,7 +75,7 @@ function Flow({ graph, dataComponentLookup = {}, artifactComponentLookup = {} }:
76
75
  id: nanoid(),
77
76
  type: NodeType.Agent,
78
77
  position: { x: 0, y: 0 },
79
- data: { name: 'Agent', isDefault: true },
78
+ data: { name: '', isDefault: true },
80
79
  deletable: false,
81
80
  },
82
81
  ],
@@ -179,25 +178,6 @@ function Flow({ graph, dataComponentLookup = {}, artifactComponentLookup = {} }:
179
178
  }
180
179
  }, []);
181
180
 
182
- const generateUniqueName = useCallback(
183
- (type: string) => {
184
- if (type !== NodeType.Agent && type !== NodeType.ExternalAgent)
185
- return newNodeDefaults[type as keyof typeof newNodeDefaults].name;
186
-
187
- const agentNodes = nodes.filter(
188
- (node) => node.type === NodeType.Agent || node.type === NodeType.ExternalAgent
189
- );
190
- const existingNames = new Set(agentNodes.map((node) => node.data.name));
191
- let counter = 1;
192
-
193
- while (existingNames.has(`Agent ${counter}`)) {
194
- counter++;
195
- }
196
-
197
- return `Agent ${counter}`;
198
- },
199
- [nodes]
200
- );
201
181
 
202
182
  // biome-ignore lint/correctness/useExhaustiveDependencies: we only want to add/connect edges once
203
183
  const onConnectWrapped = useCallback((params: Connection) => {
@@ -280,14 +260,13 @@ function Flow({ graph, dataComponentLookup = {}, artifactComponentLookup = {} }:
280
260
  selected: true,
281
261
  data: {
282
262
  ...newNodeDefaults[nodeData.type as keyof typeof newNodeDefaults],
283
- name: generateUniqueName(nodeData.type),
284
263
  },
285
264
  };
286
265
 
287
266
  clearSelection();
288
267
  commandManager.execute(new AddNodeCommand(newNode as Node));
289
268
  },
290
- [screenToFlowPosition, generateUniqueName, clearSelection]
269
+ [screenToFlowPosition, clearSelection]
291
270
  );
292
271
 
293
272
  const onSelectionChange = useCallback(
@@ -401,7 +380,6 @@ function Flow({ graph, dataComponentLookup = {}, artifactComponentLookup = {} }:
401
380
  artifactComponentLookup
402
381
  );
403
382
 
404
-
405
383
  const res = await saveGraph(
406
384
  tenantId,
407
385
  projectId,
@@ -79,7 +79,7 @@ export function AgentNode(props: NodeProps & { data: AgentNodeData }) {
79
79
  <BaseNodeHeader className="flex items-center justify-between gap-2">
80
80
  <div className="flex items-center gap-2">
81
81
  <Bot className="size-4 text-muted-foreground" />
82
- <BaseNodeHeaderTitle>{name}</BaseNodeHeaderTitle>
82
+ <BaseNodeHeaderTitle>{name || 'Agent'}</BaseNodeHeaderTitle>
83
83
  </div>
84
84
  {hasErrors && (
85
85
  <ErrorIndicator errors={nodeErrors} className="absolute -top-2 -right-2 w-6 h-6" />
@@ -30,7 +30,7 @@ export function ExternalAgentNode(props: NodeProps & { data: AgentNodeData }) {
30
30
  <BaseNodeHeader className="flex items-center justify-between gap-2">
31
31
  <div className="flex items-center gap-2">
32
32
  <BotMessageSquare className="size-4 text-muted-foreground" />
33
- <BaseNodeHeaderTitle>{name}</BaseNodeHeaderTitle>
33
+ <BaseNodeHeaderTitle>{name || 'External agent'}</BaseNodeHeaderTitle>
34
34
  </div>
35
35
  {hasErrors && (
36
36
  <ErrorIndicator errors={nodeErrors} className="absolute -top-2 -right-2 w-6 h-6" />
@@ -1,10 +1,10 @@
1
1
  'use client';
2
- import { useRef, useEffect } from 'react';
3
2
  import { InkeepEmbeddedChat } from '@inkeep/cxkit-react-oss';
4
3
  import type { ComponentsConfig, InkeepCallbackEvent } from '@inkeep/cxkit-react-oss/types';
4
+ import { nanoid } from 'nanoid';
5
+ import { useEffect, useRef } from 'react';
5
6
  import { EXECUTION_API_BASE_URL } from '@/lib/api/api-config';
6
7
  import { IkpMessage as IkpMessageComponent } from './ikp-message';
7
- import { nanoid } from 'nanoid';
8
8
 
9
9
  interface ChatWidgetProps {
10
10
  graphId?: string;
@@ -15,7 +15,7 @@ import {
15
15
  Sparkles,
16
16
  Users,
17
17
  } from 'lucide-react';
18
- import { type FC, useEffect, useMemo, useState, useRef } from 'react';
18
+ import { type FC, useEffect, useMemo, useRef, useState } from 'react';
19
19
  import supersub from 'remark-supersub';
20
20
  import { Streamdown } from 'streamdown';
21
21
  import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
@@ -278,17 +278,17 @@ const OperationStep: FC<{ operation: any; isLast: boolean }> = ({ operation, isL
278
278
  const renderStructuredLabel = (operationType: string, context: any) => {
279
279
  // Convert snake_case to readable format
280
280
  const readableType = operationType.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
281
-
281
+
282
282
  // Try to find the most meaningful fields to display
283
283
  const meaningfulFields = Object.entries(context).filter(
284
284
  ([key, value]) => typeof value === 'string' && value.length > 0 && value.length < 100
285
285
  );
286
-
286
+
287
287
  if (meaningfulFields.length > 0) {
288
288
  const [firstKey, firstValue] = meaningfulFields[0];
289
289
  return `${readableType}: ${firstValue}`;
290
290
  }
291
-
291
+
292
292
  return readableType;
293
293
  };
294
294
 
@@ -315,20 +315,20 @@ const OperationStep: FC<{ operation: any; isLast: boolean }> = ({ operation, isL
315
315
 
316
316
  return (
317
317
  <div className="relative">
318
- <div className="flex items-center gap-2 relative">
319
- {/* Connection line */}
320
- {!isLast && (
321
- <div className="absolute left-1.5 top-6 bottom-0 w-px bg-gray-200 dark:bg-border" />
322
- )}
318
+ <div className="flex items-center gap-2 relative">
319
+ {/* Connection line */}
320
+ {!isLast && (
321
+ <div className="absolute left-1.5 top-6 bottom-0 w-px bg-gray-200 dark:bg-border" />
322
+ )}
323
323
 
324
- {/* Step indicator */}
325
- <div className={cn('flex items-center justify-center w-3 h-3 z-10', getStepColor())}>
326
- {getStepIcon()}
327
- </div>
324
+ {/* Step indicator */}
325
+ <div className={cn('flex items-center justify-center w-3 h-3 z-10', getStepColor())}>
326
+ {getStepIcon()}
327
+ </div>
328
+
329
+ {/* Step label */}
330
+ <span className={cn('text-xs font-medium', getStepColor())}>{getStepLabel()}</span>
328
331
 
329
- {/* Step label */}
330
- <span className={cn('text-xs font-medium', getStepColor())}>{getStepLabel()}</span>
331
-
332
332
  {/* Expand button for structured operations */}
333
333
  {isStructuredOperation && Object.keys(ctx).length > 1 && (
334
334
  <button
@@ -1,11 +1,11 @@
1
+ import { ArrowLeft } from 'lucide-react';
2
+ import { nanoid } from 'nanoid';
1
3
  import { useState } from 'react';
2
- import { ChatWidget } from './chat-widget';
3
- import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable';
4
4
  import { TimelineWrapper } from '@/components/traces/timeline/timeline-wrapper';
5
- import { useChatActivitiesPolling } from '@/hooks/use-chat-activities-polling';
6
- import { nanoid } from 'nanoid';
7
5
  import { Button } from '@/components/ui/button';
8
- import { ArrowLeft } from 'lucide-react';
6
+ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable';
7
+ import { useChatActivitiesPolling } from '@/hooks/use-chat-activities-polling';
8
+ import { ChatWidget } from './chat-widget';
9
9
 
10
10
  interface PlaygroundProps {
11
11
  graphId: string;
@@ -7,23 +7,24 @@ import { ExpandableJsonEditor } from '@/components/form/expandable-json-editor';
7
7
  import { Button } from '@/components/ui/button';
8
8
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
9
9
  import { CopyableSingleLineCode } from '@/components/ui/copyable-single-line-code';
10
+ import {
11
+ getExecutionLimitInheritanceStatus,
12
+ getModelInheritanceStatus,
13
+ InheritanceIndicator,
14
+ } from '@/components/ui/inheritance-indicator';
10
15
  import { Input } from '@/components/ui/input';
11
16
  import { Label } from '@/components/ui/label';
12
17
  import { Separator } from '@/components/ui/separator';
13
18
  import { Textarea } from '@/components/ui/textarea';
14
- import {
15
- InheritanceIndicator,
16
- getModelInheritanceStatus,
17
- getExecutionLimitInheritanceStatus,
18
- } from '@/components/ui/inheritance-indicator';
19
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
19
20
  import { useGraphStore } from '@/features/graph/state/use-graph-store';
20
- import { EXECUTION_API_BASE_URL } from '@/lib/api/api-config';
21
21
  import { useProjectData } from '@/hooks/use-project-data';
22
+ import { useAutoPrefillIdZustand } from '@/hooks/use-auto-prefill-id-zustand';
23
+ import { EXECUTION_API_BASE_URL } from '@/lib/api/api-config';
24
+ import { ExpandableTextArea } from '../nodes/expandable-text-area';
25
+ import { InputField, TextareaField } from '../nodes/form-fields';
22
26
  import { ModelSelector } from '../nodes/model-selector';
23
27
  import { ContextConfigForm } from './context-config';
24
- import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
25
- import { InputField, TextareaField } from '../nodes/form-fields';
26
- import { ExpandableTextArea } from '../nodes/expandable-text-area';
27
28
 
28
29
  function MetadataEditor() {
29
30
  const params = useParams();
@@ -46,6 +47,21 @@ function MetadataEditor() {
46
47
  [setMetadata, markUnsaved]
47
48
  );
48
49
 
50
+ const handleIdChange = useCallback(
51
+ (generatedId: string) => {
52
+ updateMetadata('id', generatedId);
53
+ },
54
+ [updateMetadata]
55
+ );
56
+
57
+ // Auto-prefill ID based on name field (only for new graphs)
58
+ useAutoPrefillIdZustand({
59
+ nameValue: name,
60
+ idValue: id,
61
+ onIdChange: handleIdChange,
62
+ isEditing: !!graphId,
63
+ });
64
+
49
65
  return (
50
66
  <div className="space-y-8">
51
67
  {graphId && (
@@ -66,6 +82,15 @@ function MetadataEditor() {
66
82
  <CopyableSingleLineCode code={graphUrl} />
67
83
  </div>
68
84
  )}
85
+ <InputField
86
+ id="name"
87
+ name="name"
88
+ label="Name"
89
+ value={name}
90
+ onChange={(e) => updateMetadata('name', e.target.value)}
91
+ placeholder="My graph"
92
+ isRequired
93
+ />
69
94
  <InputField
70
95
  id="id"
71
96
  name="id"
@@ -79,14 +104,6 @@ function MetadataEditor() {
79
104
  ? 'Choose a unique identifier for this graph. Using an existing id will replace that graph.'
80
105
  : undefined
81
106
  }
82
- />
83
- <InputField
84
- id="name"
85
- name="name"
86
- label="Name"
87
- value={name}
88
- onChange={(e) => updateMetadata('name', e.target.value)}
89
- placeholder="My graph"
90
107
  isRequired
91
108
  />
92
109
  <TextareaField
@@ -1,18 +1,19 @@
1
1
  import type { Node } from '@xyflow/react';
2
2
  import { useParams } from 'next/navigation';
3
3
  import { useCallback } from 'react';
4
+ import {
5
+ getExecutionLimitInheritanceStatus,
6
+ InheritanceIndicator,
7
+ } from '@/components/ui/inheritance-indicator';
8
+ import { Input } from '@/components/ui/input';
9
+ import { Label } from '@/components/ui/label';
10
+ import { useGraphStore } from '@/features/graph/state/use-graph-store';
4
11
  import type { ErrorHelpers } from '@/hooks/use-graph-errors';
5
12
  import { useNodeEditor } from '@/hooks/use-node-editor';
6
13
  import { useProjectData } from '@/hooks/use-project-data';
7
- import { useGraphStore } from '@/features/graph/state/use-graph-store';
14
+ import { useAutoPrefillIdZustand } from '@/hooks/use-auto-prefill-id-zustand';
8
15
  import type { ArtifactComponent } from '@/lib/api/artifact-components';
9
16
  import type { DataComponent } from '@/lib/api/data-components';
10
- import { Input } from '@/components/ui/input';
11
- import { Label } from '@/components/ui/label';
12
- import {
13
- InheritanceIndicator,
14
- getExecutionLimitInheritanceStatus,
15
- } from '@/components/ui/inheritance-indicator';
16
17
  import type { AgentNodeData } from '../../configuration/node-types';
17
18
  import { ComponentSelector } from './component-selector/component-selector';
18
19
  import { ExpandableTextArea } from './expandable-text-area';
@@ -53,8 +54,34 @@ export function AgentNodeEditor({
53
54
  [updateNestedPath, selectedNode.data]
54
55
  );
55
56
 
57
+ const handleIdChange = useCallback(
58
+ (generatedId: string) => {
59
+ updatePath('id', generatedId);
60
+ },
61
+ [updatePath]
62
+ );
63
+
64
+ // Auto-prefill ID based on name field (always enabled for agent nodes)
65
+ useAutoPrefillIdZustand({
66
+ nameValue: selectedNode.data.name,
67
+ idValue: selectedNode.data.id,
68
+ onIdChange: handleIdChange,
69
+ isEditing: false,
70
+ });
71
+
56
72
  return (
57
73
  <div className="space-y-8 flex flex-col">
74
+ <InputField
75
+ ref={(el) => setFieldRef('name', el)}
76
+ id="name"
77
+ name="name"
78
+ label="Name"
79
+ value={selectedNode.data.name || ''}
80
+ onChange={(e) => updatePath('name', e.target.value)}
81
+ placeholder="Support agent"
82
+ error={getFieldError('name')}
83
+ isRequired
84
+ />
58
85
  <InputField
59
86
  ref={(el) => setFieldRef('id', el)}
60
87
  id="id"
@@ -65,20 +92,8 @@ export function AgentNodeEditor({
65
92
  placeholder="my-agent"
66
93
  error={getFieldError('id')}
67
94
  description="Choose a unique identifier for this agent. Using an existing id will replace that agent."
68
- />
69
-
70
- <InputField
71
- ref={(el) => setFieldRef('name', el)}
72
- id="name"
73
- name="name"
74
- label="Name"
75
- value={selectedNode.data.name || ''}
76
- onChange={(e) => updatePath('name', e.target.value)}
77
- placeholder="Support agent"
78
- error={getFieldError('name')}
79
95
  isRequired
80
96
  />
81
-
82
97
  <TextareaField
83
98
  ref={(el) => setFieldRef('description', el)}
84
99
  id="description"
@@ -3,6 +3,8 @@ import type { ErrorHelpers } from '@/hooks/use-graph-errors';
3
3
  import { useNodeEditor } from '@/hooks/use-node-editor';
4
4
  import type { ExternalAgentNodeData } from '../../configuration/node-types';
5
5
  import { InputField, TextareaField } from './form-fields';
6
+ import { useCallback } from 'react';
7
+ import { useAutoPrefillIdZustand } from '@/hooks/use-auto-prefill-id-zustand';
6
8
 
7
9
  interface ExternalAgentNodeEditorProps {
8
10
  selectedNode: Node<ExternalAgentNodeData>;
@@ -18,6 +20,23 @@ export function ExternalAgentNodeEditor({
18
20
  errorHelpers,
19
21
  });
20
22
 
23
+ const handleIdChange = useCallback(
24
+ (generatedId: string) => {
25
+ handleInputChange({
26
+ target: { name: 'id', value: generatedId },
27
+ } as React.ChangeEvent<HTMLInputElement>);
28
+ },
29
+ [handleInputChange]
30
+ );
31
+
32
+ // Auto-prefill ID based on name field (always enabled for agent nodes)
33
+ useAutoPrefillIdZustand({
34
+ nameValue: selectedNode.data.name,
35
+ idValue: selectedNode.data.id,
36
+ onIdChange: handleIdChange,
37
+ isEditing: false,
38
+ });
39
+
21
40
  return (
22
41
  <div className="space-y-8 flex flex-col">
23
42
  <p className="text-sm text-muted-foreground">
@@ -26,6 +45,17 @@ export function ExternalAgentNodeEditor({
26
45
  within the agent framework or to third-party services.
27
46
  </p>
28
47
 
48
+ <InputField
49
+ ref={(el) => setFieldRef('name', el)}
50
+ id="name"
51
+ name="name"
52
+ label="Name"
53
+ value={selectedNode.data.name || ''}
54
+ onChange={handleInputChange}
55
+ placeholder="Support agent"
56
+ error={getFieldError('name')}
57
+ />
58
+
29
59
  <InputField
30
60
  ref={(el) => setFieldRef('id', el)}
31
61
  id="id"
@@ -36,17 +66,7 @@ export function ExternalAgentNodeEditor({
36
66
  placeholder="my-external-agent"
37
67
  error={getFieldError('id')}
38
68
  description="Choose a unique identifier for this agent. Using an existing id will replace that agent."
39
- />
40
-
41
- <InputField
42
- ref={(el) => setFieldRef('name', el)}
43
- id="name"
44
- name="name"
45
- label="Name"
46
- value={selectedNode.data.name || ''}
47
- onChange={handleInputChange}
48
- placeholder="Support agent"
49
- error={getFieldError('name')}
69
+ isRequired
50
70
  />
51
71
 
52
72
  <TextareaField
@@ -1,10 +1,9 @@
1
1
  import { type Node, useReactFlow } from '@xyflow/react';
2
- import { ExternalLink } from 'lucide-react';
3
- import Link from 'next/link';
4
2
  import { useParams } from 'next/navigation';
5
3
  import { getActiveTools } from '@/app/utils/active-tools';
6
4
  import { MCPToolImage } from '@/components/mcp-servers/mcp-tool-image';
7
5
  import { Badge } from '@/components/ui/badge';
6
+ import { ExternalLink } from '@/components/ui/external-link';
8
7
  import { Input } from '@/components/ui/input';
9
8
  import { Label } from '@/components/ui/label';
10
9
  import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
@@ -126,15 +125,11 @@ export function MCPServerNodeEditor({ selectedNode }: MCPServerNodeEditorProps)
126
125
  )}
127
126
  </div>
128
127
 
129
- <Link
128
+ <ExternalLink
130
129
  href={`/${tenantId}/projects/${projectId}/mcp-servers/${selectedNode.data.id}/edit`}
131
- target="_blank"
132
- rel="noopener noreferrer"
133
- className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors"
134
130
  >
135
- <ExternalLink className="w-3 h-3" />
136
- Click here to edit
137
- </Link>
131
+ Edit MCP Server
132
+ </ExternalLink>
138
133
  </div>
139
134
  );
140
135
  }
@@ -4,8 +4,8 @@ import type { AgentNodeData } from '@/components/graph/configuration/node-types'
4
4
  import { Button } from '@/components/ui/button';
5
5
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
6
6
  import {
7
- InheritanceIndicator,
8
7
  getModelInheritanceStatus,
8
+ InheritanceIndicator,
9
9
  } from '@/components/ui/inheritance-indicator';
10
10
  import { ModelSelector } from './model-selector';
11
11
 
@@ -1,4 +1,4 @@
1
- import { Settings, Play } from 'lucide-react';
1
+ import { Play, Settings } from 'lucide-react';
2
2
  import { Button } from '@/components/ui/button';
3
3
  import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
4
4
  import { useGraphStore } from '@/features/graph/state/use-graph-store';
@@ -104,8 +104,10 @@ export function ActiveToolsSelector<
104
104
  return availableTools.some((tool) => tool.name === toolName);
105
105
  case 'selective':
106
106
  // Only return true if tool is selected AND still exists in availableTools
107
- return safeToolsConfig.tools.includes(toolName) &&
108
- availableTools.some((tool) => tool.name === toolName);
107
+ return (
108
+ safeToolsConfig.tools.includes(toolName) &&
109
+ availableTools.some((tool) => tool.name === toolName)
110
+ );
109
111
  default:
110
112
  return false;
111
113
  }
@@ -9,11 +9,12 @@ import { GenericTextarea } from '@/components/form/generic-textarea';
9
9
  import { Button } from '@/components/ui/button';
10
10
  import { Form } from '@/components/ui/form';
11
11
  import { Separator } from '@/components/ui/separator';
12
+ import { useAutoPrefillId } from '@/hooks/use-auto-prefill-id';
12
13
  import { createProjectAction, updateProjectAction } from '@/lib/actions/projects';
13
14
  import { defaultValues } from './form-configuration';
14
- import { type ProjectFormData, projectSchema } from './validation';
15
15
  import { ProjectModelsSection } from './project-models-section';
16
16
  import { ProjectStopWhenSection } from './project-stopwhen-section';
17
+ import { type ProjectFormData, projectSchema } from './validation';
17
18
 
18
19
  interface ProjectFormProps {
19
20
  tenantId: string;
@@ -38,6 +39,14 @@ export function ProjectForm({
38
39
  const { isSubmitting } = form.formState;
39
40
  const router = useRouter();
40
41
 
42
+ // Auto-prefill ID based on name field (only for new components)
43
+ useAutoPrefillId({
44
+ form,
45
+ nameField: 'name',
46
+ idField: 'id',
47
+ isEditing: !!projectId,
48
+ });
49
+
41
50
  const onSubmit = async (data: ProjectFormData) => {
42
51
  try {
43
52
  if (projectId) {
@@ -75,6 +84,14 @@ export function ProjectForm({
75
84
  return (
76
85
  <Form {...form}>
77
86
  <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
87
+ <GenericInput
88
+ control={form.control}
89
+ name="name"
90
+ label="Project Name"
91
+ placeholder="My Project"
92
+ description="A friendly name for your project"
93
+ isRequired
94
+ />
78
95
  <GenericInput
79
96
  control={form.control}
80
97
  name="id"
@@ -84,14 +101,6 @@ export function ProjectForm({
84
101
  disabled={!!projectId}
85
102
  isRequired
86
103
  />
87
- <GenericInput
88
- control={form.control}
89
- name="name"
90
- label="Project Name"
91
- placeholder="My Project"
92
- description="A friendly name for your project"
93
- isRequired
94
- />
95
104
  <GenericTextarea
96
105
  control={form.control}
97
106
  name="description"