@@ -34,6 +34,8 @@ import useOrganization from 'sentry/utils/useOrganization';
34
34
import usePrevious from 'sentry/utils/usePrevious' ;
35
35
import { useTraceExploreAiQueryContext } from 'sentry/views/explore/contexts/traceExploreAiQueryContext' ;
36
36
37
+ const ASK_SEER_ITEM_KEY = 'ask_seer' ;
38
+
37
39
interface FilterKeyListBoxProps < T > extends CustomComboboxMenuProps < T > {
38
40
recentFilters : Array < TokenResult < Token . FILTER > > ;
39
41
sections : Section [ ] ;
@@ -138,6 +140,39 @@ function RecentSearchFilterOption<T>({
138
140
) ;
139
141
}
140
142
143
+ function AskSeerOption < T > ( { state} : { state : ComboBoxState < T > } ) {
144
+ const ref = useRef < HTMLLIElement > ( null ) ;
145
+ const { setDisplaySeerResults} = useSearchQueryBuilder ( ) ;
146
+ const organization = useOrganization ( ) ;
147
+
148
+ const { optionProps, labelProps, isFocused, isPressed} = useOption (
149
+ {
150
+ key : ASK_SEER_ITEM_KEY ,
151
+ 'aria-label' : 'Ask Seer' ,
152
+ shouldFocusOnHover : true ,
153
+ shouldSelectOnPressUp : true ,
154
+ } ,
155
+ state ,
156
+ ref
157
+ ) ;
158
+
159
+ const handleClick = ( ) => {
160
+ trackAnalytics ( 'trace.explorer.ai_query_interface' , {
161
+ organization,
162
+ action : 'opened' ,
163
+ } ) ;
164
+ setDisplaySeerResults ( true ) ;
165
+ } ;
166
+
167
+ return (
168
+ < AskSeerListItem ref = { ref } onClick = { handleClick } { ...optionProps } >
169
+ < InteractionStateLayer isHovered = { isFocused } isPressed = { isPressed } />
170
+ < IconSeer />
171
+ < AskSeerLabel { ...labelProps } > { t ( 'Ask Seer' ) } </ AskSeerLabel >
172
+ </ AskSeerListItem >
173
+ ) ;
174
+ }
175
+
141
176
function useHighlightFirstOptionOnSectionChange ( {
142
177
state,
143
178
selectedSection,
@@ -215,9 +250,12 @@ function FilterKeyMenuContent<T extends SelectOptionOrSectionWithKey<string>>({
215
250
fullWidth,
216
251
sections,
217
252
} : FilterKeyMenuContentProps < T > ) {
218
- const { filterKeys, setDisplaySeerResults} = useSearchQueryBuilder ( ) ;
219
- const focusedItem = state . collection . getItem ( state . selectionManager . focusedKey ?? '' )
220
- ?. props ?. value as string | undefined ;
253
+ const { filterKeys} = useSearchQueryBuilder ( ) ;
254
+ const focusedItem = state . selectionManager . focusedKey
255
+ ? ( state . collection . getItem ( state . selectionManager . focusedKey ) ?. props ?. value as
256
+ | string
257
+ | undefined )
258
+ : undefined ;
221
259
const focusedKey = focusedItem ? filterKeys [ focusedItem ] : null ;
222
260
const showRecentFilters = recentFilters . length > 0 ;
223
261
const showDetailsPane = fullWidth && selectedSection !== RECENT_SEARCH_CATEGORY_VALUE ;
@@ -228,28 +266,17 @@ function FilterKeyMenuContent<T extends SelectOptionOrSectionWithKey<string>>({
228
266
const areAiFeaturesAllowed =
229
267
! organization ?. hideAiFeatures && organization . features . includes ( 'gen-ai-features' ) ;
230
268
269
+ const showAskSeerOption = traceExploreAiQueryContext && areAiFeaturesAllowed ;
270
+
231
271
return (
232
272
< Fragment >
233
- < Feature features = "organizations:gen-ai-explore-traces" >
234
- { traceExploreAiQueryContext && areAiFeaturesAllowed ? (
235
- < SeerButtonWrapper >
236
- < SeerFullWidthButton
237
- size = "md"
238
- icon = { < IconSeer /> }
239
- onClick = { ( ) => {
240
- trackAnalytics ( 'trace.explorer.ai_query_interface' , {
241
- organization,
242
- action : 'opened' ,
243
- } ) ;
244
- setDisplaySeerResults ( true ) ;
245
- } }
246
- borderless
247
- >
248
- { t ( 'Ask Seer' ) }
249
- </ SeerFullWidthButton >
250
- </ SeerButtonWrapper >
251
- ) : null }
252
- </ Feature >
273
+ { showAskSeerOption ? (
274
+ < Feature features = "organizations:gen-ai-explore-traces" >
275
+ < AskSeerPane >
276
+ < AskSeerOption state = { state } />
277
+ </ AskSeerPane >
278
+ </ Feature >
279
+ ) : null }
253
280
{ showRecentFilters ? (
254
281
< RecentFiltersPane >
255
282
{ recentFilters . map ( filter => (
@@ -332,19 +359,23 @@ export function FilterKeyListBox<T extends SelectOptionOrSectionWithKey<string>>
332
359
const areAiFeaturesAllowed =
333
360
! organization ?. hideAiFeatures && organization . features . includes ( 'gen-ai-features' ) ;
334
361
335
- // Add recent filters to hiddenOptions so they don't show up the ListBox component.
336
- // We render recent filters manually in the RecentFiltersPane component.
337
- const hiddenOptionsWithRecentsAdded = useMemo < Set < SelectKey > > ( ( ) => {
338
- return new Set ( [
362
+ const hiddenOptionsWithRecentsAndAskSeerAdded = useMemo < Set < SelectKey > > ( ( ) => {
363
+ const baseHidden = [
339
364
...hiddenOptions ,
340
365
...recentFilters . map ( filter => createRecentFilterOptionKey ( getKeyName ( filter . key ) ) ) ,
341
- ] ) ;
342
- } , [ hiddenOptions , recentFilters ] ) ;
366
+ ] ;
367
+
368
+ if ( traceExploreAiQueryContext && areAiFeaturesAllowed ) {
369
+ baseHidden . push ( ASK_SEER_ITEM_KEY ) ;
370
+ }
371
+
372
+ return new Set ( baseHidden ) ;
373
+ } , [ hiddenOptions , recentFilters , traceExploreAiQueryContext , areAiFeaturesAllowed ] ) ;
343
374
344
375
useHighlightFirstOptionOnSectionChange ( {
345
376
state,
346
377
selectedSection,
347
- hiddenOptions : hiddenOptionsWithRecentsAdded ,
378
+ hiddenOptions : hiddenOptionsWithRecentsAndAskSeerAdded ,
348
379
sections,
349
380
isOpen,
350
381
} ) ;
@@ -389,7 +420,7 @@ export function FilterKeyListBox<T extends SelectOptionOrSectionWithKey<string>>
389
420
{ isOpen ? (
390
421
< FilterKeyMenuContent
391
422
fullWidth = { fullWidth }
392
- hiddenOptions = { hiddenOptionsWithRecentsAdded }
423
+ hiddenOptions = { hiddenOptionsWithRecentsAndAskSeerAdded }
393
424
listBoxProps = { listBoxProps }
394
425
listBoxRef = { listBoxRef }
395
426
recentFilters = { recentFilters }
@@ -415,7 +446,7 @@ export function FilterKeyListBox<T extends SelectOptionOrSectionWithKey<string>>
415
446
{ isOpen ? (
416
447
< FilterKeyMenuContent
417
448
fullWidth = { fullWidth }
418
- hiddenOptions = { hiddenOptionsWithRecentsAdded }
449
+ hiddenOptions = { hiddenOptionsWithRecentsAndAskSeerAdded }
419
450
listBoxProps = { listBoxProps }
420
451
listBoxRef = { listBoxRef }
421
452
recentFilters = { recentFilters }
@@ -602,7 +633,7 @@ const EmptyState = styled('div')`
602
633
}
603
634
` ;
604
635
605
- const SeerButtonWrapper = styled ( 'div' ) `
636
+ const AskSeerPane = styled ( 'div' ) `
606
637
grid-area: seer;
607
638
display: flex;
608
639
align-items: center;
@@ -613,8 +644,13 @@ const SeerButtonWrapper = styled('div')`
613
644
width: 100%;
614
645
` ;
615
646
616
- const SeerFullWidthButton = styled ( Button ) `
647
+ const AskSeerListItem = styled ( 'li' ) `
648
+ position: relative;
649
+ display: flex;
650
+ align-items: center;
617
651
width: 100%;
652
+ padding: ${ space ( 1 ) } ${ space ( 1.5 ) } ;
653
+ background: transparent;
618
654
border-radius: 0;
619
655
background-color: none;
620
656
box-shadow: none;
@@ -623,14 +659,24 @@ const SeerFullWidthButton = styled(Button)`
623
659
font-weight: ${ p => p . theme . fontWeight . bold } ;
624
660
text-align: left;
625
661
justify-content: flex-start;
626
- padding: ${ space ( 1 ) } ${ space ( 2 ) } ;
627
- display: flex;
628
- align-items: center;
629
662
gap: ${ space ( 1 ) } ;
663
+ list-style: none;
664
+ margin: 0;
665
+
630
666
&:hover,
631
667
&:focus {
632
668
background-color: ${ p => p . theme . purple100 } ;
633
669
color: ${ p => p . theme . purple400 } ;
634
- box-shadow: none;
635
670
}
671
+
672
+ &[aria-selected='true'] {
673
+ background: ${ p => p . theme . purple100 } ;
674
+ color: ${ p => p . theme . purple400 } ;
675
+ }
676
+ ` ;
677
+ const AskSeerLabel = styled ( 'span' ) `
678
+ ${ p => p . theme . overflowEllipsis } ;
679
+ color: ${ p => p . theme . purple400 } ;
680
+ font-size: ${ p => p . theme . fontSize . md } ;
681
+ font-weight: ${ p => p . theme . fontWeight . bold } ;
636
682
` ;
0 commit comments