Skip to content

Commit c475108

Browse files
kkkk666web3jenks
andauthored
feat: Enhance AI Chat Widget functionality and styling (#398)
* feat: Enhance AI Chat Widget functionality and styling - Implement dynamic visibility for the AI chat button based on API health checks. - Inject CSS styles to control button display on different screen sizes. - Update health check logic to manage button visibility more effectively. - Remove redundant CSS rules related to button visibility, streamlining the code. * style: dark mode ghost/outlined button for AI chat buttons * add /test to gitignore --------- Co-authored-by: Jenks <jenks@babylonlabs.io>
1 parent 2b2efd3 commit c475108

4 files changed

Lines changed: 108 additions & 31 deletions

File tree

src/components/ChatWidget.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,21 @@
200200
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
201201
}
202202

203+
/* Dark mode: ghost/outlined style */
204+
[data-theme='dark'] .chat-trigger-btn {
205+
background: transparent;
206+
color: rgb(var(--docs-color-primary-100));
207+
border: 1px solid rgb(var(--docs-color-primary-100));
208+
}
209+
210+
[data-theme='dark'] .chat-trigger-btn:hover {
211+
transform: scale(1.05);
212+
border-color: rgb(var(--docs-color-primary-200));
213+
color: #33C5CE;
214+
background: transparent;
215+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
216+
}
217+
203218
/* Send Button Styles */
204219
.chat-input button[type="submit"] {
205220
border: none !important;

src/components/ChatWidget.tsx

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -172,33 +172,65 @@ export default function ChatWidget() {
172172

173173
const apiBaseUrl = siteConfig.customFields?.apiBaseUrl || '/api';
174174

175-
// Check API health on mount
175+
// Style tag ID for AI button visibility
176+
const STYLE_TAG_ID = 'babylon-ai-button-style';
177+
178+
// Check API health on mount - inject style tag to show button when API is available
176179
useEffect(() => {
180+
if (typeof window === 'undefined') return;
181+
182+
// Inject or remove style tag to control button visibility
183+
const setButtonVisible = (visible: boolean) => {
184+
let styleTag = document.getElementById(STYLE_TAG_ID) as HTMLStyleElement | null;
185+
186+
if (visible) {
187+
// Create style tag if it doesn't exist
188+
if (!styleTag) {
189+
styleTag = document.createElement('style');
190+
styleTag.id = STYLE_TAG_ID;
191+
document.head.appendChild(styleTag);
192+
}
193+
// CSS to show the button - this overrides the default display:none
194+
styleTag.textContent = `
195+
.header-ai-chat-link { display: flex !important; }
196+
.navbar-sidebar .header-ai-chat-link { display: inline-flex !important; }
197+
@media (max-width: 768px) {
198+
.navbar__items .header-ai-chat-link { display: none !important; }
199+
}
200+
`;
201+
} else {
202+
// Remove style tag to hide button (falls back to CSS default: display:none)
203+
if (styleTag) {
204+
styleTag.remove();
205+
}
206+
}
207+
};
208+
177209
const checkHealth = async () => {
210+
const controller = new AbortController();
211+
const timeoutId = setTimeout(() => controller.abort(), 5000);
212+
178213
try {
179-
const response = await fetch(`${apiBaseUrl}/health`);
180-
if (response.ok) {
181-
setIsApiHealthy(true);
182-
// Show header AI button
183-
document.body.classList.add('ai-chat-available');
184-
} else {
185-
setIsApiHealthy(false);
186-
// Hide header AI button
187-
document.body.classList.remove('ai-chat-available');
188-
}
214+
const response = await fetch(`${apiBaseUrl}/health`, {
215+
signal: controller.signal
216+
});
217+
clearTimeout(timeoutId);
218+
219+
const healthy = response.ok;
220+
setIsApiHealthy(healthy);
221+
setButtonVisible(healthy);
189222
} catch (error) {
223+
clearTimeout(timeoutId);
190224
console.error('Health check failed:', error);
191225
setIsApiHealthy(false);
192-
// Hide header AI button
193-
document.body.classList.remove('ai-chat-available');
226+
setButtonVisible(false);
194227
}
195228
};
229+
196230
checkHealth();
197-
198-
// Cleanup on unmount
199-
return () => {
200-
document.body.classList.remove('ai-chat-available');
201-
};
231+
232+
// Cleanup: remove style tag when component unmounts (optional, keeps it clean)
233+
// Note: We don't remove it because we want the button to stay visible across navigations
202234
}, [apiBaseUrl]);
203235

204236
// Fetch token limits on mount (only if API is healthy)

src/css/custom.css

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,7 +1158,6 @@ details p {
11581158
@apply !text-xs;
11591159
}
11601160

1161-
/* Hide AI header button on small screens, keep on desktop */
11621161
@media (max-width: 768px) {
11631162
.navbar__items .header-ai-chat-link,
11641163
.navbar-sidebar .header-ai-chat-link {
@@ -1257,6 +1256,10 @@ article ul {
12571256
content: url('data:image/svg+xml,<svg class="sidebar-icon-header ___12fm75w f1w7gpdv fez10in fg4l7m0" aria-hidden="true" width="1em" height="1em" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10.76 6.43a2 2 0 1 1 2.82 2.82 2 2 0 0 1-2.82-2.82Zm2.11.7a1 1 0 1 0-1.4 1.41 1 1 0 0 0 1.4-1.4ZM9.44 13.6l.6.6a1.5 1.5 0 0 0 1.71.3l1.12 1.12c.2.2.51.2.7 0L15 14.2c.86-.86.96-2.17.31-3.14l.85-.85a6.3 6.3 0 0 0 1.56-6.33 2.43 2.43 0 0 0-1.6-1.6A6.3 6.3 0 0 0 9.8 3.86l-.85.85A2.5 2.5 0 0 0 5.8 5L4.38 6.43a.5.5 0 0 0 0 .7L5.5 8.27a1.5 1.5 0 0 0 .3 1.7l.6.61-1.21.73a.5.5 0 0 0-.1.78l2.83 2.83a.5.5 0 0 0 .79-.1l.73-1.21Zm7.32-9.42a5.3 5.3 0 0 1-1.3 5.33l-4 4a.5.5 0 0 1-.7 0l-1.07-1.07-2.12-2.12L6.5 9.24a.5.5 0 0 1 0-.7l3.99-4a5.3 5.3 0 0 1 5.33-1.3c.44.13.8.48.93.93ZM5.8 14.91a.5.5 0 0 0-.7-.7l-1.77 1.76a.5.5 0 1 0 .7.7l1.77-1.76ZM4.4 12.79c.2.2.2.5 0 .7l-.7.71a.5.5 0 1 1-.72-.7l.71-.71c.2-.2.51-.2.71 0Zm2.83 3.53a.5.5 0 1 0-.7-.7l-.72.7a.5.5 0 1 0 .71.71l.71-.7Z" fill="currentColor"></path></svg>');
12581257
}
12591258

1259+
.networks_sidebar_header > div > a::before {
1260+
@apply mt-1 mr-2;
1261+
content: url('data:image/svg+xml,<svg class="sidebar-icon-header ___12fm75w f1w7gpdv fez10in fg4l7m0" aria-hidden="true" width="1em" height="1em" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M2 4.5c0-.28.22-.5.5-.5h15a.5.5 0 0 1 0 1h-15a.5.5 0 0 1-.5-.5Zm0 10c0-.83.67-1.5 1.5-1.5h13a1.5 1.5 0 0 1 0 3h-13A1.5 1.5 0 0 1 2 14.5ZM3 8a1 1 0 0 0 0 2h14a1 1 0 1 0 0-2H3Z" fill="currentColor"></path></svg>');
1262+
}
12601263

12611264
.research_sidebar_header > div > a::before {
12621265
@apply mt-1 mr-2;
@@ -1310,6 +1313,10 @@ html[data-theme='dark'] .the_launch_sidebar_header > div > a::before {
13101313
content: url('data:image/svg+xml,<svg fill="currentColor" class="sidebar-icon-header ___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0" aria-hidden="true" width="1em" height="1em" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10.76 6.43a2 2 0 1 1 2.82 2.82 2 2 0 0 1-2.82-2.82Zm2.11.7a1 1 0 1 0-1.4 1.41 1 1 0 0 0 1.4-1.4ZM9.44 13.6l.6.6a1.5 1.5 0 0 0 1.71.3l1.12 1.12c.2.2.51.2.7 0L15 14.2c.86-.86.96-2.17.31-3.14l.85-.85a6.3 6.3 0 0 0 1.56-6.33 2.43 2.43 0 0 0-1.6-1.6A6.3 6.3 0 0 0 9.8 3.86l-.85.85A2.5 2.5 0 0 0 5.8 5L4.38 6.43a.5.5 0 0 0 0 .7L5.5 8.27a1.5 1.5 0 0 0 .3 1.7l.6.61-1.21.73a.5.5 0 0 0-.1.78l2.83 2.83a.5.5 0 0 0 .79-.1l.73-1.21Zm7.32-9.42a5.3 5.3 0 0 1-1.3 5.33l-4 4a.5.5 0 0 1-.7 0l-1.07-1.07-2.12-2.12L6.5 9.24a.5.5 0 0 1 0-.7l3.99-4a5.3 5.3 0 0 1 5.33-1.3c.44.13.8.48.93.93ZM5.8 14.91a.5.5 0 0 0-.7-.7l-1.77 1.76a.5.5 0 1 0 .7.7l1.77-1.76ZM4.4 12.79c.2.2.2.5 0 .7l-.7.71a.5.5 0 1 1-.72-.7l.71-.71c.2-.2.51-.2.71 0Zm2.83 3.53a.5.5 0 1 0-.7-.7l-.72.7a.5.5 0 1 0 .71.71l.71-.7Z" fill="white"></path></svg>');
13111314
}
13121315

1316+
html[data-theme='dark'] .networks_sidebar_header > div > a::before {
1317+
@apply mt-1 mr-2;
1318+
content: url('data:image/svg+xml,<svg fill="currentColor" class="sidebar-icon-header ___12fm75w_v8ls9a0 f1w7gpdv fez10in fg4l7m0" aria-hidden="true" width="1em" height="1em" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M2 4.5c0-.28.22-.5.5-.5h15a.5.5 0 0 1 0 1h-15a.5.5 0 0 1-.5-.5Zm0 10c0-.83.67-1.5 1.5-1.5h13a1.5 1.5 0 0 1 0 3h-13A1.5 1.5 0 0 1 2 14.5ZM3 8a1 1 0 0 0 0 2h14a1 1 0 1 0 0-2H3Z" fill="white"></path></svg>');
1319+
}
13131320

13141321

13151322
html[data-theme='dark'] .architecture_sidebar_header > div > a::before {
@@ -1794,12 +1801,12 @@ div:has(> .openapi-tabs__schema-container) {
17941801
/* CSS for hello bar ends*/
17951802

17961803

1797-
/* AI Header Button Styles - Updated Override */
1804+
/* AI Header Button Styles - Hidden by default, shown via injected style when API available */
17981805
.header-ai-chat-link {
1799-
display: none; /* Hidden by default until health check passes */
1806+
display: none; /* Hidden by default */
18001807
align-items: center;
18011808
padding: 0.4rem 1rem !important;
1802-
background-color: var(--ifm-color-primary) !important; /* Match Ask Babylon AI */
1809+
background-color: var(--ifm-color-primary) !important;
18031810
color: #fff !important;
18041811
border-radius: 8px !important;
18051812
font-weight: 700;
@@ -1809,10 +1816,7 @@ div:has(> .openapi-tabs__schema-container) {
18091816
border: none !important;
18101817
}
18111818

1812-
/* Show AI chat button only when API is healthy */
1813-
body.ai-chat-available .header-ai-chat-link {
1814-
display: flex;
1815-
}
1819+
/* Note: Button visibility is controlled by dynamically injected <style> tag in ChatWidget.tsx */
18161820

18171821
.header-ai-chat-link:hover {
18181822
filter: brightness(0.9); /* Simple darkening for hover */
@@ -1822,6 +1826,23 @@ body.ai-chat-available .header-ai-chat-link {
18221826
box-shadow: 0 4px 8px rgba(0,0,0,0.15) !important;
18231827
}
18241828

1829+
/* Dark mode: ghost/outlined button style */
1830+
html[data-theme='dark'] .header-ai-chat-link {
1831+
background-color: transparent !important;
1832+
color: rgb(var(--docs-color-primary-100)) !important;
1833+
border: 1px solid rgb(var(--docs-color-primary-100)) !important;
1834+
box-shadow: none !important;
1835+
}
1836+
1837+
html[data-theme='dark'] .header-ai-chat-link:hover {
1838+
filter: none !important;
1839+
border-color: rgb(var(--docs-color-primary-200)) !important;
1840+
color: #33C5CE !important;
1841+
background-color: transparent !important;
1842+
box-shadow: none !important;
1843+
transform: none;
1844+
}
1845+
18251846
.header-ai-chat-link::before {
18261847
content: '';
18271848
width: 20px;
@@ -1867,9 +1888,8 @@ body.ai-chat-available .header-ai-chat-link {
18671888
}
18681889
}
18691890

1870-
/* Mobile sidebar menu - show button with text */
1871-
body.ai-chat-available .navbar-sidebar .header-ai-chat-link {
1872-
display: inline-flex;
1891+
/* Mobile sidebar menu - styles only, visibility controlled by injected style */
1892+
.navbar-sidebar .header-ai-chat-link {
18731893
align-items: center;
18741894
justify-content: center;
18751895
width: auto;

src/theme/ThemedImage/index.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,20 @@ export default function ThemedImage(props) {
1818
const { colorMode } = useColorMode();
1919
const curSrc = getSrcFromThemedImageProps(props, colorMode);
2020
const { isLogo, ...restProps } = props;
21-
// Exclude navbar logo from Zoom effect
22-
if (isLogo || (typeof props.src === 'string' && props.src.includes('logo'))) {
21+
22+
// Exclude navbar/footer logo from Zoom effect
23+
// Check multiple sources for 'logo' in path
24+
const isLogoImage =
25+
isLogo ||
26+
(typeof props.src === 'string' && props.src.includes('logo')) ||
27+
(props.sources?.light && props.sources.light.includes('logo')) ||
28+
(props.sources?.dark && props.sources.dark.includes('logo')) ||
29+
(typeof curSrc === 'string' && curSrc.includes('logo'));
30+
31+
if (isLogoImage) {
2332
return <OriginalThemedImage {...restProps} />;
2433
}
34+
2535
return (
2636
<Zoom key={curSrc + colorMode}>
2737
<OriginalThemedImage {...restProps} />

0 commit comments

Comments
 (0)