@chatbi-v/cli 2.0.1 → 2.0.2
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/README.md +45 -0
- package/dist/bench-ACSHVGHE.mjs +77 -0
- package/dist/build-UB4D3WNI.mjs +11 -0
- package/dist/chunk-4OD6C56P.mjs +89 -0
- package/dist/chunk-7A54IJI5.mjs +6368 -0
- package/dist/chunk-LJFX6MNO.mjs +255 -0
- package/dist/chunk-SBGVKO4C.mjs +2255 -0
- package/dist/chunk-TX5M36S5.mjs +55 -0
- package/dist/chunk-V7IEPMC4.mjs +52 -0
- package/dist/chunk-WCPZB47I.mjs +262 -0
- package/dist/chunk-WIVHOK75.mjs +5292 -0
- package/dist/chunk-Y24V4GQG.mjs +9577 -0
- package/dist/commands/add.js +182 -0
- package/dist/commands/bench.js +100 -0
- package/dist/commands/build.js +290 -0
- package/dist/commands/dev.js +8 -0
- package/dist/commands/discover.js +25 -0
- package/dist/commands/doctor.js +231 -0
- package/dist/commands/fetch.js +41 -0
- package/dist/commands/gl.js +151 -0
- package/dist/commands/init.js +253 -0
- package/dist/commands/install.js +85 -0
- package/dist/commands/ls.js +46 -0
- package/dist/commands/sync.js +78 -0
- package/dist/commands/use.js +31 -0
- package/dist/config.js +70 -0
- package/dist/corekit.js +370 -0
- package/dist/execa-METROS6Z.mjs +17 -0
- package/dist/fetch-7X2UFWIV.mjs +10 -0
- package/dist/index.cjs +27278 -0
- package/dist/index.js +193 -23981
- package/dist/index.mjs +2769 -0
- package/dist/init-QFRFYEA5.mjs +12 -0
- package/dist/sandbox.js +522 -0
- package/dist/sync-7HPKGVFY.mjs +11 -0
- package/dist/utils.js +99 -0
- package/package.json +4 -3
- package/templates/app/.env.hbs +2 -2
- package/templates/app/README.md.hbs +29 -11
- package/templates/app/chatbi.config.ts.hbs +10 -33
- package/templates/app/index.html.hbs +1 -1
- package/templates/app/package.json.hbs +12 -14
- package/templates/app/postcss.config.cjs.hbs +5 -1
- package/templates/app/src/App.tsx.hbs +66 -36
- package/templates/app/src/components/GlobalSettingsModal.tsx.hbs +11 -1
- package/templates/app/src/hooks/useAppRoutes.ts.hbs +3 -6
- package/templates/app/src/hooks/usePluginLoader.ts.hbs +54 -7
- package/templates/app/src/hooks/usePluginStateSync.ts.hbs +81 -0
- package/templates/app/src/hooks/useThemeSync.ts.hbs +25 -74
- package/templates/app/src/layouts/MainContent.tsx.hbs +1 -1
- package/templates/app/src/layouts/SidebarNav.tsx.hbs +15 -14
- package/templates/app/src/main.tsx.hbs +2 -9
- package/templates/app/src/providers/AppProviders.tsx.hbs +2 -0
- package/templates/app/src/stores/useSessionStore.ts.hbs +1 -1
- package/templates/app/src/stores/useUIStore.ts.hbs +22 -3
- package/templates/app/src/vite-env.d.ts.hbs +1 -0
- package/templates/app/tailwind.config.cjs.hbs +26 -7
- package/templates/app/tsconfig.json.hbs +2 -5
- package/templates/app/vite.config.ts.hbs +67 -52
- package/templates/monorepo/package.json.hbs +1 -1
- package/templates/plugin/README.md.hbs +36 -0
- package/templates/plugin/package.json.hbs +26 -21
- package/templates/plugin/src/api/index.ts.hbs +3 -0
- package/templates/plugin/src/components/MainView.tsx.hbs +12 -0
- package/templates/plugin/src/index.tsx.hbs +39 -72
- package/templates/plugin/tsconfig.json.hbs +4 -15
- package/templates/app/src/services/api/modules/auth.ts.hbs +0 -18
package/templates/app/.env.hbs
CHANGED
|
@@ -1,16 +1,34 @@
|
|
|
1
|
-
#
|
|
1
|
+
# {{projectTitle}}
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
基于 @chatbi-v/core 的容器应用脚手架。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 核心功能
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
7
|
+
- **插件化架构**: 自动发现并加载符合规范的业务插件。
|
|
8
|
+
- **微内核设计**: 核心逻辑下沉到 `@chatbi-v/core`,容器层只负责骨架维护。
|
|
9
|
+
- **主题与布局**: 内置主题同步与响应式布局切换支持。
|
|
10
|
+
- **配置驱动**: 通过 `chatbi.config.ts` 统一管理应用和插件配置。
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## 快速开始
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
1. **安装依赖**
|
|
15
|
+
```bash
|
|
16
|
+
pnpm install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
2. **本地开发**
|
|
20
|
+
```bash
|
|
21
|
+
pnpm dev
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
3. **构建发布**
|
|
25
|
+
```bash
|
|
26
|
+
pnpm build
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 目录结构
|
|
30
|
+
|
|
31
|
+
- `src/layouts`: 核心布局骨架(侧边栏、内容区)。
|
|
32
|
+
- `src/hooks`: 核心基础设施 Hooks(插件加载、状态同步)。
|
|
33
|
+
- `src/stores`: 状态管理(UI 状态、会话状态)。
|
|
34
|
+
- `src/features/settings`: 插件配置渲染中心。
|
|
@@ -1,60 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* {{projectTitle}} 配置文件
|
|
3
3
|
* @description 定义应用的全局配置和插件参数
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { DiscoveryRule } from '@chatbi-v/core';
|
|
7
7
|
|
|
8
|
-
interface
|
|
8
|
+
interface AppConfig {
|
|
9
9
|
/**
|
|
10
10
|
* 插件配置表
|
|
11
|
-
* Key: 插件 ID
|
|
11
|
+
* Key: 插件 ID
|
|
12
12
|
* Value: 插件配置项
|
|
13
13
|
*/
|
|
14
14
|
plugins: Record<string, any>;
|
|
15
15
|
system: {
|
|
16
16
|
title: string;
|
|
17
|
+
logo?: string;
|
|
17
18
|
version: string;
|
|
18
19
|
language: string;
|
|
19
|
-
/** 插件发现规则 */
|
|
20
|
-
discoveryRules
|
|
20
|
+
/** 插件发现规则 (可选,不传则使用默认约定) */
|
|
21
|
+
discoveryRules?: DiscoveryRule[];
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
export const chatbiConfig:
|
|
25
|
+
export const chatbiConfig: AppConfig = {
|
|
25
26
|
/**
|
|
26
27
|
* 插件配置中心
|
|
27
|
-
* 这里只定义插件的"运行参数",插件的加载已由 AutoLoader 自动处理
|
|
28
28
|
*/
|
|
29
29
|
plugins: {
|
|
30
|
-
|
|
31
|
-
defaultTheme: '{{theme}}-light',
|
|
32
|
-
defaultLayout: 'standard',
|
|
33
|
-
},
|
|
34
|
-
'@chatbi-v/plugin-layout-transform': {},
|
|
35
|
-
'@chatbi-v/plugin-system-monitor': {},
|
|
30
|
+
// 在此注册和配置插件
|
|
36
31
|
},
|
|
37
|
-
/**
|
|
38
|
-
* 系统配置
|
|
39
|
-
*/
|
|
40
32
|
system: {
|
|
41
|
-
title: '{{
|
|
42
|
-
version: '
|
|
33
|
+
title: '{{projectTitle}}',
|
|
34
|
+
version: '{{projectVersion}}',
|
|
43
35
|
language: 'zh-CN',
|
|
44
|
-
/** 插件发现规则 */
|
|
45
|
-
discoveryRules: [
|
|
46
|
-
{
|
|
47
|
-
pattern: '@chatbi-plugins/*/src/index.{ts,tsx}',
|
|
48
|
-
pathSegment: 'plugins',
|
|
49
|
-
idPrefix: '@chatbi-v/plugin',
|
|
50
|
-
alias: '@chatbi-plugins',
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
pattern: '@chatbi-apps/*/src/index.{ts,tsx}',
|
|
54
|
-
pathSegment: 'apps',
|
|
55
|
-
idPrefix: '@chatbi-v/app',
|
|
56
|
-
alias: '@chatbi-apps',
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
36
|
},
|
|
60
37
|
};
|
|
@@ -1,36 +1,34 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "{{
|
|
3
|
-
"version": "
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "{{projectVersion}}",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"private": true,
|
|
5
6
|
"scripts": {
|
|
6
|
-
"dev": "
|
|
7
|
-
"build": "
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
8
9
|
"preview": "vite preview",
|
|
9
|
-
"test": "vitest"
|
|
10
|
-
"sync": "chatbi-cli sync"
|
|
10
|
+
"test": "vitest"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@ant-design/icons": "^5.2.6",
|
|
14
14
|
"@ant-design/x": "^2.1.1",
|
|
15
|
-
"@chatbi-v/core":
|
|
16
|
-
"@chatbi-v/mocks":
|
|
17
|
-
"@chatbi-v/tailwind-config": "^{{version}}",
|
|
15
|
+
"@chatbi-v/core":"^2.0.2",
|
|
16
|
+
"@chatbi-v/mocks":"^2.0.2",
|
|
18
17
|
"antd": "^5.29.3",
|
|
19
18
|
"react": "^18.3.1",
|
|
20
19
|
"react-dom": "^18.3.1",
|
|
21
|
-
"react-router": "^
|
|
22
|
-
"react-router-dom": "^6.22.3",
|
|
20
|
+
"react-router-dom": "^7.10.1",
|
|
23
21
|
"zustand": "^5.0.9"
|
|
24
22
|
},
|
|
25
23
|
"devDependencies": {
|
|
26
|
-
"@
|
|
24
|
+
"@chatbi-v/tsconfig":"^2.0.2",
|
|
25
|
+
"@types/node": "^25.0.3",
|
|
27
26
|
"@types/react": "^18.2.43",
|
|
28
27
|
"@types/react-dom": "^18.2.17",
|
|
29
28
|
"@vitejs/plugin-react": "^4.2.1",
|
|
30
|
-
"less": "^4.
|
|
29
|
+
"less": "^4.5.1",
|
|
31
30
|
"typescript": "^5.3.3",
|
|
32
31
|
"vite": "^5.0.8",
|
|
33
|
-
"vite-tsconfig-paths": "^4.3.1",
|
|
34
32
|
"tailwindcss": "^3.4.1",
|
|
35
33
|
"autoprefixer": "^10.4.17",
|
|
36
34
|
"postcss": "^8.4.35"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { lazy, Suspense, useMemo } from 'react';
|
|
2
2
|
import { useLocation } from 'react-router-dom';
|
|
3
|
+
import { PluginSlot, Slot } from '@chatbi-v/core';
|
|
3
4
|
|
|
4
5
|
import { GlobalSettingsModal } from './components/GlobalSettingsModal';
|
|
5
6
|
import { ContentSkeleton, NavSkeleton } from './components/LayoutSkeletons';
|
|
@@ -13,6 +14,28 @@ const BackgroundEffects = lazy(() => import('./layouts/BackgroundEffects').then(
|
|
|
13
14
|
const MainContent = lazy(() => import('./layouts/MainContent').then(module => ({ default: module.MainContent })));
|
|
14
15
|
const SidebarNav = lazy(() => import('./layouts/SidebarNav').then(module => ({ default: module.SidebarNav })));
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* 默认布局 (左侧导航,右侧内容)
|
|
19
|
+
*/
|
|
20
|
+
const DefaultLayout: React.FC<{
|
|
21
|
+
sidebar: React.ReactNode;
|
|
22
|
+
content: React.ReactNode;
|
|
23
|
+
isMaximized: boolean;
|
|
24
|
+
collapsed: boolean;
|
|
25
|
+
}> = ({ sidebar, content, isMaximized, collapsed }) => (
|
|
26
|
+
<div className="flex h-screen w-screen bg-background overflow-hidden text-text-main selection:bg-primary/30 font-sans transition-colors duration-300">
|
|
27
|
+
<div className={`relative z-20 flex flex-col items-center py-4 gap-[var(--layout-gap)] transition-all duration-500 ease-[cubic-bezier(0.2,0,0,1)]
|
|
28
|
+
border-r border-border/60 backdrop-blur-xl bg-surface/80
|
|
29
|
+
${isMaximized ? 'w-0 overflow-hidden opacity-0 p-0 border-none' : (collapsed ? 'w-[72px]' : 'w-[var(--layout-sidebar-width)]')}
|
|
30
|
+
h-screen
|
|
31
|
+
`}>
|
|
32
|
+
{sidebar}
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
{content}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
|
|
16
39
|
const App: React.FC = () => {
|
|
17
40
|
const location = useLocation();
|
|
18
41
|
const activeTab = location.pathname.replace(/^\//, '') || '';
|
|
@@ -25,7 +48,7 @@ const App: React.FC = () => {
|
|
|
25
48
|
const {
|
|
26
49
|
isRightPanelOpen, setIsRightPanelOpen,
|
|
27
50
|
isMaximized, setIsMaximized,
|
|
28
|
-
collapsed
|
|
51
|
+
collapsed, currentLayoutId
|
|
29
52
|
} = useUIStore();
|
|
30
53
|
|
|
31
54
|
// 3. 构造共享 Props
|
|
@@ -45,46 +68,53 @@ const App: React.FC = () => {
|
|
|
45
68
|
setIsMaximized
|
|
46
69
|
]);
|
|
47
70
|
|
|
71
|
+
const sidebar = (
|
|
72
|
+
<Suspense fallback={<NavSkeleton />}>
|
|
73
|
+
<SidebarNav
|
|
74
|
+
activeTab={activeTab}
|
|
75
|
+
pluginsLoaded={pluginsLoaded}
|
|
76
|
+
/>
|
|
77
|
+
</Suspense>
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const content = (
|
|
81
|
+
<Suspense fallback={
|
|
82
|
+
<main className="flex-1 flex flex-col relative z-10 overflow-hidden bg-surface/50 backdrop-blur-sm">
|
|
83
|
+
<ContentSkeleton />
|
|
84
|
+
</main>
|
|
85
|
+
}>
|
|
86
|
+
<MainContent
|
|
87
|
+
pluginsLoaded={pluginsLoaded}
|
|
88
|
+
sharedProps={sharedProps}
|
|
89
|
+
/>
|
|
90
|
+
</Suspense>
|
|
91
|
+
);
|
|
92
|
+
|
|
48
93
|
return (
|
|
49
94
|
<AppProviders>
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<BackgroundEffects />
|
|
54
|
-
</Suspense>
|
|
55
|
-
|
|
56
|
-
{/* Sidebar */}
|
|
57
|
-
<Suspense fallback={
|
|
58
|
-
<div className={`relative z-20 flex flex-col items-center py-4 gap-[var(--layout-gap)] transition-all duration-500 ease-[cubic-bezier(0.2,0,0,1)]
|
|
59
|
-
border-r border-slate-200/60 dark:border-white/10 backdrop-blur-xl bg-white/80 dark:bg-slate-900/80
|
|
60
|
-
${isMaximized ? 'w-0 overflow-hidden opacity-0 p-0 border-none' : (collapsed ? 'w-[72px]' : 'w-[var(--layout-sidebar-width)]')}
|
|
61
|
-
h-screen
|
|
62
|
-
`}>
|
|
63
|
-
<NavSkeleton />
|
|
64
|
-
</div>
|
|
65
|
-
}>
|
|
66
|
-
<SidebarNav
|
|
67
|
-
activeTab={activeTab}
|
|
68
|
-
pluginsLoaded={pluginsLoaded}
|
|
69
|
-
/>
|
|
70
|
-
</Suspense>
|
|
95
|
+
<Suspense fallback={null}>
|
|
96
|
+
<BackgroundEffects />
|
|
97
|
+
</Suspense>
|
|
71
98
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
99
|
+
{/*
|
|
100
|
+
这里通过 PluginSlot 尝试寻找自定义布局插件。
|
|
101
|
+
如果没找到,则回退到 DefaultLayout。
|
|
102
|
+
*/}
|
|
103
|
+
<PluginSlot
|
|
104
|
+
slot={Slot.RootLayout}
|
|
105
|
+
props={{ sidebar, content, isMaximized, collapsed, currentLayoutId }}
|
|
106
|
+
fallback={
|
|
107
|
+
<DefaultLayout
|
|
108
|
+
sidebar={sidebar}
|
|
109
|
+
content={content}
|
|
110
|
+
isMaximized={isMaximized}
|
|
111
|
+
collapsed={collapsed}
|
|
81
112
|
/>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
{/* Global Settings */}
|
|
85
|
-
<GlobalSettingsModal />
|
|
113
|
+
}
|
|
114
|
+
/>
|
|
86
115
|
|
|
87
|
-
|
|
116
|
+
{/* Global Settings */}
|
|
117
|
+
<GlobalSettingsModal />
|
|
88
118
|
</AppProviders>
|
|
89
119
|
);
|
|
90
120
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Modal, Skeleton } from 'antd';
|
|
2
|
-
import
|
|
2
|
+
import { pluginManager } from '@chatbi-v/core';
|
|
3
|
+
import React, { lazy, Suspense, useEffect } from 'react';
|
|
3
4
|
|
|
4
5
|
import { useUIStore } from '../stores/useUIStore';
|
|
5
6
|
|
|
@@ -12,6 +13,15 @@ const PluginSettings = lazy(() => import('../features/settings/PluginSettings').
|
|
|
12
13
|
export const GlobalSettingsModal: React.FC = () => {
|
|
13
14
|
const { isSettingsOpen, setIsSettingsOpen } = useUIStore();
|
|
14
15
|
|
|
16
|
+
// 监听全局设置打开事件,确保在所有布局下都能响应
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const unsubscribe = pluginManager.eventBus.on('ui:open-settings', () => {
|
|
19
|
+
console.log('[GlobalSettingsModal] Received ui:open-settings');
|
|
20
|
+
setIsSettingsOpen(true);
|
|
21
|
+
});
|
|
22
|
+
return () => unsubscribe();
|
|
23
|
+
}, [setIsSettingsOpen]);
|
|
24
|
+
|
|
15
25
|
return (
|
|
16
26
|
<Modal
|
|
17
27
|
title="设置"
|
|
@@ -19,15 +19,12 @@ export const useAppRoutes = () => {
|
|
|
19
19
|
|
|
20
20
|
// 计算默认路由
|
|
21
21
|
// 策略:
|
|
22
|
-
// 1.
|
|
23
|
-
// 2.
|
|
22
|
+
// 1. 查找是否有显式标记为默认的路由(TODO: 需要扩展 RouteConfig 支持 meta.default)
|
|
23
|
+
// 2. 查找 chat 插件的路由
|
|
24
|
+
// 3. 回退到第一个可用路由
|
|
24
25
|
const defaultRoute = useMemo(() => {
|
|
25
26
|
if (routes.length === 0) return null;
|
|
26
27
|
|
|
27
|
-
// 优先尝试标记为 default 的路由
|
|
28
|
-
const markedDefault = routes.find(r => (r as any).meta?.default);
|
|
29
|
-
if (markedDefault) return markedDefault.path;
|
|
30
|
-
|
|
31
28
|
// 回退到第一个
|
|
32
29
|
return routes[0].path;
|
|
33
30
|
}, [routes]);
|
|
@@ -10,13 +10,60 @@ import { api } from '../services/api';
|
|
|
10
10
|
*/
|
|
11
11
|
export const usePluginLoader = () => {
|
|
12
12
|
// 注入应用级配置和服务
|
|
13
|
-
const options = useMemo(() =>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
const options = useMemo(() => {
|
|
14
|
+
/**
|
|
15
|
+
* 将 Vite 扫描到的物理路径转换为内核可识别的规范化 ID
|
|
16
|
+
* 规范化规则:
|
|
17
|
+
* 1. @chatbi-plugins/xxx/src/index -> packages/plugins/xxx/src/index
|
|
18
|
+
* 2. ../../plugins/xxx/src/index -> packages/plugins/xxx/src/index
|
|
19
|
+
*/
|
|
20
|
+
const normalizeModulePath = (path: string) => {
|
|
21
|
+
const mapping = [
|
|
22
|
+
{ pattern: /^(@chatbi-plugins\/|(\.\.\/)+plugins\/)/, replacement: 'packages/plugins/' },
|
|
23
|
+
{ pattern: /^(@chatbi-apps\/|(\.\.\/)+apps\/)/, replacement: 'packages/apps/' }
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
for (const { pattern, replacement } of mapping) {
|
|
27
|
+
if (pattern.test(path)) {
|
|
28
|
+
return path.replace(pattern, replacement);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return path;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// 注意:Vite 要求 import.meta.glob 必须使用字面量
|
|
35
|
+
const globModules = import.meta.glob<any>([
|
|
36
|
+
'@chatbi-plugins/*/src/index.{ts,tsx}',
|
|
37
|
+
'@chatbi-apps/*/src/index.{ts,tsx}',
|
|
38
|
+
], { eager: false });
|
|
39
|
+
|
|
40
|
+
const normalizedModules: Record<string, () => Promise<any>> = {};
|
|
41
|
+
|
|
42
|
+
Object.entries(globModules).forEach(([path, loader]) => {
|
|
43
|
+
normalizedModules[normalizeModulePath(path)] = loader;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
modules: normalizedModules,
|
|
48
|
+
pluginConfigs: chatbiConfig.plugins,
|
|
49
|
+
systemConfig: chatbiConfig.system,
|
|
50
|
+
sharedContext: { api },
|
|
51
|
+
};
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
// 针对 Shell 模式,注入当前正在开发的插件 (虚拟模块)
|
|
55
|
+
{{#if isShell}}
|
|
56
|
+
// @ts-ignore
|
|
57
|
+
if (import.meta.env.MODE !== 'production') {
|
|
58
|
+
// 构造一个符合 AutoLoader 规则的虚拟路径,确保插件能被识别
|
|
59
|
+
// AutoLoader 规则: packages/plugins/{id}/src/index -> @chatbi-v/plugin-{id}
|
|
60
|
+
// 我们使用 current-plugin 作为 ID,这样最终 ID 为 @chatbi-v/plugin-current-plugin
|
|
61
|
+
// 这只是用于绕过加载器的路径检查,实际插件 ID 由插件自身定义 (如果有)
|
|
62
|
+
const virtualKey = 'packages/plugins/current-plugin/src/index';
|
|
63
|
+
// @ts-ignore
|
|
64
|
+
options.modules[virtualKey] = () => import('virtual:user-plugin');
|
|
65
|
+
}
|
|
66
|
+
{{/if}}
|
|
20
67
|
|
|
21
68
|
return useCorePluginLoader(options);
|
|
22
69
|
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { pluginManager, configManager } from '@chatbi-v/core';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { useUIStore } from '../stores/useUIStore';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 通用插件状态同步 Hook
|
|
7
|
+
* @description 遍历所有插件的 metadata.systemStateBindings,自动建立配置与系统状态的同步
|
|
8
|
+
*/
|
|
9
|
+
export const usePluginStateSync = () => {
|
|
10
|
+
const { updateState } = useUIStore();
|
|
11
|
+
const [version, setVersion] = useState(0);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
// 订阅插件管理器变更,当插件加载完成或状态改变时重新执行同步逻辑
|
|
15
|
+
const unsubscribePluginManager = pluginManager.subscribe(() => {
|
|
16
|
+
setVersion(v => v + 1);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const plugins = pluginManager.getPlugins();
|
|
20
|
+
const unsubscribes: (() => void)[] = [];
|
|
21
|
+
|
|
22
|
+
plugins.forEach((plugin) => {
|
|
23
|
+
const bindings = plugin.metadata.systemStateBindings;
|
|
24
|
+
if (!bindings || bindings.length === 0) return;
|
|
25
|
+
|
|
26
|
+
bindings.forEach((binding) => {
|
|
27
|
+
const { configKey, stateKey, transform } = binding;
|
|
28
|
+
|
|
29
|
+
// 1. 初始同步
|
|
30
|
+
const currentConfig = configManager.get(plugin.id);
|
|
31
|
+
if (currentConfig && currentConfig[configKey] !== undefined) {
|
|
32
|
+
const value = currentConfig[configKey];
|
|
33
|
+
applyUpdate(stateKey, value, transform);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 2. 监听变更
|
|
37
|
+
const handler = (data: { pluginId: string; key: string; value: any }) => {
|
|
38
|
+
if (data.pluginId === plugin.id && data.key === configKey) {
|
|
39
|
+
applyUpdate(stateKey, data.value, transform);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const unsubscribe = pluginManager.eventBus.on('config:changed', handler);
|
|
44
|
+
unsubscribes.push(unsubscribe);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return () => {
|
|
49
|
+
unsubscribePluginManager();
|
|
50
|
+
unsubscribes.forEach((unsub) => unsub());
|
|
51
|
+
};
|
|
52
|
+
}, [updateState, version]);
|
|
53
|
+
|
|
54
|
+
// 辅助函数:处理转换逻辑并更新状态
|
|
55
|
+
function applyUpdate(stateKey: string, value: any, transform?: string) {
|
|
56
|
+
console.log(`[PluginStateSync] Applying update: ${stateKey} =`, value, transform ? `(transform: ${transform})` : '');
|
|
57
|
+
let finalValue = value;
|
|
58
|
+
|
|
59
|
+
if (transform === 'theme-mode') {
|
|
60
|
+
// 这是一个特殊的转换,通常用于根据主题 ID 自动推断模式
|
|
61
|
+
updateState(stateKey, value);
|
|
62
|
+
|
|
63
|
+
// 尝试通过服务更精准地推断 themeMode
|
|
64
|
+
const themeService = pluginManager.getService('theme-manager.theme-resolver');
|
|
65
|
+
if (themeService?.getAntdTheme) {
|
|
66
|
+
// 如果服务可用,我们可以通过服务逻辑来判断(或者在这里简单判断)
|
|
67
|
+
// 但最稳妥的是看 value 是否包含 light 关键字,或者默认为 dark
|
|
68
|
+
const isLight = value.toLowerCase().includes('light') || value === 'white';
|
|
69
|
+
updateState('themeMode', isLight ? 'light' : 'dark');
|
|
70
|
+
} else {
|
|
71
|
+
// 降级逻辑
|
|
72
|
+
const isDark = value.toLowerCase().includes('dark') ||
|
|
73
|
+
(typeof value === 'string' && ['black', 'night', 'dark', 'default', 'cyberpunk', 'forest'].some(k => value.toLowerCase().includes(k)));
|
|
74
|
+
updateState('themeMode', isDark ? 'dark' : 'light');
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
updateState(stateKey, finalValue);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
@@ -1,58 +1,27 @@
|
|
|
1
|
-
import { pluginManager
|
|
2
|
-
import { themes } from '@chatbi-v/plugin-theme-manager';
|
|
1
|
+
import { pluginManager } from '@chatbi-v/core';
|
|
3
2
|
import { theme } from 'antd';
|
|
4
|
-
import { useEffect, useMemo } from 'react';
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
5
4
|
|
|
6
5
|
import { useUIStore } from '../stores/useUIStore';
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
|
-
* 主题同步 Hook
|
|
10
|
-
* @description
|
|
8
|
+
* 主题同步 Hook (已简化,仅负责计算 Antd 配置)
|
|
9
|
+
* @description 状态同步已由 usePluginStateSync 统一处理
|
|
11
10
|
*/
|
|
12
11
|
export const useThemeSync = () => {
|
|
13
12
|
const {
|
|
14
13
|
currentThemeId,
|
|
15
|
-
setThemeId,
|
|
16
14
|
themeMode,
|
|
17
|
-
setThemeMode,
|
|
18
15
|
} = useUIStore();
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
// 直接从导出的 configManager 获取
|
|
23
|
-
const config = configManager.get('theme-manager');
|
|
24
|
-
if (config?.theme) {
|
|
25
|
-
const themeId = config.theme;
|
|
26
|
-
setThemeId(themeId);
|
|
27
|
-
|
|
28
|
-
const themeDef = themes.find((t) => t.id === themeId);
|
|
29
|
-
const isLight = themeDef ? themeDef.type === 'light' : themeId === 'light';
|
|
30
|
-
setThemeMode(isLight ? 'light' : 'dark');
|
|
31
|
-
}
|
|
32
|
-
}, [setThemeId, setThemeMode]);
|
|
17
|
+
const [refresh, setRefresh] = useState(0);
|
|
33
18
|
|
|
34
|
-
//
|
|
19
|
+
// 监听插件加载,因为 service 可能延迟注册
|
|
35
20
|
useEffect(() => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// 这里的 pluginId 必须与 ThemePlugin.id 一致 ('theme-manager')
|
|
39
|
-
if (pluginId === 'theme-manager' && key === 'theme') {
|
|
40
|
-
const themeId = value;
|
|
41
|
-
setThemeId(themeId);
|
|
42
|
-
|
|
43
|
-
const themeDef = themes.find((t) => t.id === themeId);
|
|
44
|
-
const isLight = themeDef ? themeDef.type === 'light' : themeId === 'light';
|
|
45
|
-
setThemeMode(isLight ? 'light' : 'dark');
|
|
46
|
-
}
|
|
47
|
-
};
|
|
21
|
+
return pluginManager.subscribe(() => setRefresh(prev => prev + 1));
|
|
22
|
+
}, []);
|
|
48
23
|
|
|
49
|
-
|
|
50
|
-
return () => {
|
|
51
|
-
unsubscribe();
|
|
52
|
-
};
|
|
53
|
-
}, [setThemeId, setThemeMode]);
|
|
54
|
-
|
|
55
|
-
// 3. 同步 Tailwind Dark Mode 类名
|
|
24
|
+
// 1. 同步 Tailwind Dark Mode 类名
|
|
56
25
|
useEffect(() => {
|
|
57
26
|
if (themeMode === 'dark') {
|
|
58
27
|
document.documentElement.classList.add('dark');
|
|
@@ -63,46 +32,28 @@ export const useThemeSync = () => {
|
|
|
63
32
|
}
|
|
64
33
|
}, [themeMode]);
|
|
65
34
|
|
|
66
|
-
// 计算 Ant Design 主题配置
|
|
35
|
+
// 2. 计算 Ant Design 主题配置
|
|
67
36
|
const antdThemeConfig = useMemo(() => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
37
|
+
// 尝试从服务获取更详细的配置
|
|
38
|
+
// 注意:插件注册的服务会自动加上插件 ID 作为前缀
|
|
39
|
+
const themeService = pluginManager.getService('theme-manager.theme-resolver');
|
|
40
|
+
|
|
41
|
+
if (themeService?.getAntdTheme) {
|
|
42
|
+
const baseConfig = themeService.getAntdTheme(currentThemeId, themeMode);
|
|
43
|
+
return {
|
|
44
|
+
...baseConfig,
|
|
45
|
+
algorithm: themeMode === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
72
48
|
|
|
49
|
+
// 回退逻辑
|
|
73
50
|
return {
|
|
74
|
-
primaryColor,
|
|
75
51
|
algorithm: themeMode === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm,
|
|
76
52
|
token: {
|
|
77
|
-
colorPrimary:
|
|
78
|
-
|
|
79
|
-
},
|
|
80
|
-
components: {
|
|
81
|
-
Button: {
|
|
82
|
-
colorPrimary: primaryColor,
|
|
83
|
-
algorithm: true,
|
|
84
|
-
},
|
|
85
|
-
Modal: {
|
|
86
|
-
contentBg: 'rgb(var(--color-surface))',
|
|
87
|
-
headerBg: 'rgb(var(--color-surface))',
|
|
88
|
-
},
|
|
89
|
-
Input: {
|
|
90
|
-
colorBgContainer: 'rgba(var(--color-surface), 0.5)',
|
|
91
|
-
},
|
|
92
|
-
InputNumber: {
|
|
93
|
-
colorBgContainer: 'rgba(var(--color-surface), 0.5)',
|
|
94
|
-
},
|
|
95
|
-
TextArea: {
|
|
96
|
-
colorBgContainer: 'rgba(var(--color-surface), 0.5)',
|
|
97
|
-
},
|
|
98
|
-
Select: {
|
|
99
|
-
colorBgContainer: 'rgba(var(--color-surface), 0.5)',
|
|
100
|
-
selectorBg: 'rgba(var(--color-surface), 0.5)',
|
|
101
|
-
optionSelectedBg: 'rgba(var(--color-primary), 0.2)',
|
|
102
|
-
},
|
|
103
|
-
},
|
|
53
|
+
colorPrimary: '#6366f1',
|
|
54
|
+
}
|
|
104
55
|
};
|
|
105
|
-
}, [currentThemeId, themeMode]);
|
|
56
|
+
}, [currentThemeId, themeMode, refresh]);
|
|
106
57
|
|
|
107
58
|
return antdThemeConfig;
|
|
108
59
|
};
|
|
@@ -28,7 +28,7 @@ export const MainContent: React.FC<MainContentProps> = React.memo(({
|
|
|
28
28
|
<Route
|
|
29
29
|
key={route.path}
|
|
30
30
|
path={route.path}
|
|
31
|
-
element={<route.component {...sharedProps} />}
|
|
31
|
+
element={<route.component {...sharedProps} config={route.meta?.config} />}
|
|
32
32
|
/>
|
|
33
33
|
))}
|
|
34
34
|
|