@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
@@ -1,14 +1,13 @@
1
- import { type NextRequest, NextResponse } from 'next/server';
2
- import { z } from 'zod';
3
1
  import axios from 'axios';
4
2
  import axiosRetry from 'axios-retry';
3
+ import { type NextRequest, NextResponse } from 'next/server';
4
+ import { z } from 'zod/v4';
5
5
  import { getLogger } from '@/lib/logger';
6
6
 
7
-
8
7
  // Configure axios retry
9
- axiosRetry(axios, {
8
+ axiosRetry(axios, {
10
9
  retries: 3,
11
- retryDelay: axiosRetry.exponentialDelay
10
+ retryDelay: axiosRetry.exponentialDelay,
12
11
  });
13
12
 
14
13
  const SIGNOZ_URL = process.env.SIGNOZ_URL || 'http://localhost:3080';
@@ -96,7 +95,10 @@ export async function POST(request: NextRequest) {
96
95
 
97
96
  return NextResponse.json(data);
98
97
  } catch (error) {
99
- logger.error({ error, stack: error instanceof Error ? error.stack : undefined }, 'Error proxying to SigNoz');
98
+ logger.error(
99
+ { error, stack: error instanceof Error ? error.stack : undefined },
100
+ 'Error proxying to SigNoz'
101
+ );
100
102
  return NextResponse.json(
101
103
  {
102
104
  error: 'Failed to connect to SigNoz',
@@ -100,6 +100,7 @@ export function ApiKeyForm({
100
100
  placeholder="Select expiration date"
101
101
  options={EXPIRATION_DATE_OPTIONS}
102
102
  selectTriggerClassName="w-full"
103
+ isRequired
103
104
  />
104
105
  <GenericComboBox
105
106
  control={form.control}
@@ -108,6 +109,7 @@ export function ApiKeyForm({
108
109
  options={graphsOptions}
109
110
  placeholder="Select a graph"
110
111
  searchPlaceholder="Search graphs..."
112
+ isRequired
111
113
  />
112
114
  <div className="flex justify-end">
113
115
  <Button type="submit" disabled={isSubmitting}>
@@ -1,4 +1,4 @@
1
- import { z } from 'zod';
1
+ import { z } from 'zod/v4';
2
2
 
3
3
  export const EXPIRATION_DATE_OPTIONS = [
4
4
  { value: '1d', label: '1 day' },
@@ -9,6 +9,7 @@ import { GenericTextarea } from '@/components/form/generic-textarea';
9
9
  import { JsonSchemaInput } from '@/components/form/json-schema-input';
10
10
  import { Button } from '@/components/ui/button';
11
11
  import { Form } from '@/components/ui/form';
12
+ import { useAutoPrefillId } from '@/hooks/use-auto-prefill-id';
12
13
  import {
13
14
  createArtifactComponentAction,
14
15
  updateArtifactComponentAction,
@@ -52,6 +53,14 @@ export function ArtifactComponentForm({
52
53
  const { isSubmitting } = form.formState;
53
54
  const router = useRouter();
54
55
 
56
+ // Auto-prefill ID based on name field (only for new components)
57
+ useAutoPrefillId({
58
+ form,
59
+ nameField: 'name',
60
+ idField: 'id',
61
+ isEditing: !!id,
62
+ });
63
+
55
64
  const onSubmit = async (data: ArtifactComponentFormData) => {
56
65
  try {
57
66
  const payload = { ...data } as ArtifactComponent;
@@ -81,42 +90,47 @@ export function ArtifactComponentForm({
81
90
  return (
82
91
  <Form {...form}>
83
92
  <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
93
+ <GenericInput
94
+ control={form.control}
95
+ name="name"
96
+ label="Name"
97
+ placeholder="Document Artifact"
98
+ isRequired
99
+ />
84
100
  <GenericInput
85
101
  control={form.control}
86
102
  name="id"
87
103
  label="Id"
88
104
  placeholder="my-artifact-component"
89
105
  disabled={!!id}
106
+ isRequired
90
107
  description={
91
108
  id
92
109
  ? ''
93
110
  : 'Choose a unique identifier for this component. Using an existing id will replace that component.'
94
111
  }
95
112
  />
96
- <GenericInput
97
- control={form.control}
98
- name="name"
99
- label="Name"
100
- placeholder="Document Artifact"
101
- />
102
113
  <GenericTextarea
103
114
  control={form.control}
104
115
  name="description"
105
116
  label="Description"
106
117
  placeholder="Structured factual information extracted from search results"
107
118
  className="min-h-[80px]"
119
+ isRequired
108
120
  />
109
121
  <JsonSchemaInput
110
122
  control={form.control}
111
123
  name="summaryProps"
112
124
  label="Summary props (JSON Schema)"
113
125
  placeholder="Enter a valid JSON Schema..."
126
+ isRequired
114
127
  />
115
128
  <JsonSchemaInput
116
129
  control={form.control}
117
130
  name="fullProps"
118
131
  label="Full props (JSON Schema)"
119
132
  placeholder="Enter a valid JSON Schema..."
133
+ isRequired
120
134
  />
121
135
  <Button type="submit" disabled={isSubmitting}>
122
136
  Save
@@ -1,4 +1,4 @@
1
- import { z } from 'zod';
1
+ import { z } from 'zod/v4';
2
2
  import { getJsonParseError, validateJsonSchemaForLlm } from '@/lib/json-schema-validation';
3
3
  import { idSchema } from '@/lib/validation';
4
4
 
@@ -35,8 +35,8 @@ export const artifactComponentSchema = z.object({
35
35
  id: idSchema,
36
36
  name: z.string().min(1, 'Name is required.'),
37
37
  description: z.string().min(1, 'Description is required.'),
38
- summaryProps: jsonSchemaValidation('Summary props'),
39
- fullProps: jsonSchemaValidation('Full props'),
38
+ summaryProps: jsonSchemaValidation('Summary props').optional(),
39
+ fullProps: jsonSchemaValidation('Full props').optional(),
40
40
  });
41
41
 
42
42
  export type ArtifactComponentFormData = z.infer<typeof artifactComponentSchema>;
@@ -1,4 +1,4 @@
1
- import { z } from 'zod';
1
+ import { z } from 'zod/v4';
2
2
 
3
3
  export const credentialFormSchema = z.object({
4
4
  name: z
@@ -115,6 +115,7 @@ export function CredentialForm({ onCreateCredential, tenantId, projectId }: Cred
115
115
  name="name"
116
116
  label="Name"
117
117
  placeholder="e.g., production-api-key"
118
+ isRequired
118
119
  />
119
120
 
120
121
  <div className="space-y-3">
@@ -123,6 +124,7 @@ export function CredentialForm({ onCreateCredential, tenantId, projectId }: Cred
123
124
  name="apiKeyToSet"
124
125
  label="API Key"
125
126
  placeholder="e.g., sk-1234567890abcdef1234567890abcdef"
127
+ isRequired
126
128
  />
127
129
  <div className="text-xs text-muted-foreground p-3 bg-muted/30 rounded-md">
128
130
  <p className="mb-2">
@@ -4,7 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
4
4
  import { useRouter } from 'next/navigation';
5
5
  import { useForm } from 'react-hook-form';
6
6
  import { toast } from 'sonner';
7
- import { z } from 'zod';
7
+ import { z } from 'zod/v4';
8
8
  import { CredentialToolsList } from '@/components/credentials/credential-tools-list';
9
9
  import { GenericInput } from '@/components/form/generic-input';
10
10
  import { GenericKeyValueInput } from '@/components/form/generic-key-value-input';
@@ -3,7 +3,7 @@
3
3
  import { zodResolver } from '@hookform/resolvers/zod';
4
4
  import type { ApiProvider } from '@nangohq/types';
5
5
  import { useForm } from 'react-hook-form';
6
- import * as z from 'zod';
6
+ import * as z from 'zod/v4';
7
7
  import { GenericInput } from '@/components/form/generic-input';
8
8
  import { GenericTextarea } from '@/components/form/generic-textarea';
9
9
  import { ProviderIcon } from '@/components/icons/provider-icon';
@@ -9,6 +9,7 @@ import { GenericTextarea } from '@/components/form/generic-textarea';
9
9
  import { JsonSchemaInput } from '@/components/form/json-schema-input';
10
10
  import { Button } from '@/components/ui/button';
11
11
  import { Form } from '@/components/ui/form';
12
+ import { useAutoPrefillId } from '@/hooks/use-auto-prefill-id';
12
13
  import {
13
14
  createDataComponentAction,
14
15
  updateDataComponentAction,
@@ -49,6 +50,14 @@ export function DataComponentForm({
49
50
  const { isSubmitting } = form.formState;
50
51
  const router = useRouter();
51
52
 
53
+ // Auto-prefill ID based on name field (only for new components)
54
+ useAutoPrefillId({
55
+ form,
56
+ nameField: 'name',
57
+ idField: 'id',
58
+ isEditing: !!id,
59
+ });
60
+
52
61
  const onSubmit = async (data: DataComponentFormData) => {
53
62
  try {
54
63
  const payload = { ...data } as DataComponent;
@@ -78,6 +87,13 @@ export function DataComponentForm({
78
87
  return (
79
88
  <Form {...form}>
80
89
  <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
90
+ <GenericInput
91
+ control={form.control}
92
+ name="name"
93
+ label="Name"
94
+ placeholder="List orders"
95
+ isRequired
96
+ />
81
97
  <GenericInput
82
98
  control={form.control}
83
99
  name="id"
@@ -89,20 +105,22 @@ export function DataComponentForm({
89
105
  ? ''
90
106
  : 'Choose a unique identifier for this component. Using an existing id will replace that component.'
91
107
  }
108
+ isRequired
92
109
  />
93
- <GenericInput control={form.control} name="name" label="Name" placeholder="List orders" />
94
110
  <GenericTextarea
95
111
  control={form.control}
96
112
  name="description"
97
113
  label="Description"
98
114
  placeholder="Display a list of user orders with interactive options"
99
115
  className="min-h-[80px]"
116
+ isRequired
100
117
  />
101
118
  <JsonSchemaInput
102
119
  control={form.control}
103
120
  name="props"
104
121
  label="Props (JSON Schema)"
105
122
  placeholder="Enter a valid JSON Schema..."
123
+ isRequired
106
124
  />
107
125
  <Button type="submit" disabled={isSubmitting}>
108
126
  Save
@@ -1,4 +1,4 @@
1
- import { z } from 'zod';
1
+ import { z } from 'zod/v4';
2
2
  import { getJsonParseError, validateJsonSchemaForLlm } from '@/lib/json-schema-validation';
3
3
  import { idSchema } from '@/lib/validation';
4
4
 
@@ -32,7 +32,8 @@ export const dataComponentSchema = z.object({
32
32
  });
33
33
  return z.NEVER;
34
34
  }
35
- }),
35
+ })
36
+ .optional(),
36
37
  });
37
38
 
38
39
  export type DataComponentFormData = z.infer<typeof dataComponentSchema>;
@@ -20,6 +20,7 @@ interface ExpandableFieldProps {
20
20
  expandedView: ReactNode;
21
21
  actions?: ReactNode;
22
22
  expandButtonLabel?: string;
23
+ isRequired?: boolean;
23
24
  }
24
25
 
25
26
  export function ExpandableField({
@@ -30,13 +31,17 @@ export function ExpandableField({
30
31
  expandedView,
31
32
  actions,
32
33
  expandButtonLabel = 'Expand to full screen',
34
+ isRequired = false,
33
35
  }: ExpandableFieldProps) {
34
36
  return (
35
37
  <Dialog>
36
38
  <div className={className}>
37
39
  <div className="space-y-2">
38
40
  <div className="flex items-center justify-between">
39
- <Label htmlFor={name}>{label}</Label>
41
+ <Label className="gap-1" htmlFor={name}>
42
+ {label}
43
+ {isRequired && <span className="text-red-500">*</span>}
44
+ </Label>
40
45
  {actions && <div className="flex gap-2">{actions}</div>}
41
46
  </div>
42
47
  <div className="relative">
@@ -19,6 +19,7 @@ interface FormFieldWrapperProps<T extends FieldValues> {
19
19
  children: (field: FieldValues) => React.ReactNode;
20
20
  description?: string;
21
21
  rules?: RegisterOptions<T, FieldPath<T>>;
22
+ isRequired?: boolean;
22
23
  }
23
24
 
24
25
  export function FormFieldWrapper<T extends FieldValues>({
@@ -28,6 +29,7 @@ export function FormFieldWrapper<T extends FieldValues>({
28
29
  children,
29
30
  description,
30
31
  rules,
32
+ isRequired,
31
33
  }: FormFieldWrapperProps<T>) {
32
34
  return (
33
35
  <FormField
@@ -36,7 +38,7 @@ export function FormFieldWrapper<T extends FieldValues>({
36
38
  rules={rules}
37
39
  render={({ field }) => (
38
40
  <FormItem className="relative">
39
- <FormLabel>{label}</FormLabel>
41
+ <FormLabel isRequired={isRequired}>{label}</FormLabel>
40
42
  <FormControl>{children(field)}</FormControl>
41
43
  {description && <FormDescription>{description}</FormDescription>}
42
44
  <FormMessage />
@@ -25,6 +25,7 @@ interface GenericComboBoxProps<T extends FieldValues> {
25
25
  searchPlaceholder?: string;
26
26
  placeholder?: string;
27
27
  disabled?: boolean;
28
+ isRequired?: boolean;
28
29
  }
29
30
 
30
31
  export function GenericComboBox<T extends FieldValues>({
@@ -35,11 +36,12 @@ export function GenericComboBox<T extends FieldValues>({
35
36
  searchPlaceholder = 'Search...',
36
37
  placeholder,
37
38
  disabled = false,
39
+ isRequired = false,
38
40
  }: GenericComboBoxProps<T>) {
39
41
  const [open, setOpen] = useState(false);
40
42
 
41
43
  return (
42
- <FormFieldWrapper control={control} name={name} label={label}>
44
+ <FormFieldWrapper control={control} name={name} label={label} isRequired={isRequired}>
43
45
  {(field) => (
44
46
  <Popover open={open} onOpenChange={setOpen}>
45
47
  <PopoverTrigger asChild>
@@ -13,6 +13,7 @@ interface GenericInputProps<T extends FieldValues> {
13
13
  min?: string;
14
14
  disabled?: boolean;
15
15
  description?: string;
16
+ isRequired?: boolean;
16
17
  }
17
18
 
18
19
  export function GenericInput<T extends FieldValues>({
@@ -24,9 +25,16 @@ export function GenericInput<T extends FieldValues>({
24
25
  min,
25
26
  disabled,
26
27
  description,
28
+ isRequired = false,
27
29
  }: GenericInputProps<T>) {
28
30
  return (
29
- <FormFieldWrapper control={control} name={name} label={label} description={description}>
31
+ <FormFieldWrapper
32
+ control={control}
33
+ name={name}
34
+ label={label}
35
+ description={description}
36
+ isRequired={isRequired}
37
+ >
30
38
  {(field) => (
31
39
  <Input
32
40
  type={type}
@@ -24,6 +24,7 @@ interface GenericSelectProps<T extends FieldValues> {
24
24
  options: SelectOption[];
25
25
  disabled?: boolean;
26
26
  selectTriggerClassName?: string;
27
+ isRequired?: boolean;
27
28
  }
28
29
 
29
30
  export function GenericSelect<T extends FieldValues>({
@@ -34,9 +35,10 @@ export function GenericSelect<T extends FieldValues>({
34
35
  options,
35
36
  disabled = false,
36
37
  selectTriggerClassName,
38
+ isRequired = false,
37
39
  }: GenericSelectProps<T>) {
38
40
  return (
39
- <FormFieldWrapper control={control} name={name} label={label}>
41
+ <FormFieldWrapper control={control} name={name} label={label} isRequired={isRequired}>
40
42
  {(field) => (
41
43
  <Select onValueChange={field.onChange} defaultValue={field.value} disabled={disabled}>
42
44
  <SelectTrigger disabled={disabled} className={selectTriggerClassName}>
@@ -12,6 +12,7 @@ interface GenericTextareaProps<T extends FieldValues> {
12
12
  className?: string;
13
13
  disabled?: boolean;
14
14
  readOnly?: boolean;
15
+ isRequired?: boolean;
15
16
  }
16
17
 
17
18
  export function GenericTextarea<T extends FieldValues>({
@@ -22,9 +23,10 @@ export function GenericTextarea<T extends FieldValues>({
22
23
  className,
23
24
  disabled,
24
25
  readOnly,
26
+ isRequired = false,
25
27
  }: GenericTextareaProps<T>) {
26
28
  return (
27
- <FormFieldWrapper control={control} name={name} label={label}>
29
+ <FormFieldWrapper control={control} name={name} label={label} isRequired={isRequired}>
28
30
  {(field) => (
29
31
  <Textarea
30
32
  placeholder={placeholder}
@@ -12,6 +12,7 @@ interface JsonSchemaInputProps<T extends FieldValues> {
12
12
  disabled?: boolean;
13
13
  description?: string;
14
14
  readOnly?: boolean;
15
+ isRequired?: boolean;
15
16
  }
16
17
 
17
18
  export function JsonSchemaInput<T extends FieldValues>({
@@ -22,9 +23,16 @@ export function JsonSchemaInput<T extends FieldValues>({
22
23
  disabled,
23
24
  description,
24
25
  readOnly,
26
+ isRequired = false,
25
27
  }: JsonSchemaInputProps<T>) {
26
28
  return (
27
- <FormFieldWrapper control={control} name={name} label={label} description={description}>
29
+ <FormFieldWrapper
30
+ control={control}
31
+ name={name}
32
+ label={label}
33
+ description={description}
34
+ isRequired={isRequired}
35
+ >
28
36
  {(field) => (
29
37
  <StandaloneJsonEditor
30
38
  placeholder={placeholder}
@@ -1,6 +1,4 @@
1
- import type { Node } from '@xyflow/react';
2
1
  import { Bot, BotMessageSquare, Hammer } from 'lucide-react';
3
- import { nanoid } from 'nanoid';
4
2
  import type { MCPTool } from '@/lib/api/tools';
5
3
  import { AgentNode } from '../nodes/agent-node';
6
4
  import { ExternalAgentNode } from '../nodes/external-agent-node';
@@ -58,10 +56,10 @@ export const externalAgentNodeTargetHandleId = 'target-external-agent';
58
56
 
59
57
  export const newNodeDefaults: Record<keyof typeof nodeTypes, NodeData> = {
60
58
  [NodeType.Agent]: {
61
- name: 'Agent',
59
+ name: '',
62
60
  },
63
61
  [NodeType.ExternalAgent]: {
64
- name: 'External agent',
62
+ name: '',
65
63
  },
66
64
  [NodeType.MCP]: {
67
65
  name: 'MCP',
@@ -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,9 +1,10 @@
1
1
  'use client';
2
2
  import { InkeepEmbeddedChat } from '@inkeep/cxkit-react-oss';
3
3
  import type { ComponentsConfig, InkeepCallbackEvent } from '@inkeep/cxkit-react-oss/types';
4
+ import { nanoid } from 'nanoid';
5
+ import { useEffect, useRef } from 'react';
4
6
  import { EXECUTION_API_BASE_URL } from '@/lib/api/api-config';
5
7
  import { IkpMessage as IkpMessageComponent } from './ikp-message';
6
- import { nanoid } from 'nanoid';
7
8
 
8
9
  interface ChatWidgetProps {
9
10
  graphId?: string;
@@ -107,16 +108,44 @@ export function ChatWidget({
107
108
  startPolling,
108
109
  stopPolling,
109
110
  }: ChatWidgetProps) {
111
+ const stopPollingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
112
+
113
+ useEffect(() => {
114
+ return () => {
115
+ if (stopPollingTimeoutRef.current) {
116
+ clearTimeout(stopPollingTimeoutRef.current);
117
+ stopPollingTimeoutRef.current = null;
118
+ }
119
+ };
120
+ }, []);
121
+
110
122
  return (
111
123
  <div className="h-full flex flex-row gap-4">
112
124
  <div className="flex-1 min-w-0 h-full">
113
125
  <InkeepEmbeddedChat
114
126
  baseSettings={{
115
127
  onEvent: (event: InkeepCallbackEvent) => {
128
+ if (event.eventName === 'assistant_message_received') {
129
+ // Add a delay before stopping polling to allow activity data to be available
130
+ stopPollingTimeoutRef.current = setTimeout(() => {
131
+ stopPolling();
132
+ stopPollingTimeoutRef.current = null;
133
+ }, 5000);
134
+ }
116
135
  if (event.eventName === 'user_message_submitted') {
136
+ // Cancel any pending stop polling timeout since we need to keep polling
137
+ if (stopPollingTimeoutRef.current) {
138
+ clearTimeout(stopPollingTimeoutRef.current);
139
+ stopPollingTimeoutRef.current = null;
140
+ }
117
141
  startPolling();
118
142
  }
119
143
  if (event.eventName === 'chat_clear_button_clicked') {
144
+ // Cancel any pending stop polling timeout
145
+ if (stopPollingTimeoutRef.current) {
146
+ clearTimeout(stopPollingTimeoutRef.current);
147
+ stopPollingTimeoutRef.current = null;
148
+ }
120
149
  stopPolling();
121
150
  setConversationId(nanoid());
122
151
  }
@@ -171,7 +200,7 @@ export function ChatWidget({
171
200
  components: {
172
201
  IkpMessage,
173
202
  },
174
- introMessage: 'Hi!'
203
+ introMessage: 'Hi!',
175
204
  }}
176
205
  />
177
206
  </div>