@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,119 @@
1
+ import type { PluginConfigItem } from '@chatbi-v/core';
2
+ import { Form,Input, InputNumber, Select, Switch } from 'antd';
3
+ import React from 'react';
4
+
5
+ /**
6
+ * 配置渲染策略接口
7
+ */
8
+ export type ConfigRenderer = (item: PluginConfigItem) => React.ReactNode;
9
+
10
+ /**
11
+ * 配置渲染策略工厂
12
+ * @description 使用策略模式管理不同类型的配置项渲染逻辑,便于扩展新的配置类型
13
+ */
14
+ class ConfigStrategyFactory {
15
+ private strategies: Map<string, ConfigRenderer> = new Map();
16
+
17
+ constructor() {
18
+ this.registerDefaults();
19
+ }
20
+
21
+ /**
22
+ * 注册默认的渲染策略
23
+ */
24
+ private registerDefaults() {
25
+ // Boolean -> Switch
26
+ this.register('boolean', (item) => (
27
+ <Form.Item
28
+ key={item.key}
29
+ label={item.label}
30
+ name={item.key}
31
+ tooltip={item.description}
32
+ extra={item.description}
33
+ valuePropName="checked"
34
+ >
35
+ <Switch />
36
+ </Form.Item>
37
+ ));
38
+
39
+ // Number -> InputNumber
40
+ this.register('number', (item) => (
41
+ <Form.Item
42
+ key={item.key}
43
+ label={item.label}
44
+ name={item.key}
45
+ tooltip={item.description}
46
+ extra={item.description}
47
+ >
48
+ <InputNumber min={item.min} max={item.max} className="w-full" />
49
+ </Form.Item>
50
+ ));
51
+
52
+ // Select -> Select
53
+ this.register('select', (item) => (
54
+ <Form.Item
55
+ key={item.key}
56
+ label={item.label}
57
+ name={item.key}
58
+ tooltip={item.description}
59
+ extra={item.description}
60
+ >
61
+ <Select options={item.options} mode={item.mode} />
62
+ </Form.Item>
63
+ ));
64
+
65
+ // String -> Input
66
+ this.register('string', (item) => (
67
+ <Form.Item
68
+ key={item.key}
69
+ label={item.label}
70
+ name={item.key}
71
+ tooltip={item.description}
72
+ extra={item.description}
73
+ >
74
+ <Input />
75
+ </Form.Item>
76
+ ));
77
+
78
+ // Default fallback
79
+ this.register('default', (item) => (
80
+ <Form.Item
81
+ key={item.key}
82
+ label={item.label}
83
+ name={item.key}
84
+ tooltip={item.description}
85
+ extra={item.description}
86
+ >
87
+ <Input />
88
+ </Form.Item>
89
+ ));
90
+ }
91
+
92
+ /**
93
+ * 注册自定义渲染策略
94
+ * @param type 配置类型
95
+ * @param renderer 渲染函数
96
+ */
97
+ register(type: string, renderer: ConfigRenderer) {
98
+ this.strategies.set(type, renderer);
99
+ }
100
+
101
+ /**
102
+ * 获取渲染策略
103
+ * @param type 配置类型
104
+ */
105
+ getRenderer(type: string): ConfigRenderer {
106
+ return this.strategies.get(type) || this.strategies.get('default')!;
107
+ }
108
+
109
+ /**
110
+ * 渲染配置项
111
+ * @param item 配置定义
112
+ */
113
+ render(item: PluginConfigItem): React.ReactNode {
114
+ const renderer = this.getRenderer(item.type);
115
+ return renderer(item);
116
+ }
117
+ }
118
+
119
+ export const configStrategyFactory = new ConfigStrategyFactory();
@@ -0,0 +1,52 @@
1
+ import { Plugin, PluginExtension, Slot } from '@chatbi-v/core';
2
+ import { Card, Divider, Empty } from 'antd';
3
+ import React from 'react';
4
+
5
+ import { SchemaSettingsRenderer } from './SchemaSettingsRenderer';
6
+
7
+ interface ExtensionSettingsProps {
8
+ plugin: Plugin;
9
+ }
10
+
11
+ export const ExtensionSettings: React.FC<ExtensionSettingsProps> = ({ plugin }) => {
12
+ const settingsExt = plugin.metadata.extensions?.find(e => e.slot === Slot.Settings);
13
+ const hasSchema = plugin.metadata.configuration && plugin.metadata.configuration.length > 0;
14
+
15
+ if (!settingsExt && !hasSchema) {
16
+ return (
17
+ <div className="h-full flex flex-col items-center justify-center">
18
+ <Empty description={<span style={{ color: 'rgb(var(--color-text-muted))' }}>该插件暂无配置项</span>} />
19
+ </div>
20
+ );
21
+ }
22
+
23
+ const title = settingsExt?.meta?.label || `${plugin.metadata.name} 配置`;
24
+
25
+ return (
26
+ <div className="max-w-3xl mx-auto">
27
+ <div className="mb-6">
28
+ <h2 className="text-xl font-medium mb-1" style={{ color: 'rgb(var(--color-text-main))' }}>{title}</h2>
29
+ <p className="text-sm" style={{ color: 'rgb(var(--color-text-muted))' }}>
30
+ {plugin.metadata.description}
31
+ </p>
32
+ </div>
33
+
34
+ <div
35
+ className="rounded-lg p-6 border transition-colors duration-200"
36
+ style={{
37
+ backgroundColor: 'rgb(var(--color-surface))',
38
+ borderColor: 'rgb(var(--color-border))',
39
+ }}
40
+ >
41
+ {/* 1. Custom Component Settings */}
42
+ {settingsExt && <settingsExt.component />}
43
+
44
+ {/* Divider if both exist */}
45
+ {settingsExt && hasSchema && <Divider style={{ borderColor: 'rgb(var(--color-border))', margin: '24px 0' }} />}
46
+
47
+ {/* 2. Schema Settings */}
48
+ {hasSchema && <SchemaSettingsRenderer plugin={plugin} />}
49
+ </div>
50
+ </div>
51
+ );
52
+ };
@@ -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
+ };