@inkeep/agents-manage-ui 0.1.1 → 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 (101) hide show
  1. package/.env.example +10 -0
  2. package/.turbo/turbo-build.log +48 -54
  3. package/.turbo/turbo-test.log +7 -7
  4. package/.turbo/turbo-typecheck.log +1 -1
  5. package/LICENSE.md +22 -17
  6. package/README.md +3 -3
  7. package/biome.json +3 -0
  8. package/package.json +10 -9
  9. package/src/app/api/signoz/conversations/[conversationId]/route.ts +95 -34
  10. package/src/app/api/signoz/route.ts +8 -6
  11. package/src/components/api-keys/form/api-key-form.tsx +2 -0
  12. package/src/components/api-keys/form/validation.ts +1 -1
  13. package/src/components/artifact-components/form/artifact-component-form.tsx +20 -6
  14. package/src/components/artifact-components/form/validation.ts +3 -3
  15. package/src/components/credentials/views/credential-form-validation.ts +1 -1
  16. package/src/components/credentials/views/credential-form.tsx +2 -0
  17. package/src/components/credentials/views/edit-credential-form.tsx +1 -1
  18. package/src/components/credentials/views/generic-auth-form.tsx +1 -1
  19. package/src/components/data-components/form/data-component-form.tsx +19 -1
  20. package/src/components/data-components/form/validation.ts +3 -2
  21. package/src/components/form/expandable-field.tsx +6 -1
  22. package/src/components/form/form-field-wrapper.tsx +3 -1
  23. package/src/components/form/generic-combo-box.tsx +3 -1
  24. package/src/components/form/generic-input.tsx +9 -1
  25. package/src/components/form/generic-select.tsx +3 -1
  26. package/src/components/form/generic-textarea.tsx +3 -1
  27. package/src/components/form/json-schema-input.tsx +9 -1
  28. package/src/components/graph/configuration/node-types.tsx +2 -4
  29. package/src/components/graph/graph.tsx +4 -26
  30. package/src/components/graph/nodes/agent-node.tsx +1 -1
  31. package/src/components/graph/nodes/external-agent-node.tsx +1 -1
  32. package/src/components/graph/playground/chat-widget.tsx +31 -2
  33. package/src/components/graph/playground/ikp-message.tsx +16 -16
  34. package/src/components/graph/playground/playground.tsx +12 -6
  35. package/src/components/graph/sidepane/metadata/metadata-editor.tsx +62 -45
  36. package/src/components/graph/sidepane/nodes/agent-node-editor.tsx +56 -27
  37. package/src/components/graph/sidepane/nodes/expandable-text-area.tsx +3 -1
  38. package/src/components/graph/sidepane/nodes/external-agent-node-editor.tsx +31 -11
  39. package/src/components/graph/sidepane/nodes/form-fields.tsx +10 -1
  40. package/src/components/graph/sidepane/nodes/mcp-node-editor.tsx +4 -9
  41. package/src/components/graph/sidepane/nodes/model-section.tsx +1 -1
  42. package/src/components/graph/toolbar/toolbar.tsx +1 -1
  43. package/src/components/mcp-servers/form/active-tools-selector.tsx +4 -2
  44. package/src/components/mcp-servers/form/mcp-server-form.tsx +8 -1
  45. package/src/components/mcp-servers/form/validation.ts +1 -1
  46. package/src/components/projects/edit-project-dialog.tsx +1 -1
  47. package/src/components/projects/form/project-form.tsx +20 -8
  48. package/src/components/projects/form/project-models-section.tsx +51 -23
  49. package/src/components/projects/form/project-stopwhen-section.tsx +25 -11
  50. package/src/components/projects/form/validation.ts +14 -10
  51. package/src/components/projects/new-project-dialog.tsx +1 -1
  52. package/src/components/traces/ai-calls-breakdown.tsx +1 -1
  53. package/src/components/traces/charts/area-chart-card.tsx +2 -2
  54. package/src/components/traces/charts/area-chart.tsx +2 -2
  55. package/src/components/traces/charts/chart-card.tsx +4 -4
  56. package/src/components/traces/conversation-detail.tsx +10 -8
  57. package/src/components/traces/conversation-stats/conversation-list-item.tsx +48 -37
  58. package/src/components/traces/conversation-stats/conversation-stats-card.tsx +1 -1
  59. package/src/components/traces/filters/date-picker.tsx +6 -6
  60. package/src/components/traces/filters/filter-trigger.tsx +1 -1
  61. package/src/components/traces/filters/graph-filter.tsx +3 -3
  62. package/src/components/traces/timeline/activity-details-sidepane.tsx +1 -1
  63. package/src/components/traces/timeline/activity-timeline.tsx +1 -1
  64. package/src/components/traces/timeline/blocks.tsx +2 -2
  65. package/src/components/traces/timeline/render-panel-content.tsx +11 -11
  66. package/src/components/traces/timeline/timeline-item.tsx +36 -22
  67. package/src/components/traces/timeline/timeline-wrapper.tsx +125 -37
  68. package/src/components/traces/traces-overview.tsx +3 -3
  69. package/src/components/ui/alert.tsx +60 -0
  70. package/src/components/ui/calendar.tsx +3 -4
  71. package/src/components/ui/external-link.tsx +2 -2
  72. package/src/components/ui/form.tsx +11 -4
  73. package/src/components/ui/inheritance-indicator.tsx +20 -17
  74. package/src/components/ui/resizable.tsx +13 -18
  75. package/src/constants/page-descriptions.tsx +2 -5
  76. package/src/constants/signoz.ts +15 -18
  77. package/src/features/graph/domain/__tests__/roundtrip.test.ts +5 -5
  78. package/src/features/graph/domain/serialize.ts +8 -9
  79. package/src/hooks/use-auto-prefill-id-zustand.ts +68 -0
  80. package/src/hooks/use-auto-prefill-id.ts +36 -0
  81. package/src/hooks/use-chat-activities-polling.ts +45 -12
  82. package/src/hooks/use-graph-errors.ts +2 -1
  83. package/src/hooks/use-project-data.ts +2 -2
  84. package/src/lib/actions/graph-full.ts +6 -2
  85. package/src/lib/actions/projects.ts +1 -1
  86. package/src/lib/api/api-config.ts +6 -6
  87. package/src/lib/api/api-keys.ts +4 -1
  88. package/src/lib/api/credentials.ts +1 -1
  89. package/src/lib/api/data-components.ts +1 -1
  90. package/src/lib/api/graph-full-client.ts +6 -3
  91. package/src/lib/api/projects.ts +1 -1
  92. package/src/lib/api/signoz-sql.ts +1 -1
  93. package/src/lib/api/signoz-stats.ts +958 -304
  94. package/src/lib/index.ts +1 -1
  95. package/src/lib/logger.ts +1 -2
  96. package/src/lib/types/graph-full.ts +1 -1
  97. package/src/lib/utils/generate-id.ts +14 -0
  98. package/src/lib/validation.ts +1 -1
  99. package/tsconfig.json +2 -2
  100. package/.env.sample +0 -5
  101. package/eslint.config.mjs +0 -14
@@ -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;
@@ -25,9 +25,11 @@ export const Playground = ({
25
25
  const {
26
26
  chatActivities,
27
27
  isPolling,
28
- error: _error,
28
+ error,
29
29
  startPolling,
30
30
  stopPolling,
31
+ retryConnection,
32
+ refreshOnce,
31
33
  } = useChatActivitiesPolling({
32
34
  conversationId,
33
35
  });
@@ -58,6 +60,10 @@ export const Playground = ({
58
60
  isPolling={isPolling}
59
61
  conversation={chatActivities}
60
62
  enableAutoScroll={true}
63
+ error={error}
64
+ retryConnection={retryConnection}
65
+ refreshOnce={refreshOnce}
66
+ showConversationTracesLink={true}
61
67
  />
62
68
  </ResizablePanelGroup>
63
69
  </div>
@@ -2,26 +2,29 @@
2
2
 
3
3
  import { ChevronRight, Info } from 'lucide-react';
4
4
  import { useParams } from 'next/navigation';
5
- import { useCallback, useEffect } from 'react';
5
+ import { useCallback } from 'react';
6
6
  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
28
 
26
29
  function MetadataEditor() {
27
30
  const params = useParams();
@@ -44,6 +47,21 @@ function MetadataEditor() {
44
47
  [setMetadata, markUnsaved]
45
48
  );
46
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
+
47
65
  return (
48
66
  <div className="space-y-8">
49
67
  {graphId && (
@@ -64,45 +82,44 @@ function MetadataEditor() {
64
82
  <CopyableSingleLineCode code={graphUrl} />
65
83
  </div>
66
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
+ />
94
+ <InputField
95
+ id="id"
96
+ name="id"
97
+ label="Id"
98
+ value={id || ''}
99
+ onChange={(e) => updateMetadata('id', e.target.value)}
100
+ disabled={!!graphId} // only editable if no graphId is set (i.e. new graph)
101
+ placeholder="my-graph"
102
+ description={
103
+ !graphId
104
+ ? 'Choose a unique identifier for this graph. Using an existing id will replace that graph.'
105
+ : undefined
106
+ }
107
+ isRequired
108
+ />
109
+ <TextareaField
110
+ id="description"
111
+ name="description"
112
+ label="Description"
113
+ value={description}
114
+ onChange={(e) => updateMetadata('description', e.target.value)}
115
+ placeholder="This graph is used to..."
116
+ className="max-h-96"
117
+ />
67
118
  <div className="space-y-2">
68
- <Label htmlFor="id">Id</Label>
69
- <Input
70
- id="id"
71
- value={id || ''}
72
- onChange={(e) => updateMetadata('id', e.target.value)}
73
- disabled={!!graphId} // only editable if no graphId is set (i.e. new graph)
74
- placeholder="my-graph"
75
- />
76
- {!graphId && (
77
- <p className="text-sm text-muted-foreground">
78
- Choose a unique identifier for this graph. Using an existing id will replace that graph.
79
- </p>
80
- )}
81
- </div>
82
- <div className="space-y-2">
83
- <Label htmlFor="name">Name</Label>
84
- <Input
85
- id="name"
86
- value={name}
87
- onChange={(e) => updateMetadata('name', e.target.value)}
88
- placeholder="My graph"
89
- />
90
- </div>
91
- <div className="space-y-2">
92
- <Label htmlFor="description">Description</Label>
93
- <Textarea
94
- id="description"
95
- value={description}
96
- onChange={(e) => updateMetadata('description', e.target.value)}
97
- placeholder="This graph is used to..."
98
- className="max-h-96"
99
- />
100
- </div>
101
-
102
- <div className="space-y-2">
103
- <Label htmlFor="graph-prompt">Graph Prompt</Label>
104
- <Textarea
119
+ <ExpandableTextArea
105
120
  id="graph-prompt"
121
+ name="graph-prompt"
122
+ label="Graph Prompt"
106
123
  value={graphPrompt || ''}
107
124
  onChange={(e) => updateMetadata('graphPrompt', e.target.value)}
108
125
  placeholder="System-level instructions for this graph..."
@@ -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';
@@ -35,7 +36,7 @@ export function AgentNodeEditor({
35
36
  const { tenantId, projectId } = useParams<{ tenantId: string; projectId: string }>();
36
37
  const selectedDataComponents = selectedNode.data?.dataComponents || [];
37
38
  const selectedArtifactComponents = selectedNode.data?.artifactComponents || [];
38
-
39
+
39
40
  // Get project and graph data for inheritance indicators
40
41
  const { project } = useProjectData();
41
42
  const metadata = useGraphStore((state) => state.metadata);
@@ -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,19 +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."
95
+ isRequired
68
96
  />
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
- />
80
-
81
97
  <TextareaField
82
98
  ref={(el) => setFieldRef('description', el)}
83
99
  id="description"
@@ -100,14 +116,15 @@ export function AgentNodeEditor({
100
116
  data-invalid={errorHelpers?.hasFieldError('prompt') ? '' : undefined}
101
117
  className="w-full max-h-96 data-invalid:border-red-300 data-invalid:focus-visible:border-red-300 data-invalid:focus-visible:ring-red-300"
102
118
  label="Prompt"
119
+ isRequired
103
120
  />
104
121
  {getFieldError('prompt') && (
105
122
  <p className="text-sm text-red-600">{getFieldError('prompt')}</p>
106
123
  )}
107
124
  </div>
108
125
 
109
- <ModelSection
110
- models={selectedNode.data.models}
126
+ <ModelSection
127
+ models={selectedNode.data.models}
111
128
  updatePath={updateModelPath}
112
129
  projectModels={project?.models}
113
130
  graphModels={metadata?.models}
@@ -156,12 +173,24 @@ export function AgentNodeEditor({
156
173
  </div>
157
174
 
158
175
  <div className="text-xs text-muted-foreground p-3 bg-blue-50 dark:bg-blue-950/20 rounded-md border border-blue-200 dark:border-blue-800">
159
- <p className="font-medium text-blue-900 dark:text-blue-100 mb-2">How execution limit inheritance works:</p>
176
+ <p className="font-medium text-blue-900 dark:text-blue-100 mb-2">
177
+ How execution limit inheritance works:
178
+ </p>
160
179
  <ul className="space-y-1 text-blue-800 dark:text-blue-200">
161
- <li>• <strong>stepCountIs</strong>: Project → Agent only (agent-level execution limit)</li>
162
- <li>• <strong>Explicit settings</strong> always take precedence over inherited values</li>
163
- <li>• <strong>Agent scope</strong>: This limit applies only to this specific agent's execution steps</li>
164
- <li>• <strong>Independent from transfers</strong>: Steps are counted per agent, transfers are counted per conversation</li>
180
+ <li>
181
+ <strong>stepCountIs</strong>: Project Agent only (agent-level execution limit)
182
+ </li>
183
+ <li>
184
+ • <strong>Explicit settings</strong> always take precedence over inherited values
185
+ </li>
186
+ <li>
187
+ • <strong>Agent scope</strong>: This limit applies only to this specific agent's
188
+ execution steps
189
+ </li>
190
+ <li>
191
+ • <strong>Independent from transfers</strong>: Steps are counted per agent, transfers
192
+ are counted per conversation
193
+ </li>
165
194
  </ul>
166
195
  </div>
167
196
  </div>
@@ -17,12 +17,14 @@ function ExpandedTextArea({ ...props }) {
17
17
 
18
18
  export function ExpandableTextArea({
19
19
  label,
20
+ isRequired = false,
20
21
  ...props
21
- }: { label: string } & React.ComponentProps<typeof Textarea>) {
22
+ }: { label: string; isRequired?: boolean } & React.ComponentProps<typeof Textarea>) {
22
23
  return (
23
24
  <ExpandableField
24
25
  name={props.id || 'expandable-textarea'}
25
26
  label={label}
27
+ isRequired={isRequired}
26
28
  compactView={<Textarea {...props} />}
27
29
  expandedView={<ExpandedTextArea {...props} />}
28
30
  />
@@ -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
@@ -4,6 +4,7 @@ import { Input } from '@/components/ui/input';
4
4
  import { Label } from '@/components/ui/label';
5
5
  import { Textarea } from '@/components/ui/textarea';
6
6
  import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
7
+ import { cn } from '@/lib/utils';
7
8
 
8
9
  interface BaseFieldProps {
9
10
  id: string;
@@ -16,6 +17,8 @@ interface BaseFieldProps {
16
17
  className?: string;
17
18
  description?: string;
18
19
  tooltip?: string;
20
+ isRequired?: boolean;
21
+ disabled?: boolean;
19
22
  }
20
23
 
21
24
  interface InputFieldProps extends BaseFieldProps {
@@ -40,13 +43,16 @@ export const InputField = forwardRef<HTMLInputElement, InputFieldProps>(
40
43
  description,
41
44
  tooltip,
42
45
  type = 'text',
46
+ isRequired = false,
47
+ disabled = false,
43
48
  },
44
49
  ref
45
50
  ) => {
46
51
  return (
47
52
  <div className="space-y-2">
48
- <Label htmlFor={id} className={error ? 'text-red-600' : ''}>
53
+ <Label htmlFor={id} className={cn(error ? 'text-red-600' : '', 'gap-1')}>
49
54
  {label}
55
+ {isRequired && <span className="text-red-500">*</span>}
50
56
  {tooltip && (
51
57
  <Tooltip>
52
58
  <TooltipTrigger>
@@ -66,6 +72,7 @@ export const InputField = forwardRef<HTMLInputElement, InputFieldProps>(
66
72
  placeholder={placeholder}
67
73
  data-invalid={error ? '' : undefined}
68
74
  className={`w-full data-invalid:border-red-300 data-invalid:focus-visible:border-red-300 data-invalid:focus-visible:ring-red-300 ${className}`}
75
+ disabled={disabled}
69
76
  />
70
77
  {error && <p className="text-sm text-red-600">{error}</p>}
71
78
  {description && <p className="text-sm text-muted-foreground">{description}</p>}
@@ -89,6 +96,7 @@ export const TextareaField = forwardRef<HTMLTextAreaElement, TextareaFieldProps>
89
96
  className = '',
90
97
  description,
91
98
  maxHeight = 'max-h-96',
99
+ disabled = false,
92
100
  },
93
101
  ref
94
102
  ) => {
@@ -106,6 +114,7 @@ export const TextareaField = forwardRef<HTMLTextAreaElement, TextareaFieldProps>
106
114
  placeholder={placeholder}
107
115
  data-invalid={error ? '' : undefined}
108
116
  className={`w-full ${maxHeight} data-invalid:border-red-300 data-invalid:focus-visible:border-red-300 data-invalid:focus-visible:ring-red-300 ${className}`}
117
+ disabled={disabled}
109
118
  />
110
119
  {error && <p className="text-sm text-red-600">{error}</p>}
111
120
  {description && <p className="text-sm text-muted-foreground">{description}</p>}
@@ -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
  }
@@ -105,12 +105,19 @@ export function MCPServerForm({
105
105
  return (
106
106
  <Form {...form}>
107
107
  <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
108
- <GenericInput control={form.control} name="name" label="Name" placeholder="MCP Server" />
108
+ <GenericInput
109
+ control={form.control}
110
+ name="name"
111
+ label="Name"
112
+ placeholder="MCP Server"
113
+ isRequired
114
+ />
109
115
  <GenericInput
110
116
  control={form.control}
111
117
  name="config.mcp.server.url"
112
118
  label="URL"
113
119
  placeholder="https://api.example.com/mcp"
120
+ isRequired
114
121
  />
115
122
  <GenericSelect
116
123
  control={form.control}
@@ -1,4 +1,4 @@
1
- import { z } from 'zod';
1
+ import { z } from 'zod/v4';
2
2
 
3
3
  // Discriminated union for tool selection
4
4
  const toolsConfigSchema = z.discriminatedUnion('type', [
@@ -33,7 +33,7 @@ export function EditProjectDialog({
33
33
 
34
34
  return (
35
35
  <Dialog open={isOpen} onOpenChange={setIsOpen}>
36
- <DialogContent className="max-w-2xl">
36
+ <DialogContent className="!max-w-2xl">
37
37
  <DialogHeader>
38
38
  <DialogTitle>Edit project</DialogTitle>
39
39
  <DialogDescription className="sr-only">Edit project details.</DialogDescription>