Skip to content

🌟 feat: Enhance User Experience and SEO with Accessibility Updates and robots.txt #5392

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Feb 22, 2025
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c10d96e
πŸ”ˆ fix: Refactor AudioRecorder to use button element for improved acce…
berry-13 Jan 21, 2025
70b5ccd
πŸ”ˆ fix: Update conversation menu button ID for improved accessibility
berry-13 Jan 21, 2025
7899b84
πŸ”ˆ fix: Remove redundant role attribute from SidePanel for improved ac…
berry-13 Jan 21, 2025
d1b500b
feat: Add robots.txt to manage web crawler access
berry-13 Jan 21, 2025
4b9b53e
feat: Update index.html with meta description and remove legacy file
berry-13 Jan 21, 2025
bccc7b0
fix: resolve merge conflicts.
rubentalstra Feb 12, 2025
7748cc3
fix: resolve merge conflicts.
rubentalstra Feb 12, 2025
4ce654b
Merge branch 'main' into fix/seo-and-accessibility
rubentalstra Feb 12, 2025
a281b89
fix: resolve merge conflicts.
rubentalstra Feb 12, 2025
ceac354
feat: Update index.html with meta description and remove legacy file
rubentalstra Feb 12, 2025
7704cfa
Merge branch 'main' into fix/seo-and-accessibility
rubentalstra Feb 12, 2025
2edb39e
Merge branch 'main' into fix/seo-and-accessibility
rubentalstra Feb 17, 2025
4fd0710
Merge branch 'main' into fix/seo-and-accessibility
rubentalstra Feb 22, 2025
1b05769
πŸ”§ feat: Add legacy support and improve SidePanel accessibility
rubentalstra Feb 22, 2025
9a43696
πŸ”§ feat: Integrate express-static-gzip for improved static file servin…
rubentalstra Feb 22, 2025
dc87883
πŸ”§ chore: Remove unused HTML ESLint plugin configurations and dependen…
rubentalstra Feb 22, 2025
095850e
Merge branch 'main' into fix/seo-and-accessibility
rubentalstra Feb 22, 2025
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
1 change: 1 addition & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"express-mongo-sanitize": "^2.2.0",
"express-rate-limit": "^7.4.1",
"express-session": "^1.18.1",
"express-static-gzip": "^2.2.0",
"file-type": "^18.7.0",
"firebase": "^11.0.2",
"googleapis": "^126.0.1",
Expand Down
14 changes: 7 additions & 7 deletions api/server/utils/staticCache.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
const express = require('express');
const expressStaticGzip = require('express-static-gzip');

const oneDayInSeconds = 24 * 60 * 60;

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

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

res.setHeader('Cache-Control', `public, max-age=${maxAge}, s-maxage=${sMaxAge}`);
},
});

Expand Down
2 changes: 1 addition & 1 deletion client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="description" content="LibreChat - An open source chat application with support for multiple AI models" />
<title>LibreChat</title>
<link rel="shortcut icon" href="#" />
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png" />
Expand Down Expand Up @@ -53,6 +54,5 @@
<div id="root">
<div id="loading-container"></div>
</div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
2 changes: 2 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,13 @@
"postcss": "^8.4.31",
"postcss-loader": "^7.1.0",
"postcss-preset-env": "^8.2.0",
"rollup-plugin-visualizer": "^5.14.0",
"tailwindcss": "^3.4.1",
"ts-jest": "^29.2.5",
"typescript": "^5.3.3",
"vite": "^6.1.0",
"vite-plugin-node-polyfills": "^0.17.0",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-pwa": "^0.21.1"
}
}
3 changes: 3 additions & 0 deletions client/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
User-agent: *
Disallow: /api/
Allow: /
30 changes: 19 additions & 11 deletions client/src/components/Chat/Input/AudioRecorder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,25 @@ export default function AudioRecorder({

return (
<TooltipAnchor
id="audio-recorder"
aria-label={localize('com_ui_use_micrphone')}
onClick={isListening === true ? handleStopRecording : handleStartRecording}
disabled={disabled}
className={cn(
'absolute flex size-[35px] items-center justify-center rounded-full p-1 transition-colors hover:bg-surface-hover',
isRTL ? 'bottom-2 left-2' : 'bottom-2 right-2',
)}
description={localize('com_ui_use_micrphone')}
>
{renderIcon()}
</TooltipAnchor>
render={
<button
id="audio-recorder"
type="button"
aria-label={localize('com_ui_use_micrphone')}
onClick={isListening === true ? handleStopRecording : handleStartRecording}
disabled={disabled}
className={cn(
'absolute flex size-[35px] items-center justify-center rounded-full p-1 transition-colors hover:bg-surface-hover',
isRTL ? 'bottom-2 left-2' : 'bottom-2 right-2',
disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer',
)}
title={localize('com_ui_use_micrphone')}
aria-pressed={isListening}
>
{renderIcon()}
</button>
}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ function ConvoOptions({
setIsOpen={setIsPopoverActive}
trigger={
<Menu.MenuButton
id="conversation-menu-button"
id={`conversation-menu-${conversationId}`}
aria-label={localize('com_nav_convo_menu_options')}
className={cn(
'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',
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/SidePanel/SidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ const SidePanel = ({
id="controls-nav"
order={hasArtifacts != null ? 3 : 2}
aria-label={localize('com_ui_controls')}
role="region"
role="navigation"
collapsedSize={collapsedSize}
defaultSize={defaultSize}
collapsible={true}
Expand Down
112 changes: 66 additions & 46 deletions client/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,15 @@
import path, { resolve } from 'path';
import react from '@vitejs/plugin-react';
import { VitePWA } from 'vite-plugin-pwa';
import { defineConfig, createLogger } from 'vite';
import { defineConfig } from 'vite';
// import { visualizer } from 'rollup-plugin-visualizer';
import { nodePolyfills } from 'vite-plugin-node-polyfills';
import compression from 'vite-plugin-compression';
import type { Plugin } from 'vite';

const logger = createLogger();
const originalWarning = logger.warn;
logger.warn = (msg, options) => {
/* Suppresses:
[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()'.
*/
if (msg.includes('vite:css') && msg.includes('^^^^^^^')) {
return;
}
/* Suppresses:
(!) Some chunks are larger than 500 kB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
*/
if (msg.includes('Use build.rollupOptions.output.manualChunks')) {
return;
}
originalWarning(msg, options);
};

// https://vitejs.dev/config/
export default defineConfig({
customLogger: logger,
server: {
fs: {
cachedChecks: false,
},
host: 'localhost',
port: 3090,
strictPort: false,
Expand All @@ -47,7 +24,7 @@ export default defineConfig({
},
},
},
// All other env variables are filtered out
// Set the directory where environment variables are loaded from and restrict prefixes
envDir: '../',
envPrefix: ['VITE_', 'SCRIPT_', 'DOMAIN_', 'ALLOW_'],
plugins: [
Expand All @@ -57,11 +34,14 @@ export default defineConfig({
injectRegister: 'auto', // 'auto' | 'manual' | 'disabled'
registerType: 'autoUpdate', // 'prompt' | 'autoUpdate'
devOptions: {
enabled: false, // enable/disable registering SW in development mode
enabled: false, // disable service worker registration in development mode
},
useCredentials: true,
workbox: {
globPatterns: ['assets/**/*.{png,jpg,svg,ico}', '**/*.{js,css,html,ico,woff2}'],
globPatterns: [
'assets/**/*.{png,jpg,svg,ico}',
'**/*.{js,css,html,ico,woff2}',
],
maximumFileSizeToCacheInBytes: 4 * 1024 * 1024,
navigateFallbackDenylist: [/^\/oauth/],
},
Expand Down Expand Up @@ -98,33 +78,75 @@ export default defineConfig({
},
}),
sourcemapExclude({ excludeNodeModules: true }),
// visualizer({
// filename: 'stats.html',
// template: 'treemap',
// gzipSize: true,
// }),
compression({
verbose: true,
disable: false,
threshold: 10240, // compress files larger than 10KB
algorithm: 'gzip',
ext: '.gz',
}),
],
publicDir: './public',
build: {
sourcemap: process.env.NODE_ENV === 'development',
outDir: './dist',
minify: 'terser',
rollupOptions: {
preserveEntrySignatures: 'strict',
// external: ['uuid'],
output: {
manualChunks: (id) => {
if (id.includes('node_modules/highlight.js')) {
return 'markdown_highlight';
}
if (id.includes('node_modules/hast-util-raw')) {
return 'markdown_large';
}
if (id.includes('node_modules/katex')) {
return 'markdown_large';
}
manualChunks(id: string) {
if (id.includes('node_modules')) {
// Group Radix UI libraries together.
if (id.includes('@radix-ui')) {
return 'radix-ui';
}
// Group framer-motion separately.
if (id.includes('framer-motion')) {
return 'framer-motion';
}
// Group markdown-related libraries.
if (id.includes('node_modules/highlight.js')) {
return 'markdown_highlight';
}
if (
id.includes('node_modules/hast-util-raw') ||
id.includes('node_modules/katex')
) {
return 'markdown_large';
}
// Group TanStack libraries together.
if (id.includes('@tanstack')) {
return 'tanstack-vendor';
}
// Additional grouping for other node_modules:
if (id.includes('@headlessui')) {
return 'headlessui';
}

// Everything else falls into a generic vendor chunk.
return 'vendor';
}
// Create a separate chunk for all locale files under src/locales.
if (id.includes(path.join('src', 'locales'))) {
return 'locales';
}
// Let Rollup decide automatically for any other files.
return null;
},
entryFileNames: 'assets/[name].[hash].js',
chunkFileNames: 'assets/[name].[hash].js',
assetFileNames: (assetInfo) => {
if (assetInfo.name && /\.(woff|woff2|eot|ttf|otf)$/.test(assetInfo.name)) {
return 'assets/[name][extname]';
if (
assetInfo.names &&
/\.(woff|woff2|eot|ttf|otf)$/.test(assetInfo.names)
) {
return 'assets/fonts/[name][extname]';
}
return 'assets/[name].[hash][extname]';
},
Expand All @@ -134,15 +156,13 @@ export default defineConfig({
* @see {@link https://github.com/TanStack/query/pull/5161#issuecomment-1477389761 Preserve 'use client' directives TanStack/query#5161}
*/
onwarn(warning, warn) {
if (
// warning.code === 'MODULE_LEVEL_DIRECTIVE' &&
warning.message.includes('Error when using sourcemap')
) {
if (warning.message.includes('Error when using sourcemap')) {
return;
}
warn(warning);
},
},
chunkSizeWarningLimit: 1200,
},
resolve: {
alias: {
Expand All @@ -168,4 +188,4 @@ export function sourcemapExclude(opts?: SourcemapExclude): Plugin {
}
},
};
}
}
19 changes: 19 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import reactHooks from 'eslint-plugin-react-hooks';
import tsParser from '@typescript-eslint/parser';
import importPlugin from 'eslint-plugin-import';
import { FlatCompat } from '@eslint/eslintrc';
// import html from '@html-eslint/eslint-plugin';
// import htmlParser from '@html-eslint/parser';
import jsxA11Y from 'eslint-plugin-jsx-a11y';
import i18next from 'eslint-plugin-i18next';
import react from 'eslint-plugin-react';
Expand All @@ -23,6 +25,21 @@ const compat = new FlatCompat({
});

export default [
// {
// ...html.configs['flat/recommended'],
// files: ['**/*.html'],
// plugins: {
// '@html-eslint': html,
// },
// languageOptions: {
// parser: htmlParser,
// parserOptions: {
// templateEngineSyntax: {
// '{{': '}}',
// },
// },
// },
// },
{
ignores: [
'client/dist/**/*',
Expand Down Expand Up @@ -59,6 +76,7 @@ export default [
import: importPlugin,
'jsx-a11y': fixupPluginRules(jsxA11Y),
'import/parsers': tsParser,
// '@html-eslint': html,
i18next,
// perfectionist,
},
Expand Down Expand Up @@ -307,6 +325,7 @@ export default [
'no-constant-binary-expression': 'off',
'import/no-cycle': 'off',
'no-nested-ternary': 'off',
// '@html-eslint/require-explicit-size': 'error',
},
},
{
Expand Down
Loading
Loading