@lobehub/lobehub 2.0.0-next.344 → 2.0.0-next.345
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.
- package/.cursor/rules/i18n.mdc +1 -1
- package/.cursor/rules/modal-imperative.mdc +162 -0
- package/.cursor/rules/rules-index.mdc +1 -0
- package/.env.example +0 -14
- package/.eslintrc.js +8 -1
- package/CHANGELOG.md +41 -0
- package/Dockerfile +3 -13
- package/README.md +3 -5
- package/README.zh-CN.md +3 -5
- package/changelog/v1.json +15 -0
- package/docs/self-hosting/advanced/auth/clerk-to-betterauth.mdx +11 -42
- package/docs/self-hosting/advanced/auth/clerk-to-betterauth.zh-CN.mdx +10 -41
- package/e2e/src/support/webServer.ts +2 -0
- package/locales/ar/error.json +0 -4
- package/locales/bg-BG/error.json +0 -4
- package/locales/de-DE/error.json +0 -4
- package/locales/en-US/error.json +0 -4
- package/locales/es-ES/error.json +0 -4
- package/locales/fa-IR/error.json +0 -4
- package/locales/fr-FR/error.json +0 -4
- package/locales/it-IT/error.json +0 -4
- package/locales/ja-JP/error.json +0 -4
- package/locales/ko-KR/error.json +0 -4
- package/locales/nl-NL/error.json +0 -4
- package/locales/pl-PL/error.json +0 -4
- package/locales/pt-BR/error.json +0 -4
- package/locales/ru-RU/error.json +0 -4
- package/locales/tr-TR/error.json +0 -4
- package/locales/vi-VN/error.json +0 -4
- package/locales/zh-CN/error.json +0 -4
- package/locales/zh-TW/error.json +0 -4
- package/package.json +7 -9
- package/packages/builtin-agents/package.json +2 -0
- package/packages/builtin-agents/src/agents/agent-builder/index.ts +4 -2
- package/packages/builtin-agents/src/agents/group-agent-builder/index.ts +4 -2
- package/packages/builtin-agents/src/agents/page-agent/index.ts +5 -2
- package/packages/context-engine/src/engine/messages/MessagesEngine.ts +9 -9
- package/packages/context-engine/src/providers/GroupContextInjector.ts +19 -33
- package/packages/context-engine/src/providers/__tests__/GroupContextInjector.test.ts +79 -43
- package/packages/context-engine/src/providers/__tests__/__snapshots__/GroupContextInjector.test.ts.snap +5 -15
- package/packages/database/src/repositories/userMemory/__tests__/UserMemoryTopicRepository.test.ts +24 -3
- package/packages/model-bank/src/modelProviders/comfyui.ts +0 -1
- package/packages/model-bank/src/modelProviders/fal.ts +0 -1
- package/packages/types/src/fetch.ts +1 -2
- package/packages/utils/src/server/__tests__/auth.test.ts +0 -47
- package/packages/utils/src/server/auth.ts +1 -9
- package/scripts/_shared/checkDeprecatedClerkEnv.js +42 -0
- package/scripts/changelogWorkflow/buildStaticChangelog.ts +2 -1
- package/scripts/clerk-to-betterauth/_internal/types.ts +53 -20
- package/scripts/clerk-to-betterauth/export-clerk-users-with-api.ts +43 -36
- package/scripts/countEnWord.ts +1 -1
- package/scripts/electronWorkflow/modifiers/appCode.mts +2 -131
- package/scripts/i18nWorkflow/protectedPatterns.ts +1 -2
- package/scripts/prebuild.mts +10 -8
- package/scripts/serverLauncher/startServer.js +23 -5
- package/src/app/(backend)/middleware/auth/index.test.ts +8 -4
- package/src/app/(backend)/middleware/auth/index.ts +0 -15
- package/src/app/(backend)/middleware/auth/utils.test.ts +0 -28
- package/src/app/(backend)/middleware/auth/utils.ts +2 -17
- package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +3 -51
- package/src/app/(backend)/webapi/models/[provider]/route.test.ts +8 -4
- package/src/app/[variants]/(auth)/next-auth/signin/AuthSignInBox.tsx +7 -6
- package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -16
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/index.tsx +1 -1
- package/src/app/[variants]/(main)/home/features/InputArea/SkillInstallBanner.tsx +13 -13
- package/src/app/[variants]/(main)/home/features/RecentPage/Item.tsx +2 -2
- package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +3 -21
- package/src/app/[variants]/(main)/settings/profile/features/AvatarRow.tsx +1 -2
- package/src/app/[variants]/(main)/settings/security/index.tsx +1 -22
- package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +12 -14
- package/src/app/[variants]/(main)/settings/skill/features/LobehubSkillItem.tsx +8 -14
- package/src/app/[variants]/(main)/settings/skill/index.tsx +7 -5
- package/src/app/[variants]/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +2 -35
- package/src/app/[variants]/(mobile)/me/(home)/__tests__/useCategory.test.tsx +0 -20
- package/src/app/[variants]/(mobile)/me/(home)/features/UserBanner.tsx +1 -2
- package/src/app/[variants]/(mobile)/me/profile/features/Category.tsx +3 -13
- package/src/app/[variants]/(mobile)/settings/_layout/Header.tsx +2 -3
- package/src/app/[variants]/share/t/[id]/_layout/index.tsx +1 -1
- package/src/app/[variants]/share/t/[id]/index.tsx +1 -1
- package/src/app/robots.tsx +1 -1
- package/src/envs/auth.ts +2 -27
- package/src/envs/llm.ts +2 -2
- package/src/features/AgentSetting/AgentPlugin/index.tsx +9 -12
- package/src/features/ChatInput/ActionBar/Tools/index.tsx +7 -5
- package/src/features/ChatMiniMap/utils.ts +1 -1
- package/src/features/CommandMenu/SearchResults.tsx +1 -1
- package/src/features/Conversation/ChatList/components/AutoScroll/DebugInspector.tsx +166 -0
- package/src/features/Conversation/ChatList/components/AutoScroll/index.tsx +86 -0
- package/src/features/Conversation/ChatList/components/VirtualizedList.tsx +11 -17
- package/src/features/Conversation/Messages/AgentCouncil/components/AutoScrollShadow.tsx +25 -14
- package/src/features/Conversation/Messages/AgentCouncil/components/CouncilMember.tsx +1 -1
- package/src/features/IntegrationDetailModal/IntegrationDetailContent.tsx +305 -0
- package/src/features/IntegrationDetailModal/index.tsx +21 -283
- package/src/features/MCPPluginDetail/Deployment/index.tsx +1 -1
- package/src/features/MCPPluginDetail/Schema/Prompts.tsx +1 -1
- package/src/features/MCPPluginDetail/Schema/Tools.tsx +1 -1
- package/src/features/ProfileEditor/AgentTool.tsx +14 -20
- package/src/features/ResourceManager/components/Explorer/MasonryView/MasonryFileItem/NoteFileItem.tsx +1 -1
- package/src/features/SkillStore/LobeHubList/index.tsx +50 -87
- package/src/features/SkillStore/Search/index.tsx +1 -1
- package/src/features/SkillStore/{Content.tsx → SkillStoreContent.tsx} +3 -8
- package/src/features/SkillStore/index.tsx +15 -33
- package/src/features/User/UserPanel/PanelContent.tsx +0 -8
- package/src/features/User/__tests__/PanelContent.test.tsx +1 -35
- package/src/features/User/__tests__/UserAvatar.test.tsx +30 -57
- package/src/features/User/__tests__/useMenu.test.tsx +2 -43
- package/src/layout/AuthProvider/index.tsx +0 -5
- package/src/libs/next/config/define-config.ts +6 -0
- package/src/libs/next/proxy/createRouteMatcher.test.ts +121 -0
- package/src/libs/next/proxy/createRouteMatcher.ts +18 -0
- package/src/libs/next/proxy/define-config.ts +4 -53
- package/src/libs/next-auth/adapter/index.ts +1 -2
- package/src/libs/oidc-provider/provider.test.ts +5 -316
- package/src/libs/trpc/lambda/context.test.ts +0 -13
- package/src/libs/trpc/lambda/context.ts +3 -22
- package/src/libs/trpc/middleware/userAuth.ts +2 -4
- package/src/libs/trusted-client/getSessionUser.ts +2 -17
- package/src/locales/default/error.ts +0 -6
- package/src/locales/default/index.ts +0 -2
- package/src/proxy.ts +0 -1
- package/src/server/routers/lambda/__tests__/user.test.ts +0 -71
- package/src/server/routers/lambda/user.ts +6 -63
- package/src/server/services/changelog/index.test.ts +3 -2
- package/src/server/services/changelog/index.ts +1 -1
- package/src/server/services/user/index.ts +0 -83
- package/src/services/chat/index.ts +1 -2
- package/src/services/chat/mecha/agentConfigResolver.test.ts +43 -0
- package/src/services/chat/mecha/agentConfigResolver.ts +3 -1
- package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +58 -14
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +10 -2
- package/src/store/user/slices/auth/action.test.ts +1 -81
- package/src/store/user/slices/auth/action.ts +3 -28
- package/src/store/user/slices/auth/initialState.ts +1 -18
- package/src/store/user/slices/auth/selectors.test.ts +2 -127
- package/src/store/user/slices/auth/selectors.ts +1 -21
- package/src/utils/errorResponse.ts +1 -4
- package/src/utils/markdownToTxt.ts +20 -0
- package/locales/ar/clerk.json +0 -545
- package/locales/bg-BG/clerk.json +0 -545
- package/locales/de-DE/clerk.json +0 -545
- package/locales/en-US/clerk.json +0 -545
- package/locales/es-ES/clerk.json +0 -545
- package/locales/fa-IR/clerk.json +0 -545
- package/locales/fr-FR/clerk.json +0 -545
- package/locales/it-IT/clerk.json +0 -545
- package/locales/ja-JP/clerk.json +0 -545
- package/locales/ko-KR/clerk.json +0 -545
- package/locales/nl-NL/clerk.json +0 -545
- package/locales/pl-PL/clerk.json +0 -545
- package/locales/pt-BR/clerk.json +0 -545
- package/locales/ru-RU/clerk.json +0 -545
- package/locales/tr-TR/clerk.json +0 -545
- package/locales/vi-VN/clerk.json +0 -545
- package/locales/zh-CN/clerk.json +0 -545
- package/locales/zh-TW/clerk.json +0 -545
- package/src/app/(backend)/api/webhooks/clerk/__tests__/fixtures/createUser.json +0 -73
- package/src/app/(backend)/api/webhooks/clerk/route.ts +0 -95
- package/src/app/(backend)/api/webhooks/clerk/validateRequest.ts +0 -22
- package/src/app/[variants]/(auth)/login/[[...login]]/page.tsx +0 -27
- package/src/app/[variants]/(main)/settings/security/features/ClerkProfile.tsx +0 -67
- package/src/features/Conversation/ChatList/components/AutoScroll.tsx +0 -25
- package/src/layout/AuthProvider/Clerk/UserUpdater.tsx +0 -40
- package/src/layout/AuthProvider/Clerk/index.tsx +0 -54
- package/src/layout/AuthProvider/Clerk/useAppearance.ts +0 -133
- package/src/libs/clerk-auth/index.test.ts +0 -216
- package/src/libs/clerk-auth/index.ts +0 -80
- package/src/locales/default/clerk.ts +0 -677
- package/src/server/services/user/index.test.ts +0 -220
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { ChartColumnBigIcon, LogOut,
|
|
3
|
+
import { ChartColumnBigIcon, LogOut, UserCircle } from 'lucide-react';
|
|
4
4
|
import { memo } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import { useNavigate } from 'react-router-dom';
|
|
@@ -11,11 +11,7 @@ import { useUserStore } from '@/store/user';
|
|
|
11
11
|
import { authSelectors } from '@/store/user/selectors';
|
|
12
12
|
|
|
13
13
|
const Category = memo(() => {
|
|
14
|
-
const [isLogin,
|
|
15
|
-
authSelectors.isLogin(s),
|
|
16
|
-
authSelectors.isLoginWithClerk(s),
|
|
17
|
-
s.logout,
|
|
18
|
-
]);
|
|
14
|
+
const [isLogin, signOut] = useUserStore((s) => [authSelectors.isLogin(s), s.logout]);
|
|
19
15
|
const navigate = useNavigate();
|
|
20
16
|
const { t } = useTranslation('auth');
|
|
21
17
|
const items: CellProps[] = [
|
|
@@ -25,12 +21,6 @@ const Category = memo(() => {
|
|
|
25
21
|
label: t('tab.profile'),
|
|
26
22
|
onClick: () => navigate('/settings/profile'),
|
|
27
23
|
},
|
|
28
|
-
isLoginWithClerk && {
|
|
29
|
-
icon: ShieldCheck,
|
|
30
|
-
key: ProfileTabs.Security,
|
|
31
|
-
label: t('tab.security'),
|
|
32
|
-
onClick: () => navigate('/settings/security'),
|
|
33
|
-
},
|
|
34
24
|
{
|
|
35
25
|
icon: ChartColumnBigIcon,
|
|
36
26
|
key: ProfileTabs.Stats,
|
|
@@ -46,7 +36,7 @@ const Category = memo(() => {
|
|
|
46
36
|
label: t('signout', { ns: 'auth' }),
|
|
47
37
|
onClick: () => {
|
|
48
38
|
signOut();
|
|
49
|
-
navigate('/
|
|
39
|
+
navigate('/signin');
|
|
50
40
|
},
|
|
51
41
|
},
|
|
52
42
|
].filter(Boolean) as CellProps[];
|
|
@@ -6,7 +6,6 @@ import { memo } from 'react';
|
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
8
8
|
|
|
9
|
-
import { enableAuth } from '@/envs/auth';
|
|
10
9
|
import { useShowMobileWorkspace } from '@/hooks/useShowMobileWorkspace';
|
|
11
10
|
import { type SettingsTabs } from '@/store/global/initialState';
|
|
12
11
|
import { useSessionStore } from '@/store/session';
|
|
@@ -16,7 +15,7 @@ const Header = memo(() => {
|
|
|
16
15
|
const { t } = useTranslation('setting');
|
|
17
16
|
const showMobileWorkspace = useShowMobileWorkspace();
|
|
18
17
|
const navigate = useNavigate();
|
|
19
|
-
const params = useParams<{ providerId?: string
|
|
18
|
+
const params = useParams<{ providerId?: string; tab?: string }>();
|
|
20
19
|
|
|
21
20
|
const isSessionActive = useSessionStore((s) => !!s.activeId);
|
|
22
21
|
const isProvider = params.providerId && params.providerId !== 'all';
|
|
@@ -27,7 +26,7 @@ const Header = memo(() => {
|
|
|
27
26
|
} else if (isProvider) {
|
|
28
27
|
navigate('/settings/provider/all');
|
|
29
28
|
} else {
|
|
30
|
-
navigate(
|
|
29
|
+
navigate('/me/settings');
|
|
31
30
|
}
|
|
32
31
|
};
|
|
33
32
|
|
package/src/app/robots.tsx
CHANGED
package/src/envs/auth.ts
CHANGED
|
@@ -6,11 +6,6 @@ declare global {
|
|
|
6
6
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
7
7
|
namespace NodeJS {
|
|
8
8
|
interface ProcessEnv {
|
|
9
|
-
// ===== Clerk ===== //
|
|
10
|
-
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY?: string;
|
|
11
|
-
CLERK_SECRET_KEY?: string;
|
|
12
|
-
CLERK_WEBHOOK_SECRET?: string;
|
|
13
|
-
|
|
14
9
|
// ===== Auth (shared by Better Auth / Next Auth) ===== //
|
|
15
10
|
AUTH_SECRET?: string;
|
|
16
11
|
AUTH_EMAIL_VERIFICATION?: string;
|
|
@@ -136,10 +131,6 @@ declare global {
|
|
|
136
131
|
export const getAuthConfig = () => {
|
|
137
132
|
return createEnv({
|
|
138
133
|
client: {
|
|
139
|
-
// ---------------------------------- clerk ----------------------------------
|
|
140
|
-
NEXT_PUBLIC_ENABLE_CLERK_AUTH: z.boolean().optional().default(false),
|
|
141
|
-
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().optional(),
|
|
142
|
-
|
|
143
134
|
// ---------------------------------- better auth ----------------------------------
|
|
144
135
|
NEXT_PUBLIC_ENABLE_BETTER_AUTH: z.boolean().optional(),
|
|
145
136
|
|
|
@@ -147,10 +138,6 @@ export const getAuthConfig = () => {
|
|
|
147
138
|
NEXT_PUBLIC_ENABLE_NEXT_AUTH: z.boolean().optional(),
|
|
148
139
|
},
|
|
149
140
|
server: {
|
|
150
|
-
// ---------------------------------- clerk ----------------------------------
|
|
151
|
-
CLERK_SECRET_KEY: z.string().optional(),
|
|
152
|
-
CLERK_WEBHOOK_SECRET: z.string().optional(),
|
|
153
|
-
|
|
154
141
|
// ---------------------------------- better auth ----------------------------------
|
|
155
142
|
AUTH_SECRET: z.string().optional(),
|
|
156
143
|
AUTH_SSO_PROVIDERS: z.string().optional().default(''),
|
|
@@ -261,14 +248,6 @@ export const getAuthConfig = () => {
|
|
|
261
248
|
},
|
|
262
249
|
|
|
263
250
|
runtimeEnv: {
|
|
264
|
-
// Clerk
|
|
265
|
-
NEXT_PUBLIC_ENABLE_CLERK_AUTH:
|
|
266
|
-
process.env.NEXT_PUBLIC_ENABLE_CLERK_AUTH === '1' ||
|
|
267
|
-
!!process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
|
|
268
|
-
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
|
|
269
|
-
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
|
|
270
|
-
CLERK_WEBHOOK_SECRET: process.env.CLERK_WEBHOOK_SECRET,
|
|
271
|
-
|
|
272
251
|
// ---------------------------------- better auth ----------------------------------
|
|
273
252
|
NEXT_PUBLIC_ENABLE_BETTER_AUTH: process.env.NEXT_PUBLIC_ENABLE_BETTER_AUTH === '1',
|
|
274
253
|
// Fallback to NEXT_PUBLIC_* for seamless migration
|
|
@@ -396,13 +375,9 @@ export const getAuthConfig = () => {
|
|
|
396
375
|
export const authEnv = getAuthConfig();
|
|
397
376
|
|
|
398
377
|
// Auth flags - use process.env directly for build-time dead code elimination
|
|
399
|
-
|
|
400
|
-
process.env.NEXT_PUBLIC_ENABLE_CLERK_AUTH === '1'
|
|
401
|
-
? true
|
|
402
|
-
: !!process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
|
403
|
-
export const enableBetterAuth = process.env.NEXT_PUBLIC_ENABLE_BETTER_AUTH === '1';
|
|
378
|
+
// Better Auth is the default auth solution when NextAuth is not explicitly enabled
|
|
404
379
|
export const enableNextAuth = process.env.NEXT_PUBLIC_ENABLE_NEXT_AUTH === '1';
|
|
405
|
-
export const
|
|
380
|
+
export const enableBetterAuth = !enableNextAuth;
|
|
406
381
|
|
|
407
382
|
// Auth headers and constants
|
|
408
383
|
export const LOBE_CHAT_AUTH_HEADER = 'X-lobe-chat-auth';
|
package/src/envs/llm.ts
CHANGED
|
@@ -241,7 +241,7 @@ export const getLLMConfig = () => {
|
|
|
241
241
|
ENABLED_DEEPSEEK: !!process.env.DEEPSEEK_API_KEY,
|
|
242
242
|
DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY,
|
|
243
243
|
|
|
244
|
-
ENABLED_GOOGLE:
|
|
244
|
+
ENABLED_GOOGLE: process.env.ENABLED_GOOGLE !== '0',
|
|
245
245
|
GOOGLE_API_KEY: process.env.GOOGLE_API_KEY,
|
|
246
246
|
|
|
247
247
|
ENABLED_VERTEXAI: !!process.env.VERTEXAI_CREDENTIALS,
|
|
@@ -252,7 +252,7 @@ export const getLLMConfig = () => {
|
|
|
252
252
|
ENABLED_PERPLEXITY: !!process.env.PERPLEXITY_API_KEY,
|
|
253
253
|
PERPLEXITY_API_KEY: process.env.PERPLEXITY_API_KEY,
|
|
254
254
|
|
|
255
|
-
ENABLED_ANTHROPIC:
|
|
255
|
+
ENABLED_ANTHROPIC: process.env.ENABLED_ANTHROPIC !== '0',
|
|
256
256
|
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
|
|
257
257
|
|
|
258
258
|
ENABLED_MINIMAX: !!process.env.MINIMAX_API_KEY,
|
|
@@ -5,14 +5,14 @@ import { Center, Flexbox } from '@lobehub/ui';
|
|
|
5
5
|
import { Space, Switch } from 'antd';
|
|
6
6
|
import isEqual from 'fast-deep-equal';
|
|
7
7
|
import { LucideTrash2, Plug2, Store } from 'lucide-react';
|
|
8
|
-
import { memo,
|
|
8
|
+
import { memo, useCallback } from 'react';
|
|
9
9
|
import { Trans, useTranslation } from 'react-i18next';
|
|
10
10
|
import { Link, useNavigate } from 'react-router-dom';
|
|
11
11
|
|
|
12
12
|
import PluginAvatar from '@/components/Plugins/PluginAvatar';
|
|
13
13
|
import PluginTag from '@/components/Plugins/PluginTag';
|
|
14
14
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
|
15
|
-
import
|
|
15
|
+
import { createSkillStoreModal } from '@/features/SkillStore';
|
|
16
16
|
import { useFetchInstalledPlugins } from '@/hooks/useFetchInstalledPlugins';
|
|
17
17
|
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
|
18
18
|
import { pluginHelpers, useToolStore } from '@/store/tool';
|
|
@@ -27,10 +27,12 @@ import PluginAction from './PluginAction';
|
|
|
27
27
|
const AgentPlugin = memo(() => {
|
|
28
28
|
const { t } = useTranslation('setting');
|
|
29
29
|
|
|
30
|
-
const [showStore, setShowStore] = useState(false);
|
|
31
|
-
|
|
32
30
|
const navigate = useNavigate();
|
|
33
31
|
|
|
32
|
+
const handleOpenStore = useCallback(() => {
|
|
33
|
+
createSkillStoreModal();
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
34
36
|
const [userEnabledPlugins, toggleAgentPlugin] = useStore((s) => [
|
|
35
37
|
s.config.plugins || [],
|
|
36
38
|
s.toggleAgentPlugin,
|
|
@@ -120,7 +122,7 @@ const AgentPlugin = memo(() => {
|
|
|
120
122
|
icon={Store}
|
|
121
123
|
onClick={(e) => {
|
|
122
124
|
e.stopPropagation();
|
|
123
|
-
|
|
125
|
+
handleOpenStore();
|
|
124
126
|
}}
|
|
125
127
|
size={'small'}
|
|
126
128
|
/>
|
|
@@ -139,7 +141,7 @@ const AgentPlugin = memo(() => {
|
|
|
139
141
|
onClick={(e) => {
|
|
140
142
|
e.stopPropagation();
|
|
141
143
|
e.preventDefault();
|
|
142
|
-
|
|
144
|
+
handleOpenStore();
|
|
143
145
|
navigate('/community/mcp');
|
|
144
146
|
}}
|
|
145
147
|
to={'/community/mcp'}
|
|
@@ -168,12 +170,7 @@ const AgentPlugin = memo(() => {
|
|
|
168
170
|
title: t('settingPlugin.title'),
|
|
169
171
|
};
|
|
170
172
|
|
|
171
|
-
return
|
|
172
|
-
<>
|
|
173
|
-
<SkillStore open={showStore} setOpen={setShowStore} />
|
|
174
|
-
<Form items={[plugin]} itemsType={'group'} variant={'borderless'} {...FORM_STYLE} />
|
|
175
|
-
</>
|
|
176
|
-
);
|
|
173
|
+
return <Form items={[plugin]} itemsType={'group'} variant={'borderless'} {...FORM_STYLE} />;
|
|
177
174
|
});
|
|
178
175
|
|
|
179
176
|
export default AgentPlugin;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Blocks } from 'lucide-react';
|
|
2
|
-
import { Suspense, memo, useState } from 'react';
|
|
2
|
+
import { Suspense, memo, useCallback, useState } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { createSkillStoreModal } from '@/features/SkillStore';
|
|
6
6
|
import { useModelSupportToolUse } from '@/hooks/useModelSupportToolUse';
|
|
7
7
|
import { useAgentStore } from '@/store/agent';
|
|
8
8
|
import { agentByIdSelectors } from '@/store/agent/selectors';
|
|
@@ -15,7 +15,6 @@ import { useControls } from './useControls';
|
|
|
15
15
|
|
|
16
16
|
const Tools = memo(() => {
|
|
17
17
|
const { t } = useTranslation('setting');
|
|
18
|
-
const [modalOpen, setModalOpen] = useState(false);
|
|
19
18
|
const [updating, setUpdating] = useState(false);
|
|
20
19
|
const { marketItems } = useControls({
|
|
21
20
|
setUpdating,
|
|
@@ -29,6 +28,10 @@ const Tools = memo(() => {
|
|
|
29
28
|
|
|
30
29
|
const enableFC = useModelSupportToolUse(model, provider);
|
|
31
30
|
|
|
31
|
+
const handleOpenStore = useCallback(() => {
|
|
32
|
+
createSkillStoreModal();
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
32
35
|
if (!enableFC)
|
|
33
36
|
return <Action disabled icon={Blocks} showTooltip={true} title={t('tools.disabled')} />;
|
|
34
37
|
|
|
@@ -42,7 +45,7 @@ const Tools = memo(() => {
|
|
|
42
45
|
<PopoverContent
|
|
43
46
|
enableKlavis={enableKlavis}
|
|
44
47
|
items={marketItems}
|
|
45
|
-
onOpenStore={
|
|
48
|
+
onOpenStore={handleOpenStore}
|
|
46
49
|
/>
|
|
47
50
|
),
|
|
48
51
|
maxWidth: 320,
|
|
@@ -56,7 +59,6 @@ const Tools = memo(() => {
|
|
|
56
59
|
showTooltip={false}
|
|
57
60
|
title={t('tools.title')}
|
|
58
61
|
/>
|
|
59
|
-
<SkillStore open={modalOpen} setOpen={setModalOpen} />
|
|
60
62
|
</Suspense>
|
|
61
63
|
);
|
|
62
64
|
});
|
|
@@ -11,12 +11,12 @@ import {
|
|
|
11
11
|
Puzzle,
|
|
12
12
|
Sparkles,
|
|
13
13
|
} from 'lucide-react';
|
|
14
|
-
import { markdownToTxt } from 'markdown-to-txt';
|
|
15
14
|
import { memo } from 'react';
|
|
16
15
|
import { useTranslation } from 'react-i18next';
|
|
17
16
|
import { useNavigate } from 'react-router-dom';
|
|
18
17
|
|
|
19
18
|
import type { SearchResult } from '@/database/repositories/search';
|
|
19
|
+
import { markdownToTxt } from '@/utils/markdownToTxt';
|
|
20
20
|
|
|
21
21
|
import { CommandItem } from './components';
|
|
22
22
|
import { styles } from './styles';
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
|
|
6
|
+
import { messageStateSelectors, useConversationStore, virtuaListSelectors } from '../../../store';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 判断是否在底部的阈值(单位:px)
|
|
10
|
+
* 当距离底部小于等于此值时,认为在底部
|
|
11
|
+
*/
|
|
12
|
+
export const AT_BOTTOM_THRESHOLD = 300;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 是否开启调试面板
|
|
16
|
+
* 设为 true 可以显示滚动位置调试信息
|
|
17
|
+
*/
|
|
18
|
+
export const OPEN_DEV_INSPECTOR = false;
|
|
19
|
+
|
|
20
|
+
const DebugInspector = memo(() => {
|
|
21
|
+
const atBottom = useConversationStore(virtuaListSelectors.atBottom);
|
|
22
|
+
const isScrolling = useConversationStore(virtuaListSelectors.isScrolling);
|
|
23
|
+
const isGenerating = useConversationStore(messageStateSelectors.isAIGenerating);
|
|
24
|
+
const virtuaScrollMethods = useConversationStore((s) => s.virtuaScrollMethods);
|
|
25
|
+
|
|
26
|
+
const shouldAutoScroll = atBottom && isGenerating && !isScrolling;
|
|
27
|
+
const scrollOffset = virtuaScrollMethods?.getScrollOffset?.() ?? 0;
|
|
28
|
+
const scrollSize = virtuaScrollMethods?.getScrollSize?.() ?? 0;
|
|
29
|
+
const viewportSize = virtuaScrollMethods?.getViewportSize?.() ?? 0;
|
|
30
|
+
const distanceToBottom = scrollSize - scrollOffset - viewportSize;
|
|
31
|
+
// 可视化计算
|
|
32
|
+
const visualHeight = 120;
|
|
33
|
+
const scale = scrollSize > 0 ? visualHeight / scrollSize : 0;
|
|
34
|
+
const viewportVisualHeight = Math.max(viewportSize * scale, 10);
|
|
35
|
+
const scrollVisualOffset = scrollOffset * scale;
|
|
36
|
+
const thresholdVisualHeight = Math.min(AT_BOTTOM_THRESHOLD * scale, visualHeight * 0.3);
|
|
37
|
+
|
|
38
|
+
const panel = (
|
|
39
|
+
<div
|
|
40
|
+
style={{
|
|
41
|
+
background: 'rgba(0,0,0,0.9)',
|
|
42
|
+
borderRadius: 8,
|
|
43
|
+
bottom: 80,
|
|
44
|
+
display: 'flex',
|
|
45
|
+
fontFamily: 'monospace',
|
|
46
|
+
fontSize: 11,
|
|
47
|
+
gap: 16,
|
|
48
|
+
left: 12,
|
|
49
|
+
padding: '10px 14px',
|
|
50
|
+
position: 'fixed',
|
|
51
|
+
zIndex: 9999,
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
{/* 滚动条可视化 */}
|
|
55
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
56
|
+
<div style={{ color: '#9ca3af', fontSize: 10 }}>Scroll Position</div>
|
|
57
|
+
<div
|
|
58
|
+
style={{
|
|
59
|
+
background: '#374151',
|
|
60
|
+
borderRadius: 3,
|
|
61
|
+
height: visualHeight,
|
|
62
|
+
position: 'relative',
|
|
63
|
+
width: 24,
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
{/* threshold 区域 (底部 200px) */}
|
|
67
|
+
<div
|
|
68
|
+
style={{
|
|
69
|
+
background: atBottom ? 'rgba(34, 197, 94, 0.3)' : 'rgba(239, 68, 68, 0.3)',
|
|
70
|
+
borderRadius: '0 0 3px 3px',
|
|
71
|
+
bottom: 0,
|
|
72
|
+
height: thresholdVisualHeight,
|
|
73
|
+
left: 0,
|
|
74
|
+
position: 'absolute',
|
|
75
|
+
right: 0,
|
|
76
|
+
}}
|
|
77
|
+
/>
|
|
78
|
+
{/* 当前视口位置 */}
|
|
79
|
+
<div
|
|
80
|
+
style={{
|
|
81
|
+
background: atBottom ? '#22c55e' : '#3b82f6',
|
|
82
|
+
borderRadius: 2,
|
|
83
|
+
height: viewportVisualHeight,
|
|
84
|
+
left: 2,
|
|
85
|
+
position: 'absolute',
|
|
86
|
+
right: 2,
|
|
87
|
+
top: scrollVisualOffset,
|
|
88
|
+
transition: 'top 0.1s',
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
{/* threshold 线 */}
|
|
92
|
+
<div
|
|
93
|
+
style={{
|
|
94
|
+
background: '#f59e0b',
|
|
95
|
+
bottom: thresholdVisualHeight,
|
|
96
|
+
height: 1,
|
|
97
|
+
left: 0,
|
|
98
|
+
position: 'absolute',
|
|
99
|
+
right: 0,
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
<div style={{ color: '#f59e0b', fontSize: 9, textAlign: 'center' }}>
|
|
104
|
+
{AT_BOTTOM_THRESHOLD}px
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
{/* 数值信息 */}
|
|
109
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
110
|
+
<div style={{ color: '#9ca3af', fontSize: 10 }}>
|
|
111
|
+
scrollSize: <span style={{ color: 'white' }}>{Math.round(scrollSize)}px</span>
|
|
112
|
+
</div>
|
|
113
|
+
<div style={{ color: '#9ca3af', fontSize: 10 }}>
|
|
114
|
+
viewport: <span style={{ color: 'white' }}>{Math.round(viewportSize)}px</span>
|
|
115
|
+
</div>
|
|
116
|
+
<div style={{ color: '#9ca3af', fontSize: 10 }}>
|
|
117
|
+
offset: <span style={{ color: 'white' }}>{Math.round(scrollOffset)}px</span>
|
|
118
|
+
</div>
|
|
119
|
+
<div
|
|
120
|
+
style={{
|
|
121
|
+
color: atBottom ? '#22c55e' : '#ef4444',
|
|
122
|
+
fontSize: 10,
|
|
123
|
+
fontWeight: 'bold',
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
toBottom: {Math.round(distanceToBottom)}px
|
|
127
|
+
{distanceToBottom <= AT_BOTTOM_THRESHOLD ? ' ≤' : ' >'} {AT_BOTTOM_THRESHOLD}
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div style={{ borderTop: '1px solid #374151', marginTop: 4, paddingTop: 4 }}>
|
|
131
|
+
<div style={{ color: atBottom ? '#22c55e' : '#ef4444', fontSize: 10 }}>
|
|
132
|
+
atBottom: {atBottom ? 'YES' : 'NO'}
|
|
133
|
+
</div>
|
|
134
|
+
<div style={{ color: isGenerating ? '#3b82f6' : '#6b7280', fontSize: 10 }}>
|
|
135
|
+
generating: {isGenerating ? 'YES' : 'NO'}
|
|
136
|
+
</div>
|
|
137
|
+
<div style={{ color: isScrolling ? '#f59e0b' : '#6b7280', fontSize: 10 }}>
|
|
138
|
+
scrolling: {isScrolling ? 'YES' : 'NO'}
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<div
|
|
143
|
+
style={{
|
|
144
|
+
background: shouldAutoScroll ? '#22c55e' : '#ef4444',
|
|
145
|
+
borderRadius: 3,
|
|
146
|
+
color: 'white',
|
|
147
|
+
fontSize: 10,
|
|
148
|
+
marginTop: 4,
|
|
149
|
+
padding: '2px 6px',
|
|
150
|
+
textAlign: 'center',
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
autoScroll: {shouldAutoScroll ? 'YES' : 'NO'}
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (typeof document === 'undefined') return null;
|
|
160
|
+
|
|
161
|
+
return createPortal(panel, document.body);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
DebugInspector.displayName = 'DebugInspector';
|
|
165
|
+
|
|
166
|
+
export default DebugInspector;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { memo, useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
dataSelectors,
|
|
7
|
+
messageStateSelectors,
|
|
8
|
+
useConversationStore,
|
|
9
|
+
virtuaListSelectors,
|
|
10
|
+
} from '../../../store';
|
|
11
|
+
import BackBottom from '../BackBottom';
|
|
12
|
+
import { AT_BOTTOM_THRESHOLD, OPEN_DEV_INSPECTOR } from './DebugInspector';
|
|
13
|
+
|
|
14
|
+
const AutoScroll = memo(() => {
|
|
15
|
+
const atBottom = useConversationStore(virtuaListSelectors.atBottom);
|
|
16
|
+
const isScrolling = useConversationStore(virtuaListSelectors.isScrolling);
|
|
17
|
+
const isGenerating = useConversationStore(messageStateSelectors.isAIGenerating);
|
|
18
|
+
const scrollToBottom = useConversationStore((s) => s.scrollToBottom);
|
|
19
|
+
const dbMessages = useConversationStore(dataSelectors.dbMessages);
|
|
20
|
+
|
|
21
|
+
const shouldAutoScroll = atBottom && isGenerating && !isScrolling;
|
|
22
|
+
|
|
23
|
+
// 获取最后一条消息的 content 长度,用于监听流式输出
|
|
24
|
+
const lastMessage = dbMessages.at(-1);
|
|
25
|
+
const lastMessageContentLength =
|
|
26
|
+
typeof lastMessage?.content === 'string' ? lastMessage.content.length : 0;
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (shouldAutoScroll) {
|
|
30
|
+
scrollToBottom(false);
|
|
31
|
+
}
|
|
32
|
+
}, [shouldAutoScroll, scrollToBottom, dbMessages.length, lastMessageContentLength]);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div style={{ position: 'relative', width: '100%' }}>
|
|
36
|
+
{OPEN_DEV_INSPECTOR && (
|
|
37
|
+
<>
|
|
38
|
+
{/* Threshold 区域顶部边界线 */}
|
|
39
|
+
<div
|
|
40
|
+
style={{
|
|
41
|
+
background: atBottom ? '#22c55e' : '#ef4444',
|
|
42
|
+
height: 2,
|
|
43
|
+
left: 0,
|
|
44
|
+
opacity: 0.5,
|
|
45
|
+
pointerEvents: 'none',
|
|
46
|
+
position: 'absolute',
|
|
47
|
+
right: 0,
|
|
48
|
+
top: -AT_BOTTOM_THRESHOLD,
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
51
|
+
|
|
52
|
+
{/* Threshold 区域 mask - 显示在指示线上方 */}
|
|
53
|
+
<div
|
|
54
|
+
style={{
|
|
55
|
+
background: atBottom
|
|
56
|
+
? 'linear-gradient(to top, rgba(34, 197, 94, 0.15), transparent)'
|
|
57
|
+
: 'linear-gradient(to top, rgba(239, 68, 68, 0.1), transparent)',
|
|
58
|
+
height: AT_BOTTOM_THRESHOLD,
|
|
59
|
+
left: 0,
|
|
60
|
+
pointerEvents: 'none',
|
|
61
|
+
position: 'absolute',
|
|
62
|
+
right: 0,
|
|
63
|
+
top: -AT_BOTTOM_THRESHOLD,
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
{/* AutoScroll 位置指示线(底部) */}
|
|
68
|
+
<div
|
|
69
|
+
style={{
|
|
70
|
+
background: atBottom ? '#22c55e' : '#ef4444',
|
|
71
|
+
height: 2,
|
|
72
|
+
position: 'relative',
|
|
73
|
+
width: '100%',
|
|
74
|
+
}}
|
|
75
|
+
/>
|
|
76
|
+
</>
|
|
77
|
+
)}
|
|
78
|
+
|
|
79
|
+
<BackBottom onScrollToBottom={() => scrollToBottom(true)} visible={!atBottom} />
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
AutoScroll.displayName = 'ConversationAutoScroll';
|
|
85
|
+
|
|
86
|
+
export default AutoScroll;
|
|
@@ -7,6 +7,10 @@ import { VList, type VListHandle } from 'virtua';
|
|
|
7
7
|
import WideScreenContainer from '../../../WideScreenContainer';
|
|
8
8
|
import { useConversationStore, virtuaListSelectors } from '../../store';
|
|
9
9
|
import AutoScroll from './AutoScroll';
|
|
10
|
+
import DebugInspector, {
|
|
11
|
+
AT_BOTTOM_THRESHOLD,
|
|
12
|
+
OPEN_DEV_INSPECTOR,
|
|
13
|
+
} from './AutoScroll/DebugInspector';
|
|
10
14
|
|
|
11
15
|
interface VirtualizedListProps {
|
|
12
16
|
dataSource: string[];
|
|
@@ -23,15 +27,11 @@ const VirtualizedList = memo<VirtualizedListProps>(({ dataSource, itemContent })
|
|
|
23
27
|
const prevDataLengthRef = useRef(dataSource.length);
|
|
24
28
|
const scrollEndTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
25
29
|
|
|
26
|
-
const atBottomThreshold = 200;
|
|
27
|
-
|
|
28
30
|
// Store actions
|
|
29
31
|
const registerVirtuaScrollMethods = useConversationStore((s) => s.registerVirtuaScrollMethods);
|
|
30
32
|
const setScrollState = useConversationStore((s) => s.setScrollState);
|
|
31
33
|
const resetVisibleItems = useConversationStore((s) => s.resetVisibleItems);
|
|
32
|
-
const scrollToBottom = useConversationStore((s) => s.scrollToBottom);
|
|
33
34
|
const setActiveIndex = useConversationStore((s) => s.setActiveIndex);
|
|
34
|
-
const atBottom = useConversationStore(virtuaListSelectors.atBottom);
|
|
35
35
|
const activeIndex = useConversationStore(virtuaListSelectors.activeIndex);
|
|
36
36
|
|
|
37
37
|
// Check if at bottom based on scroll position
|
|
@@ -43,8 +43,8 @@ const VirtualizedList = memo<VirtualizedListProps>(({ dataSource, itemContent })
|
|
|
43
43
|
const scrollSize = ref.scrollSize;
|
|
44
44
|
const viewportSize = ref.viewportSize;
|
|
45
45
|
|
|
46
|
-
return scrollSize - scrollOffset - viewportSize <=
|
|
47
|
-
}, [
|
|
46
|
+
return scrollSize - scrollOffset - viewportSize <= AT_BOTTOM_THRESHOLD;
|
|
47
|
+
}, [AT_BOTTOM_THRESHOLD]);
|
|
48
48
|
|
|
49
49
|
// Handle scroll events
|
|
50
50
|
const handleScroll = useCallback(() => {
|
|
@@ -131,6 +131,8 @@ const VirtualizedList = memo<VirtualizedListProps>(({ dataSource, itemContent })
|
|
|
131
131
|
|
|
132
132
|
return (
|
|
133
133
|
<>
|
|
134
|
+
{/* Debug Inspector - 放在 VList 外面,不会被虚拟列表回收 */}
|
|
135
|
+
{OPEN_DEV_INSPECTOR && <DebugInspector />}
|
|
134
136
|
<VList
|
|
135
137
|
bufferSize={typeof window !== 'undefined' ? window.innerHeight : 0}
|
|
136
138
|
data={dataSource}
|
|
@@ -142,12 +144,14 @@ const VirtualizedList = memo<VirtualizedListProps>(({ dataSource, itemContent })
|
|
|
142
144
|
{(messageId, index): ReactElement => {
|
|
143
145
|
const isAgentCouncil = messageId.includes('agentCouncil');
|
|
144
146
|
const content = itemContent(index, messageId);
|
|
147
|
+
const isLast = index === dataSource.length - 1;
|
|
145
148
|
|
|
146
149
|
if (isAgentCouncil) {
|
|
147
150
|
// AgentCouncil needs full width for horizontal scroll
|
|
148
151
|
return (
|
|
149
152
|
<div key={messageId} style={{ position: 'relative', width: '100%' }}>
|
|
150
153
|
{content}
|
|
154
|
+
{isLast && <AutoScroll />}
|
|
151
155
|
</div>
|
|
152
156
|
);
|
|
153
157
|
}
|
|
@@ -155,21 +159,11 @@ const VirtualizedList = memo<VirtualizedListProps>(({ dataSource, itemContent })
|
|
|
155
159
|
return (
|
|
156
160
|
<WideScreenContainer key={messageId} style={{ position: 'relative' }}>
|
|
157
161
|
{content}
|
|
162
|
+
{isLast && <AutoScroll />}
|
|
158
163
|
</WideScreenContainer>
|
|
159
164
|
);
|
|
160
165
|
}}
|
|
161
166
|
</VList>
|
|
162
|
-
<WideScreenContainer
|
|
163
|
-
onChange={() => {
|
|
164
|
-
if (!atBottom) return;
|
|
165
|
-
setTimeout(() => scrollToBottom(true), 100);
|
|
166
|
-
}}
|
|
167
|
-
style={{
|
|
168
|
-
position: 'relative',
|
|
169
|
-
}}
|
|
170
|
-
>
|
|
171
|
-
<AutoScroll />
|
|
172
|
-
</WideScreenContainer>
|
|
173
167
|
</>
|
|
174
168
|
);
|
|
175
169
|
}, isEqual);
|