@lobehub/lobehub 2.0.0-next.232 → 2.0.0-next.234

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 (112) hide show
  1. package/.github/workflows/bundle-analyzer.yml +1 -1
  2. package/.github/workflows/e2e.yml +62 -53
  3. package/.github/workflows/manual-build-desktop.yml +5 -5
  4. package/.github/workflows/pr-build-desktop.yml +4 -4
  5. package/.github/workflows/pr-build-docker.yml +2 -2
  6. package/.github/workflows/release-desktop-beta.yml +4 -4
  7. package/.github/workflows/release-docker.yml +2 -2
  8. package/.github/workflows/test.yml +44 -7
  9. package/CHANGELOG.md +59 -0
  10. package/CLAUDE.md +1 -1
  11. package/changelog/v1.json +14 -0
  12. package/docs/development/basic/feature-development.mdx +4 -5
  13. package/docs/development/basic/feature-development.zh-CN.mdx +4 -5
  14. package/docs/self-hosting/environment-variables/auth.mdx +7 -0
  15. package/docs/self-hosting/environment-variables/auth.zh-CN.mdx +7 -0
  16. package/e2e/README.md +6 -6
  17. package/e2e/src/features/community/detail-pages.feature +9 -9
  18. package/e2e/src/features/community/interactions.feature +13 -13
  19. package/e2e/src/features/community/smoke.feature +6 -6
  20. package/e2e/src/steps/agent/conversation-mgmt.steps.ts +196 -25
  21. package/e2e/src/steps/agent/conversation.steps.ts +58 -0
  22. package/e2e/src/steps/agent/message-ops.steps.ts +20 -15
  23. package/e2e/src/steps/community/detail-pages.steps.ts +60 -19
  24. package/e2e/src/steps/community/interactions.steps.ts +145 -32
  25. package/e2e/src/steps/hooks.ts +12 -2
  26. package/locales/en-US/setting.json +3 -0
  27. package/locales/zh-CN/file.json +4 -0
  28. package/locales/zh-CN/setting.json +3 -0
  29. package/package.json +5 -5
  30. package/packages/business/config/src/llm.ts +6 -1
  31. package/packages/const/src/index.ts +1 -0
  32. package/packages/const/src/lobehubSkill.ts +55 -0
  33. package/packages/const/src/settings/image.ts +1 -1
  34. package/packages/model-bank/src/aiModels/azure.ts +2 -2
  35. package/packages/model-bank/src/aiModels/google.ts +1 -0
  36. package/packages/model-bank/src/aiModels/lobehub.ts +33 -13
  37. package/packages/model-bank/src/aiModels/openai.ts +21 -4
  38. package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.ts +4 -1
  39. package/packages/model-runtime/src/providers/openai/__snapshots__/index.test.ts.snap +1 -1
  40. package/packages/ssrf-safe-fetch/index.test.ts +5 -34
  41. package/packages/ssrf-safe-fetch/index.ts +12 -2
  42. package/packages/types/package.json +1 -1
  43. package/packages/types/src/files/upload.ts +11 -1
  44. package/packages/types/src/message/common/tools.ts +1 -1
  45. package/packages/types/src/serverConfig.ts +1 -0
  46. package/public/not-compatible.html +1296 -0
  47. package/src/app/[variants]/(main)/image/_layout/ConfigPanel/components/MultiImagesUpload/index.tsx +3 -3
  48. package/src/app/[variants]/(main)/image/features/GenerationFeed/index.tsx +3 -10
  49. package/src/app/[variants]/(main)/image/index.tsx +1 -1
  50. package/src/app/[variants]/(main)/resource/features/FileDetail.tsx +20 -12
  51. package/src/app/[variants]/(main)/resource/features/modal/FullscreenModal.tsx +2 -4
  52. package/src/app/[variants]/layout.tsx +50 -1
  53. package/src/envs/auth.ts +15 -0
  54. package/src/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem.tsx +304 -0
  55. package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +74 -10
  56. package/src/features/Conversation/Messages/AssistantGroup/Tool/Inspector/ToolTitle.tsx +9 -0
  57. package/src/features/FileViewer/Renderer/Code/index.tsx +224 -0
  58. package/src/features/FileViewer/Renderer/Image/index.tsx +8 -1
  59. package/src/features/FileViewer/Renderer/PDF/index.tsx +3 -1
  60. package/src/features/FileViewer/Renderer/PDF/style.ts +2 -1
  61. package/src/features/FileViewer/index.tsx +135 -24
  62. package/src/features/PageEditor/EditorCanvas/useSlashItems.tsx +7 -4
  63. package/src/features/PageEditor/store/initialState.ts +2 -1
  64. package/src/features/ResourceManager/components/Editor/FileContent.tsx +1 -4
  65. package/src/features/ResourceManager/components/Editor/FileCopilot.tsx +64 -0
  66. package/src/features/ResourceManager/components/Editor/index.tsx +98 -31
  67. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +3 -2
  68. package/src/features/ResourceManager/components/Explorer/ListView/ColumnResizeHandle.tsx +119 -0
  69. package/src/features/ResourceManager/components/Explorer/ListView/ListItem/index.tsx +67 -22
  70. package/src/features/ResourceManager/components/Explorer/ListView/Skeleton.tsx +46 -11
  71. package/src/features/ResourceManager/components/Explorer/ListView/index.tsx +140 -81
  72. package/src/features/ResourceManager/components/Explorer/ToolBar/SortDropdown.tsx +20 -12
  73. package/src/features/ResourceManager/components/Explorer/ToolBar/ViewSwitcher.tsx +18 -10
  74. package/src/features/ResourceManager/components/UploadDock/Item.tsx +38 -6
  75. package/src/features/ResourceManager/components/UploadDock/index.tsx +62 -41
  76. package/src/features/ResourceManager/index.tsx +1 -0
  77. package/src/helpers/toolEngineering/index.test.ts +3 -0
  78. package/src/helpers/toolEngineering/index.ts +12 -1
  79. package/src/hooks/useFetchAiImageConfig.ts +54 -10
  80. package/src/libs/trpc/utils/internalJwt.ts +2 -2
  81. package/src/locales/default/file.ts +4 -0
  82. package/src/locales/default/setting.ts +3 -0
  83. package/src/server/globalConfig/index.ts +1 -0
  84. package/src/server/modules/ModelRuntime/index.test.ts +214 -1
  85. package/src/server/modules/ModelRuntime/index.ts +43 -7
  86. package/src/server/routers/lambda/document.ts +44 -0
  87. package/src/server/routers/tools/market.ts +261 -0
  88. package/src/server/services/document/index.ts +22 -0
  89. package/src/services/document/index.ts +4 -0
  90. package/src/services/upload.ts +22 -2
  91. package/src/store/chat/slices/plugin/actions/internals.ts +15 -2
  92. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +104 -0
  93. package/src/store/file/slices/fileManager/action.test.ts +9 -3
  94. package/src/store/file/slices/fileManager/action.ts +165 -70
  95. package/src/store/file/slices/upload/action.ts +3 -0
  96. package/src/store/global/actions/general.ts +15 -0
  97. package/src/store/global/initialState.ts +13 -0
  98. package/src/store/image/slices/generationConfig/initialState.ts +5 -5
  99. package/src/store/image/slices/generationConfig/selectors.test.ts +11 -4
  100. package/src/store/serverConfig/selectors.ts +1 -0
  101. package/src/store/tool/initialState.ts +11 -2
  102. package/src/store/tool/selectors/index.ts +1 -0
  103. package/src/store/tool/selectors/tool.ts +3 -1
  104. package/src/store/tool/slices/lobehubSkillStore/action.ts +361 -0
  105. package/src/store/tool/slices/lobehubSkillStore/index.ts +4 -0
  106. package/src/store/tool/slices/lobehubSkillStore/initialState.ts +24 -0
  107. package/src/store/tool/slices/lobehubSkillStore/selectors.ts +145 -0
  108. package/src/store/tool/slices/lobehubSkillStore/types.ts +100 -0
  109. package/src/store/tool/store.ts +8 -2
  110. package/vitest.config.mts +11 -6
  111. package/src/features/FileViewer/Renderer/JavaScript/index.tsx +0 -66
  112. package/src/features/FileViewer/Renderer/TXT/index.tsx +0 -50
@@ -1,4 +1,9 @@
1
- import { KLAVIS_SERVER_TYPES, type KlavisServerType } from '@lobechat/const';
1
+ import {
2
+ KLAVIS_SERVER_TYPES,
3
+ type KlavisServerType,
4
+ LOBEHUB_SKILL_PROVIDERS,
5
+ type LobehubSkillProviderType,
6
+ } from '@lobechat/const';
2
7
  import { Avatar, Flexbox, Icon, Image, type ItemType } from '@lobehub/ui';
3
8
  import { cssVar } from 'antd-style';
4
9
  import isEqual from 'fast-deep-equal';
@@ -16,11 +21,13 @@ import { useToolStore } from '@/store/tool';
16
21
  import {
17
22
  builtinToolSelectors,
18
23
  klavisStoreSelectors,
24
+ lobehubSkillStoreSelectors,
19
25
  pluginSelectors,
20
26
  } from '@/store/tool/selectors';
21
27
 
22
28
  import { useAgentId } from '../../hooks/useAgentId';
23
29
  import KlavisServerItem from './KlavisServerItem';
30
+ import LobehubSkillServerItem from './LobehubSkillServerItem';
24
31
  import ToolItem from './ToolItem';
25
32
 
26
33
  /**
@@ -39,6 +46,21 @@ const KlavisIcon = memo<Pick<KlavisServerType, 'icon' | 'label'>>(({ icon, label
39
46
 
40
47
  KlavisIcon.displayName = 'KlavisIcon';
41
48
 
49
+ /**
50
+ * LobeHub Skill Provider 图标组件
51
+ */
52
+ const LobehubSkillIcon = memo<Pick<LobehubSkillProviderType, 'icon' | 'label'>>(
53
+ ({ icon, label }) => {
54
+ if (typeof icon === 'string') {
55
+ return <Image alt={label} height={18} src={icon} style={{ flex: 'none' }} width={18} />;
56
+ }
57
+
58
+ return <Icon fill={cssVar.colorText} icon={icon} size={18} />;
59
+ },
60
+ );
61
+
62
+ LobehubSkillIcon.displayName = 'LobehubSkillIcon';
63
+
42
64
  export const useControls = ({
43
65
  setModalOpen,
44
66
  setUpdating,
@@ -66,10 +88,16 @@ export const useControls = ({
66
88
  const allKlavisServers = useToolStore(klavisStoreSelectors.getServers, isEqual);
67
89
  const isKlavisEnabledInEnv = useServerConfigStore(serverConfigSelectors.enableKlavis);
68
90
 
69
- const [useFetchPluginStore, useFetchUserKlavisServers] = useToolStore((s) => [
70
- s.useFetchPluginStore,
71
- s.useFetchUserKlavisServers,
72
- ]);
91
+ // LobeHub Skill 相关状态
92
+ const allLobehubSkillServers = useToolStore(lobehubSkillStoreSelectors.getServers, isEqual);
93
+ const isLobehubSkillEnabled = useServerConfigStore(serverConfigSelectors.enableLobehubSkill);
94
+
95
+ const [useFetchPluginStore, useFetchUserKlavisServers, useFetchLobehubSkillConnections] =
96
+ useToolStore((s) => [
97
+ s.useFetchPluginStore,
98
+ s.useFetchUserKlavisServers,
99
+ s.useFetchLobehubSkillConnections,
100
+ ]);
73
101
 
74
102
  useFetchPluginStore();
75
103
  useFetchInstalledPlugins();
@@ -78,6 +106,9 @@ export const useControls = ({
78
106
  // 使用 SWR 加载用户的 Klavis 集成(从数据库)
79
107
  useFetchUserKlavisServers(isKlavisEnabledInEnv);
80
108
 
109
+ // 使用 SWR 加载用户的 LobeHub Skill 连接
110
+ useFetchLobehubSkillConnections(isLobehubSkillEnabled);
111
+
81
112
  // 根据 identifier 获取已连接的服务器
82
113
  const getServerByName = (identifier: string) => {
83
114
  return allKlavisServers.find((server) => server.identifier === identifier);
@@ -118,7 +149,20 @@ export const useControls = ({
118
149
  [isKlavisEnabledInEnv, allKlavisServers],
119
150
  );
120
151
 
121
- // 合并 builtin 工具和 Klavis 服务器
152
+ // LobeHub Skill Provider 列表项
153
+ const lobehubSkillItems = useMemo(
154
+ () =>
155
+ isLobehubSkillEnabled
156
+ ? LOBEHUB_SKILL_PROVIDERS.map((provider) => ({
157
+ icon: <LobehubSkillIcon icon={provider.icon} label={provider.label} />,
158
+ key: provider.id, // 使用 provider.id 作为 key,与 pluginId 保持一致
159
+ label: <LobehubSkillServerItem label={provider.label} provider={provider.id} />,
160
+ }))
161
+ : [],
162
+ [isLobehubSkillEnabled, allLobehubSkillServers],
163
+ );
164
+
165
+ // 合并 builtin 工具、Klavis 服务器和 LobeHub Skill Provider
122
166
  const builtinItems = useMemo(
123
167
  () => [
124
168
  // 原有的 builtin 工具
@@ -140,10 +184,12 @@ export const useControls = ({
140
184
  />
141
185
  ),
142
186
  })),
187
+ // LobeHub Skill Providers
188
+ ...lobehubSkillItems,
143
189
  // Klavis 服务器
144
190
  ...klavisServerItems,
145
191
  ],
146
- [filteredBuiltinList, klavisServerItems, checked, togglePlugin, setUpdating],
192
+ [filteredBuiltinList, klavisServerItems, lobehubSkillItems, checked, togglePlugin, setUpdating],
147
193
  );
148
194
 
149
195
  // 市场 tab 的 items
@@ -233,8 +279,17 @@ export const useControls = ({
233
279
  checked.includes(item.key as string),
234
280
  );
235
281
 
236
- // 合并 builtin Klavis
237
- const allBuiltinItems = [...enabledBuiltinItems, ...connectedKlavisItems];
282
+ // 已连接的 LobeHub Skill Providers
283
+ const connectedLobehubSkillItems = lobehubSkillItems.filter((item) =>
284
+ checked.includes(item.key as string),
285
+ );
286
+
287
+ // 合并 builtin、Klavis 和 LobeHub Skill
288
+ const allBuiltinItems = [
289
+ ...enabledBuiltinItems,
290
+ ...connectedKlavisItems,
291
+ ...connectedLobehubSkillItems,
292
+ ];
238
293
 
239
294
  if (allBuiltinItems.length > 0) {
240
295
  installedItems.push({
@@ -279,7 +334,16 @@ export const useControls = ({
279
334
  }
280
335
 
281
336
  return installedItems;
282
- }, [filteredBuiltinList, list, klavisServerItems, checked, togglePlugin, setUpdating, t]);
337
+ }, [
338
+ filteredBuiltinList,
339
+ list,
340
+ klavisServerItems,
341
+ lobehubSkillItems,
342
+ checked,
343
+ togglePlugin,
344
+ setUpdating,
345
+ t,
346
+ ]);
283
347
 
284
348
  return { installedPluginItems, marketItems };
285
349
  };
@@ -38,6 +38,15 @@ const ToolTitle = memo<ToolTitleProps>(({ identifier, apiName, isLoading, isAbor
38
38
  const isBuiltinPlugin = builtinToolIdentifiers.includes(identifier);
39
39
  const pluginTitle = pluginHelpers.getPluginTitle(pluginMeta) ?? t('unknownPlugin');
40
40
 
41
+ // Debug logging for LobeHub Skill title issue
42
+ console.log('[ToolTitle Debug]', {
43
+ apiName,
44
+ identifier,
45
+ isBuiltinPlugin,
46
+ pluginMeta,
47
+ pluginTitle,
48
+ });
49
+
41
50
  return (
42
51
  <div
43
52
  className={cx(
@@ -0,0 +1,224 @@
1
+ 'use client';
2
+
3
+ import { Center, Flexbox, Highlighter } from '@lobehub/ui';
4
+ import { createStaticStyles } from 'antd-style';
5
+ import { memo } from 'react';
6
+
7
+ import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
8
+
9
+ import { useTextFileLoader } from '../../hooks/useTextFileLoader';
10
+
11
+ const styles = createStaticStyles(({ css }) => ({
12
+ page: css`
13
+ width: 100%;
14
+ height: 100%;
15
+ padding-inline: 24px 4px;
16
+ `,
17
+ }));
18
+
19
+ const getLanguage = (fileName?: string): string => {
20
+ if (!fileName) return 'txt';
21
+
22
+ const ext = fileName.toLowerCase().split('.').pop();
23
+ switch (ext) {
24
+ // JavaScript/TypeScript
25
+ case 'js':
26
+ case 'mjs':
27
+ case 'cjs': {
28
+ return 'javascript';
29
+ }
30
+ case 'ts': {
31
+ return 'typescript';
32
+ }
33
+ case 'tsx': {
34
+ return 'tsx';
35
+ }
36
+ case 'jsx': {
37
+ return 'jsx';
38
+ }
39
+
40
+ // Python
41
+ case 'py':
42
+ case 'pyw': {
43
+ return 'python';
44
+ }
45
+
46
+ // Java/JVM Languages
47
+ case 'java': {
48
+ return 'java';
49
+ }
50
+ case 'kt':
51
+ case 'kts': {
52
+ return 'kotlin';
53
+ }
54
+ case 'scala': {
55
+ return 'scala';
56
+ }
57
+ case 'groovy': {
58
+ return 'groovy';
59
+ }
60
+
61
+ // C/C++
62
+ case 'c':
63
+ case 'h': {
64
+ return 'c';
65
+ }
66
+ case 'cpp':
67
+ case 'cxx':
68
+ case 'cc':
69
+ case 'hpp':
70
+ case 'hxx': {
71
+ return 'cpp';
72
+ }
73
+
74
+ // C#
75
+ case 'cs': {
76
+ return 'csharp';
77
+ }
78
+
79
+ // Go
80
+ case 'go': {
81
+ return 'go';
82
+ }
83
+
84
+ // Rust
85
+ case 'rs': {
86
+ return 'rust';
87
+ }
88
+
89
+ // Ruby
90
+ case 'rb': {
91
+ return 'ruby';
92
+ }
93
+
94
+ // PHP
95
+ case 'php': {
96
+ return 'php';
97
+ }
98
+
99
+ // Swift
100
+ case 'swift': {
101
+ return 'swift';
102
+ }
103
+
104
+ // Shell
105
+ case 'sh':
106
+ case 'bash':
107
+ case 'zsh': {
108
+ return 'bash';
109
+ }
110
+
111
+ // Web
112
+ case 'html':
113
+ case 'htm': {
114
+ return 'html';
115
+ }
116
+ case 'css': {
117
+ return 'css';
118
+ }
119
+ case 'scss': {
120
+ return 'scss';
121
+ }
122
+ case 'sass': {
123
+ return 'sass';
124
+ }
125
+ case 'less': {
126
+ return 'less';
127
+ }
128
+
129
+ // Data formats
130
+ case 'json': {
131
+ return 'json';
132
+ }
133
+ case 'xml': {
134
+ return 'xml';
135
+ }
136
+ case 'yaml':
137
+ case 'yml': {
138
+ return 'yaml';
139
+ }
140
+ case 'toml': {
141
+ return 'toml';
142
+ }
143
+
144
+ // Markdown
145
+ case 'md':
146
+ case 'mdx': {
147
+ return 'markdown';
148
+ }
149
+
150
+ // SQL
151
+ case 'sql': {
152
+ return 'sql';
153
+ }
154
+
155
+ // Other popular languages
156
+ case 'lua': {
157
+ return 'lua';
158
+ }
159
+ case 'r': {
160
+ return 'r';
161
+ }
162
+ case 'dart': {
163
+ return 'dart';
164
+ }
165
+ case 'elixir':
166
+ case 'ex':
167
+ case 'exs': {
168
+ return 'elixir';
169
+ }
170
+ case 'erl':
171
+ case 'hrl': {
172
+ return 'erlang';
173
+ }
174
+ case 'clj':
175
+ case 'cljs':
176
+ case 'cljc': {
177
+ return 'clojure';
178
+ }
179
+ case 'vim': {
180
+ return 'vim';
181
+ }
182
+ case 'dockerfile': {
183
+ return 'dockerfile';
184
+ }
185
+ case 'graphql':
186
+ case 'gql': {
187
+ return 'graphql';
188
+ }
189
+
190
+ default: {
191
+ return 'txt';
192
+ }
193
+ }
194
+ };
195
+
196
+ interface CodeViewerProps {
197
+ fileId: string;
198
+ fileName?: string;
199
+ url: string | null;
200
+ }
201
+
202
+ /**
203
+ * Render any code file.
204
+ */
205
+ const CodeViewer = memo<CodeViewerProps>(({ url, fileName }) => {
206
+ const { fileData, loading } = useTextFileLoader(url);
207
+ const language = getLanguage(fileName);
208
+
209
+ return (
210
+ <Flexbox className={styles.page}>
211
+ {!loading && fileData ? (
212
+ <Highlighter language={language} showLanguage={false} variant={'borderless'}>
213
+ {fileData}
214
+ </Highlighter>
215
+ ) : (
216
+ <Center height={'100%'}>
217
+ <NeuralNetworkLoading size={36} />
218
+ </Center>
219
+ )}
220
+ </Flexbox>
221
+ );
222
+ });
223
+
224
+ export default CodeViewer;
@@ -1,7 +1,9 @@
1
1
  'use client';
2
2
 
3
3
  import { Center } from '@lobehub/ui';
4
- import { memo } from 'react';
4
+ import { memo, useState } from 'react';
5
+
6
+ import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
5
7
 
6
8
  interface ImageViewerProps {
7
9
  fileId: string;
@@ -9,15 +11,20 @@ interface ImageViewerProps {
9
11
  }
10
12
 
11
13
  const ImageViewer = memo<ImageViewerProps>(({ url }) => {
14
+ const [isLoaded, setIsLoaded] = useState(false);
15
+
12
16
  if (!url) return null;
13
17
 
14
18
  return (
15
19
  <Center height={'100%'} width={'100%'}>
20
+ {!isLoaded && <NeuralNetworkLoading size={36} />}
16
21
  {/* eslint-disable-next-line @next/next/no-img-element */}
17
22
  <img
18
23
  alt="Image preview"
24
+ onLoad={() => setIsLoaded(true)}
19
25
  src={url}
20
26
  style={{
27
+ display: isLoaded ? 'block' : 'none',
21
28
  height: '100%',
22
29
  objectFit: 'contain',
23
30
  overflow: 'hidden',
@@ -7,6 +7,7 @@ import { Document, Page, pdfjs } from 'react-pdf';
7
7
  import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
8
8
  import 'react-pdf/dist/esm/Page/TextLayer.css';
9
9
 
10
+ import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
10
11
  import { lambdaQuery } from '@/libs/trpc/client';
11
12
 
12
13
  import HighlightLayer from './HighlightLayer';
@@ -62,13 +63,14 @@ const PDFViewer = memo<PDFViewerProps>(({ url, fileId }) => {
62
63
  <Flexbox
63
64
  align={'center'}
64
65
  className={styles.documentContainer}
66
+ justify={isLoaded ? undefined : 'center'}
65
67
  padding={24}
66
68
  ref={setContainerRef}
67
- style={{ height: isLoaded ? undefined : '100%' }}
68
69
  >
69
70
  <Document
70
71
  className={styles.document}
71
72
  file={url}
73
+ loading={<NeuralNetworkLoading size={36} />}
72
74
  onLoadSuccess={onDocumentLoadSuccess}
73
75
  options={options}
74
76
  >
@@ -2,12 +2,13 @@ import { createStaticStyles } from 'antd-style';
2
2
 
3
3
  export const styles = createStaticStyles(({ css, cssVar }) => ({
4
4
  container: css`
5
- min-height: 100%;
5
+ height: 100%;
6
6
  `,
7
7
  document: css`
8
8
  position: relative;
9
9
  `,
10
10
  documentContainer: css`
11
+ flex: 1;
11
12
  padding-block: 10px;
12
13
  background-color: ${cssVar.colorBgLayout};
13
14
  `,
@@ -5,12 +5,10 @@ import { type CSSProperties, memo } from 'react';
5
5
  import { type FileListItem } from '@/types/files';
6
6
 
7
7
  import NotSupport from './NotSupport';
8
+ import CodeViewer from './Renderer/Code';
8
9
  import ImageViewer from './Renderer/Image';
9
- import JavaScriptViewer from './Renderer/JavaScript';
10
10
  import MSDocViewer from './Renderer/MSDoc';
11
- import MarkdownViewer from './Renderer/Markdown';
12
11
  import PDFViewer from './Renderer/PDF';
13
- import TXTViewer from './Renderer/TXT';
14
12
  import VideoViewer from './Renderer/Video';
15
13
 
16
14
  // File type definitions
@@ -27,8 +25,79 @@ const IMAGE_MIME_TYPES = new Set([
27
25
  const VIDEO_EXTENSIONS = ['.mp4', '.webm', '.ogg'];
28
26
  const VIDEO_MIME_TYPES = new Set(['video/mp4', 'video/webm', 'video/ogg', 'mp4', 'webm', 'ogg']);
29
27
 
30
- const JS_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx'];
31
- const JS_MIME_TYPES = new Set([
28
+ const CODE_EXTENSIONS = [
29
+ // JavaScript/TypeScript
30
+ '.js',
31
+ '.jsx',
32
+ '.ts',
33
+ '.tsx',
34
+ '.mjs',
35
+ '.cjs',
36
+ // Python
37
+ '.py',
38
+ '.pyw',
39
+ // Java/JVM
40
+ '.java',
41
+ '.kt',
42
+ '.kts',
43
+ '.scala',
44
+ '.groovy',
45
+ // C/C++
46
+ '.c',
47
+ '.h',
48
+ '.cpp',
49
+ '.cxx',
50
+ '.cc',
51
+ '.hpp',
52
+ '.hxx',
53
+ // Other compiled languages
54
+ '.cs',
55
+ '.go',
56
+ '.rs',
57
+ '.rb',
58
+ '.php',
59
+ '.swift',
60
+ '.lua',
61
+ '.r',
62
+ '.dart',
63
+ // Shell
64
+ '.sh',
65
+ '.bash',
66
+ '.zsh',
67
+ // Web
68
+ '.html',
69
+ '.htm',
70
+ '.css',
71
+ '.scss',
72
+ '.sass',
73
+ '.less',
74
+ // Data formats
75
+ '.json',
76
+ '.xml',
77
+ '.yaml',
78
+ '.yml',
79
+ '.toml',
80
+ '.sql',
81
+ // Functional languages
82
+ '.ex',
83
+ '.exs',
84
+ '.erl',
85
+ '.hrl',
86
+ '.clj',
87
+ '.cljs',
88
+ '.cljc',
89
+ // Markdown
90
+ '.md',
91
+ '.mdx',
92
+ // Other
93
+ '.vim',
94
+ '.graphql',
95
+ '.gql',
96
+ '.txt',
97
+ ];
98
+
99
+ const CODE_MIME_TYPES = new Set([
100
+ // JavaScript/TypeScript
32
101
  'js',
33
102
  'jsx',
34
103
  'ts',
@@ -38,14 +107,66 @@ const JS_MIME_TYPES = new Set([
38
107
  'text/javascript',
39
108
  'application/typescript',
40
109
  'text/typescript',
110
+ // Python
111
+ 'python',
112
+ 'text/x-python',
113
+ 'application/x-python-code',
114
+ // Java/JVM
115
+ 'java',
116
+ 'text/x-java-source',
117
+ 'kotlin',
118
+ 'scala',
119
+ // C/C++
120
+ 'c',
121
+ 'text/x-c',
122
+ 'cpp',
123
+ 'text/x-c++',
124
+ // Other languages
125
+ 'csharp',
126
+ 'go',
127
+ 'rust',
128
+ 'ruby',
129
+ 'php',
130
+ 'text/x-php',
131
+ 'swift',
132
+ 'lua',
133
+ 'r',
134
+ 'dart',
135
+ // Shell
136
+ 'bash',
137
+ 'shell',
138
+ 'text/x-shellscript',
139
+ // Web
140
+ 'html',
141
+ 'text/html',
142
+ 'css',
143
+ 'text/css',
144
+ 'scss',
145
+ 'sass',
146
+ 'less',
147
+ // Data
148
+ 'json',
149
+ 'application/json',
150
+ 'xml',
151
+ 'text/xml',
152
+ 'application/xml',
153
+ 'yaml',
154
+ 'text/yaml',
155
+ 'application/x-yaml',
156
+ 'toml',
157
+ 'sql',
158
+ 'text/x-sql',
159
+ // Markdown
160
+ 'md',
161
+ 'mdx',
162
+ 'text/markdown',
163
+ 'text/x-markdown',
164
+ // Other
165
+ 'graphql',
166
+ 'txt',
167
+ 'text/plain',
41
168
  ]);
42
169
 
43
- const MARKDOWN_EXTENSIONS = ['.md', '.mdx'];
44
- const MARKDOWN_MIME_TYPES = new Set(['md', 'mdx', 'text/markdown', 'text/x-markdown']);
45
-
46
- const TXT_EXTENSIONS = ['.txt'];
47
- const TXT_MIME_TYPES = new Set(['txt', 'text/plain']);
48
-
49
170
  const MSDOC_EXTENSIONS = ['.doc', '.docx', '.odt', '.ppt', '.pptx', '.xls', '.xlsx'];
50
171
  const MSDOC_MIME_TYPES = new Set([
51
172
  'doc',
@@ -117,19 +238,9 @@ const FileViewer = memo<FileViewerProps>(({ id, style, fileType, url, name }) =>
117
238
  return <VideoViewer fileId={id} url={url} />;
118
239
  }
119
240
 
120
- // JavaScript/TypeScript files
121
- if (matchesFileType(fileType, name, JS_EXTENSIONS, JS_MIME_TYPES)) {
122
- return <JavaScriptViewer fileId={id} fileName={name} url={url} />;
123
- }
124
-
125
- // Markdown files
126
- if (matchesFileType(fileType, name, MARKDOWN_EXTENSIONS, MARKDOWN_MIME_TYPES)) {
127
- return <MarkdownViewer fileId={id} url={url} />;
128
- }
129
-
130
- // Text files
131
- if (matchesFileType(fileType, name, TXT_EXTENSIONS, TXT_MIME_TYPES)) {
132
- return <TXTViewer fileId={id} url={url} />;
241
+ // Code files (JavaScript, TypeScript, Python, Java, C++, Go, Rust, Markdown, etc.)
242
+ if (matchesFileType(fileType, name, CODE_EXTENSIONS, CODE_MIME_TYPES)) {
243
+ return <CodeViewer fileId={id} fileName={name} url={url} />;
133
244
  }
134
245
 
135
246
  // Microsoft Office documents
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  type IEditor,
3
3
  INSERT_CHECK_LIST_COMMAND,
4
+ INSERT_CODEMIRROR_COMMAND,
4
5
  INSERT_HEADING_COMMAND,
5
6
  INSERT_HORIZONTAL_RULE_COMMAND,
6
7
  INSERT_IMAGE_COMMAND,
@@ -28,12 +29,11 @@ import { useMemo } from 'react';
28
29
  import { useTranslation } from 'react-i18next';
29
30
 
30
31
  import { openFileSelector } from '@/features/PageEditor/EditorCanvas/actions';
31
- import { usePageEditorStore } from '@/features/PageEditor/store';
32
32
  import { useFileStore } from '@/store/file';
33
33
 
34
34
  export const useSlashItems = (editor: IEditor | undefined): SlashOptions['items'] => {
35
35
  const { t } = useTranslation('editor');
36
- const editorState = usePageEditorStore((s) => s.editorState);
36
+
37
37
  const uploadWithProgress = useFileStore((s) => s.uploadWithProgress);
38
38
 
39
39
  const handleImageUpload = async (file: File) => {
@@ -179,8 +179,11 @@ export const useSlashItems = (editor: IEditor | undefined): SlashOptions['items'
179
179
  icon: SquareDashedBottomCodeIcon,
180
180
  key: 'codeblock',
181
181
  label: t('typobar.codeblock'),
182
- onSelect: () => {
183
- editorState.codeblock();
182
+ onSelect: (editor) => {
183
+ editor.dispatchCommand(INSERT_CODEMIRROR_COMMAND, undefined);
184
+ queueMicrotask(() => {
185
+ editor.focus();
186
+ });
184
187
  },
185
188
  },
186
189
  {
@@ -1,4 +1,5 @@
1
1
  import { type IEditor } from '@lobehub/editor';
2
+ import { type EditorState } from '@lobehub/editor/react';
2
3
 
3
4
  export interface PublicState {
4
5
  autoSave?: boolean;
@@ -16,7 +17,7 @@ export interface State extends PublicState {
16
17
  currentEmoji: string | undefined;
17
18
  currentTitle: string;
18
19
  editor?: IEditor;
19
- editorState?: any; // EditorState from useEditorState hook
20
+ editorState?: EditorState;
20
21
  isDirty: boolean; // Track if there are unsaved changes
21
22
  isLoadingContent: boolean; // Track if content is being loaded
22
23
  lastSavedContent: string; // Last saved content hash for comparison