@lobehub/lobehub 2.0.0-next.221 → 2.0.0-next.222

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 (114) hide show
  1. package/.github/workflows/claude-auto-testing.yml +6 -3
  2. package/.github/workflows/claude-dedupe-issues.yml +8 -1
  3. package/.github/workflows/claude-issue-triage.yml +8 -14
  4. package/.github/workflows/claude-translate-comments.yml +6 -3
  5. package/.github/workflows/claude-translator.yml +12 -14
  6. package/.github/workflows/claude.yml +10 -20
  7. package/.github/workflows/test.yml +26 -0
  8. package/CHANGELOG.md +33 -0
  9. package/changelog/v1.json +9 -0
  10. package/locales/zh-CN/components.json +1 -0
  11. package/package.json +3 -3
  12. package/packages/const/src/index.ts +0 -1
  13. package/packages/memory-user-memory/package.json +1 -0
  14. package/packages/memory-user-memory/src/extractors/context.test.ts +3 -2
  15. package/packages/memory-user-memory/src/extractors/experience.test.ts +3 -2
  16. package/packages/memory-user-memory/src/extractors/identity.test.ts +23 -6
  17. package/packages/memory-user-memory/src/extractors/preference.test.ts +3 -2
  18. package/packages/memory-user-memory/vitest.config.ts +4 -0
  19. package/packages/model-runtime/src/providers/replicate/index.ts +1 -1
  20. package/packages/ssrf-safe-fetch/index.test.ts +2 -2
  21. package/packages/ssrf-safe-fetch/package.json +3 -2
  22. package/packages/types/src/serverConfig.ts +2 -0
  23. package/packages/utils/package.json +1 -1
  24. package/packages/utils/src/client/xor-obfuscation.test.ts +32 -32
  25. package/packages/utils/src/client/xor-obfuscation.ts +3 -4
  26. package/packages/utils/src/imageToBase64.ts +1 -1
  27. package/packages/utils/src/server/__tests__/auth.test.ts +1 -1
  28. package/packages/utils/src/server/auth.ts +1 -1
  29. package/packages/utils/src/server/xor.test.ts +9 -7
  30. package/packages/utils/src/server/xor.ts +1 -1
  31. package/packages/web-crawler/package.json +1 -1
  32. package/packages/web-crawler/src/crawImpl/__tests__/naive.test.ts +1 -1
  33. package/packages/web-crawler/src/crawImpl/naive.ts +1 -1
  34. package/scripts/prebuild.mts +58 -1
  35. package/src/app/(backend)/api/auth/[...all]/route.ts +2 -1
  36. package/src/app/(backend)/middleware/auth/index.ts +3 -3
  37. package/src/app/(backend)/middleware/auth/utils.test.ts +1 -1
  38. package/src/app/(backend)/middleware/auth/utils.ts +1 -1
  39. package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +2 -2
  40. package/src/app/(backend)/webapi/models/[provider]/route.test.ts +1 -1
  41. package/src/app/(backend)/webapi/plugin/gateway/route.ts +1 -1
  42. package/src/app/(backend)/webapi/proxy/route.ts +1 -1
  43. package/src/app/[variants]/(auth)/login/[[...login]]/page.tsx +1 -1
  44. package/src/app/[variants]/(auth)/reset-password/layout.tsx +1 -1
  45. package/src/app/[variants]/(auth)/signin/layout.tsx +1 -1
  46. package/src/app/[variants]/(auth)/signin/useSignIn.ts +2 -2
  47. package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -1
  48. package/src/app/[variants]/(auth)/signup/[[...signup]]/useSignUp.tsx +12 -6
  49. package/src/app/[variants]/(auth)/verify-email/layout.tsx +1 -1
  50. package/src/app/[variants]/(main)/settings/profile/features/AvatarRow.tsx +1 -1
  51. package/src/app/[variants]/(main)/settings/security/index.tsx +1 -1
  52. package/src/app/[variants]/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +1 -1
  53. package/src/app/[variants]/(mobile)/me/(home)/__tests__/useCategory.test.tsx +1 -1
  54. package/src/app/[variants]/(mobile)/me/(home)/features/UserBanner.tsx +1 -1
  55. package/src/app/[variants]/(mobile)/settings/_layout/Header.tsx +1 -1
  56. package/src/components/ModelSelect/index.tsx +103 -72
  57. package/src/envs/auth.ts +30 -9
  58. package/src/features/Conversation/Messages/AssistantGroup/components/EditState.tsx +15 -32
  59. package/src/features/Conversation/Messages/AssistantGroup/index.tsx +9 -7
  60. package/src/features/EditorModal/EditorCanvas.tsx +31 -50
  61. package/src/features/EditorModal/TextareCanvas.tsx +3 -1
  62. package/src/features/EditorModal/index.tsx +14 -4
  63. package/src/features/ModelSwitchPanel/components/Footer.tsx +42 -0
  64. package/src/features/ModelSwitchPanel/components/List/MultipleProvidersModelItem.tsx +103 -0
  65. package/src/features/ModelSwitchPanel/components/List/SingleProviderModelItem.tsx +24 -0
  66. package/src/features/ModelSwitchPanel/components/List/VirtualItemRenderer.tsx +180 -0
  67. package/src/features/ModelSwitchPanel/components/List/index.tsx +99 -0
  68. package/src/features/ModelSwitchPanel/components/PanelContent.tsx +77 -0
  69. package/src/features/ModelSwitchPanel/components/Toolbar.tsx +54 -0
  70. package/src/features/ModelSwitchPanel/const.ts +29 -0
  71. package/src/features/ModelSwitchPanel/hooks/useBuildVirtualItems.ts +122 -0
  72. package/src/features/ModelSwitchPanel/hooks/useCurrentModelName.ts +18 -0
  73. package/src/features/ModelSwitchPanel/hooks/useDelayedRender.ts +18 -0
  74. package/src/features/ModelSwitchPanel/hooks/useModelAndProvider.ts +14 -0
  75. package/src/features/ModelSwitchPanel/hooks/usePanelHandlers.ts +33 -0
  76. package/src/features/ModelSwitchPanel/hooks/usePanelSize.ts +33 -0
  77. package/src/features/ModelSwitchPanel/hooks/usePanelState.ts +20 -0
  78. package/src/features/ModelSwitchPanel/index.tsx +25 -706
  79. package/src/features/ModelSwitchPanel/styles.ts +58 -0
  80. package/src/features/ModelSwitchPanel/types.ts +73 -0
  81. package/src/features/ModelSwitchPanel/utils.ts +24 -0
  82. package/src/features/User/UserPanel/PanelContent.tsx +1 -1
  83. package/src/features/User/__tests__/PanelContent.test.tsx +1 -1
  84. package/src/features/User/__tests__/UserAvatar.test.tsx +1 -1
  85. package/src/features/User/__tests__/useMenu.test.tsx +1 -1
  86. package/src/layout/GlobalProvider/StoreInitialization.tsx +2 -1
  87. package/src/libs/better-auth/auth-client.ts +7 -3
  88. package/src/libs/better-auth/define-config.ts +2 -2
  89. package/src/libs/next/proxy/define-config.ts +1 -2
  90. package/src/libs/oidc-provider/provider.test.ts +1 -1
  91. package/src/libs/trpc/async/context.ts +1 -1
  92. package/src/libs/trpc/lambda/context.ts +7 -8
  93. package/src/libs/trpc/middleware/userAuth.ts +1 -1
  94. package/src/libs/trusted-client/getSessionUser.ts +1 -1
  95. package/src/locales/default/components.ts +1 -0
  96. package/src/server/globalConfig/index.ts +2 -0
  97. package/src/server/routers/async/caller.ts +1 -1
  98. package/src/server/routers/lambda/__tests__/user.test.ts +2 -2
  99. package/src/server/routers/lambda/user.ts +2 -1
  100. package/src/services/_auth.ts +3 -3
  101. package/src/services/chat/index.ts +1 -1
  102. package/src/services/chat/mecha/contextEngineering.ts +1 -1
  103. package/src/store/global/initialState.ts +10 -0
  104. package/src/store/global/selectors/systemStatus.ts +5 -0
  105. package/src/store/serverConfig/selectors.ts +5 -1
  106. package/src/store/tool/slices/mcpStore/action.ts +74 -75
  107. package/src/store/user/slices/auth/action.test.ts +1 -1
  108. package/src/store/user/slices/auth/action.ts +1 -1
  109. package/src/store/user/slices/auth/initialState.ts +1 -1
  110. package/src/store/user/slices/auth/selectors.test.ts +1 -1
  111. package/src/store/user/slices/auth/selectors.ts +1 -1
  112. package/src/store/user/slices/common/action.ts +1 -1
  113. package/src/store/userMemory/slices/context/action.ts +6 -6
  114. package/packages/const/src/auth.ts +0 -14
@@ -0,0 +1,54 @@
1
+ import { Flexbox, Icon, SearchBar, Segmented } from '@lobehub/ui';
2
+ import { ProviderIcon } from '@lobehub/ui/icons';
3
+ import { Brain } from 'lucide-react';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import { styles } from '../styles';
8
+ import type { GroupMode } from '../types';
9
+
10
+ interface ToolbarProps {
11
+ groupMode: GroupMode;
12
+ onGroupModeChange: (mode: GroupMode) => void;
13
+ onSearchKeywordChange: (keyword: string) => void;
14
+ searchKeyword: string;
15
+ }
16
+
17
+ export const Toolbar = memo<ToolbarProps>(
18
+ ({ groupMode, onGroupModeChange, searchKeyword, onSearchKeywordChange }) => {
19
+ const { t } = useTranslation('components');
20
+
21
+ return (
22
+ <Flexbox className={styles.toolbar} gap={4} horizontal paddingBlock={8} paddingInline={8}>
23
+ <SearchBar
24
+ allowClear
25
+ onChange={(e) => onSearchKeywordChange(e.target.value)}
26
+ placeholder={t('ModelSwitchPanel.searchPlaceholder')}
27
+ size="small"
28
+ style={{ flex: 1 }}
29
+ value={searchKeyword}
30
+ variant="borderless"
31
+ />
32
+ <Segmented
33
+ onChange={(value) => onGroupModeChange(value as GroupMode)}
34
+ options={[
35
+ {
36
+ icon: <Icon icon={Brain} />,
37
+ title: t('ModelSwitchPanel.byModel'),
38
+ value: 'byModel',
39
+ },
40
+ {
41
+ icon: <Icon icon={ProviderIcon} />,
42
+ title: t('ModelSwitchPanel.byProvider'),
43
+ value: 'byProvider',
44
+ },
45
+ ]}
46
+ size="small"
47
+ value={groupMode}
48
+ />
49
+ </Flexbox>
50
+ );
51
+ },
52
+ );
53
+
54
+ Toolbar.displayName = 'Toolbar';
@@ -0,0 +1,29 @@
1
+ export const STORAGE_KEY = 'MODEL_SWITCH_PANEL_WIDTH';
2
+ export const STORAGE_KEY_MODE = 'MODEL_SWITCH_PANEL_MODE';
3
+ export const DEFAULT_WIDTH = 430;
4
+ export const MIN_WIDTH = 280;
5
+ export const MAX_WIDTH = 600;
6
+ export const MAX_PANEL_HEIGHT = 460;
7
+ export const TOOLBAR_HEIGHT = 40;
8
+ export const FOOTER_HEIGHT = 48;
9
+
10
+ export const INITIAL_RENDER_COUNT = 15;
11
+ export const RENDER_ALL_DELAY_MS = 500;
12
+
13
+ export const ITEM_HEIGHT = {
14
+ 'empty-model': 32,
15
+ 'group-header': 32,
16
+ 'model-item': 32,
17
+ 'no-provider': 32,
18
+ } as const;
19
+
20
+ export const ENABLE_RESIZING = {
21
+ bottom: false,
22
+ bottomLeft: false,
23
+ bottomRight: false,
24
+ left: false,
25
+ right: true,
26
+ top: false,
27
+ topLeft: false,
28
+ topRight: false,
29
+ } as const;
@@ -0,0 +1,122 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import type { EnabledProviderWithModels } from '@/types/aiProvider';
4
+
5
+ import type { GroupMode, ModelWithProviders, VirtualItem } from '../types';
6
+
7
+ export const useBuildVirtualItems = (
8
+ enabledList: EnabledProviderWithModels[],
9
+ groupMode: GroupMode,
10
+ searchKeyword: string = '',
11
+ ): VirtualItem[] => {
12
+ return useMemo(() => {
13
+ if (enabledList.length === 0) {
14
+ return [{ type: 'no-provider' }] as VirtualItem[];
15
+ }
16
+
17
+ // Filter function for search
18
+ const matchesSearch = (text: string): boolean => {
19
+ if (!searchKeyword.trim()) return true;
20
+ const keyword = searchKeyword.toLowerCase().trim();
21
+ return text.toLowerCase().includes(keyword);
22
+ };
23
+
24
+ // Sort providers: lobehub first, then others
25
+ const sortedProviders = [...enabledList].sort((a, b) => {
26
+ const aIsLobehub = a.id === 'lobehub';
27
+ const bIsLobehub = b.id === 'lobehub';
28
+ if (aIsLobehub && !bIsLobehub) return -1;
29
+ if (!aIsLobehub && bIsLobehub) return 1;
30
+ return 0;
31
+ });
32
+
33
+ if (groupMode === 'byModel') {
34
+ // Group models by display name
35
+ const modelMap = new Map<string, ModelWithProviders>();
36
+
37
+ for (const providerItem of sortedProviders) {
38
+ for (const modelItem of providerItem.children) {
39
+ const displayName = modelItem.displayName || modelItem.id;
40
+
41
+ // Filter by search keyword
42
+ if (!matchesSearch(displayName) && !matchesSearch(providerItem.name)) {
43
+ continue;
44
+ }
45
+
46
+ if (!modelMap.has(displayName)) {
47
+ modelMap.set(displayName, {
48
+ displayName,
49
+ model: modelItem,
50
+ providers: [],
51
+ });
52
+ }
53
+
54
+ const entry = modelMap.get(displayName)!;
55
+ entry.providers.push({
56
+ id: providerItem.id,
57
+ logo: providerItem.logo,
58
+ name: providerItem.name,
59
+ source: providerItem.source,
60
+ });
61
+ }
62
+ }
63
+
64
+ // Sort providers within each model: lobehub first
65
+ const modelArray = Array.from(modelMap.values());
66
+ for (const model of modelArray) {
67
+ model.providers.sort((a, b) => {
68
+ const aIsLobehub = a.id === 'lobehub';
69
+ const bIsLobehub = b.id === 'lobehub';
70
+ if (aIsLobehub && !bIsLobehub) return -1;
71
+ if (!aIsLobehub && bIsLobehub) return 1;
72
+ return 0;
73
+ });
74
+ }
75
+
76
+ // Convert to array and sort by display name
77
+ return modelArray
78
+ .sort((a, b) => a.displayName.localeCompare(b.displayName))
79
+ .map((data) => ({
80
+ data,
81
+ type:
82
+ data.providers.length === 1
83
+ ? ('model-item-single' as const)
84
+ : ('model-item-multiple' as const),
85
+ }));
86
+ } else {
87
+ // Group by provider (original structure)
88
+ const items: VirtualItem[] = [];
89
+
90
+ for (const providerItem of sortedProviders) {
91
+ // Filter models by search keyword
92
+ const filteredModels = providerItem.children.filter(
93
+ (modelItem) =>
94
+ matchesSearch(modelItem.displayName || modelItem.id) ||
95
+ matchesSearch(providerItem.name),
96
+ );
97
+
98
+ // Only add provider group header if there are matching models or if search is empty
99
+ if (filteredModels.length > 0 || !searchKeyword.trim()) {
100
+ // Add provider group header
101
+ items.push({ provider: providerItem, type: 'group-header' });
102
+
103
+ if (filteredModels.length === 0) {
104
+ // Add empty model placeholder
105
+ items.push({ provider: providerItem, type: 'empty-model' });
106
+ } else {
107
+ // Add each filtered model item
108
+ for (const modelItem of filteredModels) {
109
+ items.push({
110
+ model: modelItem,
111
+ provider: providerItem,
112
+ type: 'provider-model-item',
113
+ });
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ return items;
120
+ }
121
+ }, [enabledList, groupMode, searchKeyword]);
122
+ };
@@ -0,0 +1,18 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import type { EnabledProviderWithModels } from '@/types/aiProvider';
4
+
5
+ export const useCurrentModelName = (
6
+ enabledList: EnabledProviderWithModels[],
7
+ model: string,
8
+ ): string => {
9
+ return useMemo(() => {
10
+ for (const providerItem of enabledList) {
11
+ const modelItem = providerItem.children.find((m) => m.id === model);
12
+ if (modelItem) {
13
+ return modelItem.displayName || modelItem.id;
14
+ }
15
+ }
16
+ return model;
17
+ }, [enabledList, model]);
18
+ };
@@ -0,0 +1,18 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ import { RENDER_ALL_DELAY_MS } from '../const';
4
+
5
+ export const useDelayedRender = (isOpen: boolean) => {
6
+ const [renderAll, setRenderAll] = useState(false);
7
+
8
+ useEffect(() => {
9
+ if (isOpen && !renderAll) {
10
+ const timer = setTimeout(() => {
11
+ setRenderAll(true);
12
+ }, RENDER_ALL_DELAY_MS);
13
+ return () => clearTimeout(timer);
14
+ }
15
+ }, [isOpen, renderAll]);
16
+
17
+ return renderAll;
18
+ };
@@ -0,0 +1,14 @@
1
+ import { useAgentStore } from '@/store/agent';
2
+ import { agentSelectors } from '@/store/agent/selectors';
3
+
4
+ export const useModelAndProvider = (modelProp?: string, providerProp?: string) => {
5
+ const [storeModel, storeProvider] = useAgentStore((s) => [
6
+ agentSelectors.currentAgentModel(s),
7
+ agentSelectors.currentAgentModelProvider(s),
8
+ ]);
9
+
10
+ const model = modelProp ?? storeModel;
11
+ const provider = providerProp ?? storeProvider;
12
+
13
+ return { model, provider };
14
+ };
@@ -0,0 +1,33 @@
1
+ import { useCallback } from 'react';
2
+
3
+ import { useAgentStore } from '@/store/agent';
4
+
5
+ interface UsePanelHandlersProps {
6
+ onModelChange?: (params: { model: string; provider: string }) => Promise<void>;
7
+ onOpenChange?: (open: boolean) => void;
8
+ }
9
+
10
+ export const usePanelHandlers = ({
11
+ onModelChange: onModelChangeProp,
12
+ onOpenChange,
13
+ }: UsePanelHandlersProps) => {
14
+ const updateAgentConfig = useAgentStore((s) => s.updateAgentConfig);
15
+
16
+ const handleModelChange = useCallback(
17
+ async (modelId: string, providerId: string) => {
18
+ const params = { model: modelId, provider: providerId };
19
+ if (onModelChangeProp) {
20
+ onModelChangeProp(params);
21
+ } else {
22
+ updateAgentConfig(params);
23
+ }
24
+ },
25
+ [onModelChangeProp, updateAgentConfig],
26
+ );
27
+
28
+ const handleClose = useCallback(() => {
29
+ onOpenChange?.(false);
30
+ }, [onOpenChange]);
31
+
32
+ return { handleClose, handleModelChange };
33
+ };
@@ -0,0 +1,33 @@
1
+ import { useCallback, useMemo } from 'react';
2
+
3
+ import { useGlobalStore } from '@/store/global';
4
+ import { systemStatusSelectors } from '@/store/global/selectors/systemStatus';
5
+
6
+ import {
7
+ FOOTER_HEIGHT,
8
+ ITEM_HEIGHT,
9
+ MAX_PANEL_HEIGHT,
10
+ TOOLBAR_HEIGHT,
11
+ } from '../const';
12
+
13
+ export const usePanelSize = (enabledListLength: number) => {
14
+ const panelWidth = useGlobalStore(systemStatusSelectors.modelSwitchPanelWidth);
15
+ const updateSystemStatus = useGlobalStore((s) => s.updateSystemStatus);
16
+
17
+ const panelHeight = useMemo(
18
+ () =>
19
+ enabledListLength === 0
20
+ ? TOOLBAR_HEIGHT + ITEM_HEIGHT['no-provider'] + FOOTER_HEIGHT
21
+ : MAX_PANEL_HEIGHT,
22
+ [enabledListLength],
23
+ );
24
+
25
+ const handlePanelWidthChange = useCallback(
26
+ (width: number) => {
27
+ updateSystemStatus({ modelSwitchPanelWidth: width });
28
+ },
29
+ [updateSystemStatus],
30
+ );
31
+
32
+ return { handlePanelWidthChange, panelHeight, panelWidth };
33
+ };
@@ -0,0 +1,20 @@
1
+ import { useCallback } from 'react';
2
+
3
+ import { useGlobalStore } from '@/store/global';
4
+ import { systemStatusSelectors } from '@/store/global/selectors/systemStatus';
5
+
6
+ import type { GroupMode } from '../types';
7
+
8
+ export const usePanelState = () => {
9
+ const groupMode = useGlobalStore(systemStatusSelectors.modelSwitchPanelGroupMode) as GroupMode;
10
+ const updateSystemStatus = useGlobalStore((s) => s.updateSystemStatus);
11
+
12
+ const handleGroupModeChange = useCallback(
13
+ (mode: GroupMode) => {
14
+ updateSystemStatus({ modelSwitchPanelGroupMode: mode });
15
+ },
16
+ [updateSystemStatus],
17
+ );
18
+
19
+ return { groupMode, handleGroupModeChange };
20
+ };