Skip to content

Commit 38b22e2

Browse files
authored
feat: implement basic kapa.ai and inkeep a/b test (#1722)
1 parent 5f8d102 commit 38b22e2

File tree

9 files changed

+247
-26
lines changed

9 files changed

+247
-26
lines changed

apify-docs-theme/src/config.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,44 @@ const plugins = [
314314
},
315315
];
316316

317+
const scripts = [
318+
{
319+
src: 'https://widget.kapa.ai/kapa-widget.bundle.js',
320+
'data-website-id': 'a9937f98-9c9d-44d9-a433-fec4cb1c114d',
321+
'data-project-name': 'Apify',
322+
'data-modal-title': 'Apify AI Assistant',
323+
'data-project-color': '#666666',
324+
'data-button-hide': 'true',
325+
'data-search-mode-enabled': 'true',
326+
'data-project-logo': 'https://apify.com/img/apify-logo/logomark-32x32.svg',
327+
'data-modal-example-questions': 'How to run an Actor?,Create a version of an Actor?',
328+
'data-modal-override-open-id': 'ask-ai-input',
329+
'data-modal-override-open-class': 'search-input',
330+
'data-font-size-xs': '1.2rem',
331+
'data-font-size-sm': '1.4rem',
332+
'data-font-size-md': '1.6rem',
333+
'data-font-size-lg': '1.8rem',
334+
'data-font-size-xl': '2.0rem',
335+
async: true,
336+
},
337+
{
338+
src: 'https://cdn.jsdelivr.net/npm/@inkeep/[email protected]/dist/embed.js',
339+
type: 'module',
340+
async: true,
341+
},
342+
];
343+
344+
const customFields = {
345+
inkeepApiKey: process.env.LOCALHOST || process.env.DEV
346+
? 'bbbb9f1001a9b66f282431a80bb743a24e2bdefb85d4f1e4' // development, works with localhost
347+
: '8af30e40009f26622237f75aab8256064c26a3063717c48a', // production, only works on apify.com (any subdomain)
348+
};
349+
317350
module.exports = {
318351
themeConfig,
319352
plugins,
320353
absoluteUrl,
321354
noIndex,
355+
scripts,
356+
customFields,
322357
};

apify-docs-theme/src/theme/Navbar/Content/index.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import NavbarLogo from '@theme/Navbar/Logo';
88
import NavbarMobileSidebarToggle from '@theme/Navbar/MobileSidebar/Toggle';
99
import NavbarSearch from '@theme/Navbar/Search';
1010
import NavbarItem from '@theme/NavbarItem';
11+
import SearchBar from '@theme/SearchBar';
1112
import React from 'react';
1213

13-
import SearchBar from '../../SearchBar';
14+
// import SearchBar from '../../SearchBar';
1415
import NavbarCTA from '../CTA';
1516

1617
function NavbarItems({ items }) {

apify-docs-theme/src/theme/SearchBar/index.js

Lines changed: 170 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
// eslint-disable-next-line simple-import-sort/imports
22
import BrowserOnly from '@docusaurus/BrowserOnly';
33
import RouterLink from '@docusaurus/Link';
4-
import { useHistory, useLocation } from '@docusaurus/router';
4+
// import { useHistory, useLocation } from '@docusaurus/router';
55
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
6-
import React, { useCallback } from 'react';
6+
import clsx from 'clsx';
7+
// import React, { useCallback } from 'react';
8+
import React, { useEffect, useState } from 'react';
79

8-
import { ApifySearch } from '@apify/docs-search-modal';
10+
// import { ApifySearch } from '@apify/docs-search-modal';
11+
import { ControlKeyIcon, SearchIcon } from '@apify/docs-search-modal/dist/utils/icons';
912

1013
// needs to be imported as the last thing, so that it can override the default styles
1114
// TODO: update simple-import-sort to allow importing css as last.
@@ -40,37 +43,182 @@ export function Link(props) {
4043
return <a {...props}>{props.children}</a>;
4144
}
4245

46+
// export default function SearchBar({ onClick }) {
47+
// const { siteConfig } = useDocusaurusContext();
48+
// const location = useLocation();
49+
// const history = useHistory();
50+
//
51+
// const navigate = useCallback((href) => {
52+
// const shortHref = href.substring('https://docs.apify.com'.length);
53+
//
54+
// if (matchesCurrentInstance(shortHref, siteConfig.baseUrl)) {
55+
// return history.push(shortHref);
56+
// }
57+
// return window.location.assign(href);
58+
// }, [history, siteConfig.baseUrl]);
59+
//
60+
// const getVersion = useCallback(() => {
61+
// const match = location.pathname.match(/\/(\d+\.\d+|next)/);
62+
//
63+
// return match ? match[1] : 'latest';
64+
// }, [location]);
65+
//
66+
// return (
67+
// <BrowserOnly>
68+
// {() => (
69+
// <div onClick={onClick}>
70+
// <ApifySearch
71+
// algoliaAppId={siteConfig.themeConfig.algolia.appId}
72+
// algoliaIndexName='apify_sdk_v2'
73+
// algoliaKey={siteConfig.themeConfig.algolia.apiKey}
74+
// filters={`version:${getVersion()}`}
75+
// navigate={navigate}
76+
// />
77+
// </div>
78+
// )}
79+
// </BrowserOnly>
80+
// );
81+
// }
82+
4383
export default function SearchBar({ onClick }) {
84+
const [variant, setVariant] = useState(null);
4485
const { siteConfig } = useDocusaurusContext();
45-
const location = useLocation();
46-
const history = useHistory();
86+
const { inkeepApiKey } = siteConfig.customFields;
87+
88+
useEffect(() => {
89+
const storedVariant = localStorage.getItem('search-provider');
90+
91+
if (storedVariant) {
92+
setVariant(storedVariant);
93+
} else {
94+
const assignedVariant = Math.random() < 0.5 ? 'inkeep' : 'kapa';
95+
localStorage.setItem('search-provider', assignedVariant);
96+
setVariant(assignedVariant);
97+
}
98+
}, []);
4799

48-
const navigate = useCallback((href) => {
49-
const shortHref = href.substring('https://docs.apify.com'.length);
100+
onClick = () => {
101+
if (variant === 'kapa') {
102+
if (window.Kapa && typeof window.Kapa.open === 'function') {
103+
window.Kapa.open();
104+
} else {
105+
console.error('Kapa.ai widget is not available.');
106+
}
107+
return;
108+
}
50109

51-
if (matchesCurrentInstance(shortHref, siteConfig.baseUrl)) {
52-
return history.push(shortHref);
110+
if (variant !== 'inkeep') {
111+
console.warn('Unknown search variant:', variant);
112+
return;
53113
}
54-
return window.location.assign(href);
55-
}, [history, siteConfig.baseUrl]);
56114

57-
const getVersion = useCallback(() => {
58-
const match = location.pathname.match(/\/(\d+\.\d+|next)/);
115+
if (window.Inkeep) {
116+
const config = {
117+
baseSettings: {
118+
apiKey: inkeepApiKey,
119+
organizationDisplayName: 'Apify',
120+
primaryBrandColor: '#FF9013',
121+
transformSource: (source) => {
122+
if (source.contentType === 'documentation') {
123+
return {
124+
...source,
125+
tabs: [...(source.tabs || []), 'Docs'],
126+
};
127+
}
128+
return source;
129+
},
130+
trigger: {
131+
disableDefaultTrigger: true,
132+
},
133+
theme: {
134+
styles: [
135+
{
136+
key: 'main',
137+
type: 'link',
138+
value: '/inkeep-overrides.css',
139+
},
140+
],
141+
},
142+
},
143+
modalSettings: {
144+
onOpenChange: handleOpenChange,
145+
},
146+
searchSettings: {
147+
tabs: [
148+
'All',
149+
'Docs',
150+
'Publications',
151+
'PDFs',
152+
'GitHub',
153+
'Forums',
154+
'Discord',
155+
'Slack',
156+
'StackOverflow',
157+
],
158+
},
159+
aiChatSettings: {
160+
aiAssistantAvatar: 'https://intercom.help/apify/assets/favicon',
161+
chatSubjectName: 'Apify',
162+
exampleQuestions: [
163+
'What is an Actor?',
164+
'How to use my own proxies?',
165+
'How to integrate Apify Actors with GitHub?',
166+
'How to share key-value stores between runs?',
167+
],
168+
getHelpOptions: [
169+
{
170+
action: {
171+
type: 'open_link',
172+
url: 'https://apify.com/contact',
173+
},
174+
icon: {
175+
builtIn: 'IoChatbubblesOutline',
176+
},
177+
name: 'Contact Us',
178+
},
179+
],
180+
},
181+
};
182+
const modal = window.Inkeep.ModalSearchAndChat(config);
59183

60-
return match ? match[1] : 'latest';
61-
}, [location]);
184+
function handleOpenChange(newOpen) {
185+
modal.update({ modalSettings: { isOpen: newOpen } });
186+
}
187+
188+
modal.update({ modalSettings: { isOpen: true } });
189+
} else {
190+
console.error('Inkeep widget is not available.');
191+
}
192+
};
193+
194+
const [key, setKey] = useState(null);
195+
196+
useEffect(() => {
197+
if (typeof navigator !== 'undefined') {
198+
const isMac = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
199+
setKey(isMac ? '⌘' : 'ctrl');
200+
}
201+
}, []);
62202

63203
return (
64204
<BrowserOnly>
65205
{() => (
66206
<div onClick={onClick}>
67-
<ApifySearch
68-
algoliaAppId={siteConfig.themeConfig.algolia.appId}
69-
algoliaIndexName='apify_sdk_v2'
70-
algoliaKey={siteConfig.themeConfig.algolia.apiKey}
71-
filters={`version:${getVersion()}`}
72-
navigate={navigate}
73-
/>
207+
<button type="button" className="DocSearch DocSearch-Button" aria-label="Search">
208+
<span className="DocSearch-Button-Container">
209+
<SearchIcon/>
210+
<span className="DocSearch-Button-Placeholder">Search</span>
211+
</span>
212+
<span className="DocSearch-Button-Keys">
213+
{key !== null && (<>
214+
<kbd className={clsx(key === 'ctrl' ? 'ctrl' : 'cmd', 'DocSearch-Button-Key')}>
215+
{key === 'ctrl' ? <ControlKeyIcon/> : key}
216+
</kbd>
217+
<kbd className="DocSearch-Button-Key">K</kbd>
218+
</>)}
219+
</span>
220+
221+
</button>
74222
</div>
75223
)}
76224
</BrowserOnly>

apify-docs-theme/src/theme/custom.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1984,3 +1984,16 @@ div[class^="navbarSearchContainer"] {
19841984
.medium-zoom-image--opened {
19851985
z-index: 999;
19861986
}
1987+
1988+
.DocSearch-Button {
1989+
cursor: pointer;
1990+
}
1991+
1992+
.DocSearch-Button-Key {
1993+
border: 0;
1994+
}
1995+
1996+
.DocSearch-Button-Key.ctrl:first-child {
1997+
width: 14px !important;
1998+
height: 15px !important;
1999+
}

docusaurus.config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ module.exports = {
1717
trailingSlash: false,
1818
organizationName: 'apify',
1919
projectName: 'apify-docs',
20-
scripts: ['/js/custom.js'],
20+
scripts: [
21+
'/js/custom.js',
22+
...config.scripts ?? [],
23+
],
2124
future: {
2225
experimental_faster: {
2326
// swcJsLoader: true,
@@ -442,6 +445,7 @@ module.exports = {
442445
'^/legal',
443446
'^/legal/*',
444447
],
448+
...config.customFields ?? [],
445449
},
446450
clientModules: ['./clientModule.js'],
447451
};

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

static/inkeep-overrides.css

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
.inkeep-widget-vars {
2+
font-size: 1.6rem !important;
3+
--ikp-font-size-3xs: 0.72rem !important;
4+
--ikp-font-size-2xs: 1rem !important;
5+
--ikp-font-size-xs: 1.2rem !important;
6+
--ikp-font-size-1sm: 1.3rem !important;
7+
--ikp-font-size-sm: 1.4rem !important;
8+
--ikp-font-size-2sm: 1.5rem !important;
9+
--ikp-font-size-md: 1.6rem !important;
10+
--ikp-font-size-lg: 1.8rem !important;
11+
--ikp-font-size-xl: 2rem !important;
12+
--ikp-font-size-2xl: 2.4rem !important;
13+
--ikp-font-size-3xl: 3rem !important;
14+
--ikp-font-size-4xl: 3.6rem !important;
15+
--ikp-font-size-5xl: 4.8rem !important;
16+
--ikp-font-size-6xl: 6rem !important;
17+
--ikp-font-size-7xl: 7.2rem !important;
18+
--ikp-font-size-8xl: 9.6rem !important;
19+
--ikp-font-size-9xl: 12.8rem !important;
20+
}

0 commit comments

Comments
 (0)