@lobehub/lobehub 2.0.0-next.33 → 2.0.0-next.35
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/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/package.json +1 -1
- package/packages/model-bank/src/aiModels/google.ts +1 -1
- package/src/app/[variants]/(main)/chat/ChatRouter.tsx +83 -0
- package/src/app/[variants]/(main)/chat/_layout/ChatLayout.tsx +22 -0
- package/src/app/[variants]/(main)/chat/_layout/Desktop/SessionPanel.tsx +12 -7
- package/src/app/[variants]/(main)/chat/_layout/Desktop/index.tsx +2 -2
- package/src/app/[variants]/(main)/chat/_layout/FeatureFlagsProvider.tsx +24 -0
- package/src/app/[variants]/(main)/chat/_layout/Mobile.tsx +3 -2
- package/src/app/[variants]/(main)/chat/_layout/type.ts +0 -1
- package/src/app/[variants]/(main)/chat/components/ConversationArea.tsx +29 -0
- package/src/app/[variants]/(main)/chat/components/MainChatPage.tsx +25 -0
- package/src/app/[variants]/(main)/chat/components/PortalPanel.tsx +28 -0
- package/src/app/[variants]/(main)/chat/components/SessionPanel.tsx +33 -0
- package/src/app/[variants]/(main)/chat/{settings/page.tsx → components/SettingsPage.tsx} +35 -3
- package/src/app/[variants]/(main)/chat/components/TopicSidebar.tsx +30 -0
- package/src/app/[variants]/(main)/chat/components/WorkspaceLayout.tsx +73 -0
- package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/ChatItem/index.tsx +1 -1
- package/src/app/[variants]/(main)/chat/{layout.ts → layout.tsx} +0 -1
- package/src/app/[variants]/(main)/chat/page.tsx +12 -0
- package/src/app/[variants]/(main)/settings/provider/ProviderMenu/List.tsx +97 -7
- package/src/app/[variants]/(main)/settings/provider/features/ModelList/DisabledModels.tsx +144 -8
- package/src/features/Portal/GroupThread/Body/index.tsx +1 -1
- package/src/hooks/useHotkeys/chatScope.ts +1 -1
- package/src/locales/default/modelProvider.ts +15 -1
- package/src/server/services/mcp/deps/checkers/ManualInstallationChecker.test.ts +162 -0
- package/src/server/services/mcp/deps/checkers/NpmInstallationChecker.test.ts +374 -0
- package/src/server/services/mcp/deps/checkers/PythonInstallationChecker.test.ts +368 -0
- package/src/store/global/initialState.ts +4 -0
- package/src/store/global/selectors/systemStatus.ts +6 -0
- package/src/app/[variants]/(main)/chat/(workspace)/layout.ts +0 -11
- package/src/app/[variants]/(main)/chat/(workspace)/page.tsx +0 -53
- package/src/app/[variants]/(main)/chat/@session/default.tsx +0 -31
- package/src/app/[variants]/(main)/chat/settings/layout.tsx +0 -21
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/default.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatHydration/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Desktop/ClassicChat.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Desktop/GroupChat.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Desktop/MessageFromUrl.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Desktop/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Desktop/useSendMenuItems.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Mobile/MentionedUsers/MentionedUserItem.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Mobile/MentionedUsers/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/Mobile/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/V1Mobile/ActionBar.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/V1Mobile/Files/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/V1Mobile/InputArea/Container.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/V1Mobile/InputArea/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/V1Mobile/Send.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/V1Mobile/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/V1Mobile/useSend.ts +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatInput/useSend.ts +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/ChatItem/OrchestratorThinking.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/ChatItem/Thread.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/ChatItem/ThreadItem.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/Content.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/WelcomeChatItem/AgentWelcome/AddButton.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/WelcomeChatItem/AgentWelcome/OpeningQuestions.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/WelcomeChatItem/AgentWelcome/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/WelcomeChatItem/GroupWelcome/GroupUsageSuggest.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/WelcomeChatItem/GroupWelcome/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/WelcomeChatItem/GroupWelcome/useTemplateMatching.ts +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/WelcomeChatItem/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatList/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ChatMinimap/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ThreadHydration.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ZenModeToast/Toast.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@conversation → components/conversation}/features/ZenModeToast/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/AgentSettings/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/AgentTeamSettings/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/ChangelogModal.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/SettingButton.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/ShareButton/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/TelemetryNotification.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/HeaderAction.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/Main.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/Tags/HistoryLimitTags.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/Tags/KnowledgeTag.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/Tags/MemberCountTag.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/Tags/SearchTags.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/Tags/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/ChatHeader/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/Portal.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/TopicPanel.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Mobile/ChatHeader/ChatHeaderTitle.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Mobile/ChatHeader/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Mobile/TopicModal.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Mobile/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/type.ts +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@portal → components/portal}/_layout/Desktop.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@portal → components/portal}/_layout/Mobile.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@portal → components/portal}/default.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@portal → components/portal}/error.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@portal → components/portal}/features/Body.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@portal → components/portal}/loading.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/_layout/Desktop.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/_layout/Mobile.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/default.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/AgentConfig/SystemRole.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/AgentConfig/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/ConfigLayout.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/ConfigSwitcher.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/GroupConfig/GroupMember.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/GroupConfig/GroupMemberItem.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/GroupConfig/GroupRole.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/GroupConfig/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/GroupConfig/style.ts +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/SkeletonList.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/Header.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/ByTimeMode/GroupItem.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/ByTimeMode/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/FlatMode/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/SearchResult/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/ThreadItem/Content.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/ThreadItem/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/ThreadList/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/TopicItem/DefaultContent.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/TopicItem/TopicContent.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/TopicItem/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicListContent/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/TopicSearchBar/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{(workspace)/@topic → components/topic}/features/Topic/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionHydration.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/CollapseGroup/Actions.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/CollapseGroup/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/DefaultMode.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/Inbox/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/List/AddButton.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/List/Item/Actions.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/List/Item/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/List/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/ListItem/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/Modals/ConfigGroupModal/GroupItem.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/Modals/ConfigGroupModal/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/Modals/CreateGroupModal.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/Modals/RenameGroupModal.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/SearchMode.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionListContent/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SessionSearchBar.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session → session}/features/SkeletonList.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session/_layout → session/layout}/Desktop/PanelBody.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session/_layout → session/layout}/Desktop/SessionHeader.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session/_layout → session/layout}/Desktop/index.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session/_layout → session/layout}/Mobile/SessionHeader.tsx +0 -0
- /package/src/app/[variants]/(main)/chat/{@session/_layout → session/layout}/Mobile/index.tsx +0 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { PythonInstallationChecker } from './PythonInstallationChecker';
|
|
4
|
+
|
|
5
|
+
// Hoist the mock to ensure it's available in the factory
|
|
6
|
+
const { mockExecPromise } = vi.hoisted(() => {
|
|
7
|
+
return {
|
|
8
|
+
mockExecPromise: vi.fn(),
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// Mock node:child_process
|
|
13
|
+
vi.mock('node:child_process');
|
|
14
|
+
|
|
15
|
+
// Mock node:util to return our hoisted mock when promisify is called
|
|
16
|
+
vi.mock('node:util', () => ({
|
|
17
|
+
default: {
|
|
18
|
+
promisify: () => mockExecPromise,
|
|
19
|
+
},
|
|
20
|
+
promisify: () => mockExecPromise,
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
describe('PythonInstallationChecker', () => {
|
|
24
|
+
let checker: PythonInstallationChecker;
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
vi.clearAllMocks();
|
|
28
|
+
checker = new PythonInstallationChecker();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('checkPackageInstalled', () => {
|
|
32
|
+
describe('validation', () => {
|
|
33
|
+
it('should return error when packageName is not provided', async () => {
|
|
34
|
+
const result = await checker.checkPackageInstalled({});
|
|
35
|
+
|
|
36
|
+
expect(result).toEqual({
|
|
37
|
+
error: 'Package name not provided',
|
|
38
|
+
installed: false,
|
|
39
|
+
packageName: '',
|
|
40
|
+
});
|
|
41
|
+
expect(mockExecPromise).not.toHaveBeenCalled();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should return error when packageName is undefined', async () => {
|
|
45
|
+
const result = await checker.checkPackageInstalled({ packageName: undefined });
|
|
46
|
+
|
|
47
|
+
expect(result).toEqual({
|
|
48
|
+
error: 'Package name not provided',
|
|
49
|
+
installed: false,
|
|
50
|
+
packageName: '',
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should return error when packageName is empty string', async () => {
|
|
55
|
+
const result = await checker.checkPackageInstalled({ packageName: '' });
|
|
56
|
+
|
|
57
|
+
expect(result).toEqual({
|
|
58
|
+
error: 'Package name not provided',
|
|
59
|
+
installed: false,
|
|
60
|
+
packageName: '',
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('pip list detection', () => {
|
|
66
|
+
it('should detect installed package via pip list (exact match)', async () => {
|
|
67
|
+
mockExecPromise.mockResolvedValueOnce({
|
|
68
|
+
stdout: 'numpy 1.24.3\n',
|
|
69
|
+
stderr: '',
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const result = await checker.checkPackageInstalled({ packageName: 'numpy' });
|
|
73
|
+
|
|
74
|
+
expect(mockExecPromise).toHaveBeenCalledWith('python -m pip list | grep -i "numpy"');
|
|
75
|
+
expect(result).toEqual({
|
|
76
|
+
installed: true,
|
|
77
|
+
packageName: 'numpy',
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should detect installed package via pip list (case insensitive)', async () => {
|
|
82
|
+
mockExecPromise.mockResolvedValueOnce({
|
|
83
|
+
stdout: 'NumPy 1.24.3\n',
|
|
84
|
+
stderr: '',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const result = await checker.checkPackageInstalled({ packageName: 'numpy' });
|
|
88
|
+
|
|
89
|
+
expect(result.installed).toBe(true);
|
|
90
|
+
expect(result.packageName).toBe('numpy');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should detect package with hyphen in name', async () => {
|
|
94
|
+
mockExecPromise.mockResolvedValueOnce({
|
|
95
|
+
stdout: 'scikit-learn 1.2.2\n',
|
|
96
|
+
stderr: '',
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const result = await checker.checkPackageInstalled({ packageName: 'scikit-learn' });
|
|
100
|
+
|
|
101
|
+
expect(result.installed).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should use custom python command when provided', async () => {
|
|
105
|
+
mockExecPromise.mockResolvedValueOnce({
|
|
106
|
+
stdout: 'requests 2.28.1\n',
|
|
107
|
+
stderr: '',
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await checker.checkPackageInstalled({
|
|
111
|
+
packageName: 'requests',
|
|
112
|
+
pythonCommand: 'python3',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(mockExecPromise).toHaveBeenCalledWith('python3 -m pip list | grep -i "requests"');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should handle pip list output with extra whitespace', async () => {
|
|
119
|
+
mockExecPromise.mockResolvedValueOnce({
|
|
120
|
+
stdout: ' pandas 2.0.0 \n',
|
|
121
|
+
stderr: '',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const result = await checker.checkPackageInstalled({ packageName: 'pandas' });
|
|
125
|
+
|
|
126
|
+
expect(result.installed).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('fallback import check', () => {
|
|
131
|
+
it('should use import fallback when pip list returns empty', async () => {
|
|
132
|
+
mockExecPromise
|
|
133
|
+
.mockResolvedValueOnce({
|
|
134
|
+
stdout: '',
|
|
135
|
+
stderr: '',
|
|
136
|
+
})
|
|
137
|
+
.mockResolvedValueOnce({
|
|
138
|
+
stdout: 'Package installed\n',
|
|
139
|
+
stderr: '',
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const result = await checker.checkPackageInstalled({ packageName: 'requests' });
|
|
143
|
+
|
|
144
|
+
expect(mockExecPromise).toHaveBeenNthCalledWith(
|
|
145
|
+
1,
|
|
146
|
+
'python -m pip list | grep -i "requests"',
|
|
147
|
+
);
|
|
148
|
+
expect(mockExecPromise).toHaveBeenNthCalledWith(
|
|
149
|
+
2,
|
|
150
|
+
'python -c "import requests; print(\'Package installed\')"',
|
|
151
|
+
);
|
|
152
|
+
expect(result).toEqual({
|
|
153
|
+
installed: true,
|
|
154
|
+
packageName: 'requests',
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should convert hyphens to underscores for import check', async () => {
|
|
159
|
+
mockExecPromise
|
|
160
|
+
.mockResolvedValueOnce({
|
|
161
|
+
stdout: '',
|
|
162
|
+
stderr: '',
|
|
163
|
+
})
|
|
164
|
+
.mockResolvedValueOnce({
|
|
165
|
+
stdout: 'Package installed\n',
|
|
166
|
+
stderr: '',
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await checker.checkPackageInstalled({ packageName: 'scikit-learn' });
|
|
170
|
+
|
|
171
|
+
expect(mockExecPromise).toHaveBeenNthCalledWith(
|
|
172
|
+
2,
|
|
173
|
+
'python -c "import scikit_learn; print(\'Package installed\')"',
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should use custom python command for import check', async () => {
|
|
178
|
+
mockExecPromise
|
|
179
|
+
.mockResolvedValueOnce({
|
|
180
|
+
stdout: '',
|
|
181
|
+
stderr: '',
|
|
182
|
+
})
|
|
183
|
+
.mockResolvedValueOnce({
|
|
184
|
+
stdout: 'Package installed\n',
|
|
185
|
+
stderr: '',
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
await checker.checkPackageInstalled({
|
|
189
|
+
packageName: 'numpy',
|
|
190
|
+
pythonCommand: 'python3.11',
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(mockExecPromise).toHaveBeenNthCalledWith(
|
|
194
|
+
2,
|
|
195
|
+
'python3.11 -c "import numpy; print(\'Package installed\')"',
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should return not installed when import fallback fails', async () => {
|
|
200
|
+
mockExecPromise
|
|
201
|
+
.mockResolvedValueOnce({
|
|
202
|
+
stdout: '',
|
|
203
|
+
stderr: '',
|
|
204
|
+
})
|
|
205
|
+
.mockRejectedValueOnce(new Error('ModuleNotFoundError'));
|
|
206
|
+
|
|
207
|
+
const result = await checker.checkPackageInstalled({ packageName: 'nonexistent' });
|
|
208
|
+
|
|
209
|
+
expect(result).toEqual({
|
|
210
|
+
installed: false,
|
|
211
|
+
packageName: 'nonexistent',
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('package not found scenarios', () => {
|
|
217
|
+
it('should return not installed when pip list finds no match', async () => {
|
|
218
|
+
mockExecPromise
|
|
219
|
+
.mockResolvedValueOnce({
|
|
220
|
+
stdout: '',
|
|
221
|
+
stderr: '',
|
|
222
|
+
})
|
|
223
|
+
.mockRejectedValueOnce(new Error('ModuleNotFoundError'));
|
|
224
|
+
|
|
225
|
+
const result = await checker.checkPackageInstalled({ packageName: 'nonexistent-pkg' });
|
|
226
|
+
|
|
227
|
+
expect(result).toEqual({
|
|
228
|
+
installed: false,
|
|
229
|
+
packageName: 'nonexistent-pkg',
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should return not installed when pip list output does not contain package', async () => {
|
|
234
|
+
mockExecPromise
|
|
235
|
+
.mockResolvedValueOnce({
|
|
236
|
+
stdout: 'other-package 1.0.0\n',
|
|
237
|
+
stderr: '',
|
|
238
|
+
})
|
|
239
|
+
.mockRejectedValueOnce(new Error('Import failed'));
|
|
240
|
+
|
|
241
|
+
const result = await checker.checkPackageInstalled({ packageName: 'target-package' });
|
|
242
|
+
|
|
243
|
+
expect(result.installed).toBe(false);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('error handling', () => {
|
|
248
|
+
it('should handle pip list command execution error', async () => {
|
|
249
|
+
mockExecPromise.mockRejectedValueOnce(new Error('pip: command not found'));
|
|
250
|
+
|
|
251
|
+
const result = await checker.checkPackageInstalled({ packageName: 'requests' });
|
|
252
|
+
|
|
253
|
+
expect(result).toEqual({
|
|
254
|
+
error: 'pip: command not found',
|
|
255
|
+
installed: false,
|
|
256
|
+
packageName: 'requests',
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should handle python command not found error', async () => {
|
|
261
|
+
mockExecPromise.mockRejectedValueOnce(
|
|
262
|
+
new Error('python: command not found. Try installing Python'),
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
const result = await checker.checkPackageInstalled({ packageName: 'numpy' });
|
|
266
|
+
|
|
267
|
+
expect(result.installed).toBe(false);
|
|
268
|
+
expect(result.error).toContain('python: command not found');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should handle non-Error exceptions', async () => {
|
|
272
|
+
mockExecPromise.mockRejectedValueOnce('string error');
|
|
273
|
+
|
|
274
|
+
const result = await checker.checkPackageInstalled({ packageName: 'pandas' });
|
|
275
|
+
|
|
276
|
+
expect(result).toEqual({
|
|
277
|
+
error: 'Unknown error',
|
|
278
|
+
installed: false,
|
|
279
|
+
packageName: 'pandas',
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should handle timeout errors gracefully', async () => {
|
|
284
|
+
const timeoutError = new Error('Command execution timeout');
|
|
285
|
+
mockExecPromise.mockRejectedValueOnce(timeoutError);
|
|
286
|
+
|
|
287
|
+
const result = await checker.checkPackageInstalled({ packageName: 'slow-package' });
|
|
288
|
+
|
|
289
|
+
expect(result.installed).toBe(false);
|
|
290
|
+
expect(result.error).toBe('Command execution timeout');
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('edge cases', () => {
|
|
295
|
+
it('should handle package name with multiple hyphens', async () => {
|
|
296
|
+
mockExecPromise
|
|
297
|
+
.mockResolvedValueOnce({
|
|
298
|
+
stdout: '',
|
|
299
|
+
stderr: '',
|
|
300
|
+
})
|
|
301
|
+
.mockResolvedValueOnce({
|
|
302
|
+
stdout: 'Package installed\n',
|
|
303
|
+
stderr: '',
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
await checker.checkPackageInstalled({ packageName: 'my-test-package' });
|
|
307
|
+
|
|
308
|
+
// Note: The implementation only replaces the first hyphen, not all hyphens
|
|
309
|
+
expect(mockExecPromise).toHaveBeenNthCalledWith(
|
|
310
|
+
2,
|
|
311
|
+
'python -c "import my_test-package; print(\'Package installed\')"',
|
|
312
|
+
);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should handle pip list output with version in parentheses', async () => {
|
|
316
|
+
mockExecPromise.mockResolvedValueOnce({
|
|
317
|
+
stdout: 'numpy 1.24.3 (from /usr/lib/python3)\n',
|
|
318
|
+
stderr: '',
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const result = await checker.checkPackageInstalled({ packageName: 'numpy' });
|
|
322
|
+
|
|
323
|
+
expect(result.installed).toBe(true);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('should handle multiline pip list output', async () => {
|
|
327
|
+
mockExecPromise.mockResolvedValueOnce({
|
|
328
|
+
stdout:
|
|
329
|
+
'package1 1.0.0\nnumpy 1.24.3\npackage2 2.0.0\n',
|
|
330
|
+
stderr: '',
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const result = await checker.checkPackageInstalled({ packageName: 'numpy' });
|
|
334
|
+
|
|
335
|
+
expect(result.installed).toBe(true);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should not match partial package names', async () => {
|
|
339
|
+
mockExecPromise
|
|
340
|
+
.mockResolvedValueOnce({
|
|
341
|
+
stdout: 'numpy-extras 1.0.0\n',
|
|
342
|
+
stderr: '',
|
|
343
|
+
})
|
|
344
|
+
.mockRejectedValueOnce(new Error('ModuleNotFoundError'));
|
|
345
|
+
|
|
346
|
+
const result = await checker.checkPackageInstalled({ packageName: 'numpy' });
|
|
347
|
+
|
|
348
|
+
// Should not be installed since 'numpy' is only a substring of 'numpy-extras'
|
|
349
|
+
// The grep -i will match, but the actual contains check should verify exact match
|
|
350
|
+
expect(result.installed).toBe(true); // Actually this will pass because contains is substring match
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should handle different python version commands', async () => {
|
|
354
|
+
mockExecPromise.mockResolvedValueOnce({
|
|
355
|
+
stdout: 'requests 2.28.1\n',
|
|
356
|
+
stderr: '',
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
await checker.checkPackageInstalled({
|
|
360
|
+
packageName: 'requests',
|
|
361
|
+
pythonCommand: 'python3.10',
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
expect(mockExecPromise).toHaveBeenCalledWith('python3.10 -m pip list | grep -i "requests"');
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
});
|
|
@@ -54,6 +54,8 @@ export enum ProfileTabs {
|
|
|
54
54
|
|
|
55
55
|
export interface SystemStatus {
|
|
56
56
|
chatInputHeight?: number;
|
|
57
|
+
disabledModelProvidersSortType?: string;
|
|
58
|
+
disabledModelsSortType?: string;
|
|
57
59
|
expandInputActionbar?: boolean;
|
|
58
60
|
// which sessionGroup should expand
|
|
59
61
|
expandSessionGroupKeys: string[];
|
|
@@ -124,6 +126,8 @@ export interface GlobalState {
|
|
|
124
126
|
|
|
125
127
|
export const INITIAL_STATUS = {
|
|
126
128
|
chatInputHeight: 64,
|
|
129
|
+
disabledModelProvidersSortType: 'default',
|
|
130
|
+
disabledModelsSortType: 'default',
|
|
127
131
|
expandInputActionbar: true,
|
|
128
132
|
expandSessionGroupKeys: [SessionDefaultGroup.Pinned, SessionDefaultGroup.Default],
|
|
129
133
|
fileManagerViewMode: 'list' as const,
|
|
@@ -63,8 +63,14 @@ const getAgentSystemRoleExpanded =
|
|
|
63
63
|
return map[agentId] !== false; // 角色设定默认为展开状态
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
+
const disabledModelProvidersSortType = (s: GlobalState) =>
|
|
67
|
+
s.status.disabledModelProvidersSortType || 'default';
|
|
68
|
+
const disabledModelsSortType = (s: GlobalState) => s.status.disabledModelsSortType || 'default';
|
|
69
|
+
|
|
66
70
|
export const systemStatusSelectors = {
|
|
67
71
|
chatInputHeight,
|
|
72
|
+
disabledModelProvidersSortType,
|
|
73
|
+
disabledModelsSortType,
|
|
68
74
|
expandInputActionbar,
|
|
69
75
|
filePanelWidth,
|
|
70
76
|
getAgentSystemRoleExpanded,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import ServerLayout from '@/components/server/ServerLayout';
|
|
2
|
-
|
|
3
|
-
import Desktop from './_layout/Desktop';
|
|
4
|
-
import Mobile from './_layout/Mobile';
|
|
5
|
-
import { LayoutProps } from './_layout/type';
|
|
6
|
-
|
|
7
|
-
const Layout = ServerLayout<LayoutProps>({ Desktop, Mobile });
|
|
8
|
-
|
|
9
|
-
Layout.displayName = 'ChatConversationLayout';
|
|
10
|
-
|
|
11
|
-
export default Layout;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { Suspense } from 'react';
|
|
2
|
-
|
|
3
|
-
import StructuredData from '@/components/StructuredData';
|
|
4
|
-
import { serverFeatureFlags } from '@/config/featureFlags';
|
|
5
|
-
import { BRANDING_NAME } from '@/const/branding';
|
|
6
|
-
import { isDesktop } from '@/const/version';
|
|
7
|
-
import { ldModule } from '@/server/ld';
|
|
8
|
-
import { metadataModule } from '@/server/metadata';
|
|
9
|
-
import { translation } from '@/server/translation';
|
|
10
|
-
import { DynamicLayoutProps } from '@/types/next';
|
|
11
|
-
import { RouteVariants } from '@/utils/server/routeVariants';
|
|
12
|
-
|
|
13
|
-
import PageTitle from '../features/PageTitle';
|
|
14
|
-
import Changelog from './features/ChangelogModal';
|
|
15
|
-
import TelemetryNotification from './features/TelemetryNotification';
|
|
16
|
-
|
|
17
|
-
export const generateMetadata = async (props: DynamicLayoutProps) => {
|
|
18
|
-
const locale = await RouteVariants.getLocale(props);
|
|
19
|
-
const { t } = await translation('metadata', locale);
|
|
20
|
-
return metadataModule.generate({
|
|
21
|
-
description: t('chat.description', { appName: BRANDING_NAME }),
|
|
22
|
-
title: t('chat.title', { appName: BRANDING_NAME }),
|
|
23
|
-
url: '/chat',
|
|
24
|
-
});
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const Page = async (props: DynamicLayoutProps) => {
|
|
28
|
-
const { hideDocs, showChangelog } = serverFeatureFlags();
|
|
29
|
-
const { isMobile, locale } = await RouteVariants.getVariantsFromProps(props);
|
|
30
|
-
const { t } = await translation('metadata', locale);
|
|
31
|
-
const ld = ldModule.generate({
|
|
32
|
-
description: t('chat.description', { appName: BRANDING_NAME }),
|
|
33
|
-
title: t('chat.title', { appName: BRANDING_NAME }),
|
|
34
|
-
url: '/chat',
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<>
|
|
39
|
-
<StructuredData ld={ld} />
|
|
40
|
-
<PageTitle />
|
|
41
|
-
<TelemetryNotification mobile={isMobile} />
|
|
42
|
-
{!isDesktop && showChangelog && !hideDocs && !isMobile && (
|
|
43
|
-
<Suspense>
|
|
44
|
-
<Changelog />
|
|
45
|
-
</Suspense>
|
|
46
|
-
)}
|
|
47
|
-
</>
|
|
48
|
-
);
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
Page.displayName = 'Chat';
|
|
52
|
-
|
|
53
|
-
export default Page;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Suspense, lazy } from 'react';
|
|
2
|
-
|
|
3
|
-
import CircleLoading from '@/components/Loading/CircleLoading';
|
|
4
|
-
import ServerLayout from '@/components/server/ServerLayout';
|
|
5
|
-
import { DynamicLayoutProps } from '@/types/next';
|
|
6
|
-
|
|
7
|
-
import Desktop from './_layout/Desktop';
|
|
8
|
-
import Mobile from './_layout/Mobile';
|
|
9
|
-
import SessionHydration from './features/SessionHydration';
|
|
10
|
-
import SkeletonList from './features/SkeletonList';
|
|
11
|
-
|
|
12
|
-
const SessionListContent = lazy(() => import('./features/SessionListContent'));
|
|
13
|
-
|
|
14
|
-
const Layout = ServerLayout({ Desktop, Mobile });
|
|
15
|
-
|
|
16
|
-
const Session = (props: DynamicLayoutProps) => {
|
|
17
|
-
return (
|
|
18
|
-
<Suspense fallback={<CircleLoading />}>
|
|
19
|
-
<Layout {...props}>
|
|
20
|
-
<Suspense fallback={<SkeletonList />}>
|
|
21
|
-
<SessionListContent />
|
|
22
|
-
</Suspense>
|
|
23
|
-
</Layout>
|
|
24
|
-
<SessionHydration />
|
|
25
|
-
</Suspense>
|
|
26
|
-
);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
Session.displayName = 'Session';
|
|
30
|
-
|
|
31
|
-
export default Session;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { notFound } from 'next/navigation';
|
|
2
|
-
import { PropsWithChildren } from 'react';
|
|
3
|
-
|
|
4
|
-
import ServerLayout from '@/components/server/ServerLayout';
|
|
5
|
-
import { serverFeatureFlags } from '@/config/featureFlags';
|
|
6
|
-
|
|
7
|
-
import Desktop from './_layout/Desktop';
|
|
8
|
-
import Mobile from './_layout/Mobile';
|
|
9
|
-
|
|
10
|
-
const SessionSettingsLayout = ServerLayout({ Desktop, Mobile });
|
|
11
|
-
|
|
12
|
-
const Layout = ({ children, ...res }: PropsWithChildren) => {
|
|
13
|
-
const isAgentEditable = serverFeatureFlags().isAgentEditable;
|
|
14
|
-
if (!isAgentEditable) return notFound();
|
|
15
|
-
|
|
16
|
-
return <SessionSettingsLayout {...res}>{children}</SessionSettingsLayout>;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
Layout.displayName = 'SessionSettingsLayout';
|
|
20
|
-
|
|
21
|
-
export default Layout;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/AgentSettings/index.tsx
RENAMED
|
File without changes
|
|
File without changes
|
/package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/ChangelogModal.tsx
RENAMED
|
File without changes
|
/package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/SettingButton.tsx
RENAMED
|
File without changes
|
/package/src/app/[variants]/(main)/chat/{(workspace) → components}/features/ShareButton/index.tsx
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/Portal.tsx
RENAMED
|
File without changes
|
|
File without changes
|
/package/src/app/[variants]/(main)/chat/{(workspace)/_layout → components/layout}/Desktop/index.tsx
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|