@chatbi-v/cli 1.0.7 → 1.0.9

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 (117) hide show
  1. package/dist/app/.env +8 -0
  2. package/dist/app/.env.hbs +8 -0
  3. package/dist/app/README.md.hbs +16 -0
  4. package/dist/app/chatbi.config.ts.hbs +60 -0
  5. package/{templates/default/apps/main → dist/app}/index.html.hbs +3 -4
  6. package/dist/app/package.json.hbs +34 -0
  7. package/dist/app/src/App.tsx.hbs +92 -0
  8. package/dist/app/src/components/GlobalErrorBoundary.tsx.hbs +69 -0
  9. package/dist/app/src/components/GlobalSettingsModal.tsx.hbs +35 -0
  10. package/dist/app/src/components/LayoutSkeletons.tsx.hbs +79 -0
  11. package/dist/app/src/components/index.ts.hbs +2 -0
  12. package/dist/app/src/custom-antd.less.hbs +7 -0
  13. package/dist/app/src/features/settings/ConfigRenderStrategy.tsx.hbs +119 -0
  14. package/dist/app/src/features/settings/ExtensionSettings.tsx.hbs +52 -0
  15. package/dist/app/src/features/settings/PluginList.tsx.hbs +115 -0
  16. package/dist/app/src/features/settings/PluginSettings.tsx.hbs +123 -0
  17. package/dist/app/src/features/settings/SchemaSettingsRenderer.tsx.hbs +56 -0
  18. package/dist/app/src/hooks/useAppRoutes.ts.hbs +39 -0
  19. package/dist/app/src/hooks/usePluginLoader.ts.hbs +22 -0
  20. package/dist/app/src/hooks/usePluginSettings.ts.hbs +29 -0
  21. package/dist/app/src/hooks/useThemeSync.ts.hbs +108 -0
  22. package/dist/app/src/index.css.hbs +45 -0
  23. package/dist/app/src/layouts/BackgroundEffects.tsx.hbs +10 -0
  24. package/dist/app/src/layouts/MainContent.tsx.hbs +58 -0
  25. package/dist/app/src/layouts/SidebarNav.tsx.hbs +182 -0
  26. package/dist/app/src/main.tsx.hbs +43 -0
  27. package/dist/app/src/providers/AppProviders.tsx.hbs +36 -0
  28. package/dist/app/src/services/api/index.ts.hbs +37 -0
  29. package/dist/app/src/services/api/modules/auth.ts.hbs +18 -0
  30. package/dist/app/src/services/config-service.ts.hbs +48 -0
  31. package/dist/app/src/stores/storage-adapter.ts.hbs +29 -0
  32. package/dist/app/src/stores/useSessionStore.ts.hbs +22 -0
  33. package/dist/app/src/stores/useUIStore.ts.hbs +64 -0
  34. package/dist/app/tailwind.config.cjs.hbs +14 -0
  35. package/dist/app/tsconfig.json.hbs +26 -0
  36. package/dist/app/vite.config.ts.hbs +89 -0
  37. package/dist/index.js +5662 -4194
  38. package/{templates/default → dist/monorepo}/package.json.hbs +5 -0
  39. package/dist/monorepo/pnpm-workspace.yaml.hbs +10 -0
  40. package/dist/{default → monorepo}/tsconfig.json.hbs +3 -1
  41. package/{templates/default/plugins/demo-plugin → dist/plugin}/package.json.hbs +9 -3
  42. package/dist/plugin/src/index.tsx.hbs +90 -0
  43. package/dist/plugin/tsconfig.json.hbs +14 -0
  44. package/package.json +18 -6
  45. package/templates/app/.env.hbs +8 -0
  46. package/templates/app/README.md.hbs +16 -0
  47. package/templates/app/chatbi.config.ts.hbs +60 -0
  48. package/{dist/default/apps/main → templates/app}/index.html.hbs +3 -4
  49. package/templates/app/package.json.hbs +34 -0
  50. package/templates/app/src/App.tsx.hbs +92 -0
  51. package/templates/app/src/components/GlobalErrorBoundary.tsx.hbs +69 -0
  52. package/templates/app/src/components/GlobalSettingsModal.tsx.hbs +35 -0
  53. package/templates/app/src/components/LayoutSkeletons.tsx.hbs +79 -0
  54. package/templates/app/src/components/index.ts.hbs +2 -0
  55. package/templates/app/src/custom-antd.less.hbs +7 -0
  56. package/templates/app/src/features/settings/ConfigRenderStrategy.tsx.hbs +119 -0
  57. package/templates/app/src/features/settings/ExtensionSettings.tsx.hbs +52 -0
  58. package/templates/app/src/features/settings/PluginList.tsx.hbs +115 -0
  59. package/templates/app/src/features/settings/PluginSettings.tsx.hbs +123 -0
  60. package/templates/app/src/features/settings/SchemaSettingsRenderer.tsx.hbs +56 -0
  61. package/templates/app/src/hooks/useAppRoutes.ts.hbs +39 -0
  62. package/templates/app/src/hooks/usePluginLoader.ts.hbs +22 -0
  63. package/templates/app/src/hooks/usePluginSettings.ts.hbs +29 -0
  64. package/templates/app/src/hooks/useThemeSync.ts.hbs +108 -0
  65. package/templates/app/src/index.css.hbs +45 -0
  66. package/templates/app/src/layouts/BackgroundEffects.tsx.hbs +10 -0
  67. package/templates/app/src/layouts/MainContent.tsx.hbs +58 -0
  68. package/templates/app/src/layouts/SidebarNav.tsx.hbs +182 -0
  69. package/templates/app/src/main.tsx.hbs +43 -0
  70. package/templates/app/src/providers/AppProviders.tsx.hbs +36 -0
  71. package/templates/app/src/services/api/index.ts.hbs +37 -0
  72. package/templates/app/src/services/api/modules/auth.ts.hbs +18 -0
  73. package/templates/app/src/services/config-service.ts.hbs +48 -0
  74. package/templates/app/src/stores/storage-adapter.ts.hbs +29 -0
  75. package/templates/app/src/stores/useSessionStore.ts.hbs +22 -0
  76. package/templates/app/src/stores/useUIStore.ts.hbs +64 -0
  77. package/templates/app/tailwind.config.cjs.hbs +14 -0
  78. package/templates/app/tsconfig.json.hbs +26 -0
  79. package/templates/app/vite.config.ts.hbs +89 -0
  80. package/templates/monorepo/.gitignore.hbs +3 -0
  81. package/{dist/default → templates/monorepo}/package.json.hbs +5 -0
  82. package/templates/monorepo/pnpm-workspace.yaml.hbs +10 -0
  83. package/templates/{default → monorepo}/tsconfig.json.hbs +3 -1
  84. package/{dist/default/plugins/demo-plugin → templates/plugin}/package.json.hbs +9 -3
  85. package/templates/plugin/src/index.tsx.hbs +90 -0
  86. package/templates/plugin/tsconfig.json.hbs +14 -0
  87. package/dist/default/apps/main/package.json.hbs +0 -20
  88. package/dist/default/apps/main/src/App.tsx.hbs +0 -162
  89. package/dist/default/apps/main/src/components/NavIcon.tsx.hbs +0 -41
  90. package/dist/default/apps/main/src/hooks/usePluginLoader.ts.hbs +0 -25
  91. package/dist/default/apps/main/src/index.css.hbs +0 -8
  92. package/dist/default/apps/main/src/main.tsx.hbs +0 -13
  93. package/dist/default/apps/main/src/pages/Guide.tsx.hbs +0 -133
  94. package/dist/default/apps/main/tailwind.config.cjs.hbs +0 -17
  95. package/dist/default/apps/main/tsconfig.json.hbs +0 -10
  96. package/dist/default/apps/main/vite.config.ts.hbs +0 -16
  97. package/dist/default/plugins/demo-plugin/src/index.tsx.hbs +0 -44
  98. package/dist/default/plugins/demo-plugin/tsconfig.json.hbs +0 -10
  99. package/dist/default/pnpm-workspace.yaml.hbs +0 -3
  100. package/templates/default/apps/main/package.json.hbs +0 -20
  101. package/templates/default/apps/main/src/App.tsx.hbs +0 -162
  102. package/templates/default/apps/main/src/components/NavIcon.tsx.hbs +0 -41
  103. package/templates/default/apps/main/src/hooks/usePluginLoader.ts.hbs +0 -25
  104. package/templates/default/apps/main/src/index.css.hbs +0 -8
  105. package/templates/default/apps/main/src/main.tsx.hbs +0 -13
  106. package/templates/default/apps/main/src/pages/Guide.tsx.hbs +0 -133
  107. package/templates/default/apps/main/tailwind.config.cjs.hbs +0 -17
  108. package/templates/default/apps/main/tsconfig.json.hbs +0 -10
  109. package/templates/default/apps/main/vite.config.ts.hbs +0 -16
  110. package/templates/default/plugins/demo-plugin/src/index.tsx.hbs +0 -44
  111. package/templates/default/plugins/demo-plugin/tsconfig.json.hbs +0 -10
  112. package/templates/default/pnpm-workspace.yaml.hbs +0 -3
  113. /package/dist/{default/apps/main → app}/postcss.config.cjs.hbs +0 -0
  114. /package/{templates/default → dist/monorepo}/.gitignore.hbs +0 -0
  115. /package/dist/{default → monorepo}/README.md.hbs +0 -0
  116. /package/templates/{default/apps/main → app}/postcss.config.cjs.hbs +0 -0
  117. /package/templates/{default → monorepo}/README.md.hbs +0 -0
@@ -0,0 +1,115 @@
1
+ import { SettingOutlined } from '@ant-design/icons';
2
+ import { Plugin,pluginManager } from '@chatbi-v/core';
3
+ import { Button, Empty, InputNumber, Switch, Tabs, Tag, Tooltip } from 'antd';
4
+ import React, { useCallback, useMemo } from 'react';
5
+
6
+ interface PluginListProps {
7
+ plugins: Plugin[];
8
+ onToggle: (id: string, enabled: boolean) => void;
9
+ onOrderChange: (id: string, order: number) => void;
10
+ onGoToSettings: (plugin: Plugin) => void;
11
+ }
12
+
13
+ export const PluginList: React.FC<PluginListProps> = ({
14
+ plugins,
15
+ onToggle,
16
+ onOrderChange,
17
+ onGoToSettings,
18
+ }) => {
19
+
20
+ const renderPluginItem = useCallback((plugin: Plugin) => {
21
+ const state = pluginManager.getPluginState(plugin.id);
22
+ const hasSettings = pluginManager.getUnifiedCapabilities(plugin.id).configurable;
23
+
24
+ return (
25
+ <div
26
+ key={plugin.id}
27
+ className="rounded-lg px-4 py-3 shadow-sm flex items-center justify-between border transition-colors duration-200"
28
+ style={{
29
+ backgroundColor: 'rgb(var(--color-surface))',
30
+ borderColor: 'rgb(var(--color-border))',
31
+ }}
32
+ >
33
+ <div className="flex-1">
34
+ <div className="flex items-center gap-2 mb-1">
35
+ <span
36
+ className="font-medium"
37
+ style={{ color: 'rgb(var(--color-text-main))' }}
38
+ >
39
+ {plugin.metadata.name}
40
+ </span>
41
+ <Tag color={plugin.metadata.type === 'business' ? 'blue' : 'green'}>{plugin.metadata.type}</Tag>
42
+ </div>
43
+ <div>
44
+ <div className="text-xs mb-1 font-mono" style={{ color: 'rgb(var(--color-text-muted))' }}>{plugin.id}</div>
45
+ <div className="text-sm" style={{ color: 'rgb(var(--color-text-muted))' }}>
46
+ {plugin.metadata.description || '暂无描述'}
47
+ </div>
48
+ </div>
49
+ </div>
50
+
51
+ <div className="flex items-center gap-4 ml-4">
52
+ {hasSettings && (
53
+ <Tooltip title="插件设置">
54
+ <Button
55
+ type="text"
56
+ icon={<SettingOutlined style={{ color: 'rgb(var(--color-text-muted))' }} />}
57
+ onClick={() => onGoToSettings(plugin)}
58
+ />
59
+ </Tooltip>
60
+ )}
61
+ <div className="flex items-center gap-2">
62
+ <span className="text-sm" style={{ color: 'rgb(var(--color-text-muted))' }}>排序:</span>
63
+ <InputNumber
64
+ size="small"
65
+ className="w-16"
66
+ value={state.order}
67
+ onChange={(val) => onOrderChange(plugin.id, val || 0)}
68
+ />
69
+ </div>
70
+ <Switch
71
+ checkedChildren="启用"
72
+ unCheckedChildren="禁用"
73
+ checked={state.enabled}
74
+ onChange={(checked) => onToggle(plugin.id, checked)}
75
+ />
76
+ </div>
77
+ </div>
78
+ );
79
+ }, [onGoToSettings, onOrderChange, onToggle]);
80
+
81
+ const items = useMemo(() => {
82
+ const typeMap = {
83
+ all: '全部',
84
+ business: '业务插件',
85
+ renderer: '渲染插件',
86
+ theme: '主题插件',
87
+ functional: '功能插件',
88
+ view: '视图插件',
89
+ };
90
+
91
+ const categories = ['all', 'business', 'renderer', 'theme', 'functional', 'view'];
92
+
93
+ return categories.map(key => {
94
+ const filteredPlugins = key === 'all'
95
+ ? plugins
96
+ : plugins.filter(p => p.metadata.type === key);
97
+
98
+ return {
99
+ key,
100
+ label: typeMap[key as keyof typeof typeMap],
101
+ children: filteredPlugins.length > 0 ? (
102
+ <div className="flex flex-col gap-3 pt-2">
103
+ {filteredPlugins.map(renderPluginItem)}
104
+ </div>
105
+ ) : (
106
+ <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无该类型插件" className="my-8" />
107
+ )
108
+ };
109
+ });
110
+ }, [plugins, renderPluginItem]);
111
+
112
+ return (
113
+ <Tabs defaultActiveKey="all" items={items} />
114
+ );
115
+ };
@@ -0,0 +1,123 @@
1
+ import { AppstoreOutlined, DownloadOutlined, SettingOutlined } from '@ant-design/icons';
2
+ import { Plugin, pluginManager } from '@chatbi-v/core';
3
+ import type { MenuProps } from 'antd';
4
+ import { Button, Card,Layout, Menu, Switch } from 'antd';
5
+ import React, { useMemo, useState } from 'react';
6
+
7
+ import { usePluginSettings } from '../../hooks/usePluginSettings';
8
+ import { exportSystemConfig } from '../../services/config-service';
9
+ import { useUIStore } from '../../stores/useUIStore';
10
+ import { ExtensionSettings } from './ExtensionSettings';
11
+ import { PluginList } from './PluginList';
12
+
13
+ const { Sider, Content } = Layout;
14
+
15
+ export const PluginSettings: React.FC = () => {
16
+ const { plugins, togglePlugin, setPluginOrder } = usePluginSettings();
17
+ const [selectedKey, setSelectedKey] = useState('manager');
18
+
19
+ const handleToggle = (id: string, enabled: boolean) => {
20
+ togglePlugin(id, enabled);
21
+ };
22
+
23
+ const handleOrderChange = (id: string, order: number) => {
24
+ setPluginOrder(id, order);
25
+ };
26
+
27
+ const handleGoToSettings = (plugin: Plugin) => {
28
+ setSelectedKey(plugin.id);
29
+ };
30
+
31
+ const handleExportConfig = () => {
32
+ exportSystemConfig();
33
+ };
34
+
35
+ const menuItems: MenuProps['items'] = useMemo(() => {
36
+ const items: MenuProps['items'] = [
37
+ { key: 'manager', label: '插件管理', icon: <AppstoreOutlined /> },
38
+ { type: 'divider' },
39
+ {
40
+ key: 'group-settings',
41
+ label: '插件配置',
42
+ type: 'group',
43
+ children: plugins
44
+ .filter(p => pluginManager.getUnifiedCapabilities(p.id).configurable)
45
+ .map(p => ({
46
+ key: p.id,
47
+ label: p.metadata.name,
48
+ icon: <SettingOutlined />
49
+ }))
50
+ }
51
+ ];
52
+ return items;
53
+ }, [plugins]);
54
+
55
+ const renderContent = () => {
56
+ if (selectedKey === 'manager') {
57
+ return (
58
+ <div className="h-full flex flex-col">
59
+ <div className="flex justify-between items-center mb-6">
60
+ <div>
61
+ <h2 className="text-xl font-medium mb-1" style={{ color: 'rgb(var(--color-text-main))' }}>插件管理</h2>
62
+ <p className="text-sm" style={{ color: 'rgb(var(--color-text-muted))' }}>管理系统中安装的所有插件及其状态</p>
63
+ </div>
64
+ <Button icon={<DownloadOutlined />} onClick={handleExportConfig}>导出配置</Button>
65
+ </div>
66
+ <div className="flex-1 overflow-auto">
67
+ <PluginList
68
+ plugins={plugins}
69
+ onToggle={handleToggle}
70
+ onOrderChange={handleOrderChange}
71
+ onGoToSettings={handleGoToSettings}
72
+ />
73
+ </div>
74
+ </div>
75
+ );
76
+ }
77
+
78
+ const selectedPlugin = plugins.find(p => p.id === selectedKey);
79
+ if (selectedPlugin) {
80
+ return (
81
+ <div className="h-full overflow-auto">
82
+ <ExtensionSettings plugin={selectedPlugin} />
83
+ </div>
84
+ );
85
+ }
86
+
87
+ return null;
88
+ };
89
+
90
+ return (
91
+ <Layout className="h-full bg-transparent">
92
+ <Sider
93
+ width={240}
94
+ theme="light"
95
+ className="border-r h-full"
96
+ style={{
97
+ backgroundColor: 'rgb(var(--color-surface))',
98
+ borderColor: 'rgb(var(--color-border))',
99
+ }}
100
+ >
101
+ <div className="overflow-auto h-full py-2">
102
+ <Menu
103
+ mode="inline"
104
+ selectedKeys={[selectedKey]}
105
+ onClick={(e) => setSelectedKey(e.key)}
106
+ items={menuItems}
107
+ style={{
108
+ backgroundColor: 'transparent',
109
+ borderRight: 'none',
110
+ color: 'rgb(var(--color-text-main))'
111
+ }}
112
+ />
113
+ </div>
114
+ </Sider>
115
+ <Content
116
+ className="h-full p-4 overflow-hidden"
117
+ style={{ backgroundColor: 'rgb(var(--color-surface))' }}
118
+ >
119
+ {renderContent()}
120
+ </Content>
121
+ </Layout>
122
+ );
123
+ };
@@ -0,0 +1,56 @@
1
+ import { Plugin } from '@chatbi-v/core';
2
+ import { pluginManager } from '@chatbi-v/core';
3
+ import { Form } from 'antd';
4
+ import React, { useEffect, useMemo } from 'react';
5
+
6
+ import { configStrategyFactory } from './ConfigRenderStrategy';
7
+
8
+ export const SchemaSettingsRenderer: React.FC<{ plugin: Plugin }> = ({ plugin }) => {
9
+ const [form] = Form.useForm();
10
+ const configSchema = useMemo(() => plugin.metadata.configuration || [], [plugin.metadata.configuration]);
11
+
12
+ useEffect(() => {
13
+ // Load config
14
+ const values: Record<string, any> = {};
15
+ const storage = pluginManager.getStorageManager().getContextStorage(plugin.id);
16
+
17
+ configSchema.forEach(item => {
18
+ // 优先从 StorageManager 获取 (自动处理 JSON 解析)
19
+ const stored = storage.get(item.key);
20
+
21
+ if (stored !== null) {
22
+ // 如果存储的是原始字符串 (旧数据兼容),可能需要类型转换
23
+ // 但 StorageManager.get 已经尝试 JSON.parse
24
+ // 这里做简单的类型校验兜底
25
+ if (item.type === 'boolean' && typeof stored === 'string') {
26
+ values[item.key] = stored === 'true';
27
+ } else if (item.type === 'number' && typeof stored === 'string') {
28
+ values[item.key] = Number(stored);
29
+ } else {
30
+ values[item.key] = stored;
31
+ }
32
+ } else {
33
+ values[item.key] = item.default;
34
+ }
35
+ });
36
+ form.setFieldsValue(values);
37
+ }, [plugin, configSchema, form]);
38
+
39
+ const handleValuesChange = (changedValues: any) => {
40
+ Object.entries(changedValues).forEach(([key, value]) => {
41
+ pluginManager.updatePluginConfig(plugin.id, key, value);
42
+ });
43
+ };
44
+
45
+ if (configSchema.length === 0) return null;
46
+
47
+ return (
48
+ <Form
49
+ form={form}
50
+ layout="vertical"
51
+ onValuesChange={handleValuesChange}
52
+ >
53
+ {configSchema.map(item => configStrategyFactory.render(item))}
54
+ </Form>
55
+ );
56
+ };
@@ -0,0 +1,39 @@
1
+ import { pluginManager, RouteConfig } from '@chatbi-v/core';
2
+ import { useEffect, useMemo, useState } from 'react';
3
+
4
+ export const useAppRoutes = () => {
5
+ // 使用 State 管理路由,以便在插件加载完成后触发更新
6
+ const [routes, setRoutes] = useState<RouteConfig[]>(() => pluginManager.getRoutes());
7
+
8
+ useEffect(() => {
9
+ // 2. 订阅插件状态变更 (加载/启用/禁用)
10
+ const unsubscribe = pluginManager.subscribe(() => {
11
+ const updatedRoutes = pluginManager.getRoutes();
12
+ setRoutes(updatedRoutes);
13
+ });
14
+
15
+ return () => {
16
+ unsubscribe();
17
+ };
18
+ }, []);
19
+
20
+ // 计算默认路由
21
+ // 策略:
22
+ // 1. 查找是否有显式标记为默认的路由
23
+ // 2. 回退到第一个可用路由
24
+ const defaultRoute = useMemo(() => {
25
+ if (routes.length === 0) return null;
26
+
27
+ // 优先尝试标记为 default 的路由
28
+ const markedDefault = routes.find(r => (r as any).meta?.default);
29
+ if (markedDefault) return markedDefault.path;
30
+
31
+ // 回退到第一个
32
+ return routes[0].path;
33
+ }, [routes]);
34
+
35
+ return {
36
+ routes,
37
+ defaultRoute
38
+ };
39
+ };
@@ -0,0 +1,22 @@
1
+ import { usePluginLoader as useCorePluginLoader } from '@chatbi-v/core';
2
+ import { useMemo } from 'react';
3
+
4
+ import { chatbiConfig } from '../../chatbi.config';
5
+ import { api } from '../services/api';
6
+
7
+ /**
8
+ * 应用级插件加载 Hook
9
+ * @description 封装核心库的 usePluginLoader,并注入应用特定的配置和服务
10
+ */
11
+ export const usePluginLoader = () => {
12
+ // 注入应用级配置和服务
13
+ const options = useMemo(() => ({
14
+ discoveryRules: chatbiConfig.system.discoveryRules,
15
+ pluginConfigs: chatbiConfig.plugins,
16
+ sharedContext: { api },
17
+ // 预注册内置插件 (可选,目前已通过 AutoLoader 自动发现)
18
+ registry: {}
19
+ }), []);
20
+
21
+ return useCorePluginLoader(options);
22
+ };
@@ -0,0 +1,29 @@
1
+ import { pluginManager } from '@chatbi-v/core';
2
+ import { Plugin } from '@chatbi-v/core';
3
+ import { useMemo, useState } from 'react';
4
+
5
+ export const usePluginSettings = () => {
6
+ // Local state to trigger re-renders when plugin state changes
7
+ const [refreshKey, setRefreshKey] = useState(0);
8
+
9
+ const plugins = useMemo(() => {
10
+ return pluginManager.getPlugins();
11
+ }, [refreshKey]);
12
+
13
+ const togglePlugin = (id: string, enabled: boolean) => {
14
+ pluginManager.togglePlugin(id, enabled);
15
+ setRefreshKey((prev) => prev + 1);
16
+ };
17
+
18
+ const setPluginOrder = (id: string, order: number) => {
19
+ pluginManager.setPluginOrder(id, order);
20
+ setRefreshKey((prev) => prev + 1);
21
+ };
22
+
23
+ return {
24
+ plugins,
25
+ togglePlugin,
26
+ setPluginOrder,
27
+ refreshKey
28
+ };
29
+ };
@@ -0,0 +1,108 @@
1
+ import { pluginManager, configManager } from '@chatbi-v/core';
2
+ import { themes } from '@chatbi-v/plugin-theme-manager';
3
+ import { theme } from 'antd';
4
+ import { useEffect, useMemo } from 'react';
5
+
6
+ import { useUIStore } from '../stores/useUIStore';
7
+
8
+ /**
9
+ * 主题同步 Hook
10
+ * @description 负责监听主题配置变更,同步 Ant Design 和 Tailwind 主题状态
11
+ */
12
+ export const useThemeSync = () => {
13
+ const {
14
+ currentThemeId,
15
+ setThemeId,
16
+ themeMode,
17
+ setThemeMode,
18
+ } = useUIStore();
19
+
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]);
33
+
34
+ // 2. 监听配置变更事件 (来自 settings 面板或 ThemeToggle)
35
+ 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
+ };
48
+
49
+ const unsubscribe = pluginManager.eventBus.on('config:changed', handleConfigChange);
50
+ return () => {
51
+ unsubscribe();
52
+ };
53
+ }, [setThemeId, setThemeMode]);
54
+
55
+ // 3. 同步 Tailwind Dark Mode 类名
56
+ useEffect(() => {
57
+ if (themeMode === 'dark') {
58
+ document.documentElement.classList.add('dark');
59
+ document.documentElement.removeAttribute('data-theme');
60
+ } else {
61
+ document.documentElement.classList.remove('dark');
62
+ document.documentElement.setAttribute('data-theme', 'light');
63
+ }
64
+ }, [themeMode]);
65
+
66
+ // 计算 Ant Design 主题配置
67
+ 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';
72
+
73
+ return {
74
+ primaryColor,
75
+ algorithm: themeMode === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm,
76
+ 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
+ },
104
+ };
105
+ }, [currentThemeId, themeMode]);
106
+
107
+ return antdThemeConfig;
108
+ };
@@ -0,0 +1,45 @@
1
+ @import 'antd/dist/reset.css';
2
+ @import './custom-antd.less'; /* 注入变量 */
3
+
4
+ @tailwind base;
5
+ @tailwind components;
6
+ @tailwind utilities;
7
+
8
+ @layer base {
9
+ :root {
10
+ /* Default Dark Theme (Slate/Indigo) */
11
+ --color-background: 15 23 42; /* slate-900 #0f172a */
12
+ --color-surface: 30 41 59; /* slate-800 #1e293b */
13
+ --color-primary: 99 102 241; /* indigo-500 #6366f1 */
14
+ --color-secondary: 168 85 247; /* purple-500 #a855f7 */
15
+ --color-accent: 34 211 238; /* cyan-400 #22d3ee */
16
+ --color-text-main: 226 232 240; /* slate-200 */
17
+ --color-text-muted: 148 163 184; /* slate-400 */
18
+ --color-border: 51 65 85; /* slate-700 */
19
+
20
+ /* Layout Variables Default (Compact) */
21
+ --layout-sidebar-width: 240px;
22
+ --layout-content-padding: 16px;
23
+ --layout-gap: 8px;
24
+ --layout-font-scale: 1;
25
+ }
26
+
27
+ [data-theme='light'] {
28
+ /* Light Theme */
29
+ --color-background: 248 250 252; /* slate-50 */
30
+ --color-surface: 255 255 255; /* white */
31
+ --color-primary: 79 70 229; /* indigo-600 */
32
+ --color-secondary: 147 51 234; /* purple-600 */
33
+ --color-accent: 6 182 212; /* cyan-500 */
34
+ --color-text-main: 15 23 42; /* slate-900 */
35
+ --color-text-muted: 100 116 139; /* slate-500 */
36
+ --color-border: 226 232 240; /* slate-200 */
37
+ }
38
+
39
+ body {
40
+ @apply bg-background text-text-main overflow-hidden antialiased;
41
+ }
42
+ }
43
+
44
+ @layer utilities {
45
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+
3
+ export const BackgroundEffects: React.FC = () => {
4
+ return (
5
+ <div className="fixed top-0 left-0 w-full h-full overflow-hidden pointer-events-none z-0">
6
+ <div className="absolute top-[-20%] left-[-10%] w-[50%] h-[50%] bg-primary/10 rounded-full blur-[120px]" />
7
+ <div className="absolute bottom-[-20%] right-[-10%] w-[40%] h-[40%] bg-secondary/10 rounded-full blur-[100px]" />
8
+ </div>
9
+ );
10
+ };
@@ -0,0 +1,58 @@
1
+ import { PluginSlot, Slot, StatusBarItemSkeleton } from '@chatbi-v/core';
2
+ import React from 'react';
3
+ import { Navigate, Route, Routes } from 'react-router-dom';
4
+
5
+ import { ContentSkeleton } from '../components/LayoutSkeletons';
6
+ import { useAppRoutes } from '../hooks/useAppRoutes';
7
+
8
+ interface MainContentProps {
9
+ pluginsLoaded: boolean;
10
+ sharedProps: any;
11
+ }
12
+
13
+ export const MainContent: React.FC<MainContentProps> = React.memo(({
14
+ pluginsLoaded,
15
+ sharedProps,
16
+ }) => {
17
+ const { routes, defaultRoute } = useAppRoutes();
18
+
19
+ return (
20
+ <main className="flex-1 flex flex-col relative z-10 overflow-hidden bg-white/50 dark:bg-black/20 backdrop-blur-sm">
21
+ {!pluginsLoaded ? (
22
+ <ContentSkeleton />
23
+ ) : (
24
+ <>
25
+ <div className="flex-1 relative overflow-hidden min-h-0">
26
+ <Routes>
27
+ {routes.map((route) => (
28
+ <Route
29
+ key={route.path}
30
+ path={route.path}
31
+ element={<route.component {...sharedProps} />}
32
+ />
33
+ ))}
34
+
35
+ {/* 默认路由重定向 */}
36
+ {defaultRoute ? (
37
+ <Route path="/" element={<Navigate to={defaultRoute} replace />} />
38
+ ) : (
39
+ <Route path="/" element={<div className="flex h-full items-center justify-center text-slate-500">请在设置中启用至少一个业务插件</div>} />
40
+ )}
41
+
42
+ {/* 404 / Catch-all: 重定向到默认路由 */}
43
+ <Route path="*" element={
44
+ defaultRoute ? <Navigate to={defaultRoute} replace /> : <Navigate to="/" replace />
45
+ } />
46
+ </Routes>
47
+ </div>
48
+
49
+ {/* Status Bar Slot */}
50
+ <div className="h-6 bg-slate-100 dark:bg-slate-900 border-t border-slate-200 dark:border-white/5 flex items-center px-2 text-xs text-slate-500 gap-4">
51
+ <div className="flex-1">Ready</div>
52
+ <PluginSlot slot={Slot.StatusBar} skeleton={<StatusBarItemSkeleton />} />
53
+ </div>
54
+ </>
55
+ )}
56
+ </main>
57
+ );
58
+ });