@lobehub/lobehub 2.0.0-next.213 → 2.0.0-next.214

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/apps/desktop/package.json +3 -2
  3. package/apps/desktop/src/main/const/store.ts +1 -1
  4. package/apps/desktop/src/main/controllers/SystemCtr.ts +2 -3
  5. package/apps/desktop/src/main/core/App.ts +10 -3
  6. package/apps/desktop/src/main/types/store.ts +1 -1
  7. package/changelog/v1.json +5 -0
  8. package/package.json +4 -3
  9. package/packages/builtin-tool-knowledge-base/src/client/Render/SearchKnowledgeBase/Item/index.tsx +4 -2
  10. package/packages/builtin-tool-local-system/src/client/Intervention/EditLocalFile/index.tsx +3 -2
  11. package/packages/builtin-tool-local-system/src/client/Render/EditLocalFile/index.tsx +3 -2
  12. package/packages/const/src/theme.ts +0 -2
  13. package/packages/desktop-bridge/src/routeVariants.ts +2 -9
  14. package/packages/electron-client-ipc/src/types/system.ts +1 -1
  15. package/scripts/electronWorkflow/modifiers/nextConfig.mts +41 -13
  16. package/src/app/[variants]/(auth)/_layout/index.tsx +3 -2
  17. package/src/app/[variants]/(auth)/_layout/style.ts +8 -18
  18. package/src/app/[variants]/(auth)/layout.tsx +7 -3
  19. package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/index.tsx +4 -2
  20. package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/style.ts +3 -0
  21. package/src/app/[variants]/(main)/_layout/DesktopLayoutContainer.tsx +3 -2
  22. package/src/app/[variants]/(main)/chat/profile/features/ProfileEditor/PluginTag.tsx +3 -2
  23. package/src/app/[variants]/(main)/community/(list)/_layout/Footer.tsx +3 -2
  24. package/src/app/[variants]/(main)/group/features/Conversation/ChatItem/Thread.tsx +3 -2
  25. package/src/app/[variants]/(main)/group/profile/features/AgentBuilder/index.tsx +0 -1
  26. package/src/app/[variants]/(main)/group/profile/features/ProfileEditor/PluginTag.tsx +3 -2
  27. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/Editing.tsx +2 -2
  28. package/src/app/[variants]/(main)/home/_layout/Footer/index.tsx +1 -1
  29. package/src/app/[variants]/(main)/home/_layout/index.tsx +3 -2
  30. package/src/app/[variants]/(main)/home/features/CommunityAgents/Item.tsx +3 -2
  31. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/AspectRatioSelect/index.tsx +4 -2
  32. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/ModelSelect/ImageModelItem.tsx +3 -2
  33. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/Select/index.tsx +4 -2
  34. package/src/app/[variants]/(main)/image/features/PromptInput/index.tsx +3 -2
  35. package/src/app/[variants]/(main)/memory/features/TimeLineView/index.tsx +9 -4
  36. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/Editing.tsx +2 -2
  37. package/src/app/[variants]/(main)/settings/common/features/Common/Common.tsx +11 -11
  38. package/src/app/[variants]/(main)/settings/provider/(list)/ProviderGrid/Card.tsx +3 -2
  39. package/src/app/[variants]/(main)/settings/stats/features/overview/ShareButton/TotalCard.tsx +4 -2
  40. package/src/app/[variants]/(mobile)/me/(home)/features/Header.tsx +6 -8
  41. package/src/app/[variants]/layout.tsx +10 -15
  42. package/src/app/[variants]/onboarding/_layout/index.tsx +3 -2
  43. package/src/app/[variants]/onboarding/features/ModeSelectionStep.tsx +3 -2
  44. package/src/app/[variants]/router/index.tsx +12 -8
  45. package/src/components/Cell/Divider.tsx +4 -2
  46. package/src/components/DataStyleModal/index.tsx +4 -2
  47. package/src/components/FeatureList/index.tsx +4 -2
  48. package/src/components/FileParsingStatus/EmbeddingStatus.tsx +3 -2
  49. package/src/components/FileParsingStatus/index.tsx +3 -2
  50. package/src/components/Notification/index.tsx +4 -2
  51. package/src/components/client/ClientOnly.tsx +17 -0
  52. package/src/features/AlertBanner/CloudBanner.tsx +4 -3
  53. package/src/features/CommandMenu/ThemeMenu.tsx +1 -1
  54. package/src/features/CommandMenu/types.ts +5 -2
  55. package/src/features/CommandMenu/useCommandMenu.ts +3 -2
  56. package/src/features/Conversation/Markdown/plugins/LobeArtifact/Render/index.tsx +3 -2
  57. package/src/features/Conversation/Messages/components/FileChunks/ChunkItem.tsx +3 -2
  58. package/src/features/Conversation/Messages/components/FileChunks/index.tsx +4 -2
  59. package/src/features/Conversation/Messages/components/SearchGrounding.tsx +3 -2
  60. package/src/features/ElectronTitlebar/hooks/useWatchThemeUpdate.ts +21 -38
  61. package/src/features/GroupChatSettings/AgentCard.tsx +3 -2
  62. package/src/features/GroupChatSettings/HostMemberCard.tsx +3 -2
  63. package/src/features/PageEditor/DiffAllToolbar.tsx +4 -2
  64. package/src/features/User/UserPanel/ThemeButton.tsx +18 -29
  65. package/src/hooks/useIsDark.ts +11 -0
  66. package/src/layout/AuthProvider/Clerk/useAppearance.ts +4 -2
  67. package/src/layout/AuthProvider/MarketAuth/MarketAuthConfirmModal.tsx +3 -2
  68. package/src/layout/GlobalProvider/AppTheme.tsx +15 -19
  69. package/src/layout/GlobalProvider/NextThemeProvider.tsx +22 -0
  70. package/src/layout/GlobalProvider/StyleRegistry.tsx +18 -13
  71. package/src/layout/GlobalProvider/index.tsx +38 -36
  72. package/src/libs/next/proxy/define-config.ts +2 -11
  73. package/src/store/global/action.test.ts +0 -15
  74. package/src/store/global/actions/__tests__/general.test.ts +0 -37
  75. package/src/store/global/actions/general.ts +0 -21
  76. package/src/store/global/initialState.ts +0 -6
  77. package/src/store/global/selectors/systemStatus.test.ts +0 -20
  78. package/src/store/global/selectors/systemStatus.ts +0 -2
  79. package/src/styles/global.ts +0 -2
  80. package/src/utils/server/routeVariants.test.ts +17 -51
  81. package/src/utils/server/routeVariants.ts +0 -1
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
 
3
3
  import { LOBE_CHAT_CLOUD, UTM_SOURCE } from '@lobechat/business-const';
4
- import { Button, Center, Flexbox, Icon , lobeStaticStylish } from '@lobehub/ui';
4
+ import { Button, Center, Flexbox, Icon, lobeStaticStylish } from '@lobehub/ui';
5
5
  import { useSize } from 'ahooks';
6
- import { createStaticStyles, cx, useThemeMode } from 'antd-style';
6
+ import { createStaticStyles, cx } from 'antd-style';
7
7
  import { ArrowRightIcon } from 'lucide-react';
8
8
  import Link from 'next/link';
9
9
  import { memo, useEffect, useRef, useState } from 'react';
@@ -11,6 +11,7 @@ import Marquee from 'react-fast-marquee';
11
11
  import { useTranslation } from 'react-i18next';
12
12
 
13
13
  import { OFFICIAL_URL } from '@/const/url';
14
+ import { useIsDark } from '@/hooks/useIsDark';
14
15
  import { isOnServerSide } from '@/utils/env';
15
16
 
16
17
  export const BANNER_HEIGHT = 40;
@@ -50,7 +51,7 @@ const CloudBanner = memo<{ mobile?: boolean }>(({ mobile }) => {
50
51
  const contentRef = useRef(null);
51
52
  const size = useSize(ref);
52
53
  const contentSize = useSize(contentRef);
53
- const { isDarkMode } = useThemeMode();
54
+ const isDarkMode = useIsDark();
54
55
  const { t } = useTranslation('common');
55
56
  const [isTruncated, setIsTruncated] = useState(mobile);
56
57
 
@@ -24,7 +24,7 @@ const ThemeMenu = memo(() => {
24
24
  <div className={styles.itemLabel}>{t('cmdk.themeDark')}</div>
25
25
  </div>
26
26
  </Command.Item>
27
- <Command.Item onSelect={() => handleThemeChange('auto')} value="theme-auto">
27
+ <Command.Item onSelect={() => handleThemeChange('system')} value="theme-system">
28
28
  <Monitor className={styles.icon} />
29
29
  <div className={styles.itemContent}>
30
30
  <div className={styles.itemLabel}>{t('cmdk.themeAuto')}</div>
@@ -4,7 +4,7 @@ export interface ChatMessage {
4
4
  role: 'user' | 'assistant';
5
5
  }
6
6
 
7
- export type ThemeMode = 'light' | 'dark' | 'auto';
7
+ export type ThemeMode = 'light' | 'dark' | 'system';
8
8
 
9
9
  export type PageType = 'theme' | 'ask-ai' | string;
10
10
 
@@ -25,4 +25,7 @@ export type MenuContext =
25
25
  | 'page'
26
26
  | 'painting';
27
27
 
28
- export type ContextType = Extract<MenuContext, 'agent' | 'group' | 'resource' | 'settings' | 'page' | 'painting'>;
28
+ export type ContextType = Extract<
29
+ MenuContext,
30
+ 'agent' | 'group' | 'resource' | 'settings' | 'page' | 'painting'
31
+ >;
@@ -1,4 +1,5 @@
1
1
  import { useDebounce } from 'ahooks';
2
+ import { useTheme as useNextThemesTheme } from 'next-themes';
2
3
  import { useEffect, useMemo } from 'react';
3
4
  import { useNavigate } from 'react-router-dom';
4
5
  import useSWR from 'swr';
@@ -37,7 +38,7 @@ export const useCommandMenu = () => {
37
38
  } = useCommandMenuContext();
38
39
 
39
40
  const navigate = useNavigate();
40
- const switchThemeMode = useGlobalStore((s) => s.switchThemeMode);
41
+ const { setTheme } = useNextThemesTheme();
41
42
  const createAgent = useAgentStore((s) => s.createAgent);
42
43
  const refreshAgentList = useHomeStore((s) => s.refreshAgentList);
43
44
  const inboxAgentId = useAgentStore(builtinAgentSelectors.inboxAgentId);
@@ -106,7 +107,7 @@ export const useCommandMenu = () => {
106
107
  };
107
108
 
108
109
  const handleThemeChange = (theme: ThemeMode) => {
109
- switchThemeMode(theme);
110
+ setTheme(theme);
110
111
  closeCommandMenu();
111
112
  };
112
113
 
@@ -1,9 +1,10 @@
1
1
  import { Center, Flexbox, Icon } from '@lobehub/ui';
2
- import { createStaticStyles, cx, useThemeMode } from 'antd-style';
2
+ import { createStaticStyles, cx } from 'antd-style';
3
3
  import { Loader2 } from 'lucide-react';
4
4
  import { memo, useEffect } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
+ import { useIsDark } from '@/hooks/useIsDark';
7
8
  import { useChatStore } from '@/store/chat';
8
9
  import { chatPortalSelectors, messageStateSelectors } from '@/store/chat/selectors';
9
10
  import { dotLoading } from '@/styles/loading';
@@ -57,7 +58,7 @@ interface ArtifactProps extends MarkdownElementProps {
57
58
 
58
59
  const Render = memo<ArtifactProps>(({ identifier, title, type, language, children, id }) => {
59
60
  const { t } = useTranslation('chat');
60
- const { isDarkMode } = useThemeMode();
61
+ const isDarkMode = useIsDark();
61
62
 
62
63
  const hasChildren = !!children;
63
64
  const str = ((children as string) || '').toString?.();
@@ -1,9 +1,10 @@
1
1
  import { type ChatFileChunk } from '@lobechat/types';
2
2
  import { Center, Flexbox, Text, Tooltip } from '@lobehub/ui';
3
- import { cx, useThemeMode } from 'antd-style';
3
+ import { cx } from 'antd-style';
4
4
  import { memo } from 'react';
5
5
 
6
6
  import FileIcon from '@/components/FileIcon';
7
+ import { useIsDark } from '@/hooks/useIsDark';
7
8
  import { useChatStore } from '@/store/chat';
8
9
 
9
10
  import { styles } from './style';
@@ -13,7 +14,7 @@ export interface ChunkItemProps extends ChatFileChunk {
13
14
  }
14
15
 
15
16
  const ChunkItem = memo<ChunkItemProps>(({ id, fileId, similarity, text, filename, fileType }) => {
16
- const { isDarkMode } = useThemeMode();
17
+ const isDarkMode = useIsDark();
17
18
  // Note: openFilePreview is a portal action, kept in ChatStore as it's a global UI state
18
19
  const openFilePreview = useChatStore((s) => s.openFilePreview);
19
20
 
@@ -1,10 +1,12 @@
1
1
  import { type ChatFileChunk } from '@lobechat/types';
2
2
  import { Flexbox, Icon } from '@lobehub/ui';
3
- import { createStaticStyles, cssVar, cx, useThemeMode } from 'antd-style';
3
+ import { createStaticStyles, cssVar, cx } from 'antd-style';
4
4
  import { BookOpenTextIcon, ChevronDown, ChevronRight } from 'lucide-react';
5
5
  import { memo, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
+ import { useIsDark } from '@/hooks/useIsDark';
9
+
8
10
  import ChunkItem from './ChunkItem';
9
11
 
10
12
  const styles = createStaticStyles(({ css }) => ({
@@ -47,7 +49,7 @@ interface FileChunksProps {
47
49
 
48
50
  const FileChunks = memo<FileChunksProps>(({ data }) => {
49
51
  const { t } = useTranslation('chat');
50
- const { isDarkMode } = useThemeMode();
52
+ const isDarkMode = useIsDark();
51
53
 
52
54
  const [showDetail, setShowDetail] = useState(false);
53
55
 
@@ -1,11 +1,12 @@
1
1
  import { Flexbox, Icon, SearchResultCards, Tag } from '@lobehub/ui';
2
- import { createStaticStyles, cssVar, cx, useThemeMode } from 'antd-style';
2
+ import { createStaticStyles, cssVar, cx } from 'antd-style';
3
3
  import { ChevronDown, ChevronRight, Globe } from 'lucide-react';
4
4
  import { AnimatePresence, m as motion } from 'motion/react';
5
5
  import Image from 'next/image';
6
6
  import { memo, useState } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
 
9
+ import { useIsDark } from '@/hooks/useIsDark';
9
10
  import { type GroundingSearch } from '@/types/search';
10
11
 
11
12
  const styles = createStaticStyles(({ css, cssVar }) => ({
@@ -46,7 +47,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
46
47
 
47
48
  const SearchGrounding = memo<GroundingSearch>(({ searchQueries, citations }) => {
48
49
  const { t } = useTranslation('chat');
49
- const { isDarkMode } = useThemeMode();
50
+ const isDarkMode = useIsDark();
50
51
 
51
52
  const [showDetail, setShowDetail] = useState(false);
52
53
 
@@ -1,33 +1,15 @@
1
1
  import { useWatchBroadcast } from '@lobechat/electron-client-ipc';
2
- import { LOBE_THEME_APP_ID } from '@lobehub/ui';
3
- import { useLayoutEffect } from 'react';
2
+ import { useTheme } from 'next-themes';
3
+ import { useEffect } from 'react';
4
4
 
5
+ import { isDesktop } from '@/const/version';
5
6
  import { useElectronStore } from '@/store/electron';
6
7
  import { useGlobalStore } from '@/store/global';
7
- import { systemStatusSelectors } from '@/store/global/selectors';
8
8
  import { ensureElectronIpc } from '@/utils/electron/ipc';
9
9
 
10
- const sidebarColors = {
11
- dark: '#000',
12
- light: '#f8f8f8',
13
- };
14
10
  export const useWatchThemeUpdate = () => {
15
- const [isAppStateInit, systemAppearance, updateElectronAppState, isMac] = useElectronStore(
16
- (s) => [
17
- s.isAppStateInit,
18
- s.appState.systemAppearance,
19
- s.updateElectronAppState,
20
- s.appState.isMac,
21
- ],
22
- );
23
- const [switchThemeMode, switchLocale] = useGlobalStore((s) => [
24
- s.switchThemeMode,
25
- s.switchLocale,
26
- ]);
27
-
28
- useWatchBroadcast('themeChanged', ({ themeMode }) => {
29
- switchThemeMode(themeMode, { skipBroadcast: true });
30
- });
11
+ const [updateElectronAppState] = useElectronStore((s) => [s.updateElectronAppState]);
12
+ const [switchLocale] = useGlobalStore((s) => [s.switchLocale]);
31
13
 
32
14
  useWatchBroadcast('localeChanged', ({ locale }) => {
33
15
  switchLocale(locale as any, { skipBroadcast: true });
@@ -36,20 +18,21 @@ export const useWatchThemeUpdate = () => {
36
18
  useWatchBroadcast('systemThemeChanged', ({ themeMode }) => {
37
19
  updateElectronAppState({ systemAppearance: themeMode });
38
20
  });
39
- const themeMode = useGlobalStore(systemStatusSelectors.themeMode);
40
- useLayoutEffect(() => {
41
- ensureElectronIpc().system.setSystemThemeMode(themeMode);
42
- }, []);
43
-
44
- useLayoutEffect(() => {
45
- if (!isAppStateInit || !isMac) return;
46
- document.documentElement.style.background = 'none';
47
-
48
- const lobeApp = document.querySelector('#' + LOBE_THEME_APP_ID);
49
- if (!lobeApp) return;
50
21
 
51
- if (systemAppearance) {
52
- document.body.style.background = `color-mix(in srgb, ${sidebarColors[systemAppearance as 'dark' | 'light']} 86%, transparent)`;
53
- }
54
- }, [systemAppearance, isAppStateInit, isMac]);
22
+ const { theme } = useTheme();
23
+
24
+ useEffect(() => {
25
+ if (!isDesktop) return;
26
+ if (!theme) return;
27
+
28
+ (async () => {
29
+ try {
30
+ await ensureElectronIpc().system.updateThemeModeHandler(
31
+ theme as 'dark' | 'light' | 'system',
32
+ );
33
+ } catch {
34
+ // Ignore errors in non-electron environment
35
+ }
36
+ })();
37
+ }, [theme]);
55
38
  };
@@ -2,12 +2,13 @@
2
2
 
3
3
  import { Avatar, Flexbox, Icon, Skeleton, Text, Tooltip } from '@lobehub/ui';
4
4
  import { Switch } from 'antd';
5
- import { createStaticStyles, cssVar, cx, useThemeMode } from 'antd-style';
5
+ import { createStaticStyles, cssVar, cx } from 'antd-style';
6
6
  import { Bot, Loader2 } from 'lucide-react';
7
7
  import { memo } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
9
 
10
10
  import { DEFAULT_AVATAR } from '@/const/meta';
11
+ import { useIsDark } from '@/hooks/useIsDark';
11
12
  import { type LobeAgentSession } from '@/types/session';
12
13
 
13
14
  const styles = createStaticStyles(({ css }) => ({
@@ -59,7 +60,7 @@ interface AgentCardProps {
59
60
  const AgentCard = memo<AgentCardProps>(
60
61
  ({ agent, inGroup, isHost, loading, onAction, operationLoading }) => {
61
62
  const { t } = useTranslation('setting');
62
- const { isDarkMode } = useThemeMode();
63
+ const isDarkMode = useIsDark();
63
64
 
64
65
  if (loading) {
65
66
  return (
@@ -2,12 +2,13 @@
2
2
 
3
3
  import { Avatar, Flexbox, Icon, Text, Tooltip } from '@lobehub/ui';
4
4
  import { Switch } from 'antd';
5
- import { createStaticStyles, cssVar, cx, useThemeMode } from 'antd-style';
5
+ import { createStaticStyles, cssVar, cx } from 'antd-style';
6
6
  import { Bot, Loader2 } from 'lucide-react';
7
7
  import { memo } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
9
 
10
10
  import { DEFAULT_SUPERVISOR_AVATAR } from '@/const/meta';
11
+ import { useIsDark } from '@/hooks/useIsDark';
11
12
 
12
13
  const styles = createStaticStyles(({ css }) => ({
13
14
  container: css`
@@ -53,7 +54,7 @@ interface HostMemberCardProps {
53
54
  }
54
55
 
55
56
  const HostMemberCard = memo<HostMemberCardProps>(({ checked, loading, onToggle }) => {
56
- const { isDarkMode } = useThemeMode();
57
+ const isDarkMode = useIsDark();
57
58
  const { t } = useTranslation('setting');
58
59
 
59
60
  const title = t('settingGroupMembers.host.title');
@@ -3,11 +3,13 @@
3
3
  import { DiffAction, LITEXML_DIFFNODE_ALL_COMMAND } from '@lobehub/editor';
4
4
  import { Block, Icon } from '@lobehub/ui';
5
5
  import { Button, Space } from 'antd';
6
- import { createStaticStyles, cssVar, cx, useThemeMode } from 'antd-style';
6
+ import { createStaticStyles, cssVar, cx } from 'antd-style';
7
7
  import { Check, X } from 'lucide-react';
8
8
  import { memo, useEffect, useState } from 'react';
9
9
  import { useTranslation } from 'react-i18next';
10
10
 
11
+ import { useIsDark } from '@/hooks/useIsDark';
12
+
11
13
  import { usePageEditorStore } from './store';
12
14
 
13
15
  const styles = createStaticStyles(({ css }) => ({
@@ -36,7 +38,7 @@ const styles = createStaticStyles(({ css }) => ({
36
38
 
37
39
  const DiffAllToolbar = memo(() => {
38
40
  const { t } = useTranslation('editor');
39
- const { isDarkMode } = useThemeMode();
41
+ const isDarkMode = useIsDark();
40
42
  const [editor, performSave] = usePageEditorStore((s) => [s.editor, s.performSave]);
41
43
  const [hasPendingDiffs, setHasPendingDiffs] = useState(false);
42
44
 
@@ -1,65 +1,54 @@
1
- import { ActionIcon, Dropdown, type DropdownProps, Icon } from '@lobehub/ui';
1
+ import { ActionIcon, DropdownMenu, type DropdownMenuProps, Icon } from '@lobehub/ui';
2
2
  import { Monitor, Moon, Sun } from 'lucide-react';
3
+ import { useTheme as useNextThemesTheme } from 'next-themes';
3
4
  import { FC, useMemo } from 'react';
4
5
  import { useTranslation } from 'react-i18next';
5
6
 
6
- import { type MenuProps } from '@/components/Menu';
7
- import { useGlobalStore } from '@/store/global';
8
- import { systemStatusSelectors } from '@/store/global/selectors';
9
-
10
7
  const themeIcons = {
11
- auto: Monitor,
12
8
  dark: Moon,
13
9
  light: Sun,
10
+ system: Monitor,
14
11
  };
15
12
 
16
- const ThemeButton: FC<{ placement?: DropdownProps['placement']; size?: number }> = ({
13
+ const ThemeButton: FC<{ placement?: DropdownMenuProps['placement']; size?: number }> = ({
17
14
  placement,
18
15
  size,
19
16
  }) => {
20
- const [themeMode, switchThemeMode] = useGlobalStore((s) => [
21
- systemStatusSelectors.themeMode(s),
22
- s.switchThemeMode,
23
- ]);
17
+ const { setTheme, theme } = useNextThemesTheme();
24
18
 
25
19
  const { t } = useTranslation('setting');
26
20
 
27
- const items: MenuProps['items'] = useMemo(
21
+ const items = useMemo<DropdownMenuProps['items']>(
28
22
  () => [
29
23
  {
30
- icon: <Icon icon={themeIcons.auto} />,
31
- key: 'auto',
24
+ icon: <Icon icon={themeIcons.system} />,
25
+ key: 'system',
32
26
  label: t('settingCommon.themeMode.auto'),
33
- onClick: () => switchThemeMode('auto'),
27
+ onClick: () => setTheme('system'),
34
28
  },
35
29
  {
36
30
  icon: <Icon icon={themeIcons.light} />,
37
31
  key: 'light',
38
32
  label: t('settingCommon.themeMode.light'),
39
- onClick: () => switchThemeMode('light'),
33
+ onClick: () => setTheme('light'),
40
34
  },
41
35
  {
42
36
  icon: <Icon icon={themeIcons.dark} />,
43
37
  key: 'dark',
44
38
  label: t('settingCommon.themeMode.dark'),
45
- onClick: () => switchThemeMode('dark'),
39
+ onClick: () => setTheme('dark'),
46
40
  },
47
41
  ],
48
- [t],
42
+ [setTheme, t],
49
43
  );
50
44
 
51
45
  return (
52
- <Dropdown
53
- arrow={false}
54
- menu={{
55
- items,
56
- selectable: true,
57
- selectedKeys: [themeMode],
58
- }}
59
- placement={placement}
60
- >
61
- <ActionIcon icon={themeIcons[themeMode]} size={size || { blockSize: 32, size: 16 }} />
62
- </Dropdown>
46
+ <DropdownMenu items={items} placement={placement}>
47
+ <ActionIcon
48
+ icon={themeIcons[(theme as 'dark' | 'light' | 'system') || 'system']}
49
+ size={size || { blockSize: 32, size: 16 }}
50
+ />
51
+ </DropdownMenu>
63
52
  );
64
53
  };
65
54
 
@@ -0,0 +1,11 @@
1
+ import { useTheme as useNextThemesTheme } from 'next-themes';
2
+
3
+ /**
4
+ * Hook to check if the current theme is dark
5
+ * @returns boolean - true if current theme is dark, false otherwise
6
+ */
7
+ export const useIsDark = (): boolean => {
8
+ const { resolvedTheme } = useNextThemesTheme();
9
+
10
+ return resolvedTheme === 'dark';
11
+ };
@@ -3,7 +3,9 @@
3
3
  import { dark } from '@clerk/themes';
4
4
  import { type ElementsConfig, type Theme } from '@clerk/types';
5
5
  import { BRANDING_URL } from '@lobechat/business-const';
6
- import { createStaticStyles, cssVar, cx, useThemeMode } from 'antd-style';
6
+ import { createStaticStyles, cssVar, cx } from 'antd-style';
7
+
8
+ import { useIsDark } from '@/hooks/useIsDark';
7
9
 
8
10
  const prefixCls = 'cl';
9
11
 
@@ -95,7 +97,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
95
97
  })) as Partial<Record<keyof ElementsConfig, any>>;
96
98
 
97
99
  export const useAppearance = () => {
98
- const { isDarkMode } = useThemeMode();
100
+ const isDarkMode = useIsDark();
99
101
 
100
102
  const navbarStyle = cx(styles.navbar, isDarkMode && (styles as any).navbar_dark);
101
103
  const scrollBoxStyle = cx(styles.scrollBox, isDarkMode && (styles as any).scrollBox_dark);
@@ -2,12 +2,13 @@
2
2
 
3
3
  import { BRANDING_NAME } from '@lobechat/business-const';
4
4
  import { Block, Modal, Text } from '@lobehub/ui';
5
- import { createStaticStyles, cx, useThemeMode } from 'antd-style';
5
+ import { createStaticStyles, cx } from 'antd-style';
6
6
  import { memo } from 'react';
7
7
  import { Trans, useTranslation } from 'react-i18next';
8
8
 
9
9
  import { PRIVACY_URL, TERMS_URL } from '@/const/url';
10
10
  import AuthCard from '@/features/AuthCard';
11
+ import { useIsDark } from '@/hooks/useIsDark';
11
12
 
12
13
  const styles = createStaticStyles(({ css }) => ({
13
14
  container: css`
@@ -34,7 +35,7 @@ interface MarketAuthConfirmModalProps {
34
35
  const MarketAuthConfirmModal = memo<MarketAuthConfirmModalProps>(
35
36
  ({ open, onConfirm, onCancel }) => {
36
37
  const { t } = useTranslation('marketAuth');
37
- const { isDarkMode } = useThemeMode();
38
+ const isDarkMode = useIsDark();
38
39
 
39
40
  const footer = (
40
41
  <Text align={'center'} as={'div'} fontSize={13} type={'secondary'}>
@@ -8,7 +8,7 @@ import {
8
8
  ThemeProvider,
9
9
  } from '@lobehub/ui';
10
10
  import { message as antdMessage } from 'antd';
11
- import { type ThemeAppearance, createStaticStyles, cx, useTheme } from 'antd-style';
11
+ import { createStaticStyles, cx, useTheme } from 'antd-style';
12
12
  import 'antd/dist/reset.css';
13
13
  import { AppConfigContext } from 'antd/es/app/context';
14
14
  import * as motion from 'motion/react-m';
@@ -17,13 +17,10 @@ import Link from 'next/link';
17
17
  import { type ReactNode, memo, useEffect, useMemo, useState } from 'react';
18
18
 
19
19
  import AntdStaticMethods from '@/components/AntdStaticMethods';
20
- import {
21
- LOBE_THEME_APPEARANCE,
22
- LOBE_THEME_NEUTRAL_COLOR,
23
- LOBE_THEME_PRIMARY_COLOR,
24
- } from '@/const/theme';
20
+ import { LOBE_THEME_NEUTRAL_COLOR, LOBE_THEME_PRIMARY_COLOR } from '@/const/theme';
25
21
  import { isDesktop } from '@/const/version';
26
22
  import { TITLE_BAR_HEIGHT } from '@/features/ElectronTitlebar';
23
+ import { useIsDark } from '@/hooks/useIsDark';
27
24
  import { getUILocaleAndResources } from '@/libs/getUILocaleAndResources';
28
25
  import { useGlobalStore } from '@/store/global';
29
26
  import { systemStatusSelectors } from '@/store/global/selectors';
@@ -92,7 +89,6 @@ export interface AppThemeProps {
92
89
  children?: ReactNode;
93
90
  customFontFamily?: string;
94
91
  customFontURL?: string;
95
- defaultAppearance?: ThemeAppearance;
96
92
  defaultNeutralColor?: NeutralColors;
97
93
  defaultPrimaryColor?: PrimaryColors;
98
94
  globalCDN?: boolean;
@@ -101,16 +97,16 @@ export interface AppThemeProps {
101
97
  const AppTheme = memo<AppThemeProps>(
102
98
  ({
103
99
  children,
104
- defaultAppearance,
105
100
  defaultPrimaryColor,
106
101
  defaultNeutralColor,
107
102
  globalCDN,
108
103
  customFontURL,
109
104
  customFontFamily,
110
105
  }) => {
111
- const themeMode = useGlobalStore(systemStatusSelectors.themeMode);
112
106
  const language = useGlobalStore(systemStatusSelectors.language);
113
- const theme = useTheme();
107
+ const antdTheme = useTheme();
108
+ const isDark = useIsDark();
109
+
114
110
  const [primaryColor, neutralColor, animationMode] = useUserStore((s) => [
115
111
  userGeneralSettingsSelectors.primaryColor(s),
116
112
  userGeneralSettingsSelectors.neutralColor(s),
@@ -154,29 +150,29 @@ const AppTheme = memo<AppThemeProps>(
154
150
  antdMessage.config({ top: messageTop });
155
151
  }, [messageTop]);
156
152
 
153
+ const currentAppearence = isDark ? 'dark' : 'light';
154
+
157
155
  return (
158
156
  <AppConfigContext.Provider value={appConfig}>
159
157
  <ThemeProvider
160
- appearance={themeMode !== 'auto' ? themeMode : undefined}
158
+ appearance={currentAppearence}
161
159
  className={cx(styles.app, styles.scrollbar, styles.scrollbarPolyfill)}
162
160
  customTheme={{
163
161
  neutralColor: neutralColor ?? defaultNeutralColor,
164
162
  primaryColor: primaryColor ?? defaultPrimaryColor,
165
163
  }}
166
- defaultAppearance={defaultAppearance}
167
- onAppearanceChange={(appearance) => {
168
- if (themeMode !== 'auto') return;
169
-
170
- setCookie(LOBE_THEME_APPEARANCE, appearance);
171
- }}
164
+ defaultAppearance={currentAppearence}
165
+ defaultThemeMode={currentAppearence}
172
166
  theme={{
167
+ cssVar: { key: 'lobe-vars' },
173
168
  token: {
174
- fontFamily: customFontFamily ? `${customFontFamily},${theme.fontFamily}` : undefined,
169
+ fontFamily: customFontFamily
170
+ ? `${customFontFamily},${antdTheme.fontFamily}`
171
+ : undefined,
175
172
  motion: animationMode !== 'disabled',
176
173
  motionUnit: animationMode === 'agile' ? 0.05 : 0.1,
177
174
  },
178
175
  }}
179
- themeMode={themeMode}
180
176
  >
181
177
  {!!customFontURL && <FontLoader url={customFontURL} />}
182
178
  <GlobalStyle />
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ import { ThemeProvider as NextThemesProvider } from 'next-themes';
4
+ import { type ReactNode } from 'react';
5
+
6
+ interface NextThemeProviderProps {
7
+ children: ReactNode;
8
+ }
9
+
10
+ export default function NextThemeProvider({ children }: NextThemeProviderProps) {
11
+ return (
12
+ <NextThemesProvider
13
+ attribute="data-theme"
14
+ defaultTheme="system"
15
+ disableTransitionOnChange
16
+ enableSystem
17
+ forcedTheme={undefined}
18
+ >
19
+ {children}
20
+ </NextThemesProvider>
21
+ );
22
+ }
@@ -1,24 +1,29 @@
1
1
  'use client';
2
2
 
3
- import { StyleProvider, extractStaticStyle } from 'antd-style';
3
+ import { StyleProvider } from 'antd-style';
4
4
  import { useServerInsertedHTML } from 'next/navigation';
5
- import { type PropsWithChildren, useRef } from 'react';
5
+ import { type PropsWithChildren } from 'react';
6
6
 
7
- const StyleRegistry = ({ children }: PropsWithChildren) => {
8
- const isInsert = useRef(false);
7
+ import { isDesktop } from '@/const/version';
9
8
 
9
+ const StyleRegistry = ({ children }: PropsWithChildren) => {
10
10
  useServerInsertedHTML(() => {
11
- // avoid duplicate css insert
12
- // refs: https://github.com/vercel/next.js/discussions/49354#discussioncomment-6279917
13
- if (isInsert.current) return;
14
-
15
- isInsert.current = true;
16
-
17
- // @ts-ignore
18
- return extractStaticStyle().map((item) => item.style);
11
+ return (
12
+ <style
13
+ dangerouslySetInnerHTML={{
14
+ __html: `
15
+ html body {background: #f8f8f8;}
16
+ html[data-theme="dark"] body { background-color: #000; }
17
+ ${isDesktop ? 'html body, html { background: none; }' : ''}
18
+ ${isDesktop ? 'html[data-theme="dark"] body { background: color-mix(in srgb, #000 86%, transparent); }' : ''}
19
+ ${isDesktop ? 'html[data-theme="light"] body { background: color-mix(in srgb, #f8f8f8 86%, transparent); }' : ''}
20
+ `,
21
+ }}
22
+ />
23
+ );
19
24
  });
20
25
 
21
- return <StyleProvider cache={extractStaticStyle.cache}>{children}</StyleProvider>;
26
+ return <StyleProvider>{children}</StyleProvider>;
22
27
  };
23
28
 
24
29
  export default StyleRegistry;