@lobehub/lobehub 2.0.0-next.338 → 2.0.0-next.339
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/.gitattributes +35 -0
- package/CHANGELOG.md +44 -0
- package/changelog/v1.json +15 -0
- package/locales/ar/plugin.json +12 -2
- package/locales/ar/providers.json +1 -0
- package/locales/ar/setting.json +77 -1
- package/locales/bg-BG/models.json +5 -10
- package/locales/bg-BG/plugin.json +12 -2
- package/locales/bg-BG/providers.json +1 -0
- package/locales/bg-BG/setting.json +78 -2
- package/locales/de-DE/models.json +51 -9
- package/locales/de-DE/plugin.json +12 -2
- package/locales/de-DE/providers.json +1 -0
- package/locales/de-DE/setting.json +78 -2
- package/locales/en-US/models.json +11 -10
- package/locales/en-US/plugin.json +14 -4
- package/locales/en-US/providers.json +1 -0
- package/locales/en-US/setting.json +78 -2
- package/locales/es-ES/plugin.json +12 -2
- package/locales/es-ES/providers.json +1 -0
- package/locales/es-ES/setting.json +78 -2
- package/locales/fa-IR/plugin.json +12 -2
- package/locales/fa-IR/providers.json +1 -0
- package/locales/fa-IR/setting.json +78 -2
- package/locales/fr-FR/plugin.json +12 -2
- package/locales/fr-FR/providers.json +1 -0
- package/locales/fr-FR/setting.json +78 -2
- package/locales/it-IT/plugin.json +12 -2
- package/locales/it-IT/providers.json +1 -0
- package/locales/it-IT/setting.json +78 -2
- package/locales/ja-JP/plugin.json +12 -2
- package/locales/ja-JP/providers.json +1 -0
- package/locales/ja-JP/setting.json +78 -2
- package/locales/ko-KR/plugin.json +12 -2
- package/locales/ko-KR/providers.json +1 -0
- package/locales/ko-KR/setting.json +78 -2
- package/locales/nl-NL/models.json +4 -9
- package/locales/nl-NL/plugin.json +12 -2
- package/locales/nl-NL/providers.json +1 -0
- package/locales/nl-NL/setting.json +78 -2
- package/locales/pl-PL/plugin.json +12 -2
- package/locales/pl-PL/providers.json +1 -0
- package/locales/pl-PL/setting.json +78 -2
- package/locales/pt-BR/plugin.json +12 -2
- package/locales/pt-BR/providers.json +1 -0
- package/locales/pt-BR/setting.json +78 -2
- package/locales/ru-RU/plugin.json +12 -2
- package/locales/ru-RU/providers.json +1 -0
- package/locales/ru-RU/setting.json +78 -2
- package/locales/tr-TR/plugin.json +12 -2
- package/locales/tr-TR/providers.json +1 -0
- package/locales/tr-TR/setting.json +78 -2
- package/locales/vi-VN/plugin.json +12 -2
- package/locales/vi-VN/providers.json +1 -0
- package/locales/vi-VN/setting.json +77 -1
- package/locales/zh-CN/plugin.json +12 -2
- package/locales/zh-CN/providers.json +1 -0
- package/locales/zh-CN/setting.json +78 -2
- package/locales/zh-TW/plugin.json +12 -2
- package/locales/zh-TW/providers.json +1 -0
- package/locales/zh-TW/setting.json +78 -2
- package/package.json +1 -1
- package/packages/agent-runtime/src/groupOrchestration/GroupOrchestrationSupervisor.ts +2 -0
- package/packages/agent-runtime/src/groupOrchestration/__tests__/GroupOrchestrationSupervisor.test.ts +3 -1
- package/packages/agent-runtime/src/groupOrchestration/types.ts +5 -0
- package/packages/const/src/index.ts +1 -0
- package/packages/const/src/klavis.ts +144 -0
- package/packages/const/src/lobehubSkill.ts +34 -0
- package/packages/const/src/recommendedSkill.ts +17 -0
- package/packages/model-runtime/src/core/contextBuilders/anthropic.test.ts +38 -0
- package/packages/model-runtime/src/core/contextBuilders/anthropic.ts +20 -1
- package/packages/model-runtime/src/core/contextBuilders/google.test.ts +42 -0
- package/packages/model-runtime/src/core/contextBuilders/google.ts +17 -0
- package/scripts/electronWorkflow/modifiers/dynamicToStatic.mts +273 -0
- package/scripts/electronWorkflow/modifiers/index.mts +10 -0
- package/scripts/electronWorkflow/modifiers/nextConfig.mts +1 -0
- package/scripts/electronWorkflow/modifiers/nextDynamicToStatic.mts +233 -0
- package/scripts/electronWorkflow/modifiers/removeSuspense.mts +124 -0
- package/scripts/electronWorkflow/modifiers/routes.mts +14 -2
- package/scripts/electronWorkflow/modifiers/settingsContentToStatic.mts +148 -0
- package/scripts/electronWorkflow/modifiers/wrapChildrenWithClientOnly.mts +73 -0
- package/src/app/[variants]/(main)/home/features/InputArea/SkillInstallBanner.tsx +131 -0
- package/src/app/[variants]/(main)/home/features/InputArea/index.tsx +34 -27
- package/src/app/[variants]/(main)/settings/features/SettingHeader.tsx +8 -4
- package/src/app/[variants]/(main)/settings/features/SettingsContent.tsx +3 -0
- package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +6 -0
- package/src/{features/PluginStore/InstalledList/List/Item/Action.tsx → app/[variants]/(main)/settings/skill/features/Actions.tsx} +45 -40
- package/src/app/[variants]/(main)/settings/skill/features/KlavisSkillItem.tsx +353 -0
- package/src/app/[variants]/(main)/settings/skill/features/LobehubSkillItem.tsx +344 -0
- package/src/app/[variants]/(main)/settings/skill/features/McpSkillItem.tsx +116 -0
- package/src/app/[variants]/(main)/settings/skill/features/SkillList.tsx +244 -0
- package/src/app/[variants]/(main)/settings/skill/index.tsx +35 -0
- package/src/components/Plugins/PluginTag.tsx +23 -35
- package/src/components/client/ClientOnly.tsx +6 -2
- package/src/features/AgentSetting/AgentPlugin/index.tsx +2 -2
- package/src/features/ChatInput/ActionBar/Tools/KlavisServerItem.tsx +8 -32
- package/src/features/ChatInput/ActionBar/Tools/LobehubSkillServerItem.tsx +8 -30
- package/src/features/ChatInput/ActionBar/Tools/PopoverContent.tsx +48 -59
- package/src/features/ChatInput/ActionBar/Tools/index.tsx +5 -23
- package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +158 -56
- package/src/features/IntegrationDetailModal/index.tsx +293 -0
- package/src/features/{PluginStore/McpList/Detail → MCP/MCPDetail}/index.tsx +15 -6
- package/src/features/MCP/MCPSettings/McpSettingsModal.tsx +58 -0
- package/src/features/{PluginStore/McpList/Detail/Settings → MCP/MCPSettings}/index.tsx +39 -27
- package/src/features/PluginDetailModal/index.tsx +2 -2
- package/src/features/PluginDevModal/index.tsx +16 -40
- package/src/features/ProfileEditor/AgentTool.tsx +2 -2
- package/src/features/ProtocolUrlHandler/InstallPlugin/OfficialPluginInstallModal/index.tsx +1 -1
- package/src/features/{PluginStore/AddPluginButton.tsx → SkillStore/AddSkillButton.tsx} +3 -3
- package/src/features/SkillStore/CommunityList/Item.tsx +158 -0
- package/src/features/SkillStore/CommunityList/index.tsx +101 -0
- package/src/features/SkillStore/Content.tsx +59 -0
- package/src/features/{PluginStore/PluginEmpty.tsx → SkillStore/Empty.tsx} +8 -8
- package/src/features/SkillStore/LobeHubList/Item.tsx +118 -0
- package/src/features/SkillStore/LobeHubList/index.tsx +187 -0
- package/src/features/SkillStore/LobeHubList/useSkillConnect.ts +239 -0
- package/src/features/SkillStore/Search/index.tsx +43 -0
- package/src/features/{PluginStore → SkillStore}/index.tsx +14 -10
- package/src/features/SkillStore/style.ts +27 -0
- package/src/locales/default/plugin.ts +15 -4
- package/src/locales/default/setting.ts +185 -2
- package/src/services/chat/mecha/agentConfigResolver.test.ts +197 -0
- package/src/services/chat/mecha/agentConfigResolver.ts +44 -17
- package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +40 -37
- package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +78 -0
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +50 -16
- package/src/store/global/initialState.ts +1 -0
- package/src/store/tool/slices/lobehubSkillStore/action.test.ts +914 -0
- package/src/store/tool/slices/lobehubSkillStore/selectors.test.ts +548 -0
- package/.cursor/skills/vercel-react-best-practices/AGENTS.md +0 -2410
- package/.cursor/skills/vercel-react-best-practices/SKILL.md +0 -125
- package/.cursor/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +0 -55
- package/.cursor/skills/vercel-react-best-practices/rules/advanced-use-latest.md +0 -49
- package/.cursor/skills/vercel-react-best-practices/rules/async-api-routes.md +0 -38
- package/.cursor/skills/vercel-react-best-practices/rules/async-defer-await.md +0 -80
- package/.cursor/skills/vercel-react-best-practices/rules/async-dependencies.md +0 -36
- package/.cursor/skills/vercel-react-best-practices/rules/async-parallel.md +0 -28
- package/.cursor/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +0 -99
- package/.cursor/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +0 -59
- package/.cursor/skills/vercel-react-best-practices/rules/bundle-conditional.md +0 -31
- package/.cursor/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +0 -49
- package/.cursor/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +0 -35
- package/.cursor/skills/vercel-react-best-practices/rules/bundle-preload.md +0 -50
- package/.cursor/skills/vercel-react-best-practices/rules/client-event-listeners.md +0 -74
- package/.cursor/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +0 -71
- package/.cursor/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +0 -48
- package/.cursor/skills/vercel-react-best-practices/rules/client-swr-dedup.md +0 -56
- package/.cursor/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +0 -57
- package/.cursor/skills/vercel-react-best-practices/rules/js-cache-function-results.md +0 -80
- package/.cursor/skills/vercel-react-best-practices/rules/js-cache-property-access.md +0 -28
- package/.cursor/skills/vercel-react-best-practices/rules/js-cache-storage.md +0 -70
- package/.cursor/skills/vercel-react-best-practices/rules/js-combine-iterations.md +0 -32
- package/.cursor/skills/vercel-react-best-practices/rules/js-early-exit.md +0 -50
- package/.cursor/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +0 -45
- package/.cursor/skills/vercel-react-best-practices/rules/js-index-maps.md +0 -37
- package/.cursor/skills/vercel-react-best-practices/rules/js-length-check-first.md +0 -49
- package/.cursor/skills/vercel-react-best-practices/rules/js-min-max-loop.md +0 -82
- package/.cursor/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +0 -24
- package/.cursor/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +0 -57
- package/.cursor/skills/vercel-react-best-practices/rules/rendering-activity.md +0 -26
- package/.cursor/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
- package/.cursor/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +0 -40
- package/.cursor/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +0 -38
- package/.cursor/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +0 -46
- package/.cursor/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +0 -82
- package/.cursor/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +0 -28
- package/.cursor/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +0 -39
- package/.cursor/skills/vercel-react-best-practices/rules/rerender-dependencies.md +0 -45
- package/.cursor/skills/vercel-react-best-practices/rules/rerender-derived-state.md +0 -29
- package/.cursor/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +0 -74
- package/.cursor/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +0 -58
- package/.cursor/skills/vercel-react-best-practices/rules/rerender-memo.md +0 -44
- package/.cursor/skills/vercel-react-best-practices/rules/rerender-transitions.md +0 -40
- package/.cursor/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +0 -73
- package/.cursor/skills/vercel-react-best-practices/rules/server-cache-lru.md +0 -41
- package/.cursor/skills/vercel-react-best-practices/rules/server-cache-react.md +0 -76
- package/.cursor/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +0 -83
- package/.cursor/skills/vercel-react-best-practices/rules/server-serialization.md +0 -38
- package/src/features/PluginStore/Content.tsx +0 -54
- package/src/features/PluginStore/InstalledList/Detail/CustomPluginEmptyState.tsx +0 -79
- package/src/features/PluginStore/InstalledList/Detail/index.tsx +0 -21
- package/src/features/PluginStore/InstalledList/List/Item/index.tsx +0 -61
- package/src/features/PluginStore/InstalledList/List/index.tsx +0 -72
- package/src/features/PluginStore/InstalledList/index.tsx +0 -90
- package/src/features/PluginStore/McpList/List/Action.tsx +0 -119
- package/src/features/PluginStore/McpList/List/Item.tsx +0 -83
- package/src/features/PluginStore/McpList/List/index.tsx +0 -93
- package/src/features/PluginStore/McpList/index.tsx +0 -58
- package/src/features/PluginStore/PluginList/Detail/DetailProvider.tsx +0 -19
- package/src/features/PluginStore/PluginList/Detail/EmptyState.tsx +0 -56
- package/src/features/PluginStore/PluginList/Detail/Header.tsx +0 -130
- package/src/features/PluginStore/PluginList/Detail/InstallDetail/Nav.tsx +0 -73
- package/src/features/PluginStore/PluginList/Detail/InstallDetail/Settings.tsx +0 -19
- package/src/features/PluginStore/PluginList/Detail/InstallDetail/Tools.tsx +0 -111
- package/src/features/PluginStore/PluginList/Detail/InstallDetail/index.tsx +0 -24
- package/src/features/PluginStore/PluginList/Detail/Loading.tsx +0 -42
- package/src/features/PluginStore/PluginList/Detail/TagList.tsx +0 -35
- package/src/features/PluginStore/PluginList/Detail/index.tsx +0 -39
- package/src/features/PluginStore/PluginList/Detail/useCategory.tsx +0 -76
- package/src/features/PluginStore/PluginList/List/Action.tsx +0 -78
- package/src/features/PluginStore/PluginList/List/Item.tsx +0 -92
- package/src/features/PluginStore/PluginList/List/index.tsx +0 -94
- package/src/features/PluginStore/PluginList/index.tsx +0 -46
- package/src/features/PluginStore/Search/index.tsx +0 -40
- /package/{.codex/skills → .agents}/vercel-react-best-practices/AGENTS.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/SKILL.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/advanced-event-handler-refs.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/advanced-use-latest.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-api-routes.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-defer-await.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-dependencies.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-parallel.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/async-suspense-boundaries.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-barrel-imports.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-conditional.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-defer-third-party.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-dynamic-imports.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/bundle-preload.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-event-listeners.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-localstorage-schema.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-passive-event-listeners.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/client-swr-dedup.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-batch-dom-css.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-cache-function-results.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-cache-property-access.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-cache-storage.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-combine-iterations.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-early-exit.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-hoist-regexp.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-index-maps.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-length-check-first.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-min-max-loop.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-set-map-lookups.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/js-tosorted-immutable.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-activity.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-conditional-render.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-content-visibility.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-hoist-jsx.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rendering-svg-precision.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-defer-reads.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-dependencies.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-derived-state.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-functional-setstate.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-lazy-state-init.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-memo.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/rerender-transitions.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-after-nonblocking.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-cache-lru.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-cache-react.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-parallel-fetching.md +0 -0
- /package/{.codex/skills → .agents}/vercel-react-best-practices/rules/server-serialization.md +0 -0
- /package/src/{features/PluginStore/InstalledList → app/[variants]/(main)/settings/skill/features}/EditCustomPlugin.tsx +0 -0
- /package/src/features/{PluginStore/McpList/Detail → MCP/MCPDetail}/Loading.tsx +0 -0
- /package/src/features/{PluginStore → SkillStore}/Loading.tsx +0 -0
- /package/src/features/{PluginStore → SkillStore}/VirtuosoLoading.tsx +0 -0
|
@@ -5,6 +5,19 @@ import OpenAI from 'openai';
|
|
|
5
5
|
import { OpenAIChatMessage, UserMessageContentPart } from '../../types';
|
|
6
6
|
import { parseDataUri } from '../../utils/uriParser';
|
|
7
7
|
|
|
8
|
+
const ANTHROPIC_SUPPORTED_IMAGE_TYPES = new Set([
|
|
9
|
+
'image/jpeg',
|
|
10
|
+
'image/jpg',
|
|
11
|
+
'image/png',
|
|
12
|
+
'image/gif',
|
|
13
|
+
'image/webp',
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const isImageTypeSupported = (mimeType: string | null): boolean => {
|
|
17
|
+
if (!mimeType) return true;
|
|
18
|
+
return ANTHROPIC_SUPPORTED_IMAGE_TYPES.has(mimeType.toLowerCase());
|
|
19
|
+
};
|
|
20
|
+
|
|
8
21
|
export const buildAnthropicBlock = async (
|
|
9
22
|
content: UserMessageContentPart,
|
|
10
23
|
): Promise<Anthropic.ContentBlock | Anthropic.ImageBlockParam | undefined> => {
|
|
@@ -23,7 +36,9 @@ export const buildAnthropicBlock = async (
|
|
|
23
36
|
case 'image_url': {
|
|
24
37
|
const { mimeType, base64, type } = parseDataUri(content.image_url.url);
|
|
25
38
|
|
|
26
|
-
if (type === 'base64')
|
|
39
|
+
if (type === 'base64') {
|
|
40
|
+
if (!isImageTypeSupported(mimeType)) return undefined;
|
|
41
|
+
|
|
27
42
|
return {
|
|
28
43
|
source: {
|
|
29
44
|
data: base64 as string,
|
|
@@ -32,9 +47,13 @@ export const buildAnthropicBlock = async (
|
|
|
32
47
|
},
|
|
33
48
|
type: 'image',
|
|
34
49
|
};
|
|
50
|
+
}
|
|
35
51
|
|
|
36
52
|
if (type === 'url') {
|
|
37
53
|
const { base64, mimeType } = await imageUrlToBase64(content.image_url.url);
|
|
54
|
+
|
|
55
|
+
if (!isImageTypeSupported(mimeType)) return undefined;
|
|
56
|
+
|
|
38
57
|
return {
|
|
39
58
|
source: {
|
|
40
59
|
data: base64 as string,
|
|
@@ -149,6 +149,48 @@ describe('google contextBuilders', () => {
|
|
|
149
149
|
thoughtSignature: GEMINI_MAGIC_THOUGHT_SIGNATURE,
|
|
150
150
|
});
|
|
151
151
|
});
|
|
152
|
+
|
|
153
|
+
it('should return undefined for unsupported SVG image (base64)', async () => {
|
|
154
|
+
const svgBase64 =
|
|
155
|
+
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==';
|
|
156
|
+
|
|
157
|
+
vi.mocked(parseDataUri).mockReturnValueOnce({
|
|
158
|
+
base64: 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==',
|
|
159
|
+
mimeType: 'image/svg+xml',
|
|
160
|
+
type: 'base64',
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const content: UserMessageContentPart = {
|
|
164
|
+
image_url: { url: svgBase64 },
|
|
165
|
+
type: 'image_url',
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const result = await buildGooglePart(content);
|
|
169
|
+
expect(result).toBeUndefined();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should return undefined for unsupported SVG image (URL)', async () => {
|
|
173
|
+
const svgUrl = 'https://example.com/image.svg';
|
|
174
|
+
|
|
175
|
+
vi.mocked(parseDataUri).mockReturnValueOnce({
|
|
176
|
+
base64: null,
|
|
177
|
+
mimeType: null,
|
|
178
|
+
type: 'url',
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
vi.spyOn(imageToBase64Module, 'imageUrlToBase64').mockResolvedValueOnce({
|
|
182
|
+
base64: 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==',
|
|
183
|
+
mimeType: 'image/svg+xml',
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const content: UserMessageContentPart = {
|
|
187
|
+
image_url: { url: svgUrl },
|
|
188
|
+
type: 'image_url',
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const result = await buildGooglePart(content);
|
|
192
|
+
expect(result).toBeUndefined();
|
|
193
|
+
});
|
|
152
194
|
});
|
|
153
195
|
|
|
154
196
|
describe('buildGoogleMessage', () => {
|
|
@@ -11,6 +11,19 @@ import { ChatCompletionTool, OpenAIChatMessage, UserMessageContentPart } from '.
|
|
|
11
11
|
import { safeParseJSON } from '../../utils/safeParseJSON';
|
|
12
12
|
import { parseDataUri } from '../../utils/uriParser';
|
|
13
13
|
|
|
14
|
+
const GOOGLE_SUPPORTED_IMAGE_TYPES = new Set([
|
|
15
|
+
'image/jpeg',
|
|
16
|
+
'image/jpg',
|
|
17
|
+
'image/png',
|
|
18
|
+
'image/gif',
|
|
19
|
+
'image/webp',
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const isImageTypeSupported = (mimeType: string | null): boolean => {
|
|
23
|
+
if (!mimeType) return true;
|
|
24
|
+
return GOOGLE_SUPPORTED_IMAGE_TYPES.has(mimeType.toLowerCase());
|
|
25
|
+
};
|
|
26
|
+
|
|
14
27
|
/**
|
|
15
28
|
* Magic thoughtSignature
|
|
16
29
|
* @see https://ai.google.dev/gemini-api/docs/thought-signatures#model-behavior:~:text=context_engineering_is_the_way_to_go
|
|
@@ -43,6 +56,8 @@ export const buildGooglePart = async (
|
|
|
43
56
|
throw new TypeError("Image URL doesn't contain base64 data");
|
|
44
57
|
}
|
|
45
58
|
|
|
59
|
+
if (!isImageTypeSupported(mimeType)) return undefined;
|
|
60
|
+
|
|
46
61
|
return {
|
|
47
62
|
inlineData: { data: base64, mimeType: mimeType || 'image/png' },
|
|
48
63
|
thoughtSignature: GEMINI_MAGIC_THOUGHT_SIGNATURE,
|
|
@@ -52,6 +67,8 @@ export const buildGooglePart = async (
|
|
|
52
67
|
if (type === 'url') {
|
|
53
68
|
const { base64, mimeType } = await imageUrlToBase64(content.image_url.url);
|
|
54
69
|
|
|
70
|
+
if (!isImageTypeSupported(mimeType)) return undefined;
|
|
71
|
+
|
|
55
72
|
return {
|
|
56
73
|
inlineData: { data: base64, mimeType },
|
|
57
74
|
thoughtSignature: GEMINI_MAGIC_THOUGHT_SIGNATURE,
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/* eslint-disable no-undef */
|
|
2
|
+
import { Lang, parse } from '@ast-grep/napi';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { invariant, isDirectRun, runStandalone, updateFile } from './utils.mjs';
|
|
6
|
+
|
|
7
|
+
interface ImportInfo {
|
|
8
|
+
defaultImport?: string;
|
|
9
|
+
namedImports: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface DynamicElementInfo {
|
|
13
|
+
componentName: string;
|
|
14
|
+
end: number;
|
|
15
|
+
importPath: string;
|
|
16
|
+
isNamedExport: boolean;
|
|
17
|
+
namedExport?: string;
|
|
18
|
+
start: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const toPascalCase = (str: string): string => {
|
|
22
|
+
return str
|
|
23
|
+
.split(/[_-]/)
|
|
24
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
25
|
+
.join('');
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const generateComponentName = (
|
|
29
|
+
importPath: string,
|
|
30
|
+
namedExport?: string,
|
|
31
|
+
existingNames: Set<string> = new Set(),
|
|
32
|
+
): string => {
|
|
33
|
+
if (namedExport) {
|
|
34
|
+
let name = namedExport;
|
|
35
|
+
let counter = 1;
|
|
36
|
+
while (existingNames.has(name)) {
|
|
37
|
+
name = `${namedExport}${counter++}`;
|
|
38
|
+
}
|
|
39
|
+
return name;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const segments = importPath
|
|
43
|
+
.split('/')
|
|
44
|
+
.filter((s) => s && !s.startsWith('.'))
|
|
45
|
+
.map((s) => s.replace(/^\((.+)\)$/, '$1').replace(/^\[(.+)]$/, '$1'));
|
|
46
|
+
|
|
47
|
+
const meaningfulSegments = segments.slice(-3).filter(Boolean);
|
|
48
|
+
|
|
49
|
+
let baseName =
|
|
50
|
+
meaningfulSegments.length > 0
|
|
51
|
+
? meaningfulSegments.map((s) => toPascalCase(s)).join('') + 'Page'
|
|
52
|
+
: 'Page';
|
|
53
|
+
|
|
54
|
+
let name = baseName;
|
|
55
|
+
let counter = 1;
|
|
56
|
+
while (existingNames.has(name)) {
|
|
57
|
+
name = `${baseName}${counter++}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return name;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const extractDynamicElements = (code: string): DynamicElementInfo[] => {
|
|
64
|
+
const ast = parse(Lang.Tsx, code);
|
|
65
|
+
const root = ast.root();
|
|
66
|
+
|
|
67
|
+
const results: DynamicElementInfo[] = [];
|
|
68
|
+
const existingNames = new Set<string>();
|
|
69
|
+
|
|
70
|
+
const dynamicCalls = root.findAll({
|
|
71
|
+
rule: {
|
|
72
|
+
pattern: 'dynamicElement($IMPORT_FN, $DEBUG_ID)',
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
for (const call of dynamicCalls) {
|
|
77
|
+
const range = call.range();
|
|
78
|
+
const text = call.text();
|
|
79
|
+
|
|
80
|
+
const importMatch = text.match(/import\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
81
|
+
invariant(
|
|
82
|
+
importMatch,
|
|
83
|
+
`[convertDynamicToStatic] Failed to extract import path from dynamicElement call: ${text.slice(0, 100)}`,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const importPath = importMatch![1];
|
|
87
|
+
|
|
88
|
+
const thenMatch = text.match(/\.then\s*\(\s*\(\s*(\w+)\s*\)\s*=>\s*\1\.(\w+)\s*\)/);
|
|
89
|
+
const namedExport = thenMatch ? thenMatch[2] : undefined;
|
|
90
|
+
|
|
91
|
+
const componentName = generateComponentName(importPath, namedExport, existingNames);
|
|
92
|
+
existingNames.add(componentName);
|
|
93
|
+
|
|
94
|
+
results.push({
|
|
95
|
+
componentName,
|
|
96
|
+
end: range.end.index,
|
|
97
|
+
importPath,
|
|
98
|
+
isNamedExport: !!namedExport,
|
|
99
|
+
namedExport,
|
|
100
|
+
start: range.start.index,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return results;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const buildImportMap = (elements: DynamicElementInfo[]): Map<string, ImportInfo> => {
|
|
108
|
+
const importMap = new Map<string, ImportInfo>();
|
|
109
|
+
|
|
110
|
+
for (const el of elements) {
|
|
111
|
+
const existing = importMap.get(el.importPath) || { namedImports: [] };
|
|
112
|
+
|
|
113
|
+
if (el.isNamedExport && el.namedExport) {
|
|
114
|
+
if (!existing.namedImports.includes(el.namedExport)) {
|
|
115
|
+
existing.namedImports.push(el.namedExport);
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
existing.defaultImport = el.componentName;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
importMap.set(el.importPath, existing);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return importMap;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const generateImportStatements = (importMap: Map<string, ImportInfo>): string => {
|
|
128
|
+
const statements: string[] = [];
|
|
129
|
+
|
|
130
|
+
const sortedPaths = [...importMap.keys()].sort();
|
|
131
|
+
|
|
132
|
+
for (const importPath of sortedPaths) {
|
|
133
|
+
const info = importMap.get(importPath)!;
|
|
134
|
+
const parts: string[] = [];
|
|
135
|
+
|
|
136
|
+
if (info.defaultImport) {
|
|
137
|
+
parts.push(info.defaultImport);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (info.namedImports.length > 0) {
|
|
141
|
+
parts.push(`{ ${info.namedImports.join(', ')} }`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (parts.length > 0) {
|
|
145
|
+
statements.push(`import ${parts.join(', ')} from '${importPath}';`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return statements.join('\n');
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const findImportInsertPosition = (code: string): number => {
|
|
153
|
+
const ast = parse(Lang.Tsx, code);
|
|
154
|
+
const root = ast.root();
|
|
155
|
+
|
|
156
|
+
const imports = root.findAll({
|
|
157
|
+
rule: {
|
|
158
|
+
kind: 'import_statement',
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
invariant(imports.length > 0, '[convertDynamicToStatic] No import statements found in file');
|
|
163
|
+
|
|
164
|
+
const lastImport = imports.at(-1)!;
|
|
165
|
+
return lastImport.range().end.index;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const removeDynamicElementImport = (code: string): string => {
|
|
169
|
+
const ast = parse(Lang.Tsx, code);
|
|
170
|
+
const root = ast.root();
|
|
171
|
+
|
|
172
|
+
const utilsRouterImport = root.find({
|
|
173
|
+
rule: {
|
|
174
|
+
kind: 'import_statement',
|
|
175
|
+
pattern: "import { $$$IMPORTS } from '@/utils/router'",
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (!utilsRouterImport) {
|
|
180
|
+
return code;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const importText = utilsRouterImport.text();
|
|
184
|
+
|
|
185
|
+
if (!importText.includes('dynamicElement')) {
|
|
186
|
+
return code;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const importSpecifiers = utilsRouterImport.findAll({
|
|
190
|
+
rule: {
|
|
191
|
+
kind: 'import_specifier',
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const specifiersToKeep = importSpecifiers
|
|
196
|
+
.map((spec) => spec.text())
|
|
197
|
+
.filter((text) => !text.includes('dynamicElement'));
|
|
198
|
+
|
|
199
|
+
if (specifiersToKeep.length === 0) {
|
|
200
|
+
const range = utilsRouterImport.range();
|
|
201
|
+
let endIndex = range.end.index;
|
|
202
|
+
if (code[endIndex] === '\n') {
|
|
203
|
+
endIndex++;
|
|
204
|
+
}
|
|
205
|
+
return code.slice(0, range.start.index) + code.slice(endIndex);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const newImport = `import { ${specifiersToKeep.join(', ')} } from '@/utils/router';`;
|
|
209
|
+
const range = utilsRouterImport.range();
|
|
210
|
+
return code.slice(0, range.start.index) + newImport + code.slice(range.end.index);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export const convertDynamicToStatic = async (TEMP_DIR: string) => {
|
|
214
|
+
const routerConfigPath = path.join(
|
|
215
|
+
TEMP_DIR,
|
|
216
|
+
'src/app/[variants]/router/desktopRouter.config.tsx',
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
console.log(' Processing dynamicElement → static imports...');
|
|
220
|
+
|
|
221
|
+
await updateFile({
|
|
222
|
+
assertAfter: (code) => {
|
|
223
|
+
const noDynamicElement = !/dynamicElement\s*\(/.test(code);
|
|
224
|
+
const hasStaticImports = /^import .+ from ["']\.\.\/\(main\)/m.test(code);
|
|
225
|
+
return noDynamicElement && hasStaticImports;
|
|
226
|
+
},
|
|
227
|
+
filePath: routerConfigPath,
|
|
228
|
+
name: 'convertDynamicToStatic',
|
|
229
|
+
transformer: (code) => {
|
|
230
|
+
const elements = extractDynamicElements(code);
|
|
231
|
+
|
|
232
|
+
invariant(
|
|
233
|
+
elements.length > 0,
|
|
234
|
+
'[convertDynamicToStatic] No dynamicElement calls found in desktopRouter.config.tsx',
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
console.log(` Found ${elements.length} dynamicElement calls`);
|
|
238
|
+
|
|
239
|
+
const importMap = buildImportMap(elements);
|
|
240
|
+
const importStatements = generateImportStatements(importMap);
|
|
241
|
+
|
|
242
|
+
const edits: Array<{ end: number; start: number; text: string }> = [];
|
|
243
|
+
|
|
244
|
+
for (const el of elements) {
|
|
245
|
+
edits.push({
|
|
246
|
+
end: el.end,
|
|
247
|
+
start: el.start,
|
|
248
|
+
text: `<${el.componentName} />`,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
edits.sort((a, b) => b.start - a.start);
|
|
253
|
+
|
|
254
|
+
let result = code;
|
|
255
|
+
for (const edit of edits) {
|
|
256
|
+
result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const insertPos = findImportInsertPosition(result);
|
|
260
|
+
result = result.slice(0, insertPos) + '\n' + importStatements + result.slice(insertPos);
|
|
261
|
+
|
|
262
|
+
result = removeDynamicElementImport(result);
|
|
263
|
+
|
|
264
|
+
return result;
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
if (isDirectRun(import.meta.url)) {
|
|
270
|
+
await runStandalone('convertDynamicToStatic', convertDynamicToStatic, [
|
|
271
|
+
{ lang: Lang.Tsx, path: 'src/app/[variants]/router/desktopRouter.config.tsx' },
|
|
272
|
+
]);
|
|
273
|
+
}
|
|
@@ -3,14 +3,24 @@ import path from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
import { modifyAppCode } from './appCode.mjs';
|
|
5
5
|
import { cleanUpCode } from './cleanUp.mjs';
|
|
6
|
+
import { convertDynamicToStatic } from './dynamicToStatic.mjs';
|
|
7
|
+
import { convertNextDynamicToStatic } from './nextDynamicToStatic.mjs';
|
|
6
8
|
import { modifyNextConfig } from './nextConfig.mjs';
|
|
9
|
+
import { removeSuspenseFromConversation } from './removeSuspense.mjs';
|
|
7
10
|
import { modifyRoutes } from './routes.mjs';
|
|
11
|
+
import { convertSettingsContentToStatic } from './settingsContentToStatic.mjs';
|
|
8
12
|
import { modifyStaticExport } from './staticExport.mjs';
|
|
9
13
|
import { isDirectRun, runStandalone } from './utils.mjs';
|
|
14
|
+
import { wrapChildrenWithClientOnly } from './wrapChildrenWithClientOnly.mjs';
|
|
10
15
|
|
|
11
16
|
export const modifySourceForElectron = async (TEMP_DIR: string) => {
|
|
12
17
|
await modifyNextConfig(TEMP_DIR);
|
|
13
18
|
await modifyAppCode(TEMP_DIR);
|
|
19
|
+
await wrapChildrenWithClientOnly(TEMP_DIR);
|
|
20
|
+
await convertDynamicToStatic(TEMP_DIR);
|
|
21
|
+
await convertNextDynamicToStatic(TEMP_DIR);
|
|
22
|
+
await convertSettingsContentToStatic(TEMP_DIR);
|
|
23
|
+
await removeSuspenseFromConversation(TEMP_DIR);
|
|
14
24
|
await modifyRoutes(TEMP_DIR);
|
|
15
25
|
await modifyStaticExport(TEMP_DIR);
|
|
16
26
|
await cleanUpCode(TEMP_DIR);
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/* eslint-disable no-undef */
|
|
2
|
+
import { Lang, parse } from '@ast-grep/napi';
|
|
3
|
+
import { glob } from 'glob';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { invariant, isDirectRun, runStandalone } from './utils.mjs';
|
|
8
|
+
|
|
9
|
+
interface DynamicImportInfo {
|
|
10
|
+
componentName: string;
|
|
11
|
+
end: number;
|
|
12
|
+
importPath: string;
|
|
13
|
+
start: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const extractDynamicImports = (code: string): DynamicImportInfo[] => {
|
|
17
|
+
const ast = parse(Lang.Tsx, code);
|
|
18
|
+
const root = ast.root();
|
|
19
|
+
|
|
20
|
+
const results: DynamicImportInfo[] = [];
|
|
21
|
+
|
|
22
|
+
const dynamicCalls = root.findAll({
|
|
23
|
+
rule: {
|
|
24
|
+
pattern: 'const $NAME = dynamic(() => import($PATH))',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
for (const call of dynamicCalls) {
|
|
29
|
+
const range = call.range();
|
|
30
|
+
const text = call.text();
|
|
31
|
+
|
|
32
|
+
const nameMatch = text.match(/const\s+(\w+)\s*=/);
|
|
33
|
+
invariant(
|
|
34
|
+
nameMatch,
|
|
35
|
+
`[convertNextDynamicToStatic] Failed to extract component name from dynamic call: ${text.slice(0, 100)}`,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const importMatch = text.match(/import\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
39
|
+
invariant(
|
|
40
|
+
importMatch,
|
|
41
|
+
`[convertNextDynamicToStatic] Failed to extract import path from dynamic call: ${text.slice(0, 100)}`,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
results.push({
|
|
45
|
+
componentName: nameMatch![1],
|
|
46
|
+
end: range.end.index,
|
|
47
|
+
importPath: importMatch![1],
|
|
48
|
+
start: range.start.index,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const dynamicCallsWithOptions = root.findAll({
|
|
53
|
+
rule: {
|
|
54
|
+
pattern: 'const $NAME = dynamic(() => import($PATH), $OPTIONS)',
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
for (const call of dynamicCallsWithOptions) {
|
|
59
|
+
const range = call.range();
|
|
60
|
+
const text = call.text();
|
|
61
|
+
|
|
62
|
+
const nameMatch = text.match(/const\s+(\w+)\s*=/);
|
|
63
|
+
invariant(
|
|
64
|
+
nameMatch,
|
|
65
|
+
`[convertNextDynamicToStatic] Failed to extract component name from dynamic call: ${text.slice(0, 100)}`,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const importMatch = text.match(/import\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
69
|
+
invariant(
|
|
70
|
+
importMatch,
|
|
71
|
+
`[convertNextDynamicToStatic] Failed to extract import path from dynamic call: ${text.slice(0, 100)}`,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const alreadyExists = results.some(
|
|
75
|
+
(r) => r.componentName === nameMatch![1] && r.importPath === importMatch![1],
|
|
76
|
+
);
|
|
77
|
+
if (alreadyExists) continue;
|
|
78
|
+
|
|
79
|
+
results.push({
|
|
80
|
+
componentName: nameMatch![1],
|
|
81
|
+
end: range.end.index,
|
|
82
|
+
importPath: importMatch![1],
|
|
83
|
+
start: range.start.index,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return results;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const generateImportStatements = (imports: DynamicImportInfo[]): string => {
|
|
91
|
+
const uniqueImports = new Map<string, string>();
|
|
92
|
+
|
|
93
|
+
for (const imp of imports) {
|
|
94
|
+
if (!uniqueImports.has(imp.importPath)) {
|
|
95
|
+
uniqueImports.set(imp.importPath, imp.componentName);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const sortedPaths = [...uniqueImports.keys()].sort();
|
|
100
|
+
|
|
101
|
+
return sortedPaths
|
|
102
|
+
.map((importPath) => {
|
|
103
|
+
const componentName = uniqueImports.get(importPath)!;
|
|
104
|
+
return `import ${componentName} from '${importPath}';`;
|
|
105
|
+
})
|
|
106
|
+
.join('\n');
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const findImportInsertPosition = (code: string, filePath: string): number => {
|
|
110
|
+
const ast = parse(Lang.Tsx, code);
|
|
111
|
+
const root = ast.root();
|
|
112
|
+
|
|
113
|
+
const imports = root.findAll({
|
|
114
|
+
rule: {
|
|
115
|
+
kind: 'import_statement',
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
invariant(
|
|
120
|
+
imports.length > 0,
|
|
121
|
+
`[convertNextDynamicToStatic] No import statements found in ${filePath}`,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const lastImport = imports.at(-1)!;
|
|
125
|
+
return lastImport.range().end.index;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const removeDynamicImport = (code: string): string => {
|
|
129
|
+
const patterns = [
|
|
130
|
+
/import dynamic from ["']@\/libs\/next\/dynamic["'];\n?/g,
|
|
131
|
+
/import dynamic from ["']next\/dynamic["'];\n?/g,
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
let result = code;
|
|
135
|
+
for (const pattern of patterns) {
|
|
136
|
+
result = result.replace(pattern, '');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return result;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const removeUnusedLoadingImport = (code: string): string => {
|
|
143
|
+
const codeWithoutImport = code.replaceAll(/import Loading from ["'][^"']+["'];?\n?/g, '');
|
|
144
|
+
if (!/\bLoading\b/.test(codeWithoutImport)) {
|
|
145
|
+
return code.replaceAll(/import Loading from ["'][^"']+["'];?\n?/g, '');
|
|
146
|
+
}
|
|
147
|
+
return code;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const transformFile = (code: string, filePath: string): string => {
|
|
151
|
+
const imports = extractDynamicImports(code);
|
|
152
|
+
|
|
153
|
+
if (imports.length === 0) {
|
|
154
|
+
return code;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const importStatements = generateImportStatements(imports);
|
|
158
|
+
|
|
159
|
+
const edits: Array<{ end: number; start: number; text: string }> = [];
|
|
160
|
+
|
|
161
|
+
for (const imp of imports) {
|
|
162
|
+
edits.push({
|
|
163
|
+
end: imp.end,
|
|
164
|
+
start: imp.start,
|
|
165
|
+
text: '',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
edits.sort((a, b) => b.start - a.start);
|
|
170
|
+
|
|
171
|
+
let result = code;
|
|
172
|
+
for (const edit of edits) {
|
|
173
|
+
let endIndex = edit.end;
|
|
174
|
+
while (result[endIndex] === '\n' || result[endIndex] === '\r') {
|
|
175
|
+
endIndex++;
|
|
176
|
+
}
|
|
177
|
+
result = result.slice(0, edit.start) + result.slice(endIndex);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const insertPos = findImportInsertPosition(result, filePath);
|
|
181
|
+
if (importStatements) {
|
|
182
|
+
result = result.slice(0, insertPos) + '\n' + importStatements + result.slice(insertPos);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
result = removeDynamicImport(result);
|
|
186
|
+
result = removeUnusedLoadingImport(result);
|
|
187
|
+
|
|
188
|
+
result = result.replaceAll(/\n{3,}/g, '\n\n');
|
|
189
|
+
|
|
190
|
+
return result;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export const convertNextDynamicToStatic = async (TEMP_DIR: string) => {
|
|
194
|
+
const appDirs = [
|
|
195
|
+
{ dir: path.join(TEMP_DIR, 'src/app/(variants)'), label: 'src/app/(variants)' },
|
|
196
|
+
{ dir: path.join(TEMP_DIR, 'src/app/[variants]'), label: 'src/app/[variants]' },
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
console.log(' Processing next/dynamic → static imports...');
|
|
200
|
+
|
|
201
|
+
let processedCount = 0;
|
|
202
|
+
|
|
203
|
+
for (const { dir, label } of appDirs) {
|
|
204
|
+
if (!(await fs.pathExists(dir))) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const files = await glob('**/*.tsx', { cwd: dir });
|
|
209
|
+
|
|
210
|
+
for (const file of files) {
|
|
211
|
+
const filePath = path.join(dir, file);
|
|
212
|
+
const code = await fs.readFile(filePath, 'utf8');
|
|
213
|
+
|
|
214
|
+
if (!code.includes('dynamic(')) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const transformed = transformFile(code, `${label}/${file}`);
|
|
219
|
+
|
|
220
|
+
if (transformed !== code) {
|
|
221
|
+
await fs.writeFile(filePath, transformed);
|
|
222
|
+
processedCount++;
|
|
223
|
+
console.log(` Transformed: ${label}/${file}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.log(` Processed ${processedCount} files with dynamic imports`);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
if (isDirectRun(import.meta.url)) {
|
|
232
|
+
await runStandalone('convertNextDynamicToStatic', convertNextDynamicToStatic, []);
|
|
233
|
+
}
|