mirror of
https://github.com/LibreChat-AI/librechat.ai.git
synced 2026-03-27 02:38:32 +07:00
chore: bump dev packages, linting, logos (#521)
* chore: upgrade eslint to v9 * chore: update package dependencies in package.json and pnpm-lock.yaml - Added `minimatch` and `serialize-javascript` dependencies with updated versions. - Upgraded `ajv` to version 6.14.0. - Removed outdated dependencies from pnpm-lock.yaml for better package management. * feat: add Stripe logos to Companies section - Introduced new company entry for Stripe in the Companies component, including both light and dark logo variants. - Updated the Companies array to display 10 logos instead of 8. - Adjusted TypeScript environment reference to point to the development types directory.
This commit is contained in:
249
.eslintrc.cjs
249
.eslintrc.cjs
@@ -1,249 +0,0 @@
|
||||
const TAILWIND_CONFIG = {
|
||||
extends: ['plugin:tailwindcss/recommended'],
|
||||
rules: {
|
||||
'tailwindcss/classnames-order': 'off', // conflicts with prettier-plugin-tailwindcss
|
||||
'tailwindcss/enforces-negative-arbitrary-values': 'error',
|
||||
'tailwindcss/enforces-shorthand': 'error',
|
||||
'tailwindcss/migration-from-tailwind-2': 'error',
|
||||
'tailwindcss/no-custom-classname': 'error',
|
||||
},
|
||||
}
|
||||
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
root: true,
|
||||
reportUnusedDisableDirectives: true,
|
||||
ignorePatterns: ['next-env.d.ts'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:@next/next/recommended',
|
||||
'plugin:import/typescript',
|
||||
'prettier',
|
||||
],
|
||||
overrides: [
|
||||
// Rules for all files
|
||||
{
|
||||
files: '**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}',
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:import/typescript',
|
||||
'prettier',
|
||||
],
|
||||
plugins: ['import', 'unicorn'],
|
||||
rules: {
|
||||
'prefer-object-has-own': 'error',
|
||||
'logical-assignment-operators': ['error', 'always', { enforceForIfStatements: true }],
|
||||
'no-else-return': ['error', { allowElseIf: false }],
|
||||
'no-lonely-if': 'error',
|
||||
'prefer-destructuring': ['error', { VariableDeclarator: { object: true } }],
|
||||
'import/no-duplicates': 'error',
|
||||
'no-negated-condition': 'off',
|
||||
'unicorn/no-negated-condition': 'error',
|
||||
'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }],
|
||||
'object-shorthand': ['error', 'always'],
|
||||
'unicorn/prefer-regexp-test': 'error',
|
||||
'unicorn/no-array-for-each': 'error',
|
||||
'unicorn/prefer-string-replace-all': 'error',
|
||||
'@typescript-eslint/prefer-for-of': 'error',
|
||||
"no-sharp-comments": "off",
|
||||
"markdown/no-sharp-comments": "off",
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_' }],
|
||||
// todo: enable
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
},
|
||||
},
|
||||
// Rules for React files
|
||||
{
|
||||
files: '{packages,examples,docs}/**',
|
||||
extends: [
|
||||
'plugin:react/recommended',
|
||||
'plugin:react/jsx-runtime',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
rules: {
|
||||
'react/prop-types': 'off',
|
||||
'react/no-unknown-property': ['error', { ignore: ['jsx'] }],
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
'react/self-closing-comp': 'error',
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
// ❌ useMemo(…, [])
|
||||
selector:
|
||||
'CallExpression[callee.name=useMemo][arguments.1.type=ArrayExpression][arguments.1.elements.length=0]',
|
||||
message:
|
||||
"`useMemo` with an empty dependency array can't provide a stable reference, use `useRef` instead.",
|
||||
},
|
||||
{
|
||||
// ❌ z.object(…)
|
||||
selector: 'MemberExpression[object.name=z] > .property[name=object]',
|
||||
message: 'Use z.strictObject is more safe.',
|
||||
},
|
||||
],
|
||||
'react/jsx-filename-extension': [
|
||||
'error',
|
||||
{ extensions: ['.tsx', '.jsx'], allow: 'as-needed' },
|
||||
],
|
||||
'react/jsx-curly-brace-presence': 'error',
|
||||
'react/jsx-boolean-value': 'error',
|
||||
},
|
||||
settings: {
|
||||
react: { version: 'detect' },
|
||||
},
|
||||
},
|
||||
// Rules for TypeScript files
|
||||
{
|
||||
files: '**/*.{ts,tsx,cts,mts}',
|
||||
extends: [
|
||||
// TODO: fix errors
|
||||
// 'plugin:@typescript-eslint/recommended-requiring-type-checking'
|
||||
],
|
||||
parserOptions: {
|
||||
project: ['tsconfig.json'],
|
||||
tsconfigRootDir: __dirname
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
||||
// '@typescript-eslint/consistent-type-imports': 'error',
|
||||
'@typescript-eslint/non-nullable-type-assertion-style': 'error',
|
||||
'@typescript-eslint/prefer-optional-chain': 'error',
|
||||
},
|
||||
},
|
||||
// ⚙️ nextra-theme-docs
|
||||
{
|
||||
...TAILWIND_CONFIG,
|
||||
files: 'packages/nextra-theme-docs/**',
|
||||
plugins: ['typescript-sort-keys'],
|
||||
settings: {
|
||||
tailwindcss: {
|
||||
config: 'packages/nextra-theme-docs/tailwind.config.js',
|
||||
callees: ['cn'],
|
||||
whitelist: [
|
||||
'nextra-breadcrumb',
|
||||
'nextra-bleed',
|
||||
'nextra-menu-desktop',
|
||||
'nextra-menu-mobile',
|
||||
],
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
...TAILWIND_CONFIG.rules,
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
name: 'next/link',
|
||||
message: 'Use local <Anchor /> instead',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// ⚙️ nextra-theme-blog
|
||||
{
|
||||
...TAILWIND_CONFIG,
|
||||
files: 'packages/nextra-theme-blog/**',
|
||||
settings: {
|
||||
tailwindcss: {
|
||||
config: 'packages/nextra-theme-blog/tailwind.config.js',
|
||||
whitelist: ['subheading-', 'post-item', 'post-item-more'],
|
||||
},
|
||||
},
|
||||
},
|
||||
// ⚙️ nextra
|
||||
{
|
||||
...TAILWIND_CONFIG,
|
||||
files: 'packages/nextra/**',
|
||||
settings: {
|
||||
tailwindcss: {
|
||||
config: 'packages/nextra-theme-docs/tailwind.config.js',
|
||||
callees: ['cn'],
|
||||
whitelist: ['nextra-code-block', 'nextra-filetree'],
|
||||
},
|
||||
},
|
||||
},
|
||||
// ⚙️ Docs
|
||||
{
|
||||
...TAILWIND_CONFIG,
|
||||
files: 'docs/**',
|
||||
settings: {
|
||||
tailwindcss: {
|
||||
config: 'docs/tailwind.config.js',
|
||||
callees: ['cn'],
|
||||
whitelist: ['dash-ring', 'theme-1', 'theme-2', 'theme-3', 'theme-4'],
|
||||
},
|
||||
next: { rootDir: 'docs' },
|
||||
},
|
||||
},
|
||||
// ⚙️ SWR-site example
|
||||
{
|
||||
...TAILWIND_CONFIG,
|
||||
files: 'examples/swr-site/**',
|
||||
settings: {
|
||||
tailwindcss: {
|
||||
config: 'examples/swr-site/tailwind.config.js',
|
||||
},
|
||||
next: { rootDir: 'examples/swr-site' },
|
||||
},
|
||||
},
|
||||
// ⚙️ blog example
|
||||
{
|
||||
files: 'examples/blog/**',
|
||||
settings: {
|
||||
next: { rootDir: 'examples/blog' },
|
||||
},
|
||||
},
|
||||
// ⚙️ docs example
|
||||
{
|
||||
files: 'examples/docs/**',
|
||||
settings: {
|
||||
next: { rootDir: 'examples/docs' },
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'prettier.config.js',
|
||||
'postcss.config.js',
|
||||
'tailwind.config.js',
|
||||
'next.config.js',
|
||||
'next.config.mjs',
|
||||
'.eslintrc.cjs',
|
||||
],
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: 'packages/{nextra,nextra-theme-docs,nextra-theme-blog}/**',
|
||||
rules: {
|
||||
// disable rule because we don't have pagesDir in above folders
|
||||
'@next/next/no-html-link-for-pages': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: 'packages/nextra/src/**',
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
{
|
||||
group: ['fs', 'node:fs'],
|
||||
message: 'Use `graceful-fs` instead',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.d.ts'],
|
||||
rules: {
|
||||
'no-var': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -46,7 +46,7 @@ export function ChangelogFeed({ entries }: { entries: ChangelogEntry[] }) {
|
||||
|
||||
<section aria-label="Changelog entries" className="relative">
|
||||
{/* Timeline line */}
|
||||
<div className="absolute left-[7px] top-2 bottom-2 w-px bg-border" aria-hidden="true" />
|
||||
<div className="absolute left-[7px] inset-y-2 w-px bg-border" aria-hidden="true" />
|
||||
|
||||
<ol className="space-y-6">
|
||||
{filtered.map((entry) => (
|
||||
|
||||
@@ -162,6 +162,14 @@ const companies = [
|
||||
height: 'h-14',
|
||||
imgHeight: 56,
|
||||
},
|
||||
{
|
||||
name: 'Stripe',
|
||||
logoLight: '/images/logos/Stripe wordmark - Slate.svg',
|
||||
logoDark: '/images/logos/Stripe wordmark - White.svg',
|
||||
isSvg: true,
|
||||
height: 'h-10',
|
||||
imgHeight: 40,
|
||||
},
|
||||
]
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
|
||||
@@ -29,7 +29,7 @@ const AuthorCard: React.FC<{ author: AuthorMetadata }> = ({ author }) => {
|
||||
|
||||
return (
|
||||
<Link href={`/authors/${author.authorid}`}>
|
||||
<div className="flex flex-col items-center gap-4 bg-gray-200 bg-opacity-20 rounded-lg p-6 h-full group">
|
||||
<div className="flex flex-col items-center gap-4 bg-gray-200/20 rounded-lg p-6 h-full group">
|
||||
<div className="relative overflow-hidden rounded-full">
|
||||
<Image
|
||||
width={200}
|
||||
@@ -41,7 +41,7 @@ const AuthorCard: React.FC<{ author: AuthorMetadata }> = ({ author }) => {
|
||||
/>
|
||||
</div>
|
||||
<h2 className="font-bold text-xl">{author.name}</h2>
|
||||
<p className="text-sm text-center text-gray-600 flex-grow">{author.subtitle}</p>
|
||||
<p className="text-sm text-center text-gray-600 grow">{author.subtitle}</p>
|
||||
<div className="flex flex-wrap gap-4 justify-center mt-2">
|
||||
{isClient &&
|
||||
socialsEntries.map(([key, value]) => (
|
||||
@@ -57,7 +57,7 @@ const AuthorCard: React.FC<{ author: AuthorMetadata }> = ({ author }) => {
|
||||
>
|
||||
<SocialIcon
|
||||
url={value}
|
||||
className="absolute inset-0 w-full h-full transform scale-100 transition-transform opacity-100 hover:scale-90"
|
||||
className="absolute inset-0 size-full scale-100 transition-transform opacity-100 hover:scale-90"
|
||||
bgColor="#9B9B9B80"
|
||||
fgColor="background"
|
||||
style={{ width: '2em', height: '2em' }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { getPagesUnderRoute } from 'nextra/context'
|
||||
import { type Page } from 'nextra'
|
||||
import { SocialIcon } from 'react-social-icons'
|
||||
@@ -38,12 +38,6 @@ const AuthorProfile: React.FC<AuthorProfileProps> = ({ authorId }) => {
|
||||
(a, b) => new Date(b.frontMatter.date).getTime() - new Date(a.frontMatter.date).getTime(),
|
||||
)
|
||||
|
||||
if (!author) {
|
||||
return <div>Author not found!</div>
|
||||
}
|
||||
|
||||
const socialsEntries = Object.entries(author.socials ?? {}).filter(([, value]) => !!value)
|
||||
|
||||
// State to track whether the component is rendered on the client side
|
||||
const [isClient, setIsClient] = useState(false)
|
||||
|
||||
@@ -51,6 +45,12 @@ const AuthorProfile: React.FC<AuthorProfileProps> = ({ authorId }) => {
|
||||
setIsClient(true)
|
||||
}, [])
|
||||
|
||||
if (!author) {
|
||||
return <div>Author not found!</div>
|
||||
}
|
||||
|
||||
const socialsEntries = Object.entries(author.socials ?? {}).filter(([, value]) => !!value)
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="max-w-4xl mx-auto flex flex-col md:flex-row gap-8 mt-12 mb-24 md:mb-32">
|
||||
@@ -71,7 +71,7 @@ const AuthorProfile: React.FC<AuthorProfileProps> = ({ authorId }) => {
|
||||
height={512}
|
||||
src={author.ogImage}
|
||||
alt={author.name}
|
||||
className="rounded-box w-[12rem] md:w-[16rem] h-[12rem] md:h-[16rem] rounded-square"
|
||||
className="rounded-box size-[12rem] md:size-[16rem] rounded-square"
|
||||
style={{ borderRadius: '20px', objectFit: 'cover' }}
|
||||
/>
|
||||
|
||||
@@ -90,7 +90,7 @@ const AuthorProfile: React.FC<AuthorProfileProps> = ({ authorId }) => {
|
||||
>
|
||||
<SocialIcon
|
||||
url={value}
|
||||
className="absolute inset-0 w-full h-full transform scale-100 transition-transform opacity-100 hover:scale-90"
|
||||
className="absolute inset-0 size-full scale-100 transition-transform opacity-100 hover:scale-90"
|
||||
bgColor="#9B9B9B80"
|
||||
fgColor="background"
|
||||
// fallback={{ path: 'M32 2 A30 30 0 0 1 62 32 A30 30 0 0 1 32 62 A30 30 0 0 1 2 32 A30 30 0 0 1 32 2 Z' }}
|
||||
@@ -112,7 +112,7 @@ const AuthorProfile: React.FC<AuthorProfileProps> = ({ authorId }) => {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ marginTop: '75px' }}></div>
|
||||
<div style={{ marginTop: '75px' }} />
|
||||
<div>
|
||||
<Cards num={3}>
|
||||
<Cards.Card title="Blog" href="/blog" icon={<Blog />} image>
|
||||
|
||||
@@ -48,8 +48,8 @@ export function Banner({ storageKey }: BannerProps) {
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -101,32 +101,32 @@ const menuItems: {
|
||||
const socialLinks = [
|
||||
{
|
||||
title: 'GitHub',
|
||||
icon: <Github className="h-4 w-4" aria-hidden="true" />,
|
||||
icon: <Github className="size-4" aria-hidden="true" />,
|
||||
href: 'https://github.librechat.ai/',
|
||||
},
|
||||
{
|
||||
title: 'Discord',
|
||||
icon: <Discord className="h-4 w-4" aria-hidden="true" />,
|
||||
icon: <Discord className="size-4" aria-hidden="true" />,
|
||||
href: 'https://discord.librechat.ai/',
|
||||
},
|
||||
{
|
||||
title: 'LinkedIn',
|
||||
icon: <Linkedin className="h-4 w-4" aria-hidden="true" />,
|
||||
icon: <Linkedin className="size-4" aria-hidden="true" />,
|
||||
href: 'https://linkedin.librechat.ai/',
|
||||
},
|
||||
{
|
||||
title: 'X',
|
||||
icon: <X className="h-4 w-4" aria-hidden="true" />,
|
||||
icon: <X className="size-4" aria-hidden="true" />,
|
||||
href: 'https://x.com/LibreChatAI',
|
||||
},
|
||||
{
|
||||
title: 'YouTube',
|
||||
icon: <Youtube className="h-4 w-4" aria-hidden="true" />,
|
||||
icon: <Youtube className="size-4" aria-hidden="true" />,
|
||||
href: 'https://www.youtube.com/@LibreChat',
|
||||
},
|
||||
{
|
||||
title: 'Email',
|
||||
icon: <Mail className="h-4 w-4" aria-hidden="true" />,
|
||||
icon: <Mail className="size-4" aria-hidden="true" />,
|
||||
href: 'mailto:contact@librechat.ai',
|
||||
},
|
||||
]
|
||||
@@ -162,7 +162,7 @@ const FooterMenu = () => {
|
||||
key={link.title}
|
||||
href={link.href}
|
||||
aria-label={link.title}
|
||||
className="flex items-center justify-center h-9 w-9 rounded-full text-neutral-500 dark:text-neutral-300 transition-colors duration-200 hover:text-primary hover:bg-neutral-100 dark:hover:bg-neutral-800"
|
||||
className="flex items-center justify-center size-9 rounded-full text-neutral-500 dark:text-neutral-300 transition-colors duration-200 hover:text-primary hover:bg-neutral-100 dark:hover:bg-neutral-800"
|
||||
>
|
||||
{link.icon}
|
||||
</Link>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import validator from 'validator'
|
||||
import React, { useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import toast, { Toaster } from 'react-hot-toast'
|
||||
import style from './newsletterform.module.css'
|
||||
|
||||
|
||||
@@ -72,8 +72,8 @@ export const Video = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="p-3 md:p-6 rounded-full bg-black group-hover:ring-8 ring-black/20 bg-opacity-75 hover:bg-opacity-90 transition flex">
|
||||
<Play className="h-6 w-6 text-white" />
|
||||
<div className="p-3 md:p-6 rounded-full bg-black/75 group-hover:ring-8 ring-black/20 hover:bg-black/90 transition flex">
|
||||
<Play className="size-6 text-white" />
|
||||
</div>
|
||||
<div className="mt-3 md:mt-6 md:opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
<span className="flex gap-2 text-xs md:text-sm font-semibold bg-black/90 text-white py-1 px-3 rounded-full">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import Image from 'next/image'
|
||||
import { useRouter } from 'next/router'
|
||||
import type { Page, MdxFile } from 'nextra'
|
||||
@@ -46,22 +46,18 @@ const BlogCard = ({
|
||||
return (
|
||||
<div className="bg-popover rounded-lg shadow-md overflow-hidden blog-card">
|
||||
<div
|
||||
className="relative h-52 md:h-64 mb-1 overflow-hidden transform scale-100 transition-transform hover:scale-105 cursor-pointer"
|
||||
className="relative h-52 md:h-64 mb-1 overflow-hidden scale-100 transition-transform hover:scale-105 cursor-pointer"
|
||||
onClick={handleCardClick}
|
||||
style={{ transformOrigin: 'bottom center' }}
|
||||
>
|
||||
{page.frontMatter?.ogVideo ? (
|
||||
<Video
|
||||
src={page.frontMatter.ogVideo}
|
||||
gifStyle
|
||||
className="object-cover w-full h-full mt-0"
|
||||
/>
|
||||
<Video src={page.frontMatter.ogVideo} gifStyle className="object-cover size-full mt-0" />
|
||||
) : page.frontMatter?.ogImage ? (
|
||||
<Image
|
||||
src={page.frontMatter.gif ?? page.frontMatter.ogImage}
|
||||
width={1200}
|
||||
height={675}
|
||||
className="object-cover absolute top-0 left-0 w-full h-full"
|
||||
className="object-cover absolute top-0 left-0 size-full"
|
||||
alt={page.frontMatter?.title ?? 'Blog post image'}
|
||||
unoptimized={
|
||||
page.frontMatter.gif !== undefined || page.frontMatter.ogImage?.endsWith('.gif')
|
||||
@@ -74,7 +70,7 @@ const BlogCard = ({
|
||||
{page.frontMatter?.tags?.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className={`cursor-pointer text-xs py-1 px-2 bg-background/80 shadow-md rounded-md ml-1 mr-1 ${
|
||||
className={`cursor-pointer text-xs py-1 px-2 bg-background/80 shadow-md rounded-md mx-1 ${
|
||||
selectedTags.includes(tag) ? 'bg-gray-700/20' : ''
|
||||
}`}
|
||||
onClick={() => handleTagClick(tag)}
|
||||
@@ -84,13 +80,13 @@ const BlogCard = ({
|
||||
))}
|
||||
</div>
|
||||
{/* Modified title and description to be clickable */}
|
||||
<div className="mb-2 ml-1 mr-1 cursor-pointer" onClick={handleCardClick}>
|
||||
<div className="mb-2 mx-1 cursor-pointer" onClick={handleCardClick}>
|
||||
<h2 className="font-mono text-xl mb-2 font-bold">
|
||||
{page.meta?.title || page.frontMatter?.title || page.name}
|
||||
</h2>
|
||||
<div>{truncateDescription(page.frontMatter?.description || '')}</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between absolute bottom-4 left-4 right-4">
|
||||
<div className="flex items-center justify-between absolute bottom-4 inset-x-4">
|
||||
<Author authorid={page.frontMatter?.authorid} />
|
||||
<span className="text-sm opacity-60">{page.frontMatter?.date}</span>
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,7 @@ export const BlogHeader = () => {
|
||||
<div style={{ textAlign: 'right' }}>
|
||||
<Author authorid={authorid} />
|
||||
</div>
|
||||
<br></br>
|
||||
<br />
|
||||
{ogVideo ? (
|
||||
<Video src={ogVideo} gifStyle />
|
||||
) : ogImage ? (
|
||||
@@ -54,7 +54,7 @@ export const BlogHeader = () => {
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
<div className="mt-6 md:mt-4"></div>
|
||||
<div className="mt-6 md:mt-4" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -78,7 +78,7 @@ export function Callout({
|
||||
{title ? (
|
||||
<span className={styles['title-text']}>{title}</span>
|
||||
) : (
|
||||
<span className={styles['title-spacer']} style={{ flexGrow: 1 }}></span>
|
||||
<span className={styles['title-spacer']} style={{ flexGrow: 1 }} />
|
||||
)}
|
||||
{collapsible && <span className={styles.arrow}>{isCollapsed ? '▷' : '▽'}</span>}
|
||||
</div>
|
||||
|
||||
@@ -51,10 +51,18 @@ const Carousel = ({ children, ...props }) => {
|
||||
</div>
|
||||
{showControls && (
|
||||
<div className={styles.glide__arrows} data-glide-el="controls">
|
||||
<button className={`${styles.glide__arrow} glide__arrow--left}`} data-glide-dir="<" aria-label="Previous slide">
|
||||
<button
|
||||
className={`${styles.glide__arrow} glide__arrow--left}`}
|
||||
data-glide-dir="<"
|
||||
aria-label="Previous slide"
|
||||
>
|
||||
←
|
||||
</button>
|
||||
<button className={`${styles.glide__arrow} glide__arrow--right}`} data-glide-dir=">" aria-label="Next slide">
|
||||
<button
|
||||
className={`${styles.glide__arrow} glide__arrow--right}`}
|
||||
data-glide-dir=">"
|
||||
aria-label="Next slide"
|
||||
>
|
||||
→
|
||||
</button>
|
||||
</div>
|
||||
@@ -62,7 +70,11 @@ const Carousel = ({ children, ...props }) => {
|
||||
{showBullets && (
|
||||
<div className={styles.glide__bullets} data-glide-el="controls[nav]">
|
||||
{React.Children.map(children, (_, index) => (
|
||||
<button className={styles.glide__bullets} data-glide-dir={`=${index}`} aria-label={`Go to slide ${index + 1}`}></button>
|
||||
<button
|
||||
className={styles.glide__bullets}
|
||||
data-glide-dir={`=${index}`}
|
||||
aria-label={`Go to slide ${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -49,8 +49,8 @@ export default function Changelog({ className }: { className?: string }) {
|
||||
<div className="w-px bg-secondary" />
|
||||
</div>
|
||||
|
||||
<div className="relative flex h-6 w-6 flex-none items-center justify-center bg-background">
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-secondary ring-1 ring-primary/80 opacity-60 group-hover:opacity-100" />
|
||||
<div className="relative flex size-6 flex-none items-center justify-center bg-background">
|
||||
<div className="size-1.5 rounded-full bg-secondary ring-1 ring-primary/80 opacity-60 group-hover:opacity-100" />
|
||||
</div>
|
||||
<p className="flex-auto py-0.5 text-sm leading-5 text-primary/70 opacity-80 group-hover:opacity-100">
|
||||
<span className="font-medium text-primary">{activityItem.title}</span>{' '}
|
||||
@@ -75,12 +75,12 @@ export default function Changelog({ className }: { className?: string }) {
|
||||
role="button" // Added role for the link acting as a button
|
||||
aria-label="Read the full changelog" // Added aria-label for the link
|
||||
>
|
||||
<div className="h-6 absolute left-0 top-0 flex w-6 justify-center">
|
||||
<div className="size-6 absolute left-0 top-0 flex justify-center">
|
||||
<div className="w-px bg-secondary" />
|
||||
</div>
|
||||
|
||||
<div className="relative flex h-6 w-6 flex-none items-center justify-center bg-background">
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-secondary ring-1 ring-primary/80 opacity-60 group-hover:opacity-100" />
|
||||
<div className="relative flex size-6 flex-none items-center justify-center bg-background">
|
||||
<div className="size-1.5 rounded-full bg-secondary ring-1 ring-primary/80 opacity-60 group-hover:opacity-100" />
|
||||
</div>
|
||||
<p className="flex-auto py-0.5 text-sm leading-5 text-primary/60 opacity-80 group-hover:opacity-100">
|
||||
<span className="font-medium text-primary">Read the full changelog ...</span> {null}
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import Image from 'next/image'
|
||||
|
||||
interface Company {
|
||||
name: string
|
||||
logo: string
|
||||
logoDark?: string
|
||||
logoColor?: string
|
||||
url?: string
|
||||
}
|
||||
|
||||
const companies: Company[] = [
|
||||
{
|
||||
name: 'Shopify',
|
||||
logo: '/images/logos/Shopify_light.svg',
|
||||
logoDark: '/images/logos/Shopify_dark.svg',
|
||||
},
|
||||
{
|
||||
name: 'ClickHouse',
|
||||
logo: '/images/logos/ClickHouse_light.svg',
|
||||
logoDark: '/images/logos/ClickHouse_dark.svg',
|
||||
},
|
||||
{
|
||||
name: 'Boston University',
|
||||
logo: '/images/logos/BostonUniversity_light.png',
|
||||
logoDark: '/images/logos/BostonUniversity_dark.png',
|
||||
logoColor: '/images/logos/BostonUniversity_color.png',
|
||||
},
|
||||
{
|
||||
name: 'Daimler Truck',
|
||||
logo: '/images/logos/DaimlerTruck_light.svg',
|
||||
logoDark: '/images/logos/DaimlerTruck_dark.svg',
|
||||
},
|
||||
]
|
||||
|
||||
/** Company logos section showcasing companies that use LibreChat. */
|
||||
export const Companies: React.FC = React.memo(() => {
|
||||
const logosToShow = useMemo(
|
||||
() => Array.from({ length: 8 }, (_, i) => companies[i % companies.length]),
|
||||
[],
|
||||
)
|
||||
|
||||
return (
|
||||
<section className="py-20">
|
||||
<div className="w-full px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
|
||||
Trusted by companies worldwide
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-muted-foreground">
|
||||
Join thousands of organizations using LibreChat
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center justify-center gap-8 md:gap-12">
|
||||
{logosToShow.map((company, index) => {
|
||||
const isFirstFewLogos = index < 4
|
||||
const key = `${company.name}-${index}`
|
||||
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className={`flex items-center justify-center px-4 py-2 ${company.logoColor ? 'group' : ''}`}
|
||||
>
|
||||
{company.logoColor ? (
|
||||
<div className="relative">
|
||||
<Image
|
||||
src={company.logo}
|
||||
alt={`${company.name} logo`}
|
||||
className="dark:hidden group-hover:opacity-0 transition-opacity duration-300 h-10 w-auto object-contain"
|
||||
width={120}
|
||||
height={60}
|
||||
sizes="120px"
|
||||
unoptimized={company.logo.endsWith('.svg')}
|
||||
priority={isFirstFewLogos}
|
||||
loading={isFirstFewLogos ? 'eager' : 'lazy'}
|
||||
/>
|
||||
<Image
|
||||
src={company.logoDark || company.logo}
|
||||
alt={`${company.name} logo`}
|
||||
className="hidden dark:block group-hover:opacity-0 transition-opacity duration-300 h-10 w-auto object-contain"
|
||||
width={120}
|
||||
height={60}
|
||||
sizes="120px"
|
||||
unoptimized={(company.logoDark || company.logo).endsWith('.svg')}
|
||||
priority={isFirstFewLogos}
|
||||
loading={isFirstFewLogos ? 'eager' : 'lazy'}
|
||||
/>
|
||||
<Image
|
||||
src={company.logoColor}
|
||||
alt={`${company.name} logo`}
|
||||
className="absolute top-0 left-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300 h-10 w-auto object-contain"
|
||||
width={120}
|
||||
height={60}
|
||||
sizes="120px"
|
||||
unoptimized={company.logoColor.endsWith('.svg')}
|
||||
priority={isFirstFewLogos}
|
||||
loading={isFirstFewLogos ? 'eager' : 'lazy'}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Image
|
||||
src={company.logo}
|
||||
alt={`${company.name} logo`}
|
||||
className={`h-10 w-auto object-contain ${company.logoDark ? 'dark:hidden' : ''}`}
|
||||
width={120}
|
||||
height={60}
|
||||
sizes="120px"
|
||||
unoptimized={company.logo.endsWith('.svg')}
|
||||
priority={isFirstFewLogos}
|
||||
loading={isFirstFewLogos ? 'eager' : 'lazy'}
|
||||
/>
|
||||
{company.logoDark && (
|
||||
<Image
|
||||
src={company.logoDark}
|
||||
alt={`${company.name} logo`}
|
||||
className="hidden dark:block h-10 w-auto object-contain"
|
||||
width={120}
|
||||
height={60}
|
||||
sizes="120px"
|
||||
unoptimized={company.logoDark.endsWith('.svg')}
|
||||
priority={isFirstFewLogos}
|
||||
loading={isFirstFewLogos ? 'eager' : 'lazy'}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
})
|
||||
Companies.displayName = 'Companies'
|
||||
|
||||
export default Companies
|
||||
@@ -22,7 +22,7 @@ const HeroTitle = React.memo(() => (
|
||||
{HERO_TITLE.firstPart}{' '}
|
||||
<span className="relative inline-block text-foreground">
|
||||
{HERO_TITLE.highlight}
|
||||
<div className="absolute -bottom-1 left-0 right-0">
|
||||
<div className="absolute -bottom-1 inset-x-0">
|
||||
<div className="bg-gradient-to-r from-transparent via-muted-foreground to-transparent h-[2px] blur-sm" />
|
||||
<div className="bg-gradient-to-r from-transparent via-muted-foreground to-transparent h-px" />
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import * as React from 'react'
|
||||
|
||||
function RepoOfTheDay() {
|
||||
return (
|
||||
<svg
|
||||
@@ -9,7 +7,7 @@ function RepoOfTheDay() {
|
||||
width="250"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect fill="#111111" height="53" rx="10" width="249" x="0.5" y="0.5"></rect>
|
||||
<rect fill="#111111" height="53" rx="10" width="249" x="0.5" y="0.5" />
|
||||
<foreignObject
|
||||
height="17"
|
||||
style={{
|
||||
@@ -38,7 +36,7 @@ function RepoOfTheDay() {
|
||||
<path
|
||||
d="M70.71,40.31C75.74,44.3,80,37.86,80,37.86s-5.64-2.17-8.55,0.61c0.59-1.62,1.02-3.31,1.28-5.01 c4.08,2.16,6.44-2.95,6.44-2.95s-4.41-0.97-6.26,1.4c0.08-0.91,0.12-1.82,0.1-2.73c-0.01-0.36-0.02-0.73-0.05-1.09 c2.96-3.68-1.73-6.99-1.73-6.99s-2.14,5.09,0.98,7.09c0.02,0.33,0.03,0.66,0.03,1c0.01,0.76-0.03,1.52-0.1,2.27 c-0.85-2.69-4.91-3.69-4.91-3.69s-0.13,5.78,4.68,5.48c-0.28,1.69-0.73,3.35-1.34,4.95c-0.19-4.03-5.79-6.33-5.79-6.33 s-1.33,7.55,5.01,8.16c-0.38,0.8-0.8,1.57-1.25,2.32c-0.56,0.95-1.21,1.84-1.89,2.71c0.97-3.99-3.96-7.72-3.96-7.72 s-3.18,6.94,2.73,9.15c-0.38,0.43-0.8,0.81-1.2,1.21c-0.21,0.2-0.43,0.38-0.64,0.58l-0.32,0.29c-0.11,0.09-0.22,0.18-0.33,0.27 l-0.67,0.54l-0.7,0.51c-0.08,0.05-0.16,0.11-0.23,0.16c1.62-3.42-2.07-7.77-2.07-7.77s-4.21,5.55,0.49,8.78 c-1.34,0.79-2.74,1.45-4.2,1.98c1.91-2.59-0.23-6.89-0.23-6.89s-4.66,3.77-1.52,7.46c-1.15,0.33-2.33,0.57-3.51,0.74 c1.46-1.68,0.55-4.83,0.55-4.83s-3.7,2.03-2.18,5c-0.52,0.03-1.05,0.07-1.57,0.06c-0.29,0-0.57,0.01-0.86,0l-0.86-0.04 c-0.85-0.06-1.7-0.15-2.54-0.28l0.68-0.27l0.42-0.17l0.41-0.19l0.82-0.38c0,0,0.01,0,0.01,0c0.39-0.18,0.55-0.65,0.37-1.03 c-0.18-0.39-0.65-0.55-1.03-0.37l-0.04,0.02l-0.77,0.37l-0.39,0.18l-0.39,0.16l-0.79,0.33l-0.8,0.29l-0.4,0.14l-0.41,0.12L40,53.6 l-0.51-0.15l-0.41-0.12l-0.4-0.14l-0.8-0.29l-0.79-0.33l-0.39-0.16l-0.39-0.18l-0.77-0.37l-0.04-0.02c0,0,0,0-0.01,0 c-0.39-0.18-0.85-0.01-1.03,0.38c-0.18,0.39-0.01,0.85,0.38,1.03l0.82,0.38l0.41,0.19l0.42,0.17l0.68,0.27 c-0.84,0.14-1.69,0.22-2.54,0.28l-0.86,0.04c-0.29,0.01-0.57,0-0.86,0c-0.53,0.01-1.05-0.03-1.57-0.06c1.51-2.98-2.18-5-2.18-5 s-0.92,3.15,0.55,4.83c-1.19-0.16-2.36-0.41-3.51-0.74c3.15-3.7-1.52-7.46-1.52-7.46s-2.14,4.31-0.23,6.89 c-1.46-0.53-2.86-1.19-4.2-1.98c4.7-3.22,0.49-8.78,0.49-8.78s-3.69,4.34-2.07,7.77c-0.08-0.05-0.16-0.1-0.23-0.16l-0.7-0.51 l-0.67-0.54c-0.11-0.09-0.23-0.18-0.33-0.27l-0.32-0.29c-0.21-0.19-0.43-0.38-0.64-0.58c-0.4-0.4-0.82-0.79-1.2-1.21 c5.91-2.21,2.73-9.15,2.73-9.15s-4.93,3.73-3.96,7.72c-0.68-0.86-1.33-1.76-1.89-2.71c-0.46-0.75-0.87-1.53-1.25-2.32 c6.33-0.61,5.01-8.16,5.01-8.16s-5.6,2.31-5.79,6.33c-0.61-1.6-1.06-3.26-1.34-4.95c4.81,0.3,4.68-5.48,4.68-5.48 s-4.05,0.99-4.91,3.69c-0.07-0.76-0.1-1.51-0.1-2.27c0-0.33,0.01-0.66,0.03-1c3.11-2.01,0.98-7.09,0.98-7.09s-4.69,3.31-1.73,6.99 C7,28.46,6.99,28.82,6.98,29.18c-0.02,0.91,0.01,1.82,0.1,2.73c-1.84-2.38-6.26-1.4-6.26-1.4s2.37,5.11,6.44,2.95 c0.26,1.71,0.69,3.39,1.28,5.01C5.64,35.69,0,37.86,0,37.86s4.26,6.43,9.29,2.45c0.39,0.87,0.83,1.72,1.31,2.54 c0.47,0.83,1.01,1.63,1.58,2.4C8.71,43.7,4.11,47,4.11,47s5.7,5.1,9.56,0.08c0.04,0.04,0.07,0.08,0.11,0.12 c0.39,0.45,0.82,0.87,1.24,1.3c0.21,0.21,0.44,0.41,0.66,0.61l0.33,0.3c0.11,0.1,0.23,0.19,0.34,0.29l0.69,0.57l0.23,0.17 c-3.34-0.34-6.58,3.29-6.58,3.29s6.19,3.47,8.69-1.83c1.2,0.75,2.47,1.41,3.78,1.96c-2.76,0.6-4.62,4.13-4.62,4.13 s5.89,1.62,6.98-3.26c1.03,0.32,2.07,0.58,3.13,0.78c-1.63,0.99-2.39,3.38-2.39,3.38s4.31,0.39,4.61-3.07 c0.07,0.01,0.14,0.02,0.21,0.02c0.6,0.04,1.2,0.1,1.8,0.09c0.3,0,0.6,0.02,0.9,0.01l0.9-0.03c1.2-0.07,2.41-0.18,3.59-0.42 l0.45-0.08c0.15-0.03,0.29-0.07,0.44-0.1L40,55.13l0.81,0.19c0.15,0.03,0.29,0.07,0.44,0.1l0.45,0.08c1.18,0.23,2.39,0.35,3.59,0.42 l0.9,0.03c0.3,0.01,0.6-0.01,0.9-0.01c0.6,0,1.2-0.06,1.8-0.09c0.07-0.01,0.14-0.02,0.21-0.02c0.31,3.45,4.61,3.07,4.61,3.07 s-0.76-2.39-2.39-3.38c1.06-0.2,2.11-0.46,3.13-0.78c1.09,4.88,6.98,3.26,6.98,3.26s-1.86-3.52-4.62-4.13 c1.31-0.55,2.57-1.21,3.78-1.96c2.5,5.3,8.69,1.83,8.69,1.83s-3.24-3.63-6.58-3.29l0.23-0.17l0.69-0.57 c0.11-0.1,0.23-0.19,0.34-0.29l0.33-0.3c0.22-0.2,0.45-0.4,0.66-0.61c0.42-0.43,0.85-0.84,1.24-1.3c0.03-0.04,0.07-0.08,0.11-0.12 C70.19,52.1,75.89,47,75.89,47s-4.6-3.31-8.08-1.75c0.57-0.77,1.11-1.56,1.58-2.4C69.88,42.03,70.31,41.18,70.71,40.31z"
|
||||
fill="#ffffff"
|
||||
></path>
|
||||
/>
|
||||
</svg>
|
||||
<foreignObject
|
||||
height="35"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import * as React from 'react'
|
||||
|
||||
function RossIndex() {
|
||||
return (
|
||||
<svg
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import useCredentialsGenerator from './credentialsGenerator' // Adjust the path based on your project structure
|
||||
|
||||
const CredsGenerator = () => {
|
||||
@@ -42,7 +42,13 @@ const CredsGenerator = () => {
|
||||
<div>
|
||||
<p style={{ fontSize: '0.8rem', marginBottom: '5px', marginTop: '10px' }}>CREDS_KEY</p>
|
||||
<div className="input-container">
|
||||
<input type="text" value={credentials?.CREDS_KEY || ''} placeholder="" readOnly aria-label="CREDS_KEY value" />
|
||||
<input
|
||||
type="text"
|
||||
value={credentials?.CREDS_KEY || ''}
|
||||
placeholder=""
|
||||
readOnly
|
||||
aria-label="CREDS_KEY value"
|
||||
/>
|
||||
<button
|
||||
className="copy-button"
|
||||
onClick={() => handleCopy(credentials?.CREDS_KEY)}
|
||||
@@ -56,7 +62,13 @@ const CredsGenerator = () => {
|
||||
<div>
|
||||
<p style={{ fontSize: '0.8rem', marginBottom: '5px', marginTop: '10px' }}>CREDS_IV</p>
|
||||
<div className="input-container">
|
||||
<input type="text" value={credentials?.CREDS_IV || ''} placeholder="" readOnly aria-label="CREDS_IV value" />
|
||||
<input
|
||||
type="text"
|
||||
value={credentials?.CREDS_IV || ''}
|
||||
placeholder=""
|
||||
readOnly
|
||||
aria-label="CREDS_IV value"
|
||||
/>
|
||||
<button
|
||||
className="copy-button"
|
||||
onClick={() => handleCopy(credentials?.CREDS_IV)}
|
||||
@@ -70,7 +82,13 @@ const CredsGenerator = () => {
|
||||
<div>
|
||||
<p style={{ fontSize: '0.8rem', marginBottom: '5px', marginTop: '10px' }}>JWT_SECRET</p>
|
||||
<div className="input-container">
|
||||
<input type="text" value={credentials?.JWT_SECRET || ''} placeholder="" readOnly aria-label="JWT_SECRET value" />
|
||||
<input
|
||||
type="text"
|
||||
value={credentials?.JWT_SECRET || ''}
|
||||
placeholder=""
|
||||
readOnly
|
||||
aria-label="JWT_SECRET value"
|
||||
/>
|
||||
<button
|
||||
className="copy-button"
|
||||
onClick={() => handleCopy(credentials?.JWT_SECRET)}
|
||||
@@ -108,7 +126,13 @@ const CredsGenerator = () => {
|
||||
MEILI_MASTER_KEY
|
||||
</p>
|
||||
<div className="input-container">
|
||||
<input type="text" value={credentials?.MEILI_KEY || ''} placeholder="" readOnly aria-label="MEILI_MASTER_KEY value" />
|
||||
<input
|
||||
type="text"
|
||||
value={credentials?.MEILI_KEY || ''}
|
||||
placeholder=""
|
||||
readOnly
|
||||
aria-label="MEILI_MASTER_KEY value"
|
||||
/>
|
||||
<button
|
||||
className="copy-button"
|
||||
onClick={() => handleCopy(credentials?.MEILI_KEY)}
|
||||
|
||||
@@ -36,7 +36,7 @@ const AccordionTrigger = React.forwardRef<
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
||||
<ChevronDown className="size-4 shrink-0 transition-transform duration-200" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
))
|
||||
|
||||
@@ -12,7 +12,7 @@ const Avatar = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', className)}
|
||||
className={cn('relative flex size-10 shrink-0 overflow-hidden rounded-full', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
@@ -24,7 +24,7 @@ const AvatarImage = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Image
|
||||
ref={ref}
|
||||
className={cn('aspect-square h-full w-full', className)}
|
||||
className={cn('aspect-square size-full', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
@@ -36,10 +36,7 @@ const AvatarFallback = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex h-full w-full items-center justify-center rounded-full bg-muted',
|
||||
className,
|
||||
)}
|
||||
className={cn('flex size-full items-center justify-center rounded-full bg-muted', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
||||
@@ -44,7 +44,7 @@ const DialogContent = React.forwardRef<
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<X className="h-4 w-4" />
|
||||
<X className="size-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
|
||||
@@ -36,7 +36,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRight className="ml-auto h-4 w-4" />
|
||||
<ChevronRight className="ml-auto size-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
))
|
||||
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
|
||||
@@ -110,9 +110,9 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<span className="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
<Check className="size-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
@@ -135,9 +135,9 @@ const DropdownMenuRadioItem = React.forwardRef<
|
||||
value={value}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<span className="absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Circle className="h-2 w-2 fill-current" />
|
||||
<Circle className="size-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
|
||||
195
eslint.config.mjs
Normal file
195
eslint.config.mjs
Normal file
@@ -0,0 +1,195 @@
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { includeIgnoreFile } from '@eslint/compat';
|
||||
import js from '@eslint/js';
|
||||
import nextPlugin from '@next/eslint-plugin-next';
|
||||
import eslintConfigPrettier from 'eslint-config-prettier';
|
||||
import importPlugin from 'eslint-plugin-import';
|
||||
import react from 'eslint-plugin-react';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import tailwindcss from 'eslint-plugin-tailwindcss';
|
||||
import unicorn from 'eslint-plugin-unicorn';
|
||||
import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export default tseslint.config(
|
||||
// ── 1. Global ignores ──────────────────────────────────────────────
|
||||
includeIgnoreFile(path.resolve(__dirname, '.gitignore')),
|
||||
{ ignores: ['content/**', '.source/**'] },
|
||||
|
||||
// ── 2. Base configs ────────────────────────────────────────────────
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
|
||||
// ── 3. All JS / TS files — shared rules ────────────────────────────
|
||||
{
|
||||
files: ['**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}'],
|
||||
plugins: {
|
||||
import: importPlugin,
|
||||
unicorn,
|
||||
},
|
||||
rules: {
|
||||
'prefer-object-has-own': 'error',
|
||||
'logical-assignment-operators': ['error', 'always', { enforceForIfStatements: true }],
|
||||
'no-else-return': ['error', { allowElseIf: false }],
|
||||
'no-lonely-if': 'error',
|
||||
'prefer-destructuring': ['error', { VariableDeclarator: { object: true } }],
|
||||
'import/no-duplicates': 'error',
|
||||
'no-negated-condition': 'off',
|
||||
'unicorn/no-negated-condition': 'error',
|
||||
'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }],
|
||||
'object-shorthand': ['error', 'always'],
|
||||
'unicorn/prefer-regexp-test': 'error',
|
||||
'unicorn/no-array-for-each': 'error',
|
||||
'unicorn/prefer-string-replace-all': 'error',
|
||||
'@typescript-eslint/prefer-for-of': 'error',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{ argsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_' },
|
||||
],
|
||||
// todo: enable
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
},
|
||||
},
|
||||
|
||||
// ── 4. React files ─────────────────────────────────────────────────
|
||||
{
|
||||
files: ['app/**/*.{tsx,jsx}', 'components/**/*.{tsx,jsx}', 'lib/**/*.{tsx,jsx}'],
|
||||
...react.configs.flat.recommended,
|
||||
...react.configs.flat['jsx-runtime'],
|
||||
plugins: {
|
||||
...react.configs.flat.recommended.plugins,
|
||||
'react-hooks': reactHooks,
|
||||
},
|
||||
settings: { react: { version: 'detect' } },
|
||||
rules: {
|
||||
...react.configs.flat.recommended.rules,
|
||||
...react.configs.flat['jsx-runtime'].rules,
|
||||
...reactHooks.configs['recommended-latest'].rules,
|
||||
'react/prop-types': 'off',
|
||||
'react/no-unknown-property': ['error', { ignore: ['jsx'] }],
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
'react/self-closing-comp': 'error',
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{
|
||||
selector:
|
||||
'CallExpression[callee.name=useMemo][arguments.1.type=ArrayExpression][arguments.1.elements.length=0]',
|
||||
message:
|
||||
"`useMemo` with an empty dependency array can't provide a stable reference, use `useRef` instead.",
|
||||
},
|
||||
{
|
||||
selector: 'MemberExpression[object.name=z] > .property[name=object]',
|
||||
message: 'Use z.strictObject is more safe.',
|
||||
},
|
||||
],
|
||||
'react/jsx-filename-extension': [
|
||||
'error',
|
||||
{ extensions: ['.tsx', '.jsx'], allow: 'as-needed' },
|
||||
],
|
||||
'react/jsx-curly-brace-presence': 'error',
|
||||
'react/jsx-boolean-value': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
// ── 5. Next.js rules ───────────────────────────────────────────────
|
||||
{
|
||||
files: ['app/**/*.{tsx,jsx}', 'components/**/*.{tsx,jsx}', 'lib/**/*.{tsx,jsx}'],
|
||||
plugins: { '@next/next': nextPlugin },
|
||||
rules: {
|
||||
...nextPlugin.configs.recommended.rules,
|
||||
...nextPlugin.configs['core-web-vitals'].rules,
|
||||
},
|
||||
},
|
||||
|
||||
// ── 6. TypeScript type-checked ─────────────────────────────────────
|
||||
{
|
||||
files: ['**/*.{ts,tsx,cts,mts}'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
||||
'@typescript-eslint/non-nullable-type-assertion-style': 'error',
|
||||
'@typescript-eslint/prefer-optional-chain': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
// ── 7. Tailwind CSS ────────────────────────────────────────────────
|
||||
{
|
||||
files: ['app/**/*.{tsx,jsx}', 'components/**/*.{tsx,jsx}', 'lib/**/*.{tsx,jsx}'],
|
||||
...tailwindcss.configs['flat/recommended'][0],
|
||||
settings: {
|
||||
tailwindcss: {
|
||||
callees: ['cn'],
|
||||
whitelist: [
|
||||
'dash-ring',
|
||||
'theme-\\d+',
|
||||
'nextra-[\\w-]+',
|
||||
'blog-card',
|
||||
'glide(__.+)?',
|
||||
'credentials-\\w+',
|
||||
'input-container',
|
||||
'copy-button',
|
||||
'generate-button',
|
||||
'tooltip',
|
||||
'card',
|
||||
'cards',
|
||||
'file',
|
||||
'file-tree',
|
||||
'folder',
|
||||
'carousel',
|
||||
'frame',
|
||||
'btn(-.+)?',
|
||||
'rounded-box',
|
||||
'rounded-square',
|
||||
'bg-blackish',
|
||||
'clickhouse-highlight',
|
||||
'text-base-content\\/\\d+',
|
||||
'origin-\\(.+\\)',
|
||||
'max-h-\\(.+\\)',
|
||||
'subheading-\\w*',
|
||||
'post-item(-.+)?',
|
||||
],
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'tailwindcss/classnames-order': 'off',
|
||||
'tailwindcss/enforces-negative-arbitrary-values': 'error',
|
||||
'tailwindcss/enforces-shorthand': 'error',
|
||||
'tailwindcss/migration-from-tailwind-2': 'error',
|
||||
'tailwindcss/no-custom-classname': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
// ── 8. Node / CJS config files ─────────────────────────────────────
|
||||
{
|
||||
files: [
|
||||
'*.config.{js,cjs,mjs,ts}',
|
||||
'postcss.config.*',
|
||||
'prettier.config.*',
|
||||
'.prettierrc.{js,cjs}',
|
||||
'**/*.cjs',
|
||||
],
|
||||
languageOptions: {
|
||||
globals: globals.node,
|
||||
},
|
||||
},
|
||||
|
||||
// ── 9. Declaration files ───────────────────────────────────────────
|
||||
{
|
||||
files: ['**/*.d.ts'],
|
||||
rules: { 'no-var': 'off' },
|
||||
},
|
||||
|
||||
// ── 10. Prettier (must be last) ────────────────────────────────────
|
||||
eslintConfigPrettier,
|
||||
);
|
||||
@@ -137,7 +137,7 @@ function CardCompat({
|
||||
}) {
|
||||
const content = (
|
||||
<div className="flex flex-col gap-2">
|
||||
{icon && <div className="[&>svg]:h-6 [&>svg]:w-6">{icon}</div>}
|
||||
{icon && <div className="[&>svg]:size-6">{icon}</div>}
|
||||
{title && <h3 className="font-semibold text-fd-foreground">{title}</h3>}
|
||||
{children && <div className="text-sm text-fd-muted-foreground">{children}</div>}
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* Nextra's components use compound patterns (e.g., Cards.Card, Tabs.Tab,
|
||||
* FileTree.File) which are replicated here using Object.assign.
|
||||
*/
|
||||
import React from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
interface ChildrenProps {
|
||||
@@ -40,7 +39,7 @@ function CardComponent({
|
||||
className="nextra-card flex items-center gap-3 rounded-lg border border-gray-200 dark:border-gray-800 p-4 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800/50"
|
||||
{...props}
|
||||
>
|
||||
{icon && <span className="shrink-0 [&>svg]:h-6 [&>svg]:w-6">{icon}</span>}
|
||||
{icon && <span className="shrink-0 [&>svg]:size-6">{icon}</span>}
|
||||
{title && <h3 className="m-0 text-base font-medium">{title}</h3>}
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* to automatically provide components that Nextra used to inject
|
||||
* via theme.config.tsx's `components` property.
|
||||
*/
|
||||
import React from 'react'
|
||||
import type { ReactNode, ComponentType } from 'react'
|
||||
|
||||
interface ChildrenProps {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
* This shim is temporary and will be removed once all pages/ content
|
||||
* is migrated to the Fumadocs app/ directory.
|
||||
*/
|
||||
/* eslint-env node */
|
||||
module.exports = function metaLoader() {
|
||||
return `
|
||||
export default function MetaPlaceholder() { return null; }
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Provides stub implementations so existing pages/ components
|
||||
* can resolve their imports during the migration to Fumadocs.
|
||||
*/
|
||||
import React from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
interface ChildrenProps {
|
||||
|
||||
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference types="next/navigation-types/compat/navigation" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
/** @type {import('next-sitemap').IConfig} */
|
||||
module.exports = {
|
||||
siteUrl: 'https://www.librechat.ai',
|
||||
|
||||
22
package.json
22
package.json
@@ -9,7 +9,7 @@
|
||||
"postbuild": "node scripts/clean-cache.ts",
|
||||
"start": "next start -p 3333",
|
||||
"analyze": "cross-env ANALYZE=true next build",
|
||||
"lint": "eslint --cache --ignore-path .gitignore --max-warnings 0 .",
|
||||
"lint": "eslint --cache --max-warnings 0 .",
|
||||
"lint:prettier": "prettier --cache --check --ignore-path .gitignore --ignore-path .prettierignore .",
|
||||
"prettier": "bun run lint:prettier --write",
|
||||
"prepare": "husky"
|
||||
@@ -65,29 +65,31 @@
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1",
|
||||
"@eslint/js": "^9",
|
||||
"@next/bundle-analyzer": "^15.1.4",
|
||||
"@next/eslint-plugin-next": "^15",
|
||||
"@types/mdx": "^2.0.13",
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@types/node": "18.16.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
||||
"@typescript-eslint/parser": "^8.48.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-next": "15.1.4",
|
||||
"eslint": "^9",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-mdx": "^3.1.5",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-import": "^2",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"eslint-plugin-react-hooks": "^5",
|
||||
"eslint-plugin-tailwindcss": "^3.18.2",
|
||||
"eslint-plugin-unicorn": "^53.0.0",
|
||||
"eslint-plugin-unicorn": "^56",
|
||||
"globals": "^15",
|
||||
"husky": "^9.0.11",
|
||||
"lint-staged": "^16.2.7",
|
||||
"playwright": "^1.58.2",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.2.5",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript-eslint": "^8",
|
||||
"webpack": "^5.104.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
@@ -98,7 +100,9 @@
|
||||
"next": ">=15.5.10",
|
||||
"preact": ">=10.28.2",
|
||||
"webpack": ">=5.104.1",
|
||||
"diff": ">=5.2.2"
|
||||
"diff": ">=5.2.2",
|
||||
"minimatch": ">=3.1.4",
|
||||
"serialize-javascript": ">=7.0.3"
|
||||
}
|
||||
},
|
||||
"nextBundleAnalysis": {
|
||||
|
||||
1745
pnpm-lock.yaml
generated
1745
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
9
public/images/logos/Stripe wordmark - Slate.svg
Normal file
9
public/images/logos/Stripe wordmark - Slate.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="360" height="151" viewBox="0 0 360 151" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M360 78.2001C360 52.6001 347.6 32.4001 323.9 32.4001C300.1 32.4001 285.7 52.6001 285.7 78.0001C285.7 108.1 302.7 123.3 327.1 123.3C339 123.3 348 120.6 354.8 116.8V96.8001C348 100.2 340.2 102.3 330.3 102.3C320.6 102.3 312 98.9002 310.9 87.1002H359.8C359.8 85.8002 360 80.6002 360 78.2001ZM310.6 68.7001C310.6 57.4002 317.5 52.7001 323.8 52.7001C329.9 52.7001 336.4 57.4002 336.4 68.7001H310.6Z" fill="#061B31"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M247.1 32.4001C237.3 32.4001 231 37.0001 227.5 40.2001L226.2 34.0001H204.2V150.6L229.2 145.3L229.3 117C232.9 119.6 238.2 123.3 247 123.3C264.9 123.3 281.2 108.9 281.2 77.2001C281.1 48.2001 264.6 32.4001 247.1 32.4001ZM241.1 101.3C235.2 101.3 231.7 99.2001 229.3 96.6002L229.2 59.5001C231.8 56.6001 235.4 54.6002 241.1 54.6002C250.2 54.6002 256.5 64.8001 256.5 77.9001C256.5 91.3001 250.3 101.3 241.1 101.3Z" fill="#061B31"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M169.8 26.5L194.9 21.1V0.800049L169.8 6.10005V26.5Z" fill="#061B31"/>
|
||||
<path d="M194.9 34.1001H169.8V121.6H194.9V34.1001Z" fill="#061B31"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M142.9 41.5001L141.3 34.1001H119.7V121.6H144.7V62.3001C150.6 54.6001 160.6 56.0001 163.7 57.1001V34.1001C160.5 32.9001 148.8 30.7001 142.9 41.5001Z" fill="#061B31"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.8999 12.4001L68.4999 17.6001L68.3999 97.7001C68.3999 112.5 79.4999 123.4 94.2999 123.4C102.5 123.4 108.5 121.9 111.8 120.1V99.8001C108.6 101.1 92.7999 105.7 92.7999 90.9001V55.4001H111.8V34.1002H92.7999L92.8999 12.4001Z" fill="#061B31"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.3 59.5001C25.3 55.6001 28.5 54.1002 33.8 54.1002C41.4 54.1002 51 56.4001 58.6 60.5001V37.0001C50.3 33.7001 42.1 32.4001 33.8 32.4001C13.5 32.4001 0 43.0001 0 60.7001C0 88.3001 38 83.9001 38 95.8001C38 100.4 34 101.9 28.4 101.9C20.1 101.9 9.5 98.5002 1.1 93.9002V117.7C10.4 121.7 19.8 123.4 28.4 123.4C49.2 123.4 63.5 113.1 63.5 95.2001C63.4 65.4001 25.3 70.7001 25.3 59.5001Z" fill="#061B31"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
9
public/images/logos/Stripe wordmark - White.svg
Normal file
9
public/images/logos/Stripe wordmark - White.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="360" height="151" viewBox="0 0 360 151" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M360 78.0002C360 52.4002 347.6 32.2002 323.9 32.2002C300.1 32.2002 285.7 52.4002 285.7 77.8002C285.7 107.9 302.7 123.1 327.1 123.1C339 123.1 348 120.4 354.8 116.6V96.6002C348 100 340.2 102.1 330.3 102.1C320.6 102.1 312 98.7002 310.9 86.9002H359.8C359.8 85.6002 360 80.4002 360 78.0002ZM310.6 68.5002C310.6 57.2002 317.5 52.5002 323.8 52.5002C329.9 52.5002 336.4 57.2002 336.4 68.5002H310.6Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M247.1 32.2002C237.3 32.2002 231 36.8002 227.5 40.0002L226.2 33.8002H204.2V150.4L229.2 145.1L229.3 116.8C232.9 119.4 238.2 123.1 247 123.1C264.9 123.1 281.2 108.7 281.2 77.0002C281.1 48.0002 264.6 32.2002 247.1 32.2002ZM241.1 101.1C235.2 101.1 231.7 99.0002 229.3 96.4002L229.2 59.3002C231.8 56.4002 235.4 54.4002 241.1 54.4002C250.2 54.4002 256.5 64.6002 256.5 77.7002C256.5 91.1002 250.3 101.1 241.1 101.1Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M169.8 26.3001L194.9 20.9001V0.600098L169.8 5.9001V26.3001Z" fill="white"/>
|
||||
<path d="M194.9 33.9001H169.8V121.4H194.9V33.9001Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M142.9 41.3001L141.3 33.9001H119.7V121.4H144.7V62.1001C150.6 54.4001 160.6 55.8001 163.7 56.9001V33.9001C160.5 32.7001 148.8 30.5001 142.9 41.3001Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.8999 12.2002L68.4999 17.4002L68.3999 97.5002C68.3999 112.3 79.4999 123.2 94.2999 123.2C102.5 123.2 108.5 121.7 111.8 119.9V99.6002C108.6 100.9 92.7999 105.5 92.7999 90.7002V55.2002H111.8V33.9002H92.7999L92.8999 12.2002Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.3 59.3002C25.3 55.4002 28.5 53.9002 33.8 53.9002C41.4 53.9002 51 56.2002 58.6 60.3002V36.8002C50.3 33.5002 42.1 32.2002 33.8 32.2002C13.5 32.2002 0 42.8002 0 60.5002C0 88.1002 38 83.7002 38 95.6002C38 100.2 34 101.7 28.4 101.7C20.1 101.7 9.5 98.3002 1.1 93.7002V117.5C10.4 121.5 19.8 123.2 28.4 123.2C49.2 123.2 63.5 112.9 63.5 95.0002C63.4 65.2002 25.3 70.5002 25.3 59.3002Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const { execSync } = require('child_process')
|
||||
const os = require('os')
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { createPreset } = require('fumadocs-ui/tailwind-plugin')
|
||||
|
||||
/**
|
||||
@@ -87,6 +86,6 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
|
||||
plugins: [require('tailwindcss-animate')],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user