Skip to content

Commit 0738b79

Browse files
🌟 feat: Enhance User Experience and SEO with Accessibility Updates and robots.txt (#5392)
* πŸ”ˆ fix: Refactor AudioRecorder to use button element for improved accessibility * πŸ”ˆ fix: Update conversation menu button ID for improved accessibility * πŸ”ˆ fix: Remove redundant role attribute from SidePanel for improved accessibility * feat: Add robots.txt to manage web crawler access * feat: Update index.html with meta description and remove legacy file * fix: resolve merge conflicts. * fix: resolve merge conflicts. * fix: resolve merge conflicts. * feat: Update index.html with meta description and remove legacy file * πŸ”§ feat: Add legacy support and improve SidePanel accessibility * πŸ”§ feat: Integrate express-static-gzip for improved static file serving and add new plugins for enhanced functionality * πŸ”§ chore: Remove unused HTML ESLint plugin configurations and dependencies --------- Co-authored-by: Ruben Talstra <[email protected]>
1 parent 026a681 commit 0738b79

File tree

11 files changed

+1723
-310
lines changed

11 files changed

+1723
-310
lines changed

β€Žapi/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"express-mongo-sanitize": "^2.2.0",
6363
"express-rate-limit": "^7.4.1",
6464
"express-session": "^1.18.1",
65+
"express-static-gzip": "^2.2.0",
6566
"file-type": "^18.7.0",
6667
"firebase": "^11.0.2",
6768
"googleapis": "^126.0.1",

β€Žapi/server/utils/staticCache.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
const express = require('express');
1+
const expressStaticGzip = require('express-static-gzip');
22

33
const oneDayInSeconds = 24 * 60 * 60;
44

55
const sMaxAge = process.env.STATIC_CACHE_S_MAX_AGE || oneDayInSeconds;
66
const maxAge = process.env.STATIC_CACHE_MAX_AGE || oneDayInSeconds * 2;
77

88
const staticCache = (staticPath) =>
9-
express.static(staticPath, {
10-
setHeaders: (res) => {
11-
if (process.env.NODE_ENV?.toLowerCase() !== 'production') {
12-
return;
9+
expressStaticGzip(staticPath, {
10+
enableBrotli: false, // disable Brotli, only using gzip
11+
orderPreference: ['gz'],
12+
setHeaders: (res, _path) => {
13+
if (process.env.NODE_ENV?.toLowerCase() === 'production') {
14+
res.setHeader('Cache-Control', `public, max-age=${maxAge}, s-maxage=${sMaxAge}`);
1315
}
14-
15-
res.setHeader('Cache-Control', `public, max-age=${maxAge}, s-maxage=${sMaxAge}`);
1616
},
1717
});
1818

β€Žclient/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<meta name="mobile-web-app-capable" content="yes" />
77
<meta name="apple-mobile-web-app-capable" content="yes" />
88
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
9+
<meta name="description" content="LibreChat - An open source chat application with support for multiple AI models" />
910
<title>LibreChat</title>
1011
<link rel="shortcut icon" href="#" />
1112
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png" />
@@ -53,6 +54,5 @@
5354
<div id="root">
5455
<div id="loading-container"></div>
5556
</div>
56-
<script type="module" src="/src/main.jsx"></script>
5757
</body>
5858
</html>

β€Žclient/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145
"typescript": "^5.3.3",
146146
"vite": "^6.1.0",
147147
"vite-plugin-node-polyfills": "^0.17.0",
148+
"vite-plugin-compression": "^0.5.1",
148149
"vite-plugin-pwa": "^0.21.1"
149150
}
150151
}

β€Žclient/public/robots.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
User-agent: *
2+
Disallow: /api/
3+
Allow: /

β€Žclient/src/components/Chat/Input/AudioRecorder.tsx

+19-11
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,25 @@ export default function AudioRecorder({
8181

8282
return (
8383
<TooltipAnchor
84-
id="audio-recorder"
85-
aria-label={localize('com_ui_use_micrphone')}
86-
onClick={isListening === true ? handleStopRecording : handleStartRecording}
87-
disabled={disabled}
88-
className={cn(
89-
'absolute flex size-[35px] items-center justify-center rounded-full p-1 transition-colors hover:bg-surface-hover',
90-
isRTL ? 'bottom-2 left-2' : 'bottom-2 right-2',
91-
)}
9284
description={localize('com_ui_use_micrphone')}
93-
>
94-
{renderIcon()}
95-
</TooltipAnchor>
85+
render={
86+
<button
87+
id="audio-recorder"
88+
type="button"
89+
aria-label={localize('com_ui_use_micrphone')}
90+
onClick={isListening === true ? handleStopRecording : handleStartRecording}
91+
disabled={disabled}
92+
className={cn(
93+
'absolute flex size-[35px] items-center justify-center rounded-full p-1 transition-colors hover:bg-surface-hover',
94+
isRTL ? 'bottom-2 left-2' : 'bottom-2 right-2',
95+
disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',
96+
)}
97+
title={localize('com_ui_use_micrphone')}
98+
aria-pressed={isListening}
99+
>
100+
{renderIcon()}
101+
</button>
102+
}
103+
/>
96104
);
97105
}

β€Žclient/src/components/Conversations/ConvoOptions/ConvoOptions.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ function ConvoOptions({
121121
setIsOpen={setIsPopoverActive}
122122
trigger={
123123
<Menu.MenuButton
124-
id="conversation-menu-button"
124+
id={`conversation-menu-${conversationId}`}
125125
aria-label={localize('com_nav_convo_menu_options')}
126126
className={cn(
127127
'z-30 inline-flex h-7 w-7 items-center justify-center gap-2 rounded-md border-none p-0 text-sm font-medium ring-ring-primary transition-all duration-200 ease-in-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',

β€Žclient/src/components/SidePanel/SidePanel.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ const SidePanel = ({
143143
id="controls-nav"
144144
order={hasArtifacts != null ? 3 : 2}
145145
aria-label={localize('com_ui_controls')}
146-
role="region"
146+
role="navigation"
147147
collapsedSize={collapsedSize}
148148
defaultSize={defaultSize}
149149
collapsible={true}

β€Žclient/vite.config.ts

+60-46
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,14 @@
11
import path, { resolve } from 'path';
22
import react from '@vitejs/plugin-react';
33
import { VitePWA } from 'vite-plugin-pwa';
4-
import { defineConfig, createLogger } from 'vite';
4+
import { defineConfig } from 'vite';
55
import { nodePolyfills } from 'vite-plugin-node-polyfills';
6+
import compression from 'vite-plugin-compression';
67
import type { Plugin } from 'vite';
78

8-
const logger = createLogger();
9-
const originalWarning = logger.warn;
10-
logger.warn = (msg, options) => {
11-
/* Suppresses:
12-
[vite:css] Complex selectors in '.group:focus-within .dark\:group-focus-within\:text-gray-300:is(.dark *)' can not be transformed to an equivalent selector without ':is()'.
13-
*/
14-
if (msg.includes('vite:css') && msg.includes('^^^^^^^')) {
15-
return;
16-
}
17-
/* Suppresses:
18-
(!) Some chunks are larger than 500 kB after minification. Consider:
19-
- Using dynamic import() to code-split the application
20-
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
21-
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
22-
*/
23-
if (msg.includes('Use build.rollupOptions.output.manualChunks')) {
24-
return;
25-
}
26-
originalWarning(msg, options);
27-
};
28-
299
// https://vitejs.dev/config/
3010
export default defineConfig({
31-
customLogger: logger,
3211
server: {
33-
fs: {
34-
cachedChecks: false,
35-
},
3612
host: 'localhost',
3713
port: 3090,
3814
strictPort: false,
@@ -47,7 +23,7 @@ export default defineConfig({
4723
},
4824
},
4925
},
50-
// All other env variables are filtered out
26+
// Set the directory where environment variables are loaded from and restrict prefixes
5127
envDir: '../',
5228
envPrefix: ['VITE_', 'SCRIPT_', 'DOMAIN_', 'ALLOW_'],
5329
plugins: [
@@ -57,11 +33,14 @@ export default defineConfig({
5733
injectRegister: 'auto', // 'auto' | 'manual' | 'disabled'
5834
registerType: 'autoUpdate', // 'prompt' | 'autoUpdate'
5935
devOptions: {
60-
enabled: false, // enable/disable registering SW in development mode
36+
enabled: false, // disable service worker registration in development mode
6137
},
6238
useCredentials: true,
6339
workbox: {
64-
globPatterns: ['assets/**/*.{png,jpg,svg,ico}', '**/*.{js,css,html,ico,woff2}'],
40+
globPatterns: [
41+
'assets/**/*.{png,jpg,svg,ico}',
42+
'**/*.{js,css,html,ico,woff2}',
43+
],
6544
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
6645
navigateFallbackDenylist: [/^\/oauth/],
6746
},
@@ -103,33 +82,70 @@ export default defineConfig({
10382
},
10483
}),
10584
sourcemapExclude({ excludeNodeModules: true }),
85+
compression({
86+
verbose: true,
87+
disable: false,
88+
threshold: 10240, // compress files larger than 10KB
89+
algorithm: 'gzip',
90+
ext: '.gz',
91+
}),
10692
],
10793
publicDir: './public',
10894
build: {
10995
sourcemap: process.env.NODE_ENV === 'development',
11096
outDir: './dist',
97+
minify: 'terser',
11198
rollupOptions: {
99+
preserveEntrySignatures: 'strict',
112100
// external: ['uuid'],
113101
output: {
114-
manualChunks: (id) => {
115-
if (id.includes('node_modules/highlight.js')) {
116-
return 'markdown_highlight';
117-
}
118-
// if (id.includes('node_modules/hast-util-raw')) {
119-
// return 'markdown_large';
120-
// }
121-
// if (id.includes('node_modules/katex')) {
122-
// return 'markdown_large';
123-
// }
102+
manualChunks(id: string) {
124103
if (id.includes('node_modules')) {
104+
// Group Radix UI libraries together.
105+
if (id.includes('@radix-ui')) {
106+
return 'radix-ui';
107+
}
108+
// Group framer-motion separately.
109+
if (id.includes('framer-motion')) {
110+
return 'framer-motion';
111+
}
112+
// Group markdown-related libraries.
113+
if (id.includes('node_modules/highlight.js')) {
114+
return 'markdown_highlight';
115+
}
116+
// if (
117+
// id.includes('node_modules/hast-util-raw') ||
118+
// id.includes('node_modules/katex')
119+
// ) {
120+
// return 'markdown_large';
121+
// }
122+
// Group TanStack libraries together.
123+
if (id.includes('@tanstack')) {
124+
return 'tanstack-vendor';
125+
}
126+
// Additional grouping for other node_modules:
127+
if (id.includes('@headlessui')) {
128+
return 'headlessui';
129+
}
130+
131+
// Everything else falls into a generic vendor chunk.
125132
return 'vendor';
126133
}
134+
// Create a separate chunk for all locale files under src/locales.
135+
if (id.includes(path.join('src', 'locales'))) {
136+
return 'locales';
137+
}
138+
// Let Rollup decide automatically for any other files.
139+
return null;
127140
},
128141
entryFileNames: 'assets/[name].[hash].js',
129142
chunkFileNames: 'assets/[name].[hash].js',
130143
assetFileNames: (assetInfo) => {
131-
if (assetInfo.name && /\.(woff|woff2|eot|ttf|otf)$/.test(assetInfo.name)) {
132-
return 'assets/[name][extname]';
144+
if (
145+
assetInfo.names &&
146+
/\.(woff|woff2|eot|ttf|otf)$/.test(assetInfo.names)
147+
) {
148+
return 'assets/fonts/[name][extname]';
133149
}
134150
return 'assets/[name].[hash][extname]';
135151
},
@@ -139,15 +155,13 @@ export default defineConfig({
139155
* @see {@link https://github.com/TanStack/query/pull/5161#issuecomment-1477389761 Preserve 'use client' directives TanStack/query#5161}
140156
*/
141157
onwarn(warning, warn) {
142-
if (
143-
// warning.code === 'MODULE_LEVEL_DIRECTIVE' &&
144-
warning.message.includes('Error when using sourcemap')
145-
) {
158+
if (warning.message.includes('Error when using sourcemap')) {
146159
return;
147160
}
148161
warn(warning);
149162
},
150163
},
164+
chunkSizeWarningLimit: 1200,
151165
},
152166
resolve: {
153167
alias: {
@@ -173,4 +187,4 @@ export function sourcemapExclude(opts?: SourcemapExclude): Plugin {
173187
}
174188
},
175189
};
176-
}
190+
}

β€Žindex.html

-49
This file was deleted.

0 commit comments

Comments
Β (0)