Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
d8d00d9
Add AI Center unified search
Dnouv May 20, 2026
ac71d0e
Improve AI search UX
Dnouv May 20, 2026
17617f7
Add unified intelligent search experience
Dnouv May 22, 2026
40cc922
Improve search filters and message results
Dnouv May 22, 2026
2586bcf
Add AI LLM providers for search answers
Dnouv May 25, 2026
2bf0beb
Simplify intelligent search experience
Dnouv May 25, 2026
39ed96d
Normalize intelligent search similarity scores
Dnouv May 26, 2026
1772bdb
Polish intelligent search results page
Dnouv May 26, 2026
3cd3192
Improve intelligent search answer rendering
Dnouv May 26, 2026
dd64286
Refine AI search panel and filters
Dnouv May 27, 2026
6c9d6dd
Restore AI search icon button
Dnouv May 27, 2026
90021bb
Fix AI search inline filters
Dnouv May 27, 2026
491c673
Improve AI search filter suggestions
Dnouv May 27, 2026
566e986
Fix unfiltered AI search preview
Dnouv May 27, 2026
9e09e27
Group AI search filters inline
Dnouv May 27, 2026
d4aae42
Use user autocomplete for AI search filters
Dnouv May 27, 2026
40fff0d
Extract AI search service package
Dnouv May 27, 2026
0fd7e36
Add AI search microservice
Dnouv May 28, 2026
3fceefa
Update intelligent search architecture docs
Dnouv Jun 2, 2026
85b28a9
Polish AI search UI
Dnouv Jun 3, 2026
6551492
Polish AI settings and filter suggestions
Dnouv Jun 3, 2026
a0c1d67
Polish AI search rollout UI
Dnouv Jun 5, 2026
f59db7c
Gate AI search with feature preview
Dnouv Jun 5, 2026
a7f8365
Harden AI search API and filtering
Dnouv Jun 5, 2026
82245ab
Clean AI search branch lint issues
Dnouv Jun 5, 2026
26090c1
Polish AI Center LLM labels
Dnouv Jun 10, 2026
91a2588
improve prompt
Dnouv Jun 10, 2026
5895106
Polish AI Search admin and results UI
Dnouv Jun 10, 2026
c9c502c
Improve AI search button unavailable tooltip
Dnouv Jun 11, 2026
235788b
Tighten AI search source result spacing
Dnouv Jun 11, 2026
b746464
Align AI search source text spacing
Dnouv Jun 11, 2026
7c4a979
Add AI search test coverage
Dnouv Jun 11, 2026
0a9ef67
add changeset
Dnouv Jun 11, 2026
67b8ed3
Harden AI search URL normalization
Dnouv Jun 11, 2026
3aec443
Merge remote-tracking branch 'origin/develop' into feat/search
Dnouv Jun 11, 2026
d7513c9
Isolate AI search tooltip test
Dnouv Jun 11, 2026
ef5b363
Trim unrelated AI search branch changes
Dnouv Jun 11, 2026
dc9822c
Fix AI search CI checks
Dnouv Jun 12, 2026
00e6fd0
Fix unified search API test metadata assertions
Dnouv Jun 12, 2026
f3433e7
Fix AI Center settings defaults test
Dnouv Jun 12, 2026
05869c4
Polish AI search test fixtures
Dnouv Jun 12, 2026
ade0117
Refine AI search package boundaries
Dnouv Jun 12, 2026
608bc6d
Enable SSRF validation for AI search requests
Dnouv Jun 12, 2026
705eda2
Avoid AI Center settings group registry changes
Dnouv Jun 16, 2026
da1e62b
Merge remote-tracking branch 'origin/develop' into feat/search
Dnouv Jun 16, 2026
56ad40f
Address AI search review findings
Dnouv Jun 16, 2026
f2717b7
Validate AI answer sources server-side
Dnouv Jun 16, 2026
5748017
Avoid AI search filter side channels
Dnouv Jun 16, 2026
5b6a3a7
Authorize AI answer source rooms
Dnouv Jun 16, 2026
e80ec5a
fix lint
Dnouv Jun 17, 2026
9e52aee
Merge branch 'develop' into feat/search
Dnouv Jun 17, 2026
cbd313e
Merge remote-tracking branch 'origin/develop' into feat/search
Dnouv Jun 25, 2026
d0154cd
fix(ai-search): address PR review findings for production readiness
Dnouv Jun 25, 2026
0683fda
Add noticeable AI button
Dnouv Jun 25, 2026
70fd46e
Merge branch 'develop' into feat/search
Dnouv Jun 29, 2026
9249be6
Address AI search PR review cleanup
Dnouv Jun 30, 2026
474dd46
Simplify AI search endpoint URL construction
Dnouv Jun 30, 2026
a678efc
adjust margin of AI button
Dnouv Jun 30, 2026
53d6a3d
add customized message for AI mode on no results
Dnouv Jun 30, 2026
99a3699
Address AI search review feedback
Dnouv Jul 1, 2026
dc1b7e7
remove doc file
Dnouv Jul 1, 2026
561cbbe
Fix AI search answer source validation
Dnouv Jul 1, 2026
3da8acb
Address AI search review follow-ups
Dnouv Jul 1, 2026
6e1b0fe
Sanitize AI search endpoint logs
Dnouv Jul 1, 2026
abe14b4
fix typecheck
Dnouv Jul 1, 2026
86dd5ba
Allow empty optional search filters
Dnouv Jul 1, 2026
49031f1
UI style adjustments
Dnouv Jul 1, 2026
b38a28d
Merge branch 'develop' into feat/search
Dnouv Jul 1, 2026
35bec5b
Merge branch 'develop' into feat/search
Dnouv Jul 2, 2026
4979648
Merge remote-tracking branch 'origin/develop' into feat/search
Dnouv Jul 3, 2026
c79082e
Merge remote-tracking branch 'origin/develop' into feat/search
Dnouv Jul 8, 2026
4010f24
test: update navbar search hook expectations
Dnouv Jul 8, 2026
3227669
refactor: use fuselage props for ai search controls
Dnouv Jul 8, 2026
02d4a52
fix: align navbar search filter chips
Dnouv Jul 8, 2026
ef216ce
fix: compact navbar search filter tags
Dnouv Jul 8, 2026
c6524f5
fix: shorten navbar search filter labels
Dnouv Jul 8, 2026
fb86596
fix: tune navbar search filter tag spacing
Dnouv Jul 8, 2026
807b39a
fix: simplify navbar search filter tag
Dnouv Jul 8, 2026
b53963a
fix: use chip for navbar search filters
Dnouv Jul 8, 2026
2cd78ca
fix: reduce navbar search filter chip size
Dnouv Jul 8, 2026
62345c1
fix: constrain navbar search filter chip
Dnouv Jul 8, 2026
bf3766f
fix: polish ai search source results
Dnouv Jul 9, 2026
ab06354
fix: make ai search source cards clickable
Dnouv Jul 9, 2026
d26339f
fix: show pointer cursor on ai search source cards
Dnouv Jul 9, 2026
6123bb5
fix: use anchor semantics for ai search source cards
Dnouv Jul 9, 2026
94e55f8
Merge branch 'develop' into feat/search
Dnouv Jul 9, 2026
b8b119b
refactor: simplify ai search ui review paths
Dnouv Jul 9, 2026
80e6f0a
refactor: split navbar search result paths
Dnouv Jul 9, 2026
81af600
fix: address ai search review edge cases
Dnouv Jul 9, 2026
1206406
fix: align ai search date suggestions with utc filters
Dnouv Jul 9, 2026
7af74fc
fix: address full-review findings for ai search
Dnouv Jul 10, 2026
9566e41
Merge remote-tracking branch 'origin/develop' into feat/search
Dnouv Jul 10, 2026
0ce7f17
correct import
Dnouv Jul 10, 2026
cca0fa4
fix: restore navbar ai search preview ux and answer regeneration
Dnouv Jul 10, 2026
92ad044
fix: harden ai search integration and navbar flow
Dnouv Jul 13, 2026
46e40da
fix: refine ai search sources and citations
Dnouv Jul 13, 2026
14a2061
Merge branch 'develop' into feat/search
Dnouv Jul 13, 2026
c87f569
fix: hold ai center while license loads
Dnouv Jul 13, 2026
00aa103
perf: streamline ai search processing
Dnouv Jul 13, 2026
1cf79d4
refactor: align ai search with frontend guidelines
Dnouv Jul 13, 2026
66df30e
Merge branch 'develop' into feat/search
Dnouv Jul 14, 2026
fd8e838
fix: preserve navbar search accessibility label
Dnouv Jul 14, 2026
59dfeaa
Merge remote-tracking branch 'origin/feat/search' into feat/search
Dnouv Jul 14, 2026
e5e82b6
fix: clarify ai search actions
Dnouv Jul 14, 2026
625d690
refactor: isolate AI search preview
Dnouv Jul 14, 2026
c4c238c
fix: refine AI search result state
Dnouv Jul 14, 2026
98fec34
fix: restore AI search empty-state hint
Dnouv Jul 14, 2026
30f5b81
fix: preserve locale file ending
Dnouv Jul 14, 2026
7bec18e
fix: improve AI search source readability
Dnouv Jul 14, 2026
c13d73c
fix: use supported source row spacing props
Dnouv Jul 14, 2026
031c965
refactor: isolate AI navbar search preview
Dnouv Jul 14, 2026
5eda022
fix: address AI search review feedback
Dnouv Jul 14, 2026
ec198b4
Merge branch 'develop' into feat/search
Dnouv Jul 15, 2026
c5696ea
refactor: preserve legacy navbar search
Dnouv Jul 15, 2026
9ed95da
fix: preserve inactive navbar search semantics
Dnouv Jul 15, 2026
2b17f27
fix: preserve legacy search when AI mode is inactive
Dnouv Jul 15, 2026
bb79507
refactor: inline AI search feature preview
Dnouv Jul 15, 2026
f701abd
fix: use supported AI search plural translation
Dnouv Jul 15, 2026
5547cf9
Merge branch 'develop' into feat/search
Dnouv Jul 16, 2026
7000f7d
fix(search): align AI search controls with Fuselage
Dnouv Jul 16, 2026
802b60e
refactor(ai-search): address review feedback
Dnouv Jul 16, 2026
c7ceb62
fix(search): handle transient empty filter state
Dnouv Jul 16, 2026
047dc7f
fix(ai-search): align with server module moves
Dnouv Jul 16, 2026
7c95884
fix(ai-search): protect provider credentials
Dnouv Jul 16, 2026
6c564a1
fix(ai-search): update settings service test mock
Dnouv Jul 16, 2026
f92c79c
Merge branch 'develop' into feat/search
Dnouv Jul 16, 2026
389e6e3
Merge branch 'develop' into feat/search
Dnouv Jul 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/fancy-deserts-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@rocket.chat/core-services': minor
'@rocket.chat/rest-typings': minor
'@rocket.chat/ai-search': minor
'@rocket.chat/ui-client': minor
'@rocket.chat/i18n': minor
'@rocket.chat/meteor': minor
---

Adds AI Search with semantic message results, optional OpenAI-compatible answers, and AI Center configuration.
11 changes: 10 additions & 1 deletion apps/meteor/client/navbar/NavBarNavigation.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { FocusScope } from '@react-aria/focus';
import { NavBarGroup, NavBarItem, Box } from '@rocket.chat/fuselage';
import { FeaturePreview, FeaturePreviewOff, FeaturePreviewOn } from '@rocket.chat/ui-client';
import { useLayout, useRouter } from '@rocket.chat/ui-contexts';
import { useTranslation } from 'react-i18next';

import NavBarSearch from './NavBarSearch';
import NavBarAISearch from './NavBarSearch/NavBarAISearch';

const NavbarNavigation = () => {
const { t } = useTranslation();
Expand All @@ -13,7 +15,14 @@ const NavbarNavigation = () => {
return (
<Box display='flex' flexGrow={1} justifyContent='center'>
<FocusScope>
<NavBarSearch />
<FeaturePreview feature='aiSearch'>
<FeaturePreviewOff>
<NavBarSearch />
</FeaturePreviewOff>
<FeaturePreviewOn>
<NavBarAISearch />
</FeaturePreviewOn>
</FeaturePreview>
</FocusScope>
{!isMobile && (
<Box marginInlineEnd={8}>
Expand Down
128 changes: 128 additions & 0 deletions apps/meteor/client/navbar/NavBarSearch/NavBarAISearch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { useFocusManager } from '@react-aria/focus';
import { useOverlayTrigger } from '@react-aria/overlays';
import { useOverlayTriggerState } from '@react-stately/overlays';
import { emptySearchFilters, type NavBarSearchFormValues } from '@rocket.chat/ai-search';
import { Box, TextInput } from '@rocket.chat/fuselage';
import { useMergedRefs, useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useCallback, useEffect, useRef } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import tinykeys from 'tinykeys';

import NavBarAISearchListBox from './NavBarAISearchListbox';
import NavBarSearchInputAddon from './NavBarSearchInputAddon';
import NavBarSearchListBox from './NavBarSearchListbox';
import { getShortcutLabel } from './getShortcutLabel';
import { useNavBarAISearch } from './hooks/useNavBarAISearch';
import { useSearchClick } from './hooks/useSearchClick';
import { useSearchFocus } from './hooks/useSearchFocus';
import { useSearchInputNavigation } from './hooks/useSearchNavigation';

const NavBarAISearch = () => {
const { t } = useTranslation();
const focusManager = useFocusManager();
const shortcut = getShortcutLabel();

const methods = useForm<NavBarSearchFormValues>({ defaultValues: { filterText: '', appliedFilters: emptySearchFilters() } });
const {
formState: { isDirty },
register,
reset,
setFocus,
setValue,
watch,
} = methods;
const { filterText, appliedFilters } = watch();

const { ref: filterRef, ...rest } = register('filterText');

const triggerRef = useRef(null);
const mergedRefs = useMergedRefs(filterRef, triggerRef);

const state = useOverlayTriggerState({});
const { triggerProps, overlayProps } = useOverlayTrigger({ type: 'listbox' }, state, triggerRef);
delete triggerProps.onPress;

const handleKeyDown = useSearchInputNavigation(state);
const handleFocus = useSearchFocus(state);
const handleClick = useSearchClick(state);

const { aiSearchActive, canSearchWithAIFromTopBar, appliedFilterChips, aiSearchButtonTooltip, handleRemoveFilter, handleToggleAISearch } =
useNavBarAISearch({ filterText, appliedFilters, setFocus, setValue, state, t });

const searchLabel = aiSearchActive ? t('Search_rooms_or_ask_AI') : t('Search_rooms');
const placeholder = [searchLabel, shortcut].filter(Boolean).join(' ');

const handleEscSearch = useCallback(() => {
reset();
state.close();
}, [reset, state]);

const handleClearText = useStableCallback(() => {
reset();
setFocus('filterText');
});

useEffect(() => {
const unsubscribe = tinykeys(window, {
'$mod+K': (event) => {
event.preventDefault();
setFocus('filterText');
},
'$mod+P': (event) => {
event.preventDefault();
setFocus('filterText');
},
'Escape': (event) => {
event.preventDefault();
handleEscSearch();
},
});

return (): void => {
unsubscribe();
};
}, [focusManager, handleEscSearch, setFocus]);

return (
<FormProvider {...methods}>
<Box width='100%' maxWidth='x622' role='search' aria-label={searchLabel} mi={8} position='relative'>
<TextInput
{...rest}
{...triggerProps}
onFocus={handleFocus}
onKeyDown={handleKeyDown}
onClick={handleClick}
autoComplete='off'
placeholder={placeholder}
ref={mergedRefs}
role='combobox'
aria-label={searchLabel}
aria-autocomplete='list'
aria-keyshortcuts='Control+K Meta+K Control+P Meta+P'
small
endAddon={
<NavBarSearchInputAddon
appliedFilterChips={appliedFilterChips}
aiSearchActive={aiSearchActive}
aiSearchButtonTooltip={aiSearchButtonTooltip}
isDirty={isDirty}
onClearText={handleClearText}
onRemoveFilter={handleRemoveFilter}
onToggleAISearch={handleToggleAISearch}
t={t}
/>
}
/>
{state.isOpen &&
(aiSearchActive ? (
<NavBarAISearchListBox state={state} overlayProps={overlayProps} aiSearchActive aiSearchAvailable={canSearchWithAIFromTopBar} />
) : (
<NavBarSearchListBox state={state} overlayProps={overlayProps} />
))}
</Box>
</FormProvider>
);
};

export default NavBarAISearch;
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type { OverlayTriggerAria } from '@react-aria/overlays';
import type { OverlayTriggerState } from '@react-stately/overlays';
import type { NavBarSearchFormValues } from '@rocket.chat/ai-search';
import { Tile } from '@rocket.chat/fuselage';
import { useOutsideClick, useStableCallback } from '@rocket.chat/fuselage-hooks';
import { CustomScrollbars } from '@rocket.chat/ui-client';
import { useRef } from 'react';
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';

import NavBarSearchFilterSuggestions from './NavBarSearchFilterSuggestions';
import NavBarSearchIntelligentSection from './NavBarSearchIntelligentSection';
import NavBarSearchRoomSection from './NavBarSearchRoomSection';
import { useAISearchItems } from './hooks/useAISearchItems';
import { useAISearchRooms } from './hooks/useAISearchRooms';
import { useListboxNavigation } from './hooks/useSearchNavigation';
import ResultsLiveRegion from '../../components/ResultsLiveRegion';

export type NavBarAISearchListBoxProps = {
state: OverlayTriggerState;
overlayProps: OverlayTriggerAria['overlayProps'];
aiSearchActive: boolean;
aiSearchAvailable: boolean;
};

const NavBarAISearchListBox = ({ state, overlayProps, aiSearchActive, aiSearchAvailable }: NavBarAISearchListBoxProps) => {
const { t } = useTranslation();
const containerRef = useRef<HTMLElement>(null);

const handleKeyDown = useListboxNavigation(state);
useOutsideClick([containerRef], state.close);

const { reset, watch } = useFormContext<NavBarSearchFormValues>();
const { filterText, appliedFilters } = watch();

const handleSelect = useStableCallback(() => {
state.close();
reset();
});

const { data: aiItems, isFetching } = useAISearchItems(filterText, appliedFilters, aiSearchActive);
const { items: rooms, isLoading } = useAISearchRooms(aiSearchActive ? aiItems.searchText : filterText);
const itemCount = rooms.length + aiItems.intelligent.length + aiItems.filterSuggestions.length;
const isSearchLoading = isLoading || isFetching;
const listboxLabel = aiSearchActive ? t('AI_Search_results') : t('Channels');

return (
<Tile
ref={containerRef}
position='absolute'
zIndex={99}
padding={0}
pb={16}
mbs={4}
minHeight='x52'
maxHeight='50vh'
display='flex'
width='100%'
flexDirection='column'
>
<ResultsLiveRegion shouldAnnounce={!isSearchLoading} itemCount={itemCount} isLoading={isSearchLoading} />
<CustomScrollbars>
<div {...overlayProps} role='listbox' aria-label={listboxLabel} aria-busy={isSearchLoading} tabIndex={-1} onKeyDown={handleKeyDown}>
<NavBarSearchIntelligentSection items={aiItems.intelligent} onSelect={handleSelect} onClose={state.close} />
<NavBarSearchFilterSuggestions suggestions={aiItems.filterSuggestions} />
<NavBarSearchRoomSection
filterText={filterText}
itemCount={itemCount}
isLoading={isLoading}
isFetching={isFetching}
rooms={rooms}
suggestAISearch={aiSearchAvailable && !aiSearchActive}
onSelect={handleSelect}
/>
</div>
</CustomScrollbars>
</Tile>
);
};

export default NavBarAISearchListBox;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useTranslation } from 'react-i18next';

import GenericNoResults from '../../components/GenericNoResults';

export type NavBarAISearchNoResultsProps = {
suggestAISearch: boolean;
};

const NavBarAISearchNoResults = ({ suggestAISearch }: NavBarAISearchNoResultsProps) => {
const { t } = useTranslation();

return (
<GenericNoResults
description={t(suggestAISearch ? 'Try_entering_a_different_search_term_or_search_with_AI' : 'Try_entering_a_different_search_term')}
/>
);
};

export default NavBarAISearchNoResults;
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {
mergeSearchFilters,
parseSearchFilterText,
type NavBarSearchFormValues,
type SearchFilterSuggestion,
} from '@rocket.chat/ai-search';
import { Box, Icon, SidebarV2Item, SidebarV2ItemIcon, SidebarV2ItemTitle } from '@rocket.chat/fuselage';
import type { MouseEvent, ReactElement } from 'react';
import { useCallback, useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';

const filterSuggestionGroupLabels = {
rooms: 'Search_filter_rooms',
users: 'Search_filter_users',
dates: 'Search_filter_dates',
} as const;

export type NavBarSearchFilterSuggestionsProps = {
suggestions: SearchFilterSuggestion[];
};

const groupFilterSuggestions = (suggestions: SearchFilterSuggestion[]): [SearchFilterSuggestion['group'], SearchFilterSuggestion[]][] => {
const grouped: Record<SearchFilterSuggestion['group'], SearchFilterSuggestion[]> = { rooms: [], users: [], dates: [] };
for (const suggestion of suggestions) {
grouped[suggestion.group].push(suggestion);
}

const groups: [SearchFilterSuggestion['group'], SearchFilterSuggestion[]][] = [];
for (const group of ['rooms', 'users', 'dates'] as const) {
if (grouped[group].length > 0) {
groups.push([group, grouped[group]]);
}
}

return groups;
};

const NavBarSearchFilterSuggestions = ({ suggestions }: NavBarSearchFilterSuggestionsProps): ReactElement | null => {
const { t } = useTranslation();
const { getValues, setFocus, setValue } = useFormContext<NavBarSearchFormValues>();
const filterSuggestionGroups = useMemo(() => groupFilterSuggestions(suggestions), [suggestions]);

const handleFilterSuggestion = useCallback(
(event: MouseEvent, value: string) => {
event.preventDefault();
event.stopPropagation();
const { searchText, filters } = parseSearchFilterText(value);
setValue('appliedFilters', mergeSearchFilters(getValues('appliedFilters'), filters), { shouldDirty: true });
setValue('filterText', searchText, { shouldDirty: true });
setFocus('filterText');
},
[getValues, setFocus, setValue],
);

if (!filterSuggestionGroups.length) {
return null;
}

return (
<>
{filterSuggestionGroups.map(([group, groupSuggestions]) => (
<Box key={group} display='flex' flexDirection='column' pbs={8}>
<Box color='titles-labels' fontScale='c1' fontWeight='bold' pi={12} mbe={4} role='presentation' aria-hidden>
{t(filterSuggestionGroupLabels[group])}
</Box>
{groupSuggestions.map((item) => (
<SidebarV2Item key={item.key} role='option' onClick={(event) => handleFilterSuggestion(event, item.value)}>
<SidebarV2ItemIcon icon={<Icon name={item.icon} size='x16' />} />
<SidebarV2ItemTitle>{item.title}</SidebarV2ItemTitle>
<Box color='hint' fontScale='c1' flexShrink={0}>
{item.description}
</Box>
</SidebarV2Item>
))}
</Box>
))}
</>
);
};

export default NavBarSearchFilterSuggestions;
Loading
Loading