@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.
Files changed (67) hide show
  1. package/README.md +45 -0
  2. package/dist/bench-ACSHVGHE.mjs +77 -0
  3. package/dist/build-UB4D3WNI.mjs +11 -0
  4. package/dist/chunk-4OD6C56P.mjs +89 -0
  5. package/dist/chunk-7A54IJI5.mjs +6368 -0
  6. package/dist/chunk-LJFX6MNO.mjs +255 -0
  7. package/dist/chunk-SBGVKO4C.mjs +2255 -0
  8. package/dist/chunk-TX5M36S5.mjs +55 -0
  9. package/dist/chunk-V7IEPMC4.mjs +52 -0
  10. package/dist/chunk-WCPZB47I.mjs +262 -0
  11. package/dist/chunk-WIVHOK75.mjs +5292 -0
  12. package/dist/chunk-Y24V4GQG.mjs +9577 -0
  13. package/dist/commands/add.js +182 -0
  14. package/dist/commands/bench.js +100 -0
  15. package/dist/commands/build.js +290 -0
  16. package/dist/commands/dev.js +8 -0
  17. package/dist/commands/discover.js +25 -0
  18. package/dist/commands/doctor.js +231 -0
  19. package/dist/commands/fetch.js +41 -0
  20. package/dist/commands/gl.js +151 -0
  21. package/dist/commands/init.js +253 -0
  22. package/dist/commands/install.js +85 -0
  23. package/dist/commands/ls.js +46 -0
  24. package/dist/commands/sync.js +78 -0
  25. package/dist/commands/use.js +31 -0
  26. package/dist/config.js +70 -0
  27. package/dist/corekit.js +370 -0
  28. package/dist/execa-METROS6Z.mjs +17 -0
  29. package/dist/fetch-7X2UFWIV.mjs +10 -0
  30. package/dist/index.cjs +27278 -0
  31. package/dist/index.js +193 -23981
  32. package/dist/index.mjs +2769 -0
  33. package/dist/init-QFRFYEA5.mjs +12 -0
  34. package/dist/sandbox.js +522 -0
  35. package/dist/sync-7HPKGVFY.mjs +11 -0
  36. package/dist/utils.js +99 -0
  37. package/package.json +4 -3
  38. package/templates/app/.env.hbs +2 -2
  39. package/templates/app/README.md.hbs +29 -11
  40. package/templates/app/chatbi.config.ts.hbs +10 -33
  41. package/templates/app/index.html.hbs +1 -1
  42. package/templates/app/package.json.hbs +12 -14
  43. package/templates/app/postcss.config.cjs.hbs +5 -1
  44. package/templates/app/src/App.tsx.hbs +66 -36
  45. package/templates/app/src/components/GlobalSettingsModal.tsx.hbs +11 -1
  46. package/templates/app/src/hooks/useAppRoutes.ts.hbs +3 -6
  47. package/templates/app/src/hooks/usePluginLoader.ts.hbs +54 -7
  48. package/templates/app/src/hooks/usePluginStateSync.ts.hbs +81 -0
  49. package/templates/app/src/hooks/useThemeSync.ts.hbs +25 -74
  50. package/templates/app/src/layouts/MainContent.tsx.hbs +1 -1
  51. package/templates/app/src/layouts/SidebarNav.tsx.hbs +15 -14
  52. package/templates/app/src/main.tsx.hbs +2 -9
  53. package/templates/app/src/providers/AppProviders.tsx.hbs +2 -0
  54. package/templates/app/src/stores/useSessionStore.ts.hbs +1 -1
  55. package/templates/app/src/stores/useUIStore.ts.hbs +22 -3
  56. package/templates/app/src/vite-env.d.ts.hbs +1 -0
  57. package/templates/app/tailwind.config.cjs.hbs +26 -7
  58. package/templates/app/tsconfig.json.hbs +2 -5
  59. package/templates/app/vite.config.ts.hbs +67 -52
  60. package/templates/monorepo/package.json.hbs +1 -1
  61. package/templates/plugin/README.md.hbs +36 -0
  62. package/templates/plugin/package.json.hbs +26 -21
  63. package/templates/plugin/src/api/index.ts.hbs +3 -0
  64. package/templates/plugin/src/components/MainView.tsx.hbs +12 -0
  65. package/templates/plugin/src/index.tsx.hbs +39 -72
  66. package/templates/plugin/tsconfig.json.hbs +4 -15
  67. package/templates/app/src/services/api/modules/auth.ts.hbs +0 -18
@@ -1,8 +1,8 @@
1
1
  # API Base URL
2
- VITE_API_BASE_URL=/api
2
+ VITE_API_BASE_URL=/bron-chat-bi
3
3
 
4
4
  # 是否开启 Mock 模式 (能力开关,需配合 url mock=true 使用)
5
- VITE_USE_MOCK=true
5
+ VITE_USE_MOCK=false
6
6
 
7
7
  # API Timeout (ms)
8
8
  VITE_API_TIMEOUT=10000
@@ -1,16 +1,34 @@
1
- # @chatbi/main
1
+ # {{projectTitle}}
2
2
 
3
- ChatBI 主应用,基于 React 和 Vite 构建。
3
+ 基于 @chatbi-v/core 的容器应用脚手架。
4
4
 
5
- ## 功能
5
+ ## 核心功能
6
6
 
7
- - **会话管理**: 支持会话列表、搜索、历史记录。
8
- - **插件集成**: 集成 Chat 和 Scene 插件。
9
- - **主题切换**: 支持深色/浅色模式。
10
- - **布局管理**: 响应式布局,支持侧边栏折叠。
7
+ - **插件化架构**: 自动发现并加载符合规范的业务插件。
8
+ - **微内核设计**: 核心逻辑下沉到 `@chatbi-v/core`,容器层只负责骨架维护。
9
+ - **主题与布局**: 内置主题同步与响应式布局切换支持。
10
+ - **配置驱动**: 通过 `chatbi.config.ts` 统一管理应用和插件配置。
11
11
 
12
- ## 启动
12
+ ## 快速开始
13
13
 
14
- ```bash
15
- pnpm dev
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
- * ChatBI 应用配置文件
2
+ * {{projectTitle}} 配置文件
3
3
  * @description 定义应用的全局配置和插件参数
4
4
  */
5
5
 
6
6
  import { DiscoveryRule } from '@chatbi-v/core';
7
7
 
8
- interface ChatBIConfig {
8
+ interface AppConfig {
9
9
  /**
10
10
  * 插件配置表
11
- * Key: 插件 ID (通常是包名,如 @chatbi-v/plugin-chat)
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: DiscoveryRule[];
20
+ /** 插件发现规则 (可选,不传则使用默认约定) */
21
+ discoveryRules?: DiscoveryRule[];
21
22
  };
22
23
  }
23
24
 
24
- export const chatbiConfig: ChatBIConfig = {
25
+ export const chatbiConfig: AppConfig = {
25
26
  /**
26
27
  * 插件配置中心
27
- * 这里只定义插件的"运行参数",插件的加载已由 AutoLoader 自动处理
28
28
  */
29
29
  plugins: {
30
- '@chatbi-v/plugin-theme-manager': {
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: '{{projectName}}',
42
- version: '1.0.0',
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
  };
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>智能分析平台</title>
6
+ <title>{{projectTitle}}</title>
7
7
  </head>
8
8
  <body>
9
9
  <div id="root"></div>
@@ -1,36 +1,34 @@
1
1
  {
2
- "name": "{{name}}",
3
- "version": "0.0.1",
2
+ "name": "{{projectName}}",
3
+ "version": "{{projectVersion}}",
4
4
  "type": "module",
5
+ "private": true,
5
6
  "scripts": {
6
- "dev": "chatbi-cli dev",
7
- "build": "chatbi-cli 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": "^{{version}}",
16
- "@chatbi-v/mocks": "^{{version}}",
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": "^6.22.3",
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
- "@types/node": "^20.0.0",
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.2.0",
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,6 +1,10 @@
1
+ const path = require('path');
1
2
  module.exports = {
2
3
  plugins: {
3
- tailwindcss: {},
4
+ 'tailwindcss/nesting': {},
5
+ tailwindcss: {
6
+ config: path.resolve(__dirname, 'tailwind.config.cjs'),
7
+ },
4
8
  autoprefixer: {},
5
9
  },
6
10
  }
@@ -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
- <div className="flex h-screen w-screen bg-slate-50 dark:bg-slate-950 overflow-hidden text-slate-900 dark:text-slate-200 selection:bg-primary/30 font-sans transition-colors duration-300">
51
-
52
- <Suspense fallback={null}>
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
- {/* Main Content */}
73
- <Suspense fallback={
74
- <main className="flex-1 flex flex-col relative z-10 overflow-hidden bg-white/50 dark:bg-black/20 backdrop-blur-sm">
75
- <ContentSkeleton />
76
- </main>
77
- }>
78
- <MainContent
79
- pluginsLoaded={pluginsLoaded}
80
- sharedProps={sharedProps}
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
- </Suspense>
83
-
84
- {/* Global Settings */}
85
- <GlobalSettingsModal />
113
+ }
114
+ />
86
115
 
87
- </div>
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 React, { lazy, Suspense } from 'react';
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
- discoveryRules: chatbiConfig.system.discoveryRules,
15
- pluginConfigs: chatbiConfig.plugins,
16
- sharedContext: { api },
17
- // 预注册内置插件 (可选,目前已通过 AutoLoader 自动发现)
18
- registry: {}
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, configManager } from '@chatbi-v/core';
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 负责监听主题配置变更,同步 Ant Design 和 Tailwind 主题状态
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
- // 1. 初始同步:从 configManager 获取当前生效的配置
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
- // 2. 监听配置变更事件 (来自 settings 面板或 ThemeToggle)
19
+ // 监听插件加载,因为 service 可能延迟注册
35
20
  useEffect(() => {
36
- const handleConfigChange = (data: { pluginId: string, key: string, value: any }) => {
37
- const { pluginId, key, value } = data;
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
- const unsubscribe = pluginManager.eventBus.on('config:changed', handleConfigChange);
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
- const currentThemeDef = themes.find((t) => t.id === currentThemeId);
69
- const primaryColor = currentThemeDef
70
- ? `rgb(${currentThemeDef.colors['--color-primary'].replace(/ /g, ',')})`
71
- : '#6366f1';
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: primaryColor,
78
- colorBorder: themeMode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)',
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