mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🌟 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 <RubenTalstra1211@outlook.com>
This commit is contained in:
parent
96c091c550
commit
b404e372ec
11 changed files with 1757 additions and 344 deletions
|
|
@ -61,6 +61,7 @@
|
||||||
"express-mongo-sanitize": "^2.2.0",
|
"express-mongo-sanitize": "^2.2.0",
|
||||||
"express-rate-limit": "^7.4.1",
|
"express-rate-limit": "^7.4.1",
|
||||||
"express-session": "^1.18.1",
|
"express-session": "^1.18.1",
|
||||||
|
"express-static-gzip": "^2.2.0",
|
||||||
"file-type": "^18.7.0",
|
"file-type": "^18.7.0",
|
||||||
"firebase": "^11.0.2",
|
"firebase": "^11.0.2",
|
||||||
"googleapis": "^126.0.1",
|
"googleapis": "^126.0.1",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const express = require('express');
|
const expressStaticGzip = require('express-static-gzip');
|
||||||
|
|
||||||
const oneDayInSeconds = 24 * 60 * 60;
|
const oneDayInSeconds = 24 * 60 * 60;
|
||||||
|
|
||||||
|
|
@ -6,13 +6,13 @@ const sMaxAge = process.env.STATIC_CACHE_S_MAX_AGE || oneDayInSeconds;
|
||||||
const maxAge = process.env.STATIC_CACHE_MAX_AGE || oneDayInSeconds * 2;
|
const maxAge = process.env.STATIC_CACHE_MAX_AGE || oneDayInSeconds * 2;
|
||||||
|
|
||||||
const staticCache = (staticPath) =>
|
const staticCache = (staticPath) =>
|
||||||
express.static(staticPath, {
|
expressStaticGzip(staticPath, {
|
||||||
setHeaders: (res) => {
|
enableBrotli: false, // disable Brotli, only using gzip
|
||||||
if (process.env.NODE_ENV?.toLowerCase() !== 'production') {
|
orderPreference: ['gz'],
|
||||||
return;
|
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}`);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-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="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>
|
<title>LibreChat</title>
|
||||||
<link rel="shortcut icon" href="#" />
|
<link rel="shortcut icon" href="#" />
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png" />
|
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png" />
|
||||||
|
|
@ -53,6 +54,5 @@
|
||||||
<div id="root">
|
<div id="root">
|
||||||
<div id="loading-container"></div>
|
<div id="loading-container"></div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="/src/main.jsx"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,7 @@
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^6.1.0",
|
"vite": "^6.1.0",
|
||||||
"vite-plugin-node-polyfills": "^0.17.0",
|
"vite-plugin-node-polyfills": "^0.17.0",
|
||||||
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-pwa": "^0.21.1"
|
"vite-plugin-pwa": "^0.21.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
client/public/robots.txt
Normal file
3
client/public/robots.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /api/
|
||||||
|
Allow: /
|
||||||
|
|
@ -81,17 +81,25 @@ export default function AudioRecorder({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipAnchor
|
<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')}
|
description={localize('com_ui_use_micrphone')}
|
||||||
>
|
render={
|
||||||
{renderIcon()}
|
<button
|
||||||
</TooltipAnchor>
|
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>
|
||||||
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ function ConvoOptions({
|
||||||
setIsOpen={setIsPopoverActive}
|
setIsOpen={setIsPopoverActive}
|
||||||
trigger={
|
trigger={
|
||||||
<Menu.MenuButton
|
<Menu.MenuButton
|
||||||
id="conversation-menu-button"
|
id={`conversation-menu-${conversationId}`}
|
||||||
aria-label={localize('com_nav_convo_menu_options')}
|
aria-label={localize('com_nav_convo_menu_options')}
|
||||||
className={cn(
|
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',
|
'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',
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ const SidePanel = ({
|
||||||
id="controls-nav"
|
id="controls-nav"
|
||||||
order={hasArtifacts != null ? 3 : 2}
|
order={hasArtifacts != null ? 3 : 2}
|
||||||
aria-label={localize('com_ui_controls')}
|
aria-label={localize('com_ui_controls')}
|
||||||
role="region"
|
role="navigation"
|
||||||
collapsedSize={collapsedSize}
|
collapsedSize={collapsedSize}
|
||||||
defaultSize={defaultSize}
|
defaultSize={defaultSize}
|
||||||
collapsible={true}
|
collapsible={true}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,14 @@
|
||||||
import path, { resolve } from 'path';
|
import path, { resolve } from 'path';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import { VitePWA } from 'vite-plugin-pwa';
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
import { defineConfig, createLogger } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
||||||
|
import compression from 'vite-plugin-compression';
|
||||||
import type { Plugin } from 'vite';
|
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/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
customLogger: logger,
|
|
||||||
server: {
|
server: {
|
||||||
fs: {
|
|
||||||
cachedChecks: false,
|
|
||||||
},
|
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: 3090,
|
port: 3090,
|
||||||
strictPort: false,
|
strictPort: false,
|
||||||
|
|
@ -47,7 +23,7 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// All other env variables are filtered out
|
// Set the directory where environment variables are loaded from and restrict prefixes
|
||||||
envDir: '../',
|
envDir: '../',
|
||||||
envPrefix: ['VITE_', 'SCRIPT_', 'DOMAIN_', 'ALLOW_'],
|
envPrefix: ['VITE_', 'SCRIPT_', 'DOMAIN_', 'ALLOW_'],
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
@ -57,11 +33,14 @@ export default defineConfig({
|
||||||
injectRegister: 'auto', // 'auto' | 'manual' | 'disabled'
|
injectRegister: 'auto', // 'auto' | 'manual' | 'disabled'
|
||||||
registerType: 'autoUpdate', // 'prompt' | 'autoUpdate'
|
registerType: 'autoUpdate', // 'prompt' | 'autoUpdate'
|
||||||
devOptions: {
|
devOptions: {
|
||||||
enabled: false, // enable/disable registering SW in development mode
|
enabled: false, // disable service worker registration in development mode
|
||||||
},
|
},
|
||||||
useCredentials: true,
|
useCredentials: true,
|
||||||
workbox: {
|
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,
|
maximumFileSizeToCacheInBytes: 4 * 1024 * 1024,
|
||||||
navigateFallbackDenylist: [/^\/oauth/],
|
navigateFallbackDenylist: [/^\/oauth/],
|
||||||
},
|
},
|
||||||
|
|
@ -103,33 +82,70 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
sourcemapExclude({ excludeNodeModules: true }),
|
sourcemapExclude({ excludeNodeModules: true }),
|
||||||
|
compression({
|
||||||
|
verbose: true,
|
||||||
|
disable: false,
|
||||||
|
threshold: 10240, // compress files larger than 10KB
|
||||||
|
algorithm: 'gzip',
|
||||||
|
ext: '.gz',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
publicDir: './public',
|
publicDir: './public',
|
||||||
build: {
|
build: {
|
||||||
sourcemap: process.env.NODE_ENV === 'development',
|
sourcemap: process.env.NODE_ENV === 'development',
|
||||||
outDir: './dist',
|
outDir: './dist',
|
||||||
|
minify: 'terser',
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
|
preserveEntrySignatures: 'strict',
|
||||||
// external: ['uuid'],
|
// external: ['uuid'],
|
||||||
output: {
|
output: {
|
||||||
manualChunks: (id) => {
|
manualChunks(id: string) {
|
||||||
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';
|
|
||||||
}
|
|
||||||
if (id.includes('node_modules')) {
|
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';
|
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',
|
entryFileNames: 'assets/[name].[hash].js',
|
||||||
chunkFileNames: 'assets/[name].[hash].js',
|
chunkFileNames: 'assets/[name].[hash].js',
|
||||||
assetFileNames: (assetInfo) => {
|
assetFileNames: (assetInfo) => {
|
||||||
if (assetInfo.name && /\.(woff|woff2|eot|ttf|otf)$/.test(assetInfo.name)) {
|
if (
|
||||||
return 'assets/[name][extname]';
|
assetInfo.names &&
|
||||||
|
/\.(woff|woff2|eot|ttf|otf)$/.test(assetInfo.names)
|
||||||
|
) {
|
||||||
|
return 'assets/fonts/[name][extname]';
|
||||||
}
|
}
|
||||||
return 'assets/[name].[hash][extname]';
|
return 'assets/[name].[hash][extname]';
|
||||||
},
|
},
|
||||||
|
|
@ -139,15 +155,13 @@ export default defineConfig({
|
||||||
* @see {@link https://github.com/TanStack/query/pull/5161#issuecomment-1477389761 Preserve 'use client' directives TanStack/query#5161}
|
* @see {@link https://github.com/TanStack/query/pull/5161#issuecomment-1477389761 Preserve 'use client' directives TanStack/query#5161}
|
||||||
*/
|
*/
|
||||||
onwarn(warning, warn) {
|
onwarn(warning, warn) {
|
||||||
if (
|
if (warning.message.includes('Error when using sourcemap')) {
|
||||||
// warning.code === 'MODULE_LEVEL_DIRECTIVE' &&
|
|
||||||
warning.message.includes('Error when using sourcemap')
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
warn(warning);
|
warn(warning);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
chunkSizeWarningLimit: 1200,
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|
@ -173,4 +187,4 @@ export function sourcemapExclude(opts?: SourcemapExclude): Plugin {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
49
index.html
49
index.html
|
|
@ -1,49 +0,0 @@
|
||||||
<!-- v0.7.7-rc1 -->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="theme-color" content="#171717">
|
|
||||||
<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">
|
|
||||||
<title>LibreChat</title>
|
|
||||||
<link
|
|
||||||
rel="shortcut icon"
|
|
||||||
href="#"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
sizes="32x32"
|
|
||||||
href="/assets/favicon-32x32.png"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
sizes="16x16"
|
|
||||||
href="/assets/favicon-16x16.png"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="apple-touch-icon"
|
|
||||||
href="/assets/apple-touch-icon-180x180.png"
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1"
|
|
||||||
/>
|
|
||||||
<script
|
|
||||||
defer
|
|
||||||
type="module"
|
|
||||||
src="/client/src/main.jsx"
|
|
||||||
></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
|
|
||||||
<script
|
|
||||||
type="module"
|
|
||||||
src="/client/src/main.jsx"
|
|
||||||
></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
1891
package-lock.json
generated
1891
package-lock.json
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue