@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,16 +1,26 @@
1
1
  import {
2
+ ArrowRight,
3
+ ArrowUpRight,
4
+ CheckCircle,
5
+ ChevronDown,
6
+ ChevronRight,
7
+ Database,
8
+ Hammer,
9
+ Settings,
10
+ Sparkles,
11
+ User,
12
+ } from 'lucide-react';
13
+ import { Streamdown } from 'streamdown';
14
+ import { formatDateTime } from '@/app/utils/format-date';
15
+ import { Bubble, CodeBubble } from '@/components/traces/timeline/bubble';
16
+ import { Flow } from '@/components/traces/timeline/flow';
17
+ import { TagRow } from '@/components/traces/timeline/tag-row';
18
+ import {
19
+ ACTIVITY_TYPES,
2
20
  type ActivityItem,
3
21
  type ActivityKind,
4
- ACTIVITY_TYPES,
22
+ TOOL_TYPES,
5
23
  } from '@/components/traces/timeline/types';
6
- import { User, Settings, Database, Hammer, Sparkles, ArrowRight, ArrowUpRight, ChevronDown, ChevronRight } from 'lucide-react';
7
- import { Bubble, CodeBubble } from '@/components/traces/timeline/bubble';
8
- import { TagRow } from '@/components/traces/timeline/tag-row';
9
- import { TOOL_TYPES } from '@/components/traces/timeline/types';
10
- import { CheckCircle } from 'lucide-react';
11
- import { Flow } from '@/components/traces/timeline/flow';
12
- import { Streamdown } from 'streamdown';
13
- import { formatDateTime } from '@/app/utils/format-date';
14
24
 
15
25
  function truncateWords(s: string, nWords: number) {
16
26
  const words = s.split(/\s+/);
@@ -21,8 +31,10 @@ function truncateChars(s: string, n: number) {
21
31
  }
22
32
 
23
33
  function isAiMessage(activity: ActivityItem): boolean {
24
- return activity.type === ACTIVITY_TYPES.AI_ASSISTANT_MESSAGE ||
25
- activity.type === ACTIVITY_TYPES.AI_MODEL_STREAMED_TEXT;
34
+ return (
35
+ activity.type === ACTIVITY_TYPES.AI_ASSISTANT_MESSAGE ||
36
+ activity.type === ACTIVITY_TYPES.AI_MODEL_STREAMED_TEXT
37
+ );
26
38
  }
27
39
 
28
40
  function statusIcon(
@@ -64,12 +76,12 @@ interface TimelineItemProps {
64
76
  onToggleAiMessageCollapse?: (activityId: string) => void;
65
77
  }
66
78
 
67
- export function TimelineItem({
68
- activity,
69
- isLast,
70
- onSelect,
71
- isAiMessageCollapsed = false,
72
- onToggleAiMessageCollapse
79
+ export function TimelineItem({
80
+ activity,
81
+ isLast,
82
+ onSelect,
83
+ isAiMessageCollapsed = false,
84
+ onToggleAiMessageCollapse,
73
85
  }: TimelineItemProps) {
74
86
  const typeForIcon =
75
87
  activity.type === ACTIVITY_TYPES.TOOL_CALL && activity.toolType === TOOL_TYPES.TRANSFER
@@ -128,7 +140,7 @@ export function TimelineItem({
128
140
  type="button"
129
141
  onClick={() => onToggleAiMessageCollapse(activity.id)}
130
142
  className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
131
- title={isAiMessageCollapsed ? "Expand AI response" : "Collapse AI response"}
143
+ title={isAiMessageCollapsed ? 'Expand AI response' : 'Collapse AI response'}
132
144
  >
133
145
  {isAiMessageCollapsed ? (
134
146
  <ChevronRight className="h-3 w-3" />
@@ -154,7 +166,11 @@ export function TimelineItem({
154
166
  type="button"
155
167
  onClick={() => onToggleAiMessageCollapse(activity.id)}
156
168
  className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
157
- title={isAiMessageCollapsed ? "Expand AI streaming response" : "Collapse AI streaming response"}
169
+ title={
170
+ isAiMessageCollapsed
171
+ ? 'Expand AI streaming response'
172
+ : 'Collapse AI streaming response'
173
+ }
158
174
  >
159
175
  {isAiMessageCollapsed ? (
160
176
  <ChevronRight className="h-3 w-3" />
@@ -165,9 +181,7 @@ export function TimelineItem({
165
181
  </button>
166
182
  )}
167
183
  {!isAiMessageCollapsed && (
168
- <Bubble>
169
- {truncateWords(activity.aiStreamTextContent, 100)}
170
- </Bubble>
184
+ <Bubble>{truncateWords(activity.aiStreamTextContent, 100)}</Bubble>
171
185
  )}
172
186
  </div>
173
187
  )}
@@ -1,20 +1,21 @@
1
+ import { AlertTriangle, ChevronDown, ChevronUp, Loader2, RefreshCw } from 'lucide-react';
1
2
  import { useEffect, useMemo, useState } from 'react';
3
+ import { toast } from 'sonner';
4
+ import { StickToBottom } from 'use-stick-to-bottom';
5
+ import { ConversationTracesLink } from '@/components/traces/signoz-link';
6
+ import { ActivityDetailsSidePane } from '@/components/traces/timeline/activity-details-sidepane';
2
7
  import { ActivityTimeline } from '@/components/traces/timeline/activity-timeline';
8
+ import { renderPanelContent } from '@/components/traces/timeline/render-panel-content';
3
9
  import type {
4
10
  ActivityItem,
5
- SelectedPanel,
6
- PanelType,
7
11
  ConversationDetail,
12
+ PanelType,
13
+ SelectedPanel,
8
14
  } from '@/components/traces/timeline/types';
9
- import { ActivityDetailsSidePane } from '@/components/traces/timeline/activity-details-sidepane';
10
- import { ACTIVITY_TYPES } from '@/components/traces/timeline/types';
11
- import { TOOL_TYPES } from '@/components/traces/timeline/types';
12
- import { renderPanelContent } from '@/components/traces/timeline/render-panel-content';
13
- import { StickToBottom } from 'use-stick-to-bottom';
14
- import { ResizableHandle, ResizablePanel } from '@/components/ui/resizable';
15
- import { Loader2, ChevronDown, ChevronUp } from 'lucide-react';
16
- import { ConversationTracesLink } from '@/components/traces/signoz-link';
15
+ import { ACTIVITY_TYPES, TOOL_TYPES } from '@/components/traces/timeline/types';
16
+ import { Alert, AlertTitle } from '@/components/ui/alert';
17
17
  import { Button } from '@/components/ui/button';
18
+ import { ResizableHandle, ResizablePanel } from '@/components/ui/resizable';
18
19
 
19
20
  function panelTitle(selected: SelectedPanel) {
20
21
  switch (selected.type) {
@@ -49,9 +50,42 @@ interface TimelineWrapperProps {
49
50
  conversation?: ConversationDetail | null;
50
51
  enableAutoScroll?: boolean;
51
52
  isPolling?: boolean;
53
+ error?: string | null;
54
+ retryConnection?: () => void;
55
+ refreshOnce?: () => Promise<{ hasNewActivity: boolean }>;
56
+ showConversationTracesLink?: boolean;
52
57
  }
53
58
 
54
- function EmptyTimeline({ isPolling }: { isPolling: boolean }) {
59
+ function EmptyTimeline({
60
+ isPolling,
61
+ error,
62
+ retryConnection,
63
+ }: {
64
+ isPolling: boolean;
65
+ error?: string | null;
66
+ retryConnection?: () => void;
67
+ }) {
68
+ if (error) {
69
+ return (
70
+ <div className="flex flex-col gap-4 h-full justify-center items-center px-6">
71
+ <Alert variant="destructive" className="max-w-md">
72
+ <AlertTriangle className="h-4 w-4" />
73
+ <AlertTitle>{error}</AlertTitle>
74
+ </Alert>
75
+ {retryConnection && (
76
+ <Button
77
+ variant="outline"
78
+ size="sm"
79
+ onClick={retryConnection}
80
+ className="flex items-center gap-2"
81
+ >
82
+ <RefreshCw className="h-4 w-4" />
83
+ Retry Connection
84
+ </Button>
85
+ )}
86
+ </div>
87
+ );
88
+ }
55
89
  return (
56
90
  <div className="flex flex-col gap-2 h-full justify-center items-center">
57
91
  {isPolling ? (
@@ -72,13 +106,19 @@ export function TimelineWrapper({
72
106
  conversation,
73
107
  enableAutoScroll = false,
74
108
  isPolling = false,
109
+ error,
110
+ retryConnection,
111
+ refreshOnce,
112
+ showConversationTracesLink = false,
75
113
  }: TimelineWrapperProps) {
76
114
  const [selected, setSelected] = useState<SelectedPanel | null>(null);
77
115
  const [panelVisible, setPanelVisible] = useState(false);
78
-
116
+ const [isRefreshing, setIsRefreshing] = useState(false);
117
+
79
118
  // State for collapsible AI messages
80
119
  const [collapsedAiMessages, setCollapsedAiMessages] = useState<Set<string>>(new Set());
81
- const [aiMessagesGloballyCollapsed, setAiMessagesGloballyCollapsed] = useState<boolean>(enableAutoScroll);
120
+ const [aiMessagesGloballyCollapsed, setAiMessagesGloballyCollapsed] =
121
+ useState<boolean>(enableAutoScroll);
82
122
 
83
123
  useEffect(() => {
84
124
  if (selected) {
@@ -117,12 +157,13 @@ export function TimelineWrapper({
117
157
  // Initialize AI messages based on view type when activities change
118
158
  useEffect(() => {
119
159
  const aiMessageIds = sortedActivities
120
- .filter(activity =>
121
- activity.type === ACTIVITY_TYPES.AI_ASSISTANT_MESSAGE ||
122
- activity.type === ACTIVITY_TYPES.AI_MODEL_STREAMED_TEXT
160
+ .filter(
161
+ (activity) =>
162
+ activity.type === ACTIVITY_TYPES.AI_ASSISTANT_MESSAGE ||
163
+ activity.type === ACTIVITY_TYPES.AI_MODEL_STREAMED_TEXT
123
164
  )
124
- .map(activity => activity.id);
125
-
165
+ .map((activity) => activity.id);
166
+
126
167
  if (enableAutoScroll) {
127
168
  // Live trace view: default collapsed
128
169
  setCollapsedAiMessages(new Set(aiMessageIds));
@@ -133,7 +174,7 @@ export function TimelineWrapper({
133
174
  setAiMessagesGloballyCollapsed(false);
134
175
  }
135
176
  // eslint-disable-next-line react-hooks/exhaustive-deps
136
- }, [sortedActivities.length, enableAutoScroll]); // Run when activities count or view type changes
177
+ }, [sortedActivities.length, enableAutoScroll]); // Run when activities count or view type changes
137
178
 
138
179
  // Functions to handle expand/collapse all
139
180
  const expandAllAiMessages = () => {
@@ -143,11 +184,12 @@ export function TimelineWrapper({
143
184
 
144
185
  const collapseAllAiMessages = () => {
145
186
  const aiMessageIds = sortedActivities
146
- .filter(activity =>
147
- activity.type === ACTIVITY_TYPES.AI_ASSISTANT_MESSAGE ||
148
- activity.type === ACTIVITY_TYPES.AI_MODEL_STREAMED_TEXT
187
+ .filter(
188
+ (activity) =>
189
+ activity.type === ACTIVITY_TYPES.AI_ASSISTANT_MESSAGE ||
190
+ activity.type === ACTIVITY_TYPES.AI_MODEL_STREAMED_TEXT
149
191
  )
150
- .map(activity => activity.id);
192
+ .map((activity) => activity.id);
151
193
  setCollapsedAiMessages(new Set(aiMessageIds));
152
194
  setAiMessagesGloballyCollapsed(true);
153
195
  };
@@ -160,15 +202,16 @@ export function TimelineWrapper({
160
202
  newCollapsed.add(activityId);
161
203
  }
162
204
  setCollapsedAiMessages(newCollapsed);
163
-
205
+
164
206
  // Update global state based on current state
165
207
  const aiMessageIds = sortedActivities
166
- .filter(activity =>
167
- activity.type === ACTIVITY_TYPES.AI_ASSISTANT_MESSAGE ||
168
- activity.type === ACTIVITY_TYPES.AI_MODEL_STREAMED_TEXT
208
+ .filter(
209
+ (activity) =>
210
+ activity.type === ACTIVITY_TYPES.AI_ASSISTANT_MESSAGE ||
211
+ activity.type === ACTIVITY_TYPES.AI_MODEL_STREAMED_TEXT
169
212
  )
170
- .map(activity => activity.id);
171
- const allCollapsed = aiMessageIds.every(id => newCollapsed.has(id));
213
+ .map((activity) => activity.id);
214
+ const allCollapsed = aiMessageIds.every((id) => newCollapsed.has(id));
172
215
  setAiMessagesGloballyCollapsed(allCollapsed);
173
216
  };
174
217
 
@@ -183,7 +226,8 @@ export function TimelineWrapper({
183
226
  );
184
227
 
185
228
  const determinePanelType = (a: ActivityItem): Exclude<PanelType, 'mcp_tool_error'> => {
186
- if (a.type === ACTIVITY_TYPES.TOOL_CALL && a.toolType === TOOL_TYPES.TRANSFER) return 'transfer';
229
+ if (a.type === ACTIVITY_TYPES.TOOL_CALL && a.toolType === TOOL_TYPES.TRANSFER)
230
+ return 'transfer';
187
231
  if (a.type === ACTIVITY_TYPES.TOOL_CALL && a.toolName?.includes('delegate'))
188
232
  return 'delegation';
189
233
  if (
@@ -196,6 +240,21 @@ export function TimelineWrapper({
196
240
  return a.type;
197
241
  };
198
242
 
243
+ const handleRefresh = async () => {
244
+ if (!refreshOnce || isRefreshing) return;
245
+ setIsRefreshing(true);
246
+ try {
247
+ const result = await refreshOnce();
248
+ if (!result.hasNewActivity) {
249
+ toast.info('No new activity found');
250
+ }
251
+ setIsRefreshing(false);
252
+ } catch {
253
+ toast.error('Failed to refresh activities');
254
+ setIsRefreshing(false);
255
+ }
256
+ };
257
+
199
258
  return (
200
259
  <>
201
260
  <ResizablePanel order={2}>
@@ -205,17 +264,24 @@ export function TimelineWrapper({
205
264
  <div className="text-foreground text-md font-medium">Activity Timeline</div>
206
265
  <div className="flex items-center gap-2">
207
266
  {/* Expand/Collapse AI Messages Buttons */}
208
- {sortedActivities.some(activity =>
209
- activity.type === ACTIVITY_TYPES.AI_ASSISTANT_MESSAGE ||
210
- activity.type === ACTIVITY_TYPES.AI_MODEL_STREAMED_TEXT
267
+ {sortedActivities.some(
268
+ (activity) =>
269
+ activity.type === ACTIVITY_TYPES.AI_ASSISTANT_MESSAGE ||
270
+ activity.type === ACTIVITY_TYPES.AI_MODEL_STREAMED_TEXT
211
271
  ) && (
212
272
  <div className="flex items-center gap-1">
213
273
  <Button
214
274
  variant="ghost"
215
275
  size="sm"
216
- onClick={aiMessagesGloballyCollapsed ? expandAllAiMessages : collapseAllAiMessages}
276
+ onClick={
277
+ aiMessagesGloballyCollapsed ? expandAllAiMessages : collapseAllAiMessages
278
+ }
217
279
  className="h-7 px-2 text-xs text-muted-foreground hover:text-foreground"
218
- title={aiMessagesGloballyCollapsed ? "Expand all AI messages" : "Collapse all AI messages"}
280
+ title={
281
+ aiMessagesGloballyCollapsed
282
+ ? 'Expand all AI messages'
283
+ : 'Collapse all AI messages'
284
+ }
219
285
  >
220
286
  {aiMessagesGloballyCollapsed ? (
221
287
  <>
@@ -231,7 +297,7 @@ export function TimelineWrapper({
231
297
  </Button>
232
298
  </div>
233
299
  )}
234
- {conversation?.conversationId && (
300
+ {showConversationTracesLink && conversation?.conversationId && (
235
301
  <ConversationTracesLink conversationId={conversation.conversationId} />
236
302
  )}
237
303
  </div>
@@ -239,7 +305,11 @@ export function TimelineWrapper({
239
305
  </div>
240
306
  <div className="p-0 flex-1 min-h-0">
241
307
  {sortedActivities.length === 0 ? (
242
- <EmptyTimeline isPolling={isPolling} />
308
+ <EmptyTimeline
309
+ isPolling={isPolling}
310
+ error={error}
311
+ retryConnection={retryConnection}
312
+ />
243
313
  ) : enableAutoScroll ? (
244
314
  <StickToBottom
245
315
  className="h-full [&>div]:overflow-y-auto [&>div]:scrollbar-thin [&>div]:scrollbar-thumb-muted-foreground/30 [&>div]:scrollbar-track-transparent dark:[&>div]:scrollbar-thumb-muted-foreground/50"
@@ -255,6 +325,24 @@ export function TimelineWrapper({
255
325
  collapsedAiMessages={collapsedAiMessages}
256
326
  onToggleAiMessageCollapse={toggleAiMessageCollapse}
257
327
  />
328
+ {!isPolling && sortedActivities.length > 0 && !error && refreshOnce && (
329
+ <div className="flex justify-center items-center z-10">
330
+ <Button
331
+ variant="outline"
332
+ size="sm"
333
+ onClick={handleRefresh}
334
+ disabled={isRefreshing}
335
+ className=" text-xs bg-background/80 backdrop-blur-sm hover:bg-background/90 transition-all duration-200 opacity-70 hover:opacity-100"
336
+ >
337
+ {isRefreshing ? (
338
+ <Loader2 className="h-3 w-3 mr-1.5 animate-spin" />
339
+ ) : (
340
+ <RefreshCw className="h-3 w-3 mr-1.5" />
341
+ )}
342
+ {isRefreshing ? 'Refreshing...' : 'Refresh'}
343
+ </Button>
344
+ </div>
345
+ )}
258
346
  </StickToBottom.Content>
259
347
  </StickToBottom>
260
348
  ) : (
@@ -26,10 +26,10 @@ import {
26
26
  import { useAggregateStats, useConversationStats } from '@/hooks/use-traces';
27
27
  import { type TimeRange, useTracesQueryState } from '@/hooks/use-traces-query-state';
28
28
  import { getSigNozStatsClient, type SpanFilterOptions } from '@/lib/api/signoz-stats';
29
- import { ConversationStatsCard } from './conversation-stats/conversation-stats-card';
30
- import { StatCard } from './charts/stat-card';
31
29
  import { AreaChartCard } from './charts/area-chart-card';
32
- import { DatePickerWithPresets, CUSTOM } from './filters/date-picker';
30
+ import { StatCard } from './charts/stat-card';
31
+ import { ConversationStatsCard } from './conversation-stats/conversation-stats-card';
32
+ import { CUSTOM, DatePickerWithPresets } from './filters/date-picker';
33
33
  import { GraphFilter } from './filters/graph-filter';
34
34
 
35
35
  // Time range options
@@ -0,0 +1,60 @@
1
+ import { cva, type VariantProps } from 'class-variance-authority';
2
+ import type * as React from 'react';
3
+
4
+ import { cn } from '@/lib/utils';
5
+
6
+ const alertVariants = cva(
7
+ 'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'bg-card text-card-foreground',
12
+ destructive:
13
+ 'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90',
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: 'default',
18
+ },
19
+ }
20
+ );
21
+
22
+ function Alert({
23
+ className,
24
+ variant,
25
+ ...props
26
+ }: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) {
27
+ return (
28
+ <div
29
+ data-slot="alert"
30
+ role="alert"
31
+ className={cn(alertVariants({ variant }), className)}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
38
+ return (
39
+ <div
40
+ data-slot="alert-title"
41
+ className={cn('col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight', className)}
42
+ {...props}
43
+ />
44
+ );
45
+ }
46
+
47
+ function AlertDescription({ className, ...props }: React.ComponentProps<'div'>) {
48
+ return (
49
+ <div
50
+ data-slot="alert-description"
51
+ className={cn(
52
+ 'text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed',
53
+ className
54
+ )}
55
+ {...props}
56
+ />
57
+ );
58
+ }
59
+
60
+ export { Alert, AlertTitle, AlertDescription };
@@ -1,11 +1,10 @@
1
1
  'use client';
2
2
 
3
- import * as React from 'react';
4
3
  import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
5
- import { DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker';
6
-
7
- import { cn } from '@/lib/utils';
4
+ import * as React from 'react';
5
+ import { type DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker';
8
6
  import { Button, buttonVariants } from '@/components/ui/button';
7
+ import { cn } from '@/lib/utils';
9
8
 
10
9
  function Calendar({
11
10
  className,
@@ -10,13 +10,13 @@ export function ExternalLink({ href, children, ...props }: ComponentProps<typeof
10
10
  target="_blank"
11
11
  rel="noreferrer noopener"
12
12
  className={cn(
13
- 'text-sm text-muted-foreground underline underline-offset-2 inline-flex items-center gap-1 hover:text-primary ml-1 group font-mono',
13
+ 'text-sm text-muted-foreground underline underline-offset-2 inline-flex items-center gap-1 hover:text-primary ml-1 group/link font-mono uppercase transition-colors',
14
14
  props.className
15
15
  )}
16
16
  {...props}
17
17
  >
18
18
  {children}
19
- <ArrowUpRight className="size-3.5 text-muted-foreground/60 group-hover:text-primary" />
19
+ <ArrowUpRight className="size-3.5 text-muted-foreground/60 group-hover/link:text-primary" />
20
20
  </Link>
21
21
  );
22
22
  }
@@ -78,17 +78,24 @@ function FormItem({ className, ...props }: React.ComponentProps<'div'>) {
78
78
  );
79
79
  }
80
80
 
81
- function FormLabel({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
81
+ function FormLabel({
82
+ className,
83
+ isRequired,
84
+ children,
85
+ ...props
86
+ }: React.ComponentProps<typeof LabelPrimitive.Root> & { isRequired?: boolean }) {
82
87
  const { error, formItemId } = useFormField();
83
-
84
88
  return (
85
89
  <Label
86
90
  data-slot="form-label"
87
91
  data-error={!!error}
88
- className={cn('data-[error=true]:text-destructive', className)}
92
+ className={cn('data-[error=true]:text-destructive gap-1', className)}
89
93
  htmlFor={formItemId}
90
94
  {...props}
91
- />
95
+ >
96
+ {children}
97
+ {isRequired && <span className="text-red-500">*</span>}
98
+ </Label>
92
99
  );
93
100
  }
94
101
 
@@ -2,12 +2,7 @@
2
2
 
3
3
  import { ArrowDown, Check, Info } from 'lucide-react';
4
4
  import { Badge } from '@/components/ui/badge';
5
- import {
6
- Tooltip,
7
- TooltipContent,
8
- TooltipProvider,
9
- TooltipTrigger
10
- } from '@/components/ui/tooltip';
5
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
11
6
 
12
7
  export interface InheritanceIndicatorProps {
13
8
  /** Whether this value is explicitly set (not inherited) */
@@ -34,8 +29,8 @@ export function InheritanceIndicator({
34
29
  <TooltipProvider>
35
30
  <Tooltip>
36
31
  <TooltipTrigger asChild>
37
- <Badge
38
- variant="outline"
32
+ <Badge
33
+ variant="outline"
39
34
  className={`
40
35
  ${size === 'sm' ? 'h-5 px-1.5 text-xs' : 'h-6 px-2 text-sm'}
41
36
  ${position === 'absolute' ? 'absolute -top-2 -right-2' : 'inline-flex'}
@@ -60,8 +55,8 @@ export function InheritanceIndicator({
60
55
  <TooltipProvider>
61
56
  <Tooltip>
62
57
  <TooltipTrigger asChild>
63
- <Badge
64
- variant="outline"
58
+ <Badge
59
+ variant="outline"
65
60
  className={`
66
61
  ${size === 'sm' ? 'h-5 px-1.5 text-xs' : 'h-6 px-2 text-sm'}
67
62
  ${position === 'absolute' ? 'absolute -top-2 -right-2' : 'inline-flex'}
@@ -86,8 +81,8 @@ export function InheritanceIndicator({
86
81
  <TooltipProvider>
87
82
  <Tooltip>
88
83
  <TooltipTrigger asChild>
89
- <Badge
90
- variant="outline"
84
+ <Badge
85
+ variant="outline"
91
86
  className={`
92
87
  ${size === 'sm' ? 'h-5 px-1.5 text-xs' : 'h-6 px-2 text-sm'}
93
88
  ${position === 'absolute' ? 'absolute -top-2 -right-2' : 'inline-flex'}
@@ -118,9 +113,11 @@ export function getModelInheritanceStatus(
118
113
  inheritedFrom?: string;
119
114
  tooltip?: string;
120
115
  } {
121
- const hasCurrentValue = currentValue !== undefined && currentValue !== null && currentValue !== '';
116
+ const hasCurrentValue =
117
+ currentValue !== undefined && currentValue !== null && currentValue !== '';
122
118
  const hasParentValue = parentValue !== undefined && parentValue !== null && parentValue !== '';
123
- const hasGrandparentValue = grandparentValue !== undefined && grandparentValue !== null && grandparentValue !== '';
119
+ const hasGrandparentValue =
120
+ grandparentValue !== undefined && grandparentValue !== null && grandparentValue !== '';
124
121
 
125
122
  // For non-project levels: if current value matches parent value exactly, it's likely inherited
126
123
  // This handles the case where the builder resolves inheritance and stores the actual values
@@ -133,9 +130,15 @@ export function getModelInheritanceStatus(
133
130
  tooltip: `This model is inherited from the ${inheritedFromLevel.toLowerCase()} level`,
134
131
  };
135
132
  }
136
-
133
+
137
134
  // For agent level: also check if it matches grandparent (when graph doesn't have it set)
138
- if (currentLevel === 'agent' && hasCurrentValue && !hasParentValue && hasGrandparentValue && currentValue === grandparentValue) {
135
+ if (
136
+ currentLevel === 'agent' &&
137
+ hasCurrentValue &&
138
+ !hasParentValue &&
139
+ hasGrandparentValue &&
140
+ currentValue === grandparentValue
141
+ ) {
139
142
  return {
140
143
  isExplicit: false,
141
144
  inheritedFrom: 'Project',
@@ -261,4 +264,4 @@ export function getExecutionLimitInheritanceStatus(
261
264
  isExplicit: false,
262
265
  tooltip: `Using system default (${defaultValue})`,
263
266
  };
264
- }
267
+ }
@@ -1,10 +1,10 @@
1
- "use client"
1
+ 'use client';
2
2
 
3
- import * as React from "react"
4
- import { GripVerticalIcon } from "lucide-react"
5
- import * as ResizablePrimitive from "react-resizable-panels"
3
+ import { GripVerticalIcon } from 'lucide-react';
4
+ import type * as React from 'react';
5
+ import * as ResizablePrimitive from 'react-resizable-panels';
6
6
 
7
- import { cn } from "@/lib/utils"
7
+ import { cn } from '@/lib/utils';
8
8
 
9
9
  function ResizablePanelGroup({
10
10
  className,
@@ -13,19 +13,14 @@ function ResizablePanelGroup({
13
13
  return (
14
14
  <ResizablePrimitive.PanelGroup
15
15
  data-slot="resizable-panel-group"
16
- className={cn(
17
- "flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
18
- className
19
- )}
16
+ className={cn('flex h-full w-full data-[panel-group-direction=vertical]:flex-col', className)}
20
17
  {...props}
21
18
  />
22
- )
19
+ );
23
20
  }
24
21
 
25
- function ResizablePanel({
26
- ...props
27
- }: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
28
- return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />
22
+ function ResizablePanel({ ...props }: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
23
+ return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />;
29
24
  }
30
25
 
31
26
  function ResizableHandle({
@@ -33,13 +28,13 @@ function ResizableHandle({
33
28
  className,
34
29
  ...props
35
30
  }: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
36
- withHandle?: boolean
31
+ withHandle?: boolean;
37
32
  }) {
38
33
  return (
39
34
  <ResizablePrimitive.PanelResizeHandle
40
35
  data-slot="resizable-handle"
41
36
  className={cn(
42
- "bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90",
37
+ 'bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90',
43
38
  className
44
39
  )}
45
40
  {...props}
@@ -50,7 +45,7 @@ function ResizableHandle({
50
45
  </div>
51
46
  )}
52
47
  </ResizablePrimitive.PanelResizeHandle>
53
- )
48
+ );
54
49
  }
55
50
 
56
- export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
51
+ export { ResizablePanelGroup, ResizablePanel, ResizableHandle };