mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-14 03:30:19 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0cbe27e13c | |||
| c37a39d9b8 | |||
| efd661483a | |||
| 59e5781a97 | |||
| b60a6fe340 |
+5
-1
@@ -83,6 +83,7 @@ public/sw*
|
||||
public/swe-worker*
|
||||
|
||||
# Generated files
|
||||
public/spa/
|
||||
public/*.js
|
||||
public/sitemap.xml
|
||||
public/sitemap-index.xml
|
||||
@@ -127,4 +128,7 @@ out
|
||||
i18n-unused-keys-report.json
|
||||
.vitest-reports
|
||||
|
||||
pnpm-lock.yaml
|
||||
pnpm-lock.yaml
|
||||
.turbo
|
||||
|
||||
spaHtmlTemplates.ts
|
||||
@@ -76,6 +76,7 @@ COPY patches ./patches
|
||||
# bring in desktop workspace manifest so pnpm can resolve it
|
||||
COPY apps/desktop/src/main/package.json ./apps/desktop/src/main/package.json
|
||||
|
||||
|
||||
RUN set -e && \
|
||||
if [ "${USE_CN_MIRROR:-false}" = "true" ]; then \
|
||||
export SENTRYCLI_CDNURL="https://npmmirror.com/mirrors/sentry-cli"; \
|
||||
@@ -116,6 +117,8 @@ COPY --from=base /distroless/ /
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=builder /app/.next/standalone /app/
|
||||
# Copy SPA build output (Vite)
|
||||
COPY --from=builder /app/public/spa /app/public/spa
|
||||
# Copy Next export output for desktop renderer
|
||||
COPY --from=builder /app/apps/desktop/dist/next /app/apps/desktop/dist/next
|
||||
|
||||
|
||||
@@ -24,99 +24,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(auth)/market-auth-callback/page.tsx": {
|
||||
"no-console": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(desktop)/desktop-onboarding/features/WelcomeStep.tsx": {
|
||||
"@eslint-react/no-nested-component-definitions": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/agent/features/Conversation/AgentWelcome/ToolAuthAlert.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/agent/profile/features/Header/AgentForkTag.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/community/(detail)/agent/features/AgentForkTag.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/community/(detail)/user/features/UserAgentList.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/community/components/VirtuosoGridList/index.tsx": {
|
||||
"@eslint-react/no-nested-component-definitions": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/home/features/RecentPage/Item.tsx": {
|
||||
"regexp/no-super-linear-backtracking": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/home/features/index.tsx": {
|
||||
"@eslint-react/no-nested-component-definitions": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/memory/features/GridView/index.tsx": {
|
||||
"@eslint-react/no-nested-component-definitions": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/memory/features/MemoryAnalysis/index.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/resource/features/hooks/useResourceManagerUrlSync.ts": {
|
||||
"react-hooks/exhaustive-deps": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/settings/provider/detail/ollama/CheckError.tsx": {
|
||||
"regexp/no-dupe-characters-character-class": {
|
||||
"count": 1
|
||||
},
|
||||
"regexp/no-obscure-range": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx": {
|
||||
"no-console": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/(main)/settings/skill/features/LobehubSkillItem.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/onboarding/components/KlavisServerList/hooks/useKlavisOAuth.ts": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/onboarding/features/ResponseLanguageStep.tsx": {
|
||||
"@eslint-react/no-nested-component-definitions": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/[variants]/onboarding/features/TelemetryStep.tsx": {
|
||||
"@eslint-react/no-nested-component-definitions": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/AntdStaticMethods/index.tsx": {
|
||||
"import-x/no-mutable-exports": {
|
||||
"count": 3
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html lang="<!--LOCALE-->" dir="<!--DIR-->">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
#root {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
<!--SEO_META-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script>
|
||||
// window.__SERVER_CONFIG__ = <!--SERVER_CONFIG-->;
|
||||
window.__SERVER_CONFIG__ = undefined; /* SERVER_CONFIG */
|
||||
</script>
|
||||
<!--ANALYTICS_SCRIPTS-->
|
||||
<script type="module" src="/src/spa/entry.desktop.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="<!--LOCALE-->" dir="<!--DIR-->">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!--SEO_META-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script>
|
||||
window.__SERVER_CONFIG__ = undefined; /* SERVER_CONFIG */
|
||||
</script>
|
||||
<!--ANALYTICS_SCRIPTS-->
|
||||
<script type="module" src="/src/spa/entry.mobile.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
+17
-3
@@ -33,10 +33,13 @@
|
||||
],
|
||||
"scripts": {
|
||||
"prebuild": "tsx scripts/prebuild.mts && npm run lint",
|
||||
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 next build --webpack",
|
||||
"build": "npm run build:spa && npm run build:next",
|
||||
"postbuild": "npm run build-sitemap && npm run build-migrate-db",
|
||||
"build:analyze": "NODE_OPTIONS=--max-old-space-size=81920 ANALYZE=true next build --webpack",
|
||||
"build:docker": "npm run prebuild && NODE_OPTIONS=--max-old-space-size=8192 DOCKER=true next build --webpack && npm run build-sitemap",
|
||||
"build:docker": "npm run prebuild && npm run build:spa && NODE_OPTIONS=--max-old-space-size=8192 DOCKER=true next build --webpack && npm run build-sitemap",
|
||||
"build:next": "cross-env NODE_OPTIONS=--max-old-space-size=8192 next build --webpack",
|
||||
"build:spa": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build && tsx scripts/buildSpaTemplates.ts",
|
||||
"build:spa:analyze": "cross-env ANALYZE=true NODE_OPTIONS=--max-old-space-size=8192 vite build",
|
||||
"build:vercel": "tsx scripts/prebuild.mts && npm run lint:ts && npm run lint:style && npm run type-check:tsc && npm run lint:circular && cross-env NODE_OPTIONS=--max-old-space-size=6144 next build --webpack && npm run postbuild",
|
||||
"build-migrate-db": "bun run db:migrate",
|
||||
"build-sitemap": "tsx ./scripts/buildSitemapIndex/index.ts",
|
||||
@@ -64,6 +67,8 @@
|
||||
"dev:docker:down": "docker compose -f docker-compose/dev/docker-compose.yml down",
|
||||
"dev:docker:reset": "docker compose -f docker-compose/dev/docker-compose.yml down -v && rm -rf docker-compose/dev/data && npm run dev:docker && pnpm db:migrate",
|
||||
"dev:mobile": "next dev -p 3018",
|
||||
"dev:spa": "vite",
|
||||
"dev:turbo": "turbo run dev dev:spa --filter=@lobehub/lobehub",
|
||||
"docs:cdn": "npm run workflow:docs-cdn && npm run lint:mdx",
|
||||
"docs:i18n": "lobe-i18n md && npm run lint:mdx",
|
||||
"docs:seo": "lobe-seo && npm run lint:mdx",
|
||||
@@ -240,6 +245,7 @@
|
||||
"@react-three/fiber": "^9.5.0",
|
||||
"@saintno/comfyui-sdk": "^0.2.49",
|
||||
"@serwist/next": "^9.5.0",
|
||||
"@t3-oss/env-core": "^0.13.10",
|
||||
"@t3-oss/env-nextjs": "^0.13.10",
|
||||
"@tanstack/react-query": "^5.90.20",
|
||||
"@trpc/client": "^11.8.1",
|
||||
@@ -338,6 +344,7 @@
|
||||
"react-hotkeys-hook": "^5.2.3",
|
||||
"react-i18next": "^16.5.3",
|
||||
"react-lazy-load": "^4.0.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-pdf": "^10.3.0",
|
||||
"react-responsive": "^10.0.1",
|
||||
"react-rnd": "^10.5.2",
|
||||
@@ -384,6 +391,7 @@
|
||||
"@ast-grep/napi": "^0.40.5",
|
||||
"@commitlint/cli": "^19.8.1",
|
||||
"@edge-runtime/vm": "^5.0.0",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@huggingface/tasks": "^0.19.80",
|
||||
"@inquirer/prompts": "^8.2.0",
|
||||
"@lobechat/types": "workspace:*",
|
||||
@@ -423,6 +431,7 @@
|
||||
"@types/ws": "^8.18.1",
|
||||
"@types/xast": "^2.0.4",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260207.1",
|
||||
"@vitejs/plugin-react": "^5.1.4",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"ajv-keywords": "^5.1.0",
|
||||
"code-inspector-plugin": "1.3.3",
|
||||
@@ -446,6 +455,7 @@
|
||||
"import-in-the-middle": "^2.0.5",
|
||||
"just-diff": "^6.0.2",
|
||||
"knip": "^5.82.1",
|
||||
"linkedom": "^0.18.12",
|
||||
"lint-staged": "^16.2.7",
|
||||
"markdown-table": "^3.0.4",
|
||||
"mcp-hello-world": "^1.1.2",
|
||||
@@ -464,11 +474,15 @@
|
||||
"serwist": "^9.5.0",
|
||||
"stylelint": "^16.12.0",
|
||||
"tsx": "^4.21.0",
|
||||
"turbo": "^2.8.10",
|
||||
"type-fest": "^5.4.1",
|
||||
"typescript": "^5.9.3",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "^5.1.0",
|
||||
"vite": "^7.3.1",
|
||||
"vite": "^7",
|
||||
"vite-bundle-analyzer": "^1.3.6",
|
||||
"vite-plugin-node-polyfills": "^0.25.0",
|
||||
"vite-tsconfig-paths": "^6.1.1",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"packageManager": "pnpm@10.20.0",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { KLAVIS_SERVER_TYPES, LOBEHUB_SKILL_PROVIDERS } from '@lobechat/const';
|
||||
import type { BuiltinInterventionProps } from '@lobechat/types';
|
||||
import { Avatar, Flexbox } from '@lobehub/ui';
|
||||
import { CheckCircle } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -101,8 +101,7 @@ const InstallPluginIntervention = memo<BuiltinInterventionProps<InstallPluginPar
|
||||
>
|
||||
<Flexbox horizontal align="center" gap={12}>
|
||||
{icon ? (
|
||||
<Image
|
||||
unoptimized
|
||||
<img
|
||||
alt={klavisTypeInfo?.label || identifier}
|
||||
height={40}
|
||||
src={icon}
|
||||
@@ -144,8 +143,7 @@ const InstallPluginIntervention = memo<BuiltinInterventionProps<InstallPluginPar
|
||||
>
|
||||
<Flexbox horizontal align="center" gap={12}>
|
||||
{icon ? (
|
||||
<Image
|
||||
unoptimized
|
||||
<img
|
||||
alt={lobehubSkillProviderInfo?.label || identifier}
|
||||
height={40}
|
||||
src={icon}
|
||||
@@ -188,8 +186,7 @@ const InstallPluginIntervention = memo<BuiltinInterventionProps<InstallPluginPar
|
||||
>
|
||||
<Flexbox horizontal align="center" gap={12}>
|
||||
{pluginIcon && typeof pluginIcon === 'string' && pluginIcon.startsWith('http') ? (
|
||||
<Image
|
||||
unoptimized
|
||||
<img
|
||||
alt={pluginName}
|
||||
height={40}
|
||||
src={pluginIcon}
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
export const isDesktop = process.env.NEXT_PUBLIC_IS_DESKTOP_APP === '1';
|
||||
export const isDesktop =
|
||||
// @ts-ignore - import.meta.env is available in Vite builds
|
||||
(typeof import.meta !== 'undefined' && (import.meta as any).env?.VITE_IS_DESKTOP_APP === '1') ||
|
||||
process.env.NEXT_PUBLIC_IS_DESKTOP_APP === '1';
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
export const isDesktop = process.env.NEXT_PUBLIC_IS_DESKTOP_APP === '1';
|
||||
export const isDesktop =
|
||||
// @ts-ignore - import.meta.env is available in Vite builds
|
||||
(typeof import.meta !== 'undefined' && (import.meta as any).env?.VITE_IS_DESKTOP_APP === '1') ||
|
||||
process.env.NEXT_PUBLIC_IS_DESKTOP_APP === '1';
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import { Descriptions } from 'antd';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { ExternalLink } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { memo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -163,7 +163,7 @@ const PageContent = memo<PageContentProps>(({ result }) => {
|
||||
)}
|
||||
<Flexbox horizontal align={'center'} className={styles.url} gap={4}>
|
||||
{siteName && <div>{siteName} · </div>}
|
||||
<Link
|
||||
<a
|
||||
className={styles.url}
|
||||
href={url}
|
||||
rel={'nofollow'}
|
||||
@@ -173,7 +173,7 @@ const PageContent = memo<PageContentProps>(({ result }) => {
|
||||
>
|
||||
{result.originalUrl}
|
||||
<Icon icon={ExternalLink} />
|
||||
</Link>
|
||||
</a>
|
||||
</Flexbox>
|
||||
|
||||
<div className={styles.footer}>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { CopyButton, Flexbox, Skeleton } from '@lobehub/ui';
|
||||
import { createStaticStyles, cx } from 'antd-style';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -44,9 +44,9 @@ const LoadingCard = memo<{ url: string }>(({ url }) => {
|
||||
return (
|
||||
<Flexbox className={styles.container}>
|
||||
<Flexbox horizontal className={styles.cardBody} justify={'space-between'}>
|
||||
<Link href={url} rel={'nofollow'} target={'_blank'}>
|
||||
<a href={url} rel={'nofollow'} target={'_blank'}>
|
||||
<div className={styles.text}>{url}</div>
|
||||
</Link>
|
||||
</a>
|
||||
<CopyButton content={url} size={'small'} />
|
||||
</Flexbox>
|
||||
<Flexbox gap={4} paddingInline={16}>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ActionIcon, Alert, Block, Flexbox, Text, stopPropagation } from '@lobeh
|
||||
import { Descriptions } from 'antd';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import { ExternalLink } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -114,9 +114,9 @@ const CrawlerResultCard = memo<CrawlerData>(({ result, messageId, crawler, origi
|
||||
<Flexbox gap={8} paddingBlock={8} paddingInline={12}>
|
||||
<Flexbox horizontal align={'center'} className={styles.titleRow} justify={'space-between'}>
|
||||
<Text ellipsis>{title || originalUrl}</Text>
|
||||
<Link href={url} target={'_blank'} onClick={stopPropagation}>
|
||||
<a href={url} target={'_blank'} onClick={stopPropagation}>
|
||||
<ActionIcon icon={ExternalLink} size={'small'} />
|
||||
</Link>
|
||||
</a>
|
||||
</Flexbox>
|
||||
<Text ellipsis={{ rows: 2 }} fontSize={12} type={'secondary'}>
|
||||
{description || result.content?.slice(0, 40)}
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import type { UniformSearchResult } from '@lobechat/types';
|
||||
import { Block, Flexbox, Text } from '@lobehub/ui';
|
||||
import { createStaticStyles } from 'antd-style';
|
||||
import Link from 'next/link';
|
||||
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
@@ -24,7 +24,7 @@ const SearchResultItem = memo<UniformSearchResult & { style?: CSSProperties }>(
|
||||
const urlObj = new URL(url);
|
||||
const host = urlObj.hostname;
|
||||
return (
|
||||
<Link href={url} target={'_blank'}>
|
||||
<a href={url} target={'_blank'}>
|
||||
<Block
|
||||
clickable
|
||||
className={styles.container}
|
||||
@@ -41,7 +41,7 @@ const SearchResultItem = memo<UniformSearchResult & { style?: CSSProperties }>(
|
||||
</Text>
|
||||
</Flexbox>
|
||||
</Block>
|
||||
</Link>
|
||||
</a>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -2,6 +2,6 @@ import { isDesktop } from './version';
|
||||
|
||||
export const ELECTRON_BE_PROTOCOL_SCHEME = 'lobe-backend';
|
||||
|
||||
export const withElectronProtocolIfElectron = (url: string) => {
|
||||
return isDesktop ? `${ELECTRON_BE_PROTOCOL_SCHEME}://lobe${url}` : url;
|
||||
export const morphApiURI = (uri: string) => {
|
||||
return isDesktop ? `${ELECTRON_BE_PROTOCOL_SCHEME}://lobe${uri}` : uri;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,10 @@ import pkg from '../../../package.json';
|
||||
|
||||
export const CURRENT_VERSION = pkg.version;
|
||||
|
||||
export const isDesktop = process.env.NEXT_PUBLIC_IS_DESKTOP_APP === '1';
|
||||
export const isDesktop =
|
||||
// @ts-ignore - import.meta.env is available in Vite builds
|
||||
(typeof import.meta !== 'undefined' && (import.meta as any).env?.VITE_IS_DESKTOP_APP === '1') ||
|
||||
process.env.NEXT_PUBLIC_IS_DESKTOP_APP === '1';
|
||||
|
||||
// @ts-ignore
|
||||
export const isCustomBranding = BRANDING_NAME !== 'LobeHub';
|
||||
|
||||
@@ -50,6 +50,7 @@ export interface GlobalServerConfig {
|
||||
aiProvider: ServerLanguageModel;
|
||||
defaultAgent?: PartialDeep<UserDefaultAgent>;
|
||||
disableEmailPassword?: boolean;
|
||||
enableAuth?: boolean;
|
||||
enableBusinessFeatures?: boolean;
|
||||
/**
|
||||
* @deprecated
|
||||
|
||||
@@ -21,3 +21,10 @@ export const setCookie = (
|
||||
document.cookie = `${key}=${value};expires=${expires};path=/;`;
|
||||
}
|
||||
};
|
||||
|
||||
export const getCookie = (key: string) => {
|
||||
return document.cookie
|
||||
.split('; ')
|
||||
.find((row) => row.startsWith(`${key}=`))
|
||||
?.split('=')[1];
|
||||
};
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { Plugin, UserConfig } from 'vite';
|
||||
|
||||
export function createDependencyChunksPlugin(dependencies: string[][]): Plugin {
|
||||
return {
|
||||
name: 'dependency-chunks',
|
||||
config(config: UserConfig) {
|
||||
config.build = config.build || {};
|
||||
config.build.rollupOptions = config.build.rollupOptions || {};
|
||||
config.build.rollupOptions.output = config.build.rollupOptions.output || {};
|
||||
|
||||
const { output } = config.build.rollupOptions;
|
||||
const outputConfig = Array.isArray(output) ? output[0] : output;
|
||||
outputConfig.assetFileNames = 'assets/[name].[hash:6][extname]';
|
||||
outputConfig.chunkFileNames = (chunkInfo) => {
|
||||
return chunkInfo.name.startsWith('vendor/')
|
||||
? '[name]-[hash].js'
|
||||
: 'assets/[name]-[hash].js';
|
||||
};
|
||||
|
||||
outputConfig.manualChunks = (id: string, { getModuleInfo }) => {
|
||||
const moduleInfo = getModuleInfo(id);
|
||||
if (moduleInfo?.dynamicImporters?.length && moduleInfo?.importers?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const matchedDep = dependencies.findIndex((dep) => {
|
||||
return dep.some((d) => {
|
||||
const pattern = `/node_modules/${d}/`;
|
||||
return id.includes(pattern) && !id.includes(`${pattern}node_modules/`);
|
||||
});
|
||||
});
|
||||
|
||||
if (matchedDep !== -1) {
|
||||
return `vendor/${matchedDep}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -4,6 +4,7 @@ packages:
|
||||
- e2e
|
||||
- apps/desktop/src/main
|
||||
|
||||
|
||||
onlyBuiltDependencies:
|
||||
- '@vercel/speed-insights'
|
||||
- '@lobehub/editor'
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const SPA_DIR = resolve(process.cwd(), 'public/spa');
|
||||
const OUTPUT_PATH = resolve(process.cwd(), 'src/app/[[...path]]/spaHtmlTemplates.ts');
|
||||
|
||||
async function run() {
|
||||
const html = await readFile(resolve(SPA_DIR, 'index.html'), 'utf8');
|
||||
|
||||
const source = `// AUTO-GENERATED by scripts/buildSpaTemplates.ts
|
||||
// DO NOT EDIT MANUALLY.
|
||||
export const desktopHtmlTemplate = ${JSON.stringify(html)};
|
||||
export const mobileHtmlTemplate = ${JSON.stringify(html)};
|
||||
`;
|
||||
|
||||
await writeFile(OUTPUT_PATH, source, 'utf8');
|
||||
}
|
||||
|
||||
run().catch((error) => {
|
||||
console.error('Failed to generate SPA HTML templates:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -218,9 +218,9 @@ const assertMetadataManifestRemoved = (code: string) =>
|
||||
!/\bmanifest\s*:/.test(code) && !/\bmetadataBase\s*:/.test(code);
|
||||
|
||||
export const modifyAppCode = async (TEMP_DIR: string) => {
|
||||
// 1. Replace src/app/[variants]/page.tsx with a desktop-only entry
|
||||
const variantsPagePath = path.join(TEMP_DIR, 'src/app/[variants]/page.tsx');
|
||||
console.log(' Processing src/app/[variants]/page.tsx...');
|
||||
// 1. Replace src/routes/page.tsx with a desktop-only entry
|
||||
const variantsPagePath = path.join(TEMP_DIR, 'src/routes/page.tsx');
|
||||
console.log(' Processing src/routes/page.tsx...');
|
||||
await writeFileEnsuring({
|
||||
filePath: variantsPagePath,
|
||||
name: 'modifyAppCode:variantsPage',
|
||||
@@ -238,9 +238,9 @@ export const modifyAppCode = async (TEMP_DIR: string) => {
|
||||
assertAfter: assertDevPanelStripped,
|
||||
});
|
||||
|
||||
// 3. Delete src/app/[variants]/(main)/settings/security directory
|
||||
const securityDirPath = path.join(TEMP_DIR, 'src/app/[variants]/(main)/settings/security');
|
||||
console.log(' Deleting src/app/[variants]/(main)/settings/security directory...');
|
||||
// 3. Delete src/routes/(main)/settings/security directory
|
||||
const securityDirPath = path.join(TEMP_DIR, 'src/routes/(main)/settings/security');
|
||||
console.log(' Deleting src/routes/(main)/settings/security directory...');
|
||||
await removePathEnsuring({
|
||||
name: 'modifyAppCode:deleteSecurityDir',
|
||||
path: securityDirPath,
|
||||
@@ -249,9 +249,9 @@ export const modifyAppCode = async (TEMP_DIR: string) => {
|
||||
// 4. Remove Security tab wiring from SettingsContent
|
||||
const settingsContentPath = path.join(
|
||||
TEMP_DIR,
|
||||
'src/app/[variants]/(main)/settings/features/SettingsContent.tsx',
|
||||
'src/routes/(main)/settings/features/SettingsContent.tsx',
|
||||
);
|
||||
console.log(' Processing src/app/[variants]/(main)/settings/features/SettingsContent.tsx...');
|
||||
console.log(' Processing src/routes/(main)/settings/features/SettingsContent.tsx...');
|
||||
await updateFile({
|
||||
filePath: settingsContentPath,
|
||||
name: 'modifyAppCode:removeSecurityTab',
|
||||
@@ -259,9 +259,9 @@ export const modifyAppCode = async (TEMP_DIR: string) => {
|
||||
assertAfter: assertSecurityTabRemoved,
|
||||
});
|
||||
|
||||
// 5. Remove SpeedInsights and Analytics from src/app/[variants]/layout.tsx
|
||||
const variantsLayoutPath = path.join(TEMP_DIR, 'src/app/[variants]/layout.tsx');
|
||||
console.log(' Processing src/app/[variants]/layout.tsx...');
|
||||
// 5. Remove SpeedInsights and Analytics from src/routes/layout.tsx
|
||||
const variantsLayoutPath = path.join(TEMP_DIR, 'src/routes/layout.tsx');
|
||||
console.log(' Processing src/routes/layout.tsx...');
|
||||
await updateFile({
|
||||
filePath: variantsLayoutPath,
|
||||
name: 'modifyAppCode:removeSpeedInsightsAndAnalytics',
|
||||
@@ -280,8 +280,8 @@ export const modifyAppCode = async (TEMP_DIR: string) => {
|
||||
});
|
||||
|
||||
// 7. Remove manifest from metadata
|
||||
const metadataPath = path.join(TEMP_DIR, 'src/app/[variants]/metadata.ts');
|
||||
console.log(' Processing src/app/[variants]/metadata.ts...');
|
||||
const metadataPath = path.join(TEMP_DIR, 'src/routes/metadata.ts');
|
||||
console.log(' Processing src/routes/metadata.ts...');
|
||||
await updateFile({
|
||||
filePath: metadataPath,
|
||||
name: 'modifyAppCode:removeManifestFromMetadata',
|
||||
@@ -292,11 +292,11 @@ export const modifyAppCode = async (TEMP_DIR: string) => {
|
||||
|
||||
if (isDirectRun(import.meta.url)) {
|
||||
await runStandalone('modifyAppCode', modifyAppCode, [
|
||||
{ lang: Lang.Tsx, path: 'src/app/[variants]/page.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/routes/page.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/layout/GlobalProvider/index.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/app/[variants]/(main)/settings/features/SettingsContent.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/app/[variants]/layout.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/routes/(main)/settings/features/SettingsContent.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/routes/layout.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/components/mdx/Image.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/app/[variants]/metadata.ts' },
|
||||
{ lang: Lang.Tsx, path: 'src/routes/metadata.ts' },
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ const removeDynamicElementImport = (code: string): string => {
|
||||
export const convertDynamicToStatic = async (TEMP_DIR: string) => {
|
||||
const routerConfigPath = path.join(
|
||||
TEMP_DIR,
|
||||
'src/app/[variants]/router/desktopRouter.config.tsx',
|
||||
'src/routes/router/desktopRouter.config.tsx',
|
||||
);
|
||||
|
||||
console.log(' Processing dynamicElement → static imports...');
|
||||
@@ -268,6 +268,6 @@ export const convertDynamicToStatic = async (TEMP_DIR: string) => {
|
||||
|
||||
if (isDirectRun(import.meta.url)) {
|
||||
await runStandalone('convertDynamicToStatic', convertDynamicToStatic, [
|
||||
{ lang: Lang.Tsx, path: 'src/app/[variants]/router/desktopRouter.config.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/routes/router/desktopRouter.config.tsx' },
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ export const modifySourceForElectron = async (TEMP_DIR: string) => {
|
||||
if (isDirectRun(import.meta.url)) {
|
||||
await runStandalone('modifySourceForElectron', modifySourceForElectron, [
|
||||
{ lang: Lang.TypeScript, path: path.join(process.cwd(), 'next.config.ts') },
|
||||
{ lang: Lang.Tsx, path: 'src/app/[variants]/page.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/routes/page.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/layout/GlobalProvider/index.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/app/[variants]/router/desktopRouter.config.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/routes/router/desktopRouter.config.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/components/mdx/Image.tsx' },
|
||||
{ lang: Lang.TypeScript, path: 'src/features/DevPanel/CacheViewer/getCacheEntries.ts' },
|
||||
{ lang: Lang.TypeScript, path: 'src/server/translation.ts' },
|
||||
|
||||
@@ -193,7 +193,7 @@ const transformFile = (code: string, filePath: string): string => {
|
||||
export const convertNextDynamicToStatic = async (TEMP_DIR: string) => {
|
||||
const appDirs = [
|
||||
{ dir: path.join(TEMP_DIR, 'src/app/(variants)'), label: 'src/app/(variants)' },
|
||||
{ dir: path.join(TEMP_DIR, 'src/app/[variants]'), label: 'src/app/[variants]' },
|
||||
{ dir: path.join(TEMP_DIR, 'src/routes'), label: 'src/routes' },
|
||||
];
|
||||
|
||||
console.log(' Processing next/dynamic → static imports...');
|
||||
|
||||
@@ -95,7 +95,7 @@ const removeUnusedImports = (code: string): string => {
|
||||
export const removeSuspenseFromConversation = async (TEMP_DIR: string) => {
|
||||
const filePath = path.join(
|
||||
TEMP_DIR,
|
||||
'src/app/[variants]/(main)/agent/features/Conversation/index.tsx',
|
||||
'src/routes/(main)/agent/features/Conversation/index.tsx',
|
||||
);
|
||||
|
||||
console.log(' Removing Suspense from Conversation/index.tsx...');
|
||||
@@ -123,6 +123,6 @@ export const removeSuspenseFromConversation = async (TEMP_DIR: string) => {
|
||||
|
||||
if (isDirectRun(import.meta.url)) {
|
||||
await runStandalone('removeSuspenseFromConversation', removeSuspenseFromConversation, [
|
||||
{ lang: Lang.Tsx, path: 'src/app/[variants]/(main)/agent/features/Conversation/index.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/routes/(main)/agent/features/Conversation/index.tsx' },
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@ export const modifyRoutes = async (TEMP_DIR: string) => {
|
||||
'src/app/(backend)/market',
|
||||
|
||||
// Auth & User routes
|
||||
'src/app/[variants]/(auth)',
|
||||
'src/app/[variants]/(mobile)',
|
||||
'src/app/[variants]/(main)/(mobile)/me',
|
||||
'src/app/[variants]/(main)/changelog',
|
||||
'src/app/[variants]/oauth',
|
||||
'src/routes/(auth)',
|
||||
'src/routes/(mobile)',
|
||||
'src/routes/(main)/(mobile)/me',
|
||||
'src/routes/(main)/changelog',
|
||||
'src/routes/oauth',
|
||||
|
||||
// Other app roots
|
||||
'src/app/market-auth-callback',
|
||||
@@ -47,7 +47,7 @@ export const modifyRoutes = async (TEMP_DIR: string) => {
|
||||
}
|
||||
|
||||
// 2. Delete root loading.tsx files(not needed in Electron SPA)
|
||||
const loadingFiles = ['src/app/loading.tsx', 'src/app/[variants]/loading.tsx'];
|
||||
const loadingFiles = ['src/app/loading.tsx', 'src/routes/loading.tsx'];
|
||||
console.log(` Removing ${loadingFiles.length} root loading.tsx files...`);
|
||||
for (const file of loadingFiles) {
|
||||
const fullPath = path.join(TEMP_DIR, file);
|
||||
@@ -60,9 +60,9 @@ export const modifyRoutes = async (TEMP_DIR: string) => {
|
||||
// 3. Modify desktopRouter.config.tsx
|
||||
const routerConfigPath = path.join(
|
||||
TEMP_DIR,
|
||||
'src/app/[variants]/router/desktopRouter.config.tsx',
|
||||
'src/routes/router/desktopRouter.config.tsx',
|
||||
);
|
||||
console.log(' Processing src/app/[variants]/router/desktopRouter.config.tsx...');
|
||||
console.log(' Processing src/routes/router/desktopRouter.config.tsx...');
|
||||
await updateFile({
|
||||
assertAfter: (code) => !/\bchangelog\b/.test(code),
|
||||
filePath: routerConfigPath,
|
||||
@@ -104,6 +104,6 @@ export const modifyRoutes = async (TEMP_DIR: string) => {
|
||||
|
||||
if (isDirectRun(import.meta.url)) {
|
||||
await runStandalone('modifyRoutes', modifyRoutes, [
|
||||
{ lang: Lang.Tsx, path: 'src/app/[variants]/router/desktopRouter.config.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/routes/router/desktopRouter.config.tsx' },
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ const generateStaticComponentMap = (imports: DynamicImportInfo[]): string => {
|
||||
export const convertSettingsContentToStatic = async (TEMP_DIR: string) => {
|
||||
const filePath = path.join(
|
||||
TEMP_DIR,
|
||||
'src/app/[variants]/(main)/settings/features/SettingsContent.tsx',
|
||||
'src/routes/(main)/settings/features/SettingsContent.tsx',
|
||||
);
|
||||
|
||||
console.log(' Processing SettingsContent.tsx dynamic imports...');
|
||||
@@ -125,6 +125,6 @@ export const convertSettingsContentToStatic = async (TEMP_DIR: string) => {
|
||||
|
||||
if (isDirectRun(import.meta.url)) {
|
||||
await runStandalone('convertSettingsContentToStatic', convertSettingsContentToStatic, [
|
||||
{ lang: Lang.Tsx, path: 'src/app/[variants]/(main)/settings/features/SettingsContent.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/routes/(main)/settings/features/SettingsContent.tsx' },
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import path from 'node:path';
|
||||
import { invariant, isDirectRun, runStandalone, updateFile } from './utils.mjs';
|
||||
|
||||
export const wrapChildrenWithClientOnly = async (TEMP_DIR: string) => {
|
||||
const layoutPath = path.join(TEMP_DIR, 'src/app/[variants]/layout.tsx');
|
||||
const layoutPath = path.join(TEMP_DIR, 'src/routes/layout.tsx');
|
||||
|
||||
console.log(' Wrapping children with ClientOnly in layout.tsx...');
|
||||
|
||||
@@ -77,6 +77,6 @@ export const wrapChildrenWithClientOnly = async (TEMP_DIR: string) => {
|
||||
|
||||
if (isDirectRun(import.meta.url)) {
|
||||
await runStandalone('wrapChildrenWithClientOnly', wrapChildrenWithClientOnly, [
|
||||
{ lang: Lang.Tsx, path: 'src/app/[variants]/layout.tsx' },
|
||||
{ lang: Lang.Tsx, path: 'src/routes/layout.tsx' },
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* to React Router version hooks.
|
||||
*
|
||||
* For files in (main) directory:
|
||||
* - usePathname -> @/app/[variants]/(main)/hooks/usePathname
|
||||
* - useSearchParams -> @/app/[variants]/(main)/hooks/useSearchParams
|
||||
* - useRouter -> @/app/[variants]/(main)/hooks/useRouter
|
||||
* - usePathname -> @/routes/(main)/hooks/usePathname
|
||||
* - useSearchParams -> @/routes/(main)/hooks/useSearchParams
|
||||
* - useRouter -> @/routes/(main)/hooks/useRouter
|
||||
*
|
||||
* @see RFC 147: LOBE-2850 - Phase 3
|
||||
*/
|
||||
@@ -19,14 +19,14 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
// Files that should be migrated to React Router version
|
||||
const SPA_FILES = [
|
||||
// (main) directory files using @/libs/next/navigation
|
||||
'src/app/[variants]/(main)/community/_layout/Sidebar/Header/Nav.tsx',
|
||||
'src/app/[variants]/(main)/group/_layout/Sidebar/Header/Nav.tsx',
|
||||
'src/app/[variants]/(main)/group/_layout/Sidebar/Topic/hooks/useTopicNavigation.ts',
|
||||
'src/app/[variants]/(main)/group/_layout/Sidebar/Topic/hooks/useThreadNavigation.ts',
|
||||
'src/app/[variants]/(main)/chat/_layout/Sidebar/Header/Nav.tsx',
|
||||
'src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/hooks/useTopicNavigation.ts',
|
||||
'src/app/[variants]/(main)/chat/_layout/Sidebar/Topic/hooks/useThreadNavigation.ts',
|
||||
'src/app/[variants]/(main)/memory/_layout/Sidebar/Header/Nav.tsx',
|
||||
'src/routes/(main)/community/_layout/Sidebar/Header/Nav.tsx',
|
||||
'src/routes/(main)/group/_layout/Sidebar/Header/Nav.tsx',
|
||||
'src/routes/(main)/group/_layout/Sidebar/Topic/hooks/useTopicNavigation.ts',
|
||||
'src/routes/(main)/group/_layout/Sidebar/Topic/hooks/useThreadNavigation.ts',
|
||||
'src/routes/(main)/chat/_layout/Sidebar/Header/Nav.tsx',
|
||||
'src/routes/(main)/chat/_layout/Sidebar/Topic/hooks/useTopicNavigation.ts',
|
||||
'src/routes/(main)/chat/_layout/Sidebar/Topic/hooks/useThreadNavigation.ts',
|
||||
'src/routes/(main)/memory/_layout/Sidebar/Header/Nav.tsx',
|
||||
];
|
||||
|
||||
interface MigrationResult {
|
||||
@@ -64,21 +64,21 @@ async function migrateFile(relativePath: string): Promise<MigrationResult | null
|
||||
for (const hook of importedHooks) {
|
||||
switch (hook) {
|
||||
case 'usePathname': {
|
||||
newImports.push(`import { usePathname } from '@/app/[variants]/(main)/hooks/usePathname';`);
|
||||
newImports.push(`import { usePathname } from '@/routes/(main)/hooks/usePathname';`);
|
||||
changes.push('usePathname -> React Router version');
|
||||
|
||||
break;
|
||||
}
|
||||
case 'useSearchParams': {
|
||||
newImports.push(
|
||||
`import { useSearchParams } from '@/app/[variants]/(main)/hooks/useSearchParams';`,
|
||||
`import { useSearchParams } from '@/routes/(main)/hooks/useSearchParams';`,
|
||||
);
|
||||
changes.push('useSearchParams -> React Router version');
|
||||
|
||||
break;
|
||||
}
|
||||
case 'useRouter': {
|
||||
newImports.push(`import { useRouter } from '@/app/[variants]/(main)/hooks/useRouter';`);
|
||||
newImports.push(`import { useRouter } from '@/routes/(main)/hooks/useRouter';`);
|
||||
changes.push('useRouter -> React Router version');
|
||||
|
||||
break;
|
||||
|
||||
@@ -144,22 +144,22 @@ const partialBuildPages = [
|
||||
// {
|
||||
// name: 'changelog',
|
||||
// disabled: isDesktop,
|
||||
// paths: ['src/app/[variants]/(main)/changelog'],
|
||||
// paths: ['src/routes/(main)/changelog'],
|
||||
// },
|
||||
{
|
||||
name: 'auth',
|
||||
disabled: isDesktop,
|
||||
paths: ['src/app/[variants]/(auth)'],
|
||||
paths: ['src/app/(auth)'],
|
||||
},
|
||||
// {
|
||||
// name: 'mobile',
|
||||
// disabled: isDesktop,
|
||||
// paths: ['src/app/[variants]/(main)/(mobile)'],
|
||||
// paths: ['src/routes/(main)/(mobile)'],
|
||||
// },
|
||||
{
|
||||
name: 'oauth',
|
||||
disabled: isDesktop,
|
||||
paths: ['src/app/[variants]/oauth', 'src/app/(backend)/oidc'],
|
||||
paths: ['src/app/(auth)/oauth', 'src/app/(backend)/oidc'],
|
||||
},
|
||||
{
|
||||
name: 'api-webhooks',
|
||||
|
||||
@@ -4,7 +4,7 @@ import { SiDiscord } from '@icons-pack/react-simple-icons';
|
||||
import { SOCIAL_URL } from '@lobechat/business-const';
|
||||
import { Button, Flexbox, Icon, Text } from '@lobehub/ui';
|
||||
import { cssVar } from 'antd-style';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -16,7 +16,8 @@ const normalizeErrorCode = (code?: string | null) =>
|
||||
|
||||
const AuthErrorPage = memo(() => {
|
||||
const { t } = useTranslation('authError');
|
||||
const [error] = useQueryState('error', parseAsString);
|
||||
const searchParams = useSearchParams();
|
||||
const error = searchParams.get('error');
|
||||
|
||||
const code = normalizeErrorCode(error);
|
||||
const description = t(`codes.${code}`, { defaultValue: t('codes.UNKNOWN') });
|
||||
@@ -0,0 +1,35 @@
|
||||
import { cookies, headers } from 'next/headers';
|
||||
import { type ReactNode, Suspense } from 'react';
|
||||
import { isRtlLang } from 'rtl-detect';
|
||||
|
||||
import ClientOnly from '@/components/client/ClientOnly';
|
||||
import { DEFAULT_LANG, LOBE_LOCALE_COOKIE } from '@/const/locale';
|
||||
import AuthProvider from '@/layout/AuthProvider';
|
||||
import GlobalProvider from '@/layout/GlobalProvider';
|
||||
import { parseBrowserLanguage } from '@/utils/locale';
|
||||
|
||||
import AuthContainer from './_layout';
|
||||
|
||||
export default async function AuthRootLayout({ children }: { children: ReactNode }) {
|
||||
const cookieStore = await cookies();
|
||||
const headerStore = await headers();
|
||||
const locale =
|
||||
cookieStore.get(LOBE_LOCALE_COOKIE)?.value || parseBrowserLanguage(headerStore, DEFAULT_LANG);
|
||||
const direction = isRtlLang(locale) ? 'rtl' : 'ltr';
|
||||
|
||||
return (
|
||||
<html suppressHydrationWarning dir={direction} lang={locale}>
|
||||
<body>
|
||||
<GlobalProvider isMobile={false} locale={locale}>
|
||||
<AuthProvider>
|
||||
<ClientOnly>
|
||||
<Suspense>
|
||||
<AuthContainer>{children}</AuthContainer>
|
||||
</Suspense>
|
||||
</ClientOnly>
|
||||
</AuthProvider>
|
||||
</GlobalProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
+4
-3
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Button, Flexbox, FluentEmoji, Highlighter, Text } from '@lobehub/ui';
|
||||
import { Result } from 'antd';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import React, { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -10,8 +10,9 @@ import Link from '@/libs/next/Link';
|
||||
|
||||
const FailedPage = memo(() => {
|
||||
const { t } = useTranslation('oauth');
|
||||
const [reason] = useQueryState('reason');
|
||||
const [errorMessage] = useQueryState<string>('errorMessage', parseAsString);
|
||||
const searchParams = useSearchParams();
|
||||
const reason = searchParams.get('reason');
|
||||
const errorMessage = searchParams.get('errorMessage');
|
||||
|
||||
return (
|
||||
<Result
|
||||
+1
-1
@@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import Link from '@/libs/next/Link';
|
||||
import { useRouter, useSearchParams } from '@/libs/next/navigation';
|
||||
|
||||
import AuthCard from '../../../../features/AuthCard';
|
||||
import AuthCard from '@/features/AuthCard';
|
||||
import { ResetPasswordContent } from './ResetPasswordContent';
|
||||
|
||||
const ResetPasswordPage = () => {
|
||||
+1
-1
@@ -10,7 +10,7 @@ import { Trans, useTranslation } from 'react-i18next';
|
||||
import AuthIcons from '@/components/AuthIcons';
|
||||
import { PRIVACY_URL, TERMS_URL } from '@/const/url';
|
||||
|
||||
import AuthCard from '../../../../features/AuthCard';
|
||||
import AuthCard from '@/features/AuthCard';
|
||||
|
||||
const styles = createStaticStyles(({ css, cssVar }) => ({
|
||||
setPasswordLink: css`
|
||||
+1
-1
@@ -6,7 +6,7 @@ import { ChevronLeft, ChevronRight, Lock } from 'lucide-react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import AuthCard from '../../../../features/AuthCard';
|
||||
import AuthCard from '@/features/AuthCard';
|
||||
|
||||
export interface SignInPasswordStepProps {
|
||||
email: string;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
|
||||
import { Form } from 'antd';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -9,7 +10,6 @@ import { useBusinessSignin } from '@/business/client/hooks/useBusinessSignin';
|
||||
import { message } from '@/components/AntdStaticMethods';
|
||||
import { requestPasswordReset, signIn } from '@/libs/better-auth/auth-client';
|
||||
import { isBuiltinProvider, normalizeProviderId } from '@/libs/better-auth/utils/client';
|
||||
import { useRouter, useSearchParams } from '@/libs/next/navigation';
|
||||
import { useServerConfigStore } from '@/store/serverConfig';
|
||||
import { serverConfigSelectors } from '@/store/serverConfig/selectors';
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import Link from '@/libs/next/Link';
|
||||
import { useSearchParams } from '@/libs/next/navigation';
|
||||
|
||||
import { AuthCard } from '../../../../../features/AuthCard';
|
||||
import { AuthCard } from '@/features/AuthCard';
|
||||
import { type SignUpFormValues } from './useSignUp';
|
||||
import { useSignUp } from './useSignUp';
|
||||
|
||||
+5
-4
@@ -1,15 +1,16 @@
|
||||
import { cookies } from 'next/headers';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
import { DEFAULT_LANG, LOBE_LOCALE_COOKIE } from '@/const/locale';
|
||||
import { authEnv } from '@/envs/auth';
|
||||
import { metadataModule } from '@/server/metadata';
|
||||
import { translation } from '@/server/translation';
|
||||
import { type DynamicLayoutProps } from '@/types/next';
|
||||
import { RouteVariants } from '@/utils/server/routeVariants';
|
||||
|
||||
import BetterAuthSignUpForm from './BetterAuthSignUpForm';
|
||||
|
||||
export const generateMetadata = async (props: DynamicLayoutProps) => {
|
||||
const locale = await RouteVariants.getLocale(props);
|
||||
export const generateMetadata = async () => {
|
||||
const cookieStore = await cookies();
|
||||
const locale = cookieStore.get(LOBE_LOCALE_COOKIE)?.value || DEFAULT_LANG;
|
||||
const { t } = await translation('auth', locale);
|
||||
|
||||
return metadataModule.generate({
|
||||
+1
-1
@@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import Link from '@/libs/next/Link';
|
||||
import { useSearchParams } from '@/libs/next/navigation';
|
||||
|
||||
import AuthCard from '../../../../features/AuthCard';
|
||||
import AuthCard from '@/features/AuthCard';
|
||||
import { VerifyEmailContent } from './VerifyEmailContent';
|
||||
|
||||
const VerifyEmailPage = () => {
|
||||
@@ -1,10 +1,11 @@
|
||||
import { type NextRequest } from 'next/server';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
import { appEnv } from '@/envs/app';
|
||||
import { getTrustedClientTokenForSession } from '@/libs/trusted-client';
|
||||
import { MarketService } from '@/server/services/market';
|
||||
|
||||
const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'https://market.lobehub.com';
|
||||
const MARKET_BASE_URL = appEnv.MARKET_BASE_URL || 'https://market.lobehub.com';
|
||||
|
||||
type RouteContext = {
|
||||
params: Promise<{
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
import { type NextRequest } from 'next/server';
|
||||
import { isRtlLang } from 'rtl-detect';
|
||||
|
||||
import { getServerFeatureFlagsValue } from '@/config/featureFlags';
|
||||
import { DEFAULT_LANG, LOBE_LOCALE_COOKIE } from '@/const/locale';
|
||||
import { analyticsEnv } from '@/envs/analytics';
|
||||
import { appEnv } from '@/envs/app';
|
||||
import { fileEnv } from '@/envs/file';
|
||||
import { pythonEnv } from '@/envs/python';
|
||||
import { getServerGlobalConfig } from '@/server/globalConfig';
|
||||
import { serializeForHtml } from '@/server/utils/serializeForHtml';
|
||||
import {
|
||||
type AnalyticsConfig,
|
||||
type SPAClientEnv,
|
||||
type SPAServerConfig,
|
||||
type SPAThemeConfig,
|
||||
} from '@/types/spaServerConfig';
|
||||
import { parseBrowserLanguage } from '@/utils/locale';
|
||||
|
||||
import { desktopHtmlTemplate, mobileHtmlTemplate } from './spaHtmlTemplates';
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
const VITE_DEV_ORIGIN = process.env.VITE_DEV_ORIGIN || 'http://localhost:3011';
|
||||
|
||||
async function rewriteViteAssetUrls(html: string): Promise<string> {
|
||||
const { parseHTML } = await import('linkedom');
|
||||
const { document } = parseHTML(html);
|
||||
|
||||
document.querySelectorAll('script[src]').forEach((el) => {
|
||||
const src = el.getAttribute('src');
|
||||
if (src && src.startsWith('/')) {
|
||||
el.setAttribute('src', `${VITE_DEV_ORIGIN}${src}`);
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('link[href]').forEach((el) => {
|
||||
const href = el.getAttribute('href');
|
||||
if (href && href.startsWith('/')) {
|
||||
el.setAttribute('href', `${VITE_DEV_ORIGIN}${href}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Rewrite inline module scripts (e.g., Vite's React refresh preamble)
|
||||
document.querySelectorAll('script[type="module"]:not([src])').forEach((el) => {
|
||||
const text = el.textContent || '';
|
||||
if (text.includes('/@')) {
|
||||
el.textContent = text.replaceAll(
|
||||
/from\s+["'](\/[@\w].*?)["']/g,
|
||||
(_match, p) => `from "${VITE_DEV_ORIGIN}${p}"`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Patch Worker constructor to wrap cross-origin Vite URLs as blob URLs
|
||||
const workerPatch = document.createElement('script');
|
||||
workerPatch.textContent = `(function(){
|
||||
var O=globalThis.Worker;
|
||||
globalThis.Worker=function(u,o){
|
||||
var h=typeof u==='string'?u:u instanceof URL?u.href:'';
|
||||
if(h.startsWith('${VITE_DEV_ORIGIN}')){
|
||||
var b=new Blob(['import "'+h+'";'],{type:'application/javascript'});
|
||||
return new O(URL.createObjectURL(b),Object.assign({},o,{type:'module'}));
|
||||
}return new O(u,o)};
|
||||
globalThis.Worker.prototype=O.prototype;
|
||||
})();`;
|
||||
const head = document.querySelector('head');
|
||||
if (head?.firstChild) {
|
||||
head.insertBefore(workerPatch, head.firstChild);
|
||||
}
|
||||
|
||||
return document.toString();
|
||||
}
|
||||
|
||||
async function getTemplate(isMobile: boolean): Promise<string> {
|
||||
if (isDev) {
|
||||
const res = await fetch(VITE_DEV_ORIGIN);
|
||||
const html = await res.text();
|
||||
return await rewriteViteAssetUrls(html);
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
return mobileHtmlTemplate;
|
||||
}
|
||||
return desktopHtmlTemplate;
|
||||
}
|
||||
|
||||
function buildAnalyticsConfig(): AnalyticsConfig {
|
||||
const config: AnalyticsConfig = {};
|
||||
|
||||
if (analyticsEnv.ENABLE_GOOGLE_ANALYTICS && analyticsEnv.GOOGLE_ANALYTICS_MEASUREMENT_ID) {
|
||||
config.google = { measurementId: analyticsEnv.GOOGLE_ANALYTICS_MEASUREMENT_ID };
|
||||
}
|
||||
|
||||
if (analyticsEnv.ENABLED_PLAUSIBLE_ANALYTICS && analyticsEnv.PLAUSIBLE_DOMAIN) {
|
||||
config.plausible = {
|
||||
domain: analyticsEnv.PLAUSIBLE_DOMAIN,
|
||||
scriptBaseUrl: analyticsEnv.PLAUSIBLE_SCRIPT_BASE_URL,
|
||||
};
|
||||
}
|
||||
|
||||
if (analyticsEnv.ENABLED_UMAMI_ANALYTICS && analyticsEnv.UMAMI_WEBSITE_ID) {
|
||||
config.umami = {
|
||||
scriptUrl: analyticsEnv.UMAMI_SCRIPT_URL,
|
||||
websiteId: analyticsEnv.UMAMI_WEBSITE_ID,
|
||||
};
|
||||
}
|
||||
|
||||
if (analyticsEnv.ENABLED_CLARITY_ANALYTICS && analyticsEnv.CLARITY_PROJECT_ID) {
|
||||
config.clarity = { projectId: analyticsEnv.CLARITY_PROJECT_ID };
|
||||
}
|
||||
|
||||
if (analyticsEnv.ENABLED_POSTHOG_ANALYTICS && analyticsEnv.POSTHOG_KEY) {
|
||||
config.posthog = {
|
||||
debug: analyticsEnv.DEBUG_POSTHOG_ANALYTICS,
|
||||
host: analyticsEnv.POSTHOG_HOST,
|
||||
key: analyticsEnv.POSTHOG_KEY,
|
||||
};
|
||||
}
|
||||
|
||||
if (analyticsEnv.REACT_SCAN_MONITOR_API_KEY) {
|
||||
config.reactScan = { apiKey: analyticsEnv.REACT_SCAN_MONITOR_API_KEY };
|
||||
}
|
||||
|
||||
if (analyticsEnv.ENABLE_VERCEL_ANALYTICS) {
|
||||
config.vercel = {
|
||||
debug: analyticsEnv.DEBUG_VERCEL_ANALYTICS,
|
||||
enabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (analyticsEnv.DESKTOP_PROJECT_ID && analyticsEnv.DESKTOP_UMAMI_BASE_URL) {
|
||||
config.desktop = {
|
||||
baseUrl: analyticsEnv.DESKTOP_UMAMI_BASE_URL,
|
||||
projectId: analyticsEnv.DESKTOP_PROJECT_ID,
|
||||
};
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
function buildThemeConfig(): SPAThemeConfig {
|
||||
return {
|
||||
cdnUseGlobal: appEnv.CDN_USE_GLOBAL,
|
||||
customFontFamily: appEnv.CUSTOM_FONT_FAMILY,
|
||||
customFontURL: appEnv.CUSTOM_FONT_URL,
|
||||
};
|
||||
}
|
||||
|
||||
function buildClientEnv(): SPAClientEnv {
|
||||
return {
|
||||
marketBaseUrl: appEnv.NEXT_PUBLIC_MARKET_BASE_URL,
|
||||
pyodideIndexUrl: pythonEnv.NEXT_PUBLIC_PYODIDE_INDEX_URL,
|
||||
pyodidePipIndexUrl: pythonEnv.NEXT_PUBLIC_PYODIDE_PIP_INDEX_URL,
|
||||
s3FilePath: fileEnv.NEXT_PUBLIC_S3_FILE_PATH,
|
||||
};
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const ua = request.headers.get('user-agent') || '';
|
||||
const isMobile = /Android|iPhone|iPad|iPod|Mobile/i.test(ua);
|
||||
|
||||
const cookieLocale = request.cookies.get(LOBE_LOCALE_COOKIE)?.value;
|
||||
const browserLanguage = parseBrowserLanguage(request.headers, DEFAULT_LANG);
|
||||
const locale = cookieLocale || browserLanguage;
|
||||
|
||||
const serverConfig = await getServerGlobalConfig();
|
||||
const featureFlags = getServerFeatureFlagsValue();
|
||||
const analyticsConfig = buildAnalyticsConfig();
|
||||
const theme = buildThemeConfig();
|
||||
const clientEnv = buildClientEnv();
|
||||
|
||||
const spaConfig: SPAServerConfig = {
|
||||
analyticsConfig,
|
||||
clientEnv,
|
||||
config: serverConfig,
|
||||
featureFlags,
|
||||
isMobile,
|
||||
locale,
|
||||
theme,
|
||||
};
|
||||
|
||||
const dir = isRtlLang(locale) ? 'rtl' : 'ltr';
|
||||
|
||||
let html = await getTemplate(isMobile);
|
||||
|
||||
html = html.replace(
|
||||
/window\.__SERVER_CONFIG__\s*=\s*undefined;\s*\/\*\s*SERVER_CONFIG\s*\*\//,
|
||||
`window.__SERVER_CONFIG__ = ${serializeForHtml(spaConfig)};`,
|
||||
);
|
||||
|
||||
html = html.replace('<!--LOCALE-->', locale);
|
||||
html = html.replace('<!--DIR-->', dir);
|
||||
html = html.replace('<!--SEO_META-->', '');
|
||||
html = html.replace('<!--ANALYTICS_SCRIPTS-->', '');
|
||||
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
'cache-control': 'private, no-cache',
|
||||
'content-type': 'text/html; charset=utf-8',
|
||||
'vary': 'Accept-Language, User-Agent, Cookie',
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { NuqsAdapter } from 'nuqs/adapters/next/app';
|
||||
import { type FC, type PropsWithChildren } from 'react';
|
||||
|
||||
import ClientOnly from '@/components/client/ClientOnly';
|
||||
|
||||
import AuthContainer from './_layout';
|
||||
|
||||
const AuthLayout: FC<PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<ClientOnly>
|
||||
<NuqsAdapter>
|
||||
<AuthContainer>{children}</AuthContainer>
|
||||
</NuqsAdapter>
|
||||
</ClientOnly>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthLayout;
|
||||
@@ -1,2 +0,0 @@
|
||||
// Re-export from chat version to avoid code duplication
|
||||
export { default } from '@/app/[variants]/(main)/agent/features/Conversation/AgentWelcome/ToolAuthAlert';
|
||||
@@ -1,3 +0,0 @@
|
||||
'use client';
|
||||
|
||||
export { default } from '@/components/Error';
|
||||
@@ -1,3 +0,0 @@
|
||||
import { enableMapSet } from 'immer';
|
||||
|
||||
enableMapSet();
|
||||
@@ -1,162 +0,0 @@
|
||||
import './initialize';
|
||||
|
||||
import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
|
||||
import { SpeedInsights } from '@vercel/speed-insights/next';
|
||||
import { type ResolvingViewport } from 'next';
|
||||
import Script from 'next/script';
|
||||
import { type ReactNode } from 'react';
|
||||
import { Suspense } from 'react';
|
||||
import { isRtlLang } from 'rtl-detect';
|
||||
|
||||
import BusinessGlobalProvider from '@/business/client/BusinessGlobalProvider';
|
||||
import Analytics from '@/components/Analytics';
|
||||
import { DEFAULT_LANG } from '@/const/locale';
|
||||
import { isDesktop } from '@/const/version';
|
||||
import AuthProvider from '@/layout/AuthProvider';
|
||||
import GlobalProvider from '@/layout/GlobalProvider';
|
||||
import { type Locales } from '@/locales/resources';
|
||||
import { type DynamicLayoutProps } from '@/types/next';
|
||||
import { RouteVariants } from '@/utils/server/routeVariants';
|
||||
|
||||
const inVercel = process.env.VERCEL === '1';
|
||||
|
||||
export interface RootLayoutProps extends DynamicLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const RootLayout = async ({ children, params }: RootLayoutProps) => {
|
||||
const { variants } = await params;
|
||||
|
||||
const { locale, isMobile, primaryColor, neutralColor } =
|
||||
RouteVariants.deserializeVariants(variants);
|
||||
|
||||
const direction = isRtlLang(locale) ? 'rtl' : 'ltr';
|
||||
|
||||
const renderContent = () => {
|
||||
return (
|
||||
<GlobalProvider
|
||||
isMobile={isMobile}
|
||||
locale={locale}
|
||||
neutralColor={neutralColor}
|
||||
primaryColor={primaryColor}
|
||||
variants={variants}
|
||||
>
|
||||
<AuthProvider>{children}</AuthProvider>
|
||||
</GlobalProvider>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<html suppressHydrationWarning dir={direction} lang={locale}>
|
||||
<head>
|
||||
<script dangerouslySetInnerHTML={{ __html: `(${outdateBrowserScript.toString()})();` }} />
|
||||
{process.env.DEBUG_REACT_SCAN === '1' && (
|
||||
<Script
|
||||
crossOrigin={'anonymous'}
|
||||
src={'https://unpkg.com/react-scan/dist/auto.global.js'}
|
||||
strategy={'lazyOnload'}
|
||||
/>
|
||||
)}
|
||||
</head>
|
||||
<body>
|
||||
{ENABLE_BUSINESS_FEATURES ? (
|
||||
<BusinessGlobalProvider>{renderContent()}</BusinessGlobalProvider>
|
||||
) : (
|
||||
renderContent()
|
||||
)}
|
||||
<Suspense fallback={null}>
|
||||
<Analytics />
|
||||
{inVercel && <SpeedInsights />}
|
||||
</Suspense>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
};
|
||||
|
||||
function outdateBrowserScript() {
|
||||
function supportsImportMaps(): boolean {
|
||||
return (
|
||||
typeof HTMLScriptElement !== 'undefined' &&
|
||||
typeof (HTMLScriptElement as any).supports === 'function' &&
|
||||
(HTMLScriptElement as any).supports('importmap')
|
||||
);
|
||||
}
|
||||
|
||||
function supportsCascadeLayers(): boolean {
|
||||
if (typeof document === 'undefined') return false;
|
||||
|
||||
const el = document.createElement('div');
|
||||
el.className = '__layer_test__';
|
||||
el.style.position = 'absolute';
|
||||
el.style.left = '-99999px';
|
||||
el.style.top = '-99999px';
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@layer a, b;
|
||||
@layer a { .__layer_test__ { color: rgb(1, 2, 3); } }
|
||||
@layer b { .__layer_test__ { color: rgb(4, 5, 6); } }
|
||||
`;
|
||||
|
||||
document.documentElement.append(style);
|
||||
document.documentElement.append(el);
|
||||
|
||||
const color = getComputedStyle(el).color;
|
||||
|
||||
el.remove();
|
||||
style.remove();
|
||||
|
||||
return color === 'rgb(4, 5, 6)';
|
||||
}
|
||||
|
||||
const isOutdateBrowser = !(supportsImportMaps() && supportsCascadeLayers());
|
||||
if (isOutdateBrowser) {
|
||||
window.location.href = '/not-compatible.html';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export default RootLayout;
|
||||
|
||||
export { generateMetadata } from './metadata';
|
||||
|
||||
export const generateViewport = async (props: DynamicLayoutProps): ResolvingViewport => {
|
||||
const isMobile = await RouteVariants.getIsMobile(props);
|
||||
|
||||
const dynamicScale = isMobile ? { maximumScale: 1, userScalable: false } : {};
|
||||
|
||||
return {
|
||||
...dynamicScale,
|
||||
colorScheme: null,
|
||||
initialScale: 1,
|
||||
minimumScale: 1,
|
||||
themeColor: [
|
||||
{ color: '#f8f8f8', media: '(prefers-color-scheme: light)' },
|
||||
{ color: '#000', media: '(prefers-color-scheme: dark)' },
|
||||
],
|
||||
viewportFit: 'cover',
|
||||
width: 'device-width',
|
||||
};
|
||||
};
|
||||
|
||||
export const generateStaticParams = () => {
|
||||
const mobileOptions = isDesktop ? [false] : [true, false];
|
||||
// only static for several page, other go to dynamic
|
||||
const staticLocales: Locales[] = [DEFAULT_LANG, 'zh-CN'];
|
||||
|
||||
const variants: { variants: string }[] = [];
|
||||
|
||||
for (const locale of staticLocales) {
|
||||
for (const isMobile of mobileOptions) {
|
||||
variants.push({
|
||||
variants: RouteVariants.serializeVariants({
|
||||
isMobile,
|
||||
locale,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return variants;
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
import Loading from '@/components/Loading/BrandTextLoading';
|
||||
|
||||
export default () => <Loading debugId="Variants" />;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user