@redocly/theme 0.60.0-next.7 → 0.60.0
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.
- package/lib/components/Buttons/AIAssistantButton.js +2 -1
- package/lib/components/Search/Search.js +6 -1
- package/lib/components/Search/SearchDialog.js +11 -5
- package/lib/core/contexts/SearchContext.d.ts +10 -0
- package/lib/core/contexts/SearchContext.js +56 -0
- package/lib/core/contexts/index.d.ts +1 -0
- package/lib/core/contexts/index.js +1 -0
- package/lib/core/hooks/search/use-search-dialog.js +4 -1
- package/lib/core/hooks/use-telemetry-fallback.d.ts +2 -0
- package/lib/core/hooks/use-telemetry-fallback.js +2 -0
- package/lib/core/openapi/index.d.ts +1 -0
- package/lib/core/openapi/index.js +4 -1
- package/lib/core/types/hooks.d.ts +2 -2
- package/package.json +7 -7
- package/src/components/Buttons/AIAssistantButton.tsx +3 -2
- package/src/components/Search/Search.tsx +10 -1
- package/src/components/Search/SearchDialog.tsx +12 -5
- package/src/core/contexts/SearchContext.tsx +31 -0
- package/src/core/contexts/index.ts +1 -0
- package/src/core/hooks/search/use-search-dialog.ts +4 -1
- package/src/core/hooks/use-telemetry-fallback.ts +2 -0
- package/src/core/openapi/index.ts +1 -0
- package/src/core/types/hooks.ts +1 -5
|
@@ -46,6 +46,7 @@ const hooks_1 = require("../../core/hooks");
|
|
|
46
46
|
const ChatIcon_1 = require("../../icons/ChatIcon/ChatIcon");
|
|
47
47
|
const AiStarsGradientIcon_1 = require("../../icons/AiStarsGradientIcon/AiStarsGradientIcon");
|
|
48
48
|
const RedoclyIcon_1 = require("../../icons/RedoclyIcon/RedoclyIcon");
|
|
49
|
+
const contexts_1 = require("../../core/contexts");
|
|
49
50
|
const defaultConfig = {
|
|
50
51
|
hide: false,
|
|
51
52
|
inputType: 'button',
|
|
@@ -92,7 +93,7 @@ function AIAssistantButton() {
|
|
|
92
93
|
const handleClose = () => {
|
|
93
94
|
setIsOpen(false);
|
|
94
95
|
};
|
|
95
|
-
return (React.createElement(
|
|
96
|
+
return (React.createElement(contexts_1.SearchSessionProvider, null,
|
|
96
97
|
React.createElement(StyledAIAssistantButton, { variant: "outlined", size: "medium", "$inputType": inputType, onClick: handleOpen, "aria-label": `AI Assistant button - ${inputIcon}`, "data-component-name": "Buttons/AIAssistantButton" },
|
|
97
98
|
icon,
|
|
98
99
|
inputType === 'button' && text),
|
|
@@ -9,12 +9,17 @@ const styled_components_1 = __importDefault(require("styled-components"));
|
|
|
9
9
|
const SearchTrigger_1 = require("../../components/Search/SearchTrigger");
|
|
10
10
|
const SearchDialog_1 = require("../../components/Search/SearchDialog");
|
|
11
11
|
const hooks_1 = require("../../core/hooks");
|
|
12
|
-
|
|
12
|
+
const contexts_1 = require("../../core/contexts");
|
|
13
|
+
function SearchContent({ className }) {
|
|
13
14
|
const { isOpen, onOpen, onClose } = (0, hooks_1.useSearchDialog)();
|
|
14
15
|
return (react_1.default.createElement(SearchWrapper, { "data-component-name": "Search/Search", className: className },
|
|
15
16
|
react_1.default.createElement(SearchTrigger_1.SearchTrigger, { onClick: onOpen }),
|
|
16
17
|
isOpen && react_1.default.createElement(SearchDialog_1.SearchDialog, { onClose: onClose })));
|
|
17
18
|
}
|
|
19
|
+
function Search({ className }) {
|
|
20
|
+
return (react_1.default.createElement(contexts_1.SearchSessionProvider, null,
|
|
21
|
+
react_1.default.createElement(SearchContent, { className: className })));
|
|
22
|
+
}
|
|
18
23
|
const SearchWrapper = styled_components_1.default.div `
|
|
19
24
|
margin-left: auto;
|
|
20
25
|
`;
|
|
@@ -54,6 +54,7 @@ const SearchGroups_1 = require("../../components/Search/SearchGroups");
|
|
|
54
54
|
const Typography_1 = require("../../components/Typography/Typography");
|
|
55
55
|
const SpinnerLoader_1 = require("../../components/Loaders/SpinnerLoader");
|
|
56
56
|
const SearchAiDialog_1 = require("../../components/Search/SearchAiDialog");
|
|
57
|
+
const contexts_1 = require("../../core/contexts");
|
|
57
58
|
const SettingsIcon_1 = require("../../icons/SettingsIcon/SettingsIcon");
|
|
58
59
|
const AiStarsIcon_1 = require("../../icons/AiStarsIcon/AiStarsIcon");
|
|
59
60
|
const ReturnKeyIcon_1 = require("../../icons/ReturnKeyIcon/ReturnKeyIcon");
|
|
@@ -63,16 +64,16 @@ const AiStarsGradientIcon_1 = require("../../icons/AiStarsGradientIcon/AiStarsGr
|
|
|
63
64
|
function SearchDialog({ onClose, className, initialMode = 'search', }) {
|
|
64
65
|
const { useTranslate, useCurrentProduct, useSearch, useProducts, useAiSearch, useTelemetry } = (0, hooks_1.useThemeHooks)();
|
|
65
66
|
const telemetry = useTelemetry();
|
|
67
|
+
const { searchSessionId, refreshSearchSessionId } = (0, contexts_1.useSearchSession)();
|
|
66
68
|
const products = useProducts();
|
|
67
69
|
const currentProduct = useCurrentProduct();
|
|
68
70
|
const [product, setProduct] = (0, react_1.useState)(currentProduct);
|
|
69
|
-
const searchSessionId = `search-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
70
71
|
const [mode, setMode] = (0, react_1.useState)(initialMode);
|
|
71
72
|
const autoSearchDisabled = mode !== 'search';
|
|
72
|
-
const { query, setQuery, filter, setFilter, items, isSearchLoading, facets, setLoadMore, advancedSearch, askAi, groupField, } = useSearch(product === null || product === void 0 ? void 0 : product.name, autoSearchDisabled
|
|
73
|
+
const { query, setQuery, filter, setFilter, items, isSearchLoading, facets, setLoadMore, advancedSearch, askAi, groupField, } = useSearch(product === null || product === void 0 ? void 0 : product.name, autoSearchDisabled);
|
|
73
74
|
const { isFilterOpen, onFilterToggle, onFilterChange, onFilterReset, onFacetReset, onQuickFilterReset, } = (0, hooks_1.useSearchFilter)(filter, setFilter);
|
|
74
75
|
const { addSearchHistoryItem } = (0, hooks_1.useRecentSearches)();
|
|
75
|
-
const aiSearch = useAiSearch({ filter }
|
|
76
|
+
const aiSearch = useAiSearch({ filter });
|
|
76
77
|
const searchInputRef = (0, react_1.useRef)(null);
|
|
77
78
|
const modalRef = (0, react_1.useRef)(null);
|
|
78
79
|
const [isMobile, setIsMobile] = (0, react_1.useState)(false);
|
|
@@ -95,8 +96,10 @@ function SearchDialog({ onClose, className, initialMode = 'search', }) {
|
|
|
95
96
|
if (value) {
|
|
96
97
|
addSearchHistoryItem(value);
|
|
97
98
|
}
|
|
99
|
+
// Refresh the search session id so a new session starts on next open
|
|
100
|
+
refreshSearchSessionId();
|
|
98
101
|
onClose();
|
|
99
|
-
}, [addSearchHistoryItem, onClose]);
|
|
102
|
+
}, [addSearchHistoryItem, onClose, refreshSearchSessionId]);
|
|
100
103
|
(0, hooks_1.useDialogHotKeys)(modalRef, handleClose);
|
|
101
104
|
const focusSearchInput = () => {
|
|
102
105
|
requestAnimationFrame(() => {
|
|
@@ -199,7 +202,10 @@ function SearchDialog({ onClose, className, initialMode = 'search', }) {
|
|
|
199
202
|
? translate('search.ai.back', 'Back')
|
|
200
203
|
: translate('search.ai.backToSearch', 'Back to search')),
|
|
201
204
|
react_1.default.createElement(AiDialogHeaderActionsWrapper, null,
|
|
202
|
-
react_1.default.createElement(Button_1.Button, { variant: "secondary", disabled: !aiSearch.conversation.length, onClick: () =>
|
|
205
|
+
react_1.default.createElement(Button_1.Button, { variant: "secondary", disabled: !aiSearch.conversation.length, onClick: () => {
|
|
206
|
+
refreshSearchSessionId();
|
|
207
|
+
aiSearch.clearConversation();
|
|
208
|
+
}, tabIndex: 0, icon: react_1.default.createElement(EditIcon_1.EditIcon, null) }, translate('search.ai.newConversation', 'New conversation')),
|
|
203
209
|
isMobile && react_1.default.createElement(Button_1.Button, { variant: "text", icon: react_1.default.createElement(CloseIcon_1.CloseIcon, null), onClick: handleClose }))))),
|
|
204
210
|
react_1.default.createElement(SearchDialogBody, null, mode === 'search' ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
205
211
|
advancedSearch && isFilterOpen && (react_1.default.createElement(SearchDialogBodyFilterView, null,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type SearchSessionContextValue = {
|
|
3
|
+
searchSessionId: string;
|
|
4
|
+
refreshSearchSessionId: () => void;
|
|
5
|
+
};
|
|
6
|
+
export declare const SearchSessionContext: React.Context<SearchSessionContextValue | null>;
|
|
7
|
+
export declare const SearchSessionProvider: ({ children }: {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
}) => React.JSX.Element;
|
|
10
|
+
export declare function useSearchSession(): SearchSessionContextValue;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SearchSessionProvider = exports.SearchSessionContext = void 0;
|
|
37
|
+
exports.useSearchSession = useSearchSession;
|
|
38
|
+
const react_1 = __importStar(require("react"));
|
|
39
|
+
exports.SearchSessionContext = (0, react_1.createContext)(null);
|
|
40
|
+
const SearchSessionProvider = ({ children }) => {
|
|
41
|
+
const [searchSessionId, setSearchSessionId] = (0, react_1.useState)(() => crypto.randomUUID());
|
|
42
|
+
const refreshSearchSessionId = (0, react_1.useCallback)(() => {
|
|
43
|
+
setSearchSessionId(crypto.randomUUID());
|
|
44
|
+
}, []);
|
|
45
|
+
const value = (0, react_1.useMemo)(() => ({ searchSessionId, refreshSearchSessionId }), [searchSessionId, refreshSearchSessionId]);
|
|
46
|
+
return react_1.default.createElement(exports.SearchSessionContext.Provider, { value: value }, children);
|
|
47
|
+
};
|
|
48
|
+
exports.SearchSessionProvider = SearchSessionProvider;
|
|
49
|
+
function useSearchSession() {
|
|
50
|
+
const contextValue = (0, react_1.useContext)(exports.SearchSessionContext);
|
|
51
|
+
if (!contextValue) {
|
|
52
|
+
throw new Error('useSearchSession must be used within a SearchSessionProvider');
|
|
53
|
+
}
|
|
54
|
+
return contextValue;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=SearchContext.js.map
|
|
@@ -18,4 +18,5 @@ __exportStar(require("./ThemeDataContext"), exports);
|
|
|
18
18
|
__exportStar(require("./CodeWalkthrough/CodeWalkthroughControlsContext"), exports);
|
|
19
19
|
__exportStar(require("./CodeWalkthrough/CodeWalkthroughStepsContext"), exports);
|
|
20
20
|
__exportStar(require("./CodeSnippetContext"), exports);
|
|
21
|
+
__exportStar(require("./SearchContext"), exports);
|
|
21
22
|
//# sourceMappingURL=index.js.map
|
|
@@ -9,6 +9,7 @@ const react_router_dom_1 = require("react-router-dom");
|
|
|
9
9
|
const hotkeys_js_1 = __importDefault(require("hotkeys-js"));
|
|
10
10
|
const use_theme_hooks_1 = require("../use-theme-hooks");
|
|
11
11
|
const use_theme_config_1 = require("../use-theme-config");
|
|
12
|
+
const contexts_1 = require("../../contexts");
|
|
12
13
|
function useSearchDialog() {
|
|
13
14
|
var _a, _b;
|
|
14
15
|
const [isOpen, setIsOpen] = (0, react_1.useState)(false);
|
|
@@ -16,6 +17,7 @@ function useSearchDialog() {
|
|
|
16
17
|
const location = (0, react_router_dom_1.useLocation)();
|
|
17
18
|
const { useTelemetry } = (0, use_theme_hooks_1.useThemeHooks)();
|
|
18
19
|
const telemetry = useTelemetry();
|
|
20
|
+
const { refreshSearchSessionId } = (0, contexts_1.useSearchSession)();
|
|
19
21
|
const keyShortcuts = (_b = (_a = themeSettings === null || themeSettings === void 0 ? void 0 : themeSettings.search) === null || _a === void 0 ? void 0 : _a.shortcuts) !== null && _b !== void 0 ? _b : ['⌘+K,CTRL+K'];
|
|
20
22
|
const hotKeys = keyShortcuts === null || keyShortcuts === void 0 ? void 0 : keyShortcuts.join(',');
|
|
21
23
|
(0, react_1.useEffect)(() => {
|
|
@@ -35,8 +37,9 @@ function useSearchDialog() {
|
|
|
35
37
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
36
38
|
}, []);
|
|
37
39
|
const onClose = (0, react_1.useCallback)(() => {
|
|
40
|
+
refreshSearchSessionId();
|
|
38
41
|
setIsOpen(false);
|
|
39
|
-
}, []);
|
|
42
|
+
}, [refreshSearchSessionId]);
|
|
40
43
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
41
44
|
(0, react_1.useEffect)(onClose, [location]);
|
|
42
45
|
return { isOpen, onOpen, onClose };
|
|
@@ -52,6 +52,8 @@ export declare const useTelemetryFallback: () => {
|
|
|
52
52
|
sendSwitchServersClickedMessage: () => void;
|
|
53
53
|
sendExamplesSwitcherClickedMessage: () => void;
|
|
54
54
|
sendTryItOpenedMessage: () => void;
|
|
55
|
+
sendViewSecurityDetailsClickedMessage: () => void;
|
|
56
|
+
sendViewSecurityDetailsClosedMessage: () => void;
|
|
55
57
|
sendAsyncapiDocsViewedMessage: () => void;
|
|
56
58
|
sendAsyncapiDocsPerformanceMetricsMessage: () => void;
|
|
57
59
|
sendAsyncapiDocsSwitchMessageClickedMessage: () => void;
|
|
@@ -57,6 +57,8 @@ const useTelemetryFallback = () => ({
|
|
|
57
57
|
sendSwitchServersClickedMessage: () => { },
|
|
58
58
|
sendExamplesSwitcherClickedMessage: () => { },
|
|
59
59
|
sendTryItOpenedMessage: () => { },
|
|
60
|
+
sendViewSecurityDetailsClickedMessage: () => { },
|
|
61
|
+
sendViewSecurityDetailsClosedMessage: () => { },
|
|
60
62
|
sendAsyncapiDocsViewedMessage: () => { },
|
|
61
63
|
sendAsyncapiDocsPerformanceMetricsMessage: () => { },
|
|
62
64
|
sendAsyncapiDocsSwitchMessageClickedMessage: () => { },
|
|
@@ -24,3 +24,4 @@ export { useDialogHotKeys } from '../hooks/use-dialog-hotkeys';
|
|
|
24
24
|
export { SecurityVariablesEnvSuffix } from '../constants/environments';
|
|
25
25
|
export { isUndefined, isString, isNotNull, isObject } from '../utils/type-guards';
|
|
26
26
|
export { ThemeDataContext, type ThemeDataTransferObject } from '../contexts/ThemeDataContext';
|
|
27
|
+
export { SearchSessionProvider, SearchSessionContext } from '../contexts/SearchContext';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ThemeDataContext = exports.isObject = exports.isNotNull = exports.isString = exports.isUndefined = exports.SecurityVariablesEnvSuffix = exports.useDialogHotKeys = exports.useSearchDialog = exports.useModalScrollLock = exports.useActiveSectionId = exports.useOutsideClick = exports.useThemeHooks = exports.useFocusTrap = exports.getUserAgent = exports.ClipboardService = exports.getOperationColor = exports.isPrimitive = exports.breakpoints = exports.GlobalStyle = exports.useMount = exports.typedMemo = exports.capitalize = exports.withPathPrefix = exports.addTrailingSlash = exports.combineUrls = exports.getPathPrefix = exports.removeLeadingSlash = exports.addLeadingSlash = exports.IS_BROWSER = void 0;
|
|
3
|
+
exports.SearchSessionContext = exports.SearchSessionProvider = exports.ThemeDataContext = exports.isObject = exports.isNotNull = exports.isString = exports.isUndefined = exports.SecurityVariablesEnvSuffix = exports.useDialogHotKeys = exports.useSearchDialog = exports.useModalScrollLock = exports.useActiveSectionId = exports.useOutsideClick = exports.useThemeHooks = exports.useFocusTrap = exports.getUserAgent = exports.ClipboardService = exports.getOperationColor = exports.isPrimitive = exports.breakpoints = exports.GlobalStyle = exports.useMount = exports.typedMemo = exports.capitalize = exports.withPathPrefix = exports.addTrailingSlash = exports.combineUrls = exports.getPathPrefix = exports.removeLeadingSlash = exports.addLeadingSlash = exports.IS_BROWSER = void 0;
|
|
4
4
|
var dom_1 = require("../utils/dom");
|
|
5
5
|
Object.defineProperty(exports, "IS_BROWSER", { enumerable: true, get: function () { return dom_1.IS_BROWSER; } });
|
|
6
6
|
var urls_1 = require("../utils/urls");
|
|
@@ -51,4 +51,7 @@ Object.defineProperty(exports, "isNotNull", { enumerable: true, get: function ()
|
|
|
51
51
|
Object.defineProperty(exports, "isObject", { enumerable: true, get: function () { return type_guards_1.isObject; } });
|
|
52
52
|
var ThemeDataContext_1 = require("../contexts/ThemeDataContext");
|
|
53
53
|
Object.defineProperty(exports, "ThemeDataContext", { enumerable: true, get: function () { return ThemeDataContext_1.ThemeDataContext; } });
|
|
54
|
+
var SearchContext_1 = require("../contexts/SearchContext");
|
|
55
|
+
Object.defineProperty(exports, "SearchSessionProvider", { enumerable: true, get: function () { return SearchContext_1.SearchSessionProvider; } });
|
|
56
|
+
Object.defineProperty(exports, "SearchSessionContext", { enumerable: true, get: function () { return SearchContext_1.SearchSessionContext; } });
|
|
54
57
|
//# sourceMappingURL=index.js.map
|
|
@@ -64,7 +64,7 @@ export type ThemeHooks = {
|
|
|
64
64
|
banner: BannerConfig | undefined;
|
|
65
65
|
dismissBanner: (content: string) => void;
|
|
66
66
|
};
|
|
67
|
-
useSearch: (product?: string, autoSearchDisabled?: boolean
|
|
67
|
+
useSearch: (product?: string, autoSearchDisabled?: boolean) => {
|
|
68
68
|
query: string;
|
|
69
69
|
setQuery: React.Dispatch<React.SetStateAction<string>>;
|
|
70
70
|
filter: SearchFilterItem[];
|
|
@@ -82,7 +82,7 @@ export type ThemeHooks = {
|
|
|
82
82
|
};
|
|
83
83
|
useAiSearch: (options?: {
|
|
84
84
|
filter?: SearchFilterItem[];
|
|
85
|
-
}
|
|
85
|
+
}) => {
|
|
86
86
|
askQuestion: (question: string, history?: AiSearchConversationItem[]) => void;
|
|
87
87
|
isGeneratingResponse: boolean;
|
|
88
88
|
question: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/theme",
|
|
3
|
-
"version": "0.60.0
|
|
3
|
+
"version": "0.60.0",
|
|
4
4
|
"description": "Shared UI components lib",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"theme",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"@markdoc/markdoc": "0.5.2",
|
|
30
30
|
"lodash.debounce": "^4.0.8",
|
|
31
31
|
"lodash.throttle": "^4.1.1",
|
|
32
|
-
"react": "^19.1
|
|
33
|
-
"react-dom": "^19.1
|
|
32
|
+
"react": "^19.2.1",
|
|
33
|
+
"react-dom": "^19.2.1",
|
|
34
34
|
"react-router-dom": "^6.21.1",
|
|
35
35
|
"styled-components": "^4.1.1 || ^5.3.11 || ^6.0.0"
|
|
36
36
|
},
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"@types/lodash.throttle": "4.1.9",
|
|
46
46
|
"@types/node": "22.18.13",
|
|
47
47
|
"@types/nprogress": "0.2.3",
|
|
48
|
-
"@types/react": "^19.
|
|
49
|
-
"@types/react-dom": "^19.
|
|
48
|
+
"@types/react": "^19.2.7",
|
|
49
|
+
"@types/react-dom": "^19.2.3",
|
|
50
50
|
"@types/styled-components": "5.1.34",
|
|
51
51
|
"@vitest/coverage-v8": "^4.0.10",
|
|
52
52
|
"@vitest/ui": "3.2.4",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"vitest": "4.0.10",
|
|
64
64
|
"vitest-when": "0.6.2",
|
|
65
65
|
"webpack": "5.94.0",
|
|
66
|
-
"@redocly/realm-asyncapi-sdk": "0.6.0
|
|
66
|
+
"@redocly/realm-asyncapi-sdk": "0.6.0"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@tanstack/react-query": "5.62.3",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"openapi-sampler": "1.6.2",
|
|
82
82
|
"react-calendar": "5.1.0",
|
|
83
83
|
"react-date-picker": "11.0.0",
|
|
84
|
-
"@redocly/config": "0.
|
|
84
|
+
"@redocly/config": "0.41.0"
|
|
85
85
|
},
|
|
86
86
|
"scripts": {
|
|
87
87
|
"watch": "tsc -p tsconfig.build.json && (concurrently \"tsc -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\")",
|
|
@@ -8,6 +8,7 @@ import { useThemeConfig, useThemeHooks } from '@redocly/theme/core/hooks';
|
|
|
8
8
|
import { ChatIcon } from '@redocly/theme/icons/ChatIcon/ChatIcon';
|
|
9
9
|
import { AiStarsGradientIcon } from '@redocly/theme/icons/AiStarsGradientIcon/AiStarsGradientIcon';
|
|
10
10
|
import { RedoclyIcon } from '@redocly/theme/icons/RedoclyIcon/RedoclyIcon';
|
|
11
|
+
import { SearchSessionProvider } from '@redocly/theme/core/contexts';
|
|
11
12
|
|
|
12
13
|
type AIAssistantButtonIconType = 'chat' | 'sparkles' | 'redocly';
|
|
13
14
|
type AIAssistantButtonType = 'button' | 'icon';
|
|
@@ -86,7 +87,7 @@ export function AIAssistantButton() {
|
|
|
86
87
|
};
|
|
87
88
|
|
|
88
89
|
return (
|
|
89
|
-
|
|
90
|
+
<SearchSessionProvider>
|
|
90
91
|
<StyledAIAssistantButton
|
|
91
92
|
variant="outlined"
|
|
92
93
|
size="medium"
|
|
@@ -100,7 +101,7 @@ export function AIAssistantButton() {
|
|
|
100
101
|
</StyledAIAssistantButton>
|
|
101
102
|
|
|
102
103
|
{isOpen && <SearchDialog onClose={handleClose} initialMode="ai-dialog" />}
|
|
103
|
-
|
|
104
|
+
</SearchSessionProvider>
|
|
104
105
|
);
|
|
105
106
|
}
|
|
106
107
|
|
|
@@ -6,12 +6,13 @@ import type { JSX } from 'react';
|
|
|
6
6
|
import { SearchTrigger } from '@redocly/theme/components/Search/SearchTrigger';
|
|
7
7
|
import { SearchDialog } from '@redocly/theme/components/Search/SearchDialog';
|
|
8
8
|
import { useSearchDialog } from '@redocly/theme/core/hooks';
|
|
9
|
+
import { SearchSessionProvider } from '@redocly/theme/core/contexts';
|
|
9
10
|
|
|
10
11
|
export type SearchProps = {
|
|
11
12
|
className?: string;
|
|
12
13
|
};
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
function SearchContent({ className }: SearchProps): JSX.Element {
|
|
15
16
|
const { isOpen, onOpen, onClose } = useSearchDialog();
|
|
16
17
|
|
|
17
18
|
return (
|
|
@@ -22,6 +23,14 @@ export function Search({ className }: SearchProps): JSX.Element {
|
|
|
22
23
|
);
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
export function Search({ className }: SearchProps): JSX.Element {
|
|
27
|
+
return (
|
|
28
|
+
<SearchSessionProvider>
|
|
29
|
+
<SearchContent className={className} />
|
|
30
|
+
</SearchSessionProvider>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
25
34
|
const SearchWrapper = styled.div`
|
|
26
35
|
margin-left: auto;
|
|
27
36
|
`;
|
|
@@ -24,6 +24,7 @@ import { SearchGroups } from '@redocly/theme/components/Search/SearchGroups';
|
|
|
24
24
|
import { Typography } from '@redocly/theme/components/Typography/Typography';
|
|
25
25
|
import { SpinnerLoader } from '@redocly/theme/components/Loaders/SpinnerLoader';
|
|
26
26
|
import { SearchAiDialog } from '@redocly/theme/components/Search/SearchAiDialog';
|
|
27
|
+
import { useSearchSession } from '@redocly/theme/core/contexts';
|
|
27
28
|
import { SettingsIcon } from '@redocly/theme/icons/SettingsIcon/SettingsIcon';
|
|
28
29
|
import { AiStarsIcon } from '@redocly/theme/icons/AiStarsIcon/AiStarsIcon';
|
|
29
30
|
import { ReturnKeyIcon } from '@redocly/theme/icons/ReturnKeyIcon/ReturnKeyIcon';
|
|
@@ -45,10 +46,10 @@ export function SearchDialog({
|
|
|
45
46
|
const { useTranslate, useCurrentProduct, useSearch, useProducts, useAiSearch, useTelemetry } =
|
|
46
47
|
useThemeHooks();
|
|
47
48
|
const telemetry = useTelemetry();
|
|
49
|
+
const { searchSessionId, refreshSearchSessionId } = useSearchSession();
|
|
48
50
|
const products = useProducts();
|
|
49
51
|
const currentProduct = useCurrentProduct();
|
|
50
52
|
const [product, setProduct] = useState(currentProduct);
|
|
51
|
-
const searchSessionId = `search-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
52
53
|
const [mode, setMode] = useState<'search' | 'ai-dialog'>(initialMode);
|
|
53
54
|
const autoSearchDisabled = mode !== 'search';
|
|
54
55
|
const {
|
|
@@ -63,7 +64,7 @@ export function SearchDialog({
|
|
|
63
64
|
advancedSearch,
|
|
64
65
|
askAi,
|
|
65
66
|
groupField,
|
|
66
|
-
} = useSearch(product?.name, autoSearchDisabled
|
|
67
|
+
} = useSearch(product?.name, autoSearchDisabled);
|
|
67
68
|
const {
|
|
68
69
|
isFilterOpen,
|
|
69
70
|
onFilterToggle,
|
|
@@ -73,7 +74,7 @@ export function SearchDialog({
|
|
|
73
74
|
onQuickFilterReset,
|
|
74
75
|
} = useSearchFilter(filter, setFilter);
|
|
75
76
|
const { addSearchHistoryItem } = useRecentSearches();
|
|
76
|
-
const aiSearch = useAiSearch({ filter }
|
|
77
|
+
const aiSearch = useAiSearch({ filter });
|
|
77
78
|
|
|
78
79
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
79
80
|
const modalRef = useRef<HTMLDivElement>(null);
|
|
@@ -103,8 +104,11 @@ export function SearchDialog({
|
|
|
103
104
|
addSearchHistoryItem(value);
|
|
104
105
|
}
|
|
105
106
|
|
|
107
|
+
// Refresh the search session id so a new session starts on next open
|
|
108
|
+
refreshSearchSessionId();
|
|
109
|
+
|
|
106
110
|
onClose();
|
|
107
|
-
}, [addSearchHistoryItem, onClose]);
|
|
111
|
+
}, [addSearchHistoryItem, onClose, refreshSearchSessionId]);
|
|
108
112
|
|
|
109
113
|
useDialogHotKeys(modalRef, handleClose);
|
|
110
114
|
|
|
@@ -276,7 +280,10 @@ export function SearchDialog({
|
|
|
276
280
|
<Button
|
|
277
281
|
variant="secondary"
|
|
278
282
|
disabled={!aiSearch.conversation.length}
|
|
279
|
-
onClick={() =>
|
|
283
|
+
onClick={() => {
|
|
284
|
+
refreshSearchSessionId();
|
|
285
|
+
aiSearch.clearConversation();
|
|
286
|
+
}}
|
|
280
287
|
tabIndex={0}
|
|
281
288
|
icon={<EditIcon />}
|
|
282
289
|
>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export type SearchSessionContextValue = {
|
|
4
|
+
searchSessionId: string;
|
|
5
|
+
refreshSearchSessionId: () => void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const SearchSessionContext = createContext<SearchSessionContextValue | null>(null);
|
|
9
|
+
|
|
10
|
+
export const SearchSessionProvider = ({ children }: { children: React.ReactNode }) => {
|
|
11
|
+
const [searchSessionId, setSearchSessionId] = useState(() => crypto.randomUUID());
|
|
12
|
+
|
|
13
|
+
const refreshSearchSessionId = useCallback(() => {
|
|
14
|
+
setSearchSessionId(crypto.randomUUID());
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
const value = useMemo(
|
|
18
|
+
() => ({ searchSessionId, refreshSearchSessionId }),
|
|
19
|
+
[searchSessionId, refreshSearchSessionId],
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
return <SearchSessionContext.Provider value={value}>{children}</SearchSessionContext.Provider>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function useSearchSession(): SearchSessionContextValue {
|
|
26
|
+
const contextValue = useContext(SearchSessionContext);
|
|
27
|
+
if (!contextValue) {
|
|
28
|
+
throw new Error('useSearchSession must be used within a SearchSessionProvider');
|
|
29
|
+
}
|
|
30
|
+
return contextValue;
|
|
31
|
+
}
|
|
@@ -4,6 +4,7 @@ import hotkeys from 'hotkeys-js';
|
|
|
4
4
|
|
|
5
5
|
import { useThemeHooks } from '../use-theme-hooks';
|
|
6
6
|
import { useThemeConfig } from '../use-theme-config';
|
|
7
|
+
import { useSearchSession } from '../../contexts';
|
|
7
8
|
|
|
8
9
|
export function useSearchDialog() {
|
|
9
10
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -11,6 +12,7 @@ export function useSearchDialog() {
|
|
|
11
12
|
const location = useLocation();
|
|
12
13
|
const { useTelemetry } = useThemeHooks();
|
|
13
14
|
const telemetry = useTelemetry();
|
|
15
|
+
const { refreshSearchSessionId } = useSearchSession();
|
|
14
16
|
const keyShortcuts = themeSettings?.search?.shortcuts ?? ['⌘+K,CTRL+K'];
|
|
15
17
|
const hotKeys = keyShortcuts?.join(',');
|
|
16
18
|
|
|
@@ -34,8 +36,9 @@ export function useSearchDialog() {
|
|
|
34
36
|
}, []);
|
|
35
37
|
|
|
36
38
|
const onClose = useCallback(() => {
|
|
39
|
+
refreshSearchSessionId();
|
|
37
40
|
setIsOpen(false);
|
|
38
|
-
}, []);
|
|
41
|
+
}, [refreshSearchSessionId]);
|
|
39
42
|
|
|
40
43
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
41
44
|
useEffect(onClose, [location]);
|
|
@@ -54,6 +54,8 @@ export const useTelemetryFallback = () => ({
|
|
|
54
54
|
sendSwitchServersClickedMessage: () => {},
|
|
55
55
|
sendExamplesSwitcherClickedMessage: () => {},
|
|
56
56
|
sendTryItOpenedMessage: () => {},
|
|
57
|
+
sendViewSecurityDetailsClickedMessage: () => {},
|
|
58
|
+
sendViewSecurityDetailsClosedMessage: () => {},
|
|
57
59
|
sendAsyncapiDocsViewedMessage: () => {},
|
|
58
60
|
sendAsyncapiDocsPerformanceMetricsMessage: () => {},
|
|
59
61
|
sendAsyncapiDocsSwitchMessageClickedMessage: () => {},
|
|
@@ -37,3 +37,4 @@ export { useDialogHotKeys } from '../hooks/use-dialog-hotkeys';
|
|
|
37
37
|
export { SecurityVariablesEnvSuffix } from '../constants/environments';
|
|
38
38
|
export { isUndefined, isString, isNotNull, isObject } from '../utils/type-guards';
|
|
39
39
|
export { ThemeDataContext, type ThemeDataTransferObject } from '../contexts/ThemeDataContext';
|
|
40
|
+
export { SearchSessionProvider, SearchSessionContext } from '../contexts/SearchContext';
|
package/src/core/types/hooks.ts
CHANGED
|
@@ -91,7 +91,6 @@ export type ThemeHooks = {
|
|
|
91
91
|
useSearch: (
|
|
92
92
|
product?: string,
|
|
93
93
|
autoSearchDisabled?: boolean,
|
|
94
|
-
searchSessionId?: string,
|
|
95
94
|
) => {
|
|
96
95
|
query: string;
|
|
97
96
|
setQuery: React.Dispatch<React.SetStateAction<string>>;
|
|
@@ -113,10 +112,7 @@ export type ThemeHooks = {
|
|
|
113
112
|
advancedSearch?: boolean;
|
|
114
113
|
askAi?: boolean;
|
|
115
114
|
};
|
|
116
|
-
useAiSearch: (
|
|
117
|
-
options?: { filter?: SearchFilterItem[] },
|
|
118
|
-
searchSessionId?: string,
|
|
119
|
-
) => {
|
|
115
|
+
useAiSearch: (options?: { filter?: SearchFilterItem[] }) => {
|
|
120
116
|
askQuestion: (question: string, history?: AiSearchConversationItem[]) => void;
|
|
121
117
|
isGeneratingResponse: boolean;
|
|
122
118
|
question: string;
|