@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.
- package/dist/app/.env +8 -0
- package/dist/app/.env.hbs +8 -0
- package/dist/app/README.md.hbs +16 -0
- package/dist/app/chatbi.config.ts.hbs +60 -0
- package/{templates/default/apps/main → dist/app}/index.html.hbs +3 -4
- package/dist/app/package.json.hbs +34 -0
- package/dist/app/src/App.tsx.hbs +92 -0
- package/dist/app/src/components/GlobalErrorBoundary.tsx.hbs +69 -0
- package/dist/app/src/components/GlobalSettingsModal.tsx.hbs +35 -0
- package/dist/app/src/components/LayoutSkeletons.tsx.hbs +79 -0
- package/dist/app/src/components/index.ts.hbs +2 -0
- package/dist/app/src/custom-antd.less.hbs +7 -0
- package/dist/app/src/features/settings/ConfigRenderStrategy.tsx.hbs +119 -0
- package/dist/app/src/features/settings/ExtensionSettings.tsx.hbs +52 -0
- package/dist/app/src/features/settings/PluginList.tsx.hbs +115 -0
- package/dist/app/src/features/settings/PluginSettings.tsx.hbs +123 -0
- package/dist/app/src/features/settings/SchemaSettingsRenderer.tsx.hbs +56 -0
- package/dist/app/src/hooks/useAppRoutes.ts.hbs +39 -0
- package/dist/app/src/hooks/usePluginLoader.ts.hbs +22 -0
- package/dist/app/src/hooks/usePluginSettings.ts.hbs +29 -0
- package/dist/app/src/hooks/useThemeSync.ts.hbs +108 -0
- package/dist/app/src/index.css.hbs +45 -0
- package/dist/app/src/layouts/BackgroundEffects.tsx.hbs +10 -0
- package/dist/app/src/layouts/MainContent.tsx.hbs +58 -0
- package/dist/app/src/layouts/SidebarNav.tsx.hbs +182 -0
- package/dist/app/src/main.tsx.hbs +43 -0
- package/dist/app/src/providers/AppProviders.tsx.hbs +36 -0
- package/dist/app/src/services/api/index.ts.hbs +37 -0
- package/dist/app/src/services/api/modules/auth.ts.hbs +18 -0
- package/dist/app/src/services/config-service.ts.hbs +48 -0
- package/dist/app/src/stores/storage-adapter.ts.hbs +29 -0
- package/dist/app/src/stores/useSessionStore.ts.hbs +22 -0
- package/dist/app/src/stores/useUIStore.ts.hbs +64 -0
- package/dist/app/tailwind.config.cjs.hbs +14 -0
- package/dist/app/tsconfig.json.hbs +26 -0
- package/dist/app/vite.config.ts.hbs +89 -0
- package/dist/index.js +5662 -4194
- package/{templates/default → dist/monorepo}/package.json.hbs +5 -0
- package/dist/monorepo/pnpm-workspace.yaml.hbs +10 -0
- package/dist/{default → monorepo}/tsconfig.json.hbs +3 -1
- package/{templates/default/plugins/demo-plugin → dist/plugin}/package.json.hbs +9 -3
- package/dist/plugin/src/index.tsx.hbs +90 -0
- package/dist/plugin/tsconfig.json.hbs +14 -0
- package/package.json +18 -6
- package/templates/app/.env.hbs +8 -0
- package/templates/app/README.md.hbs +16 -0
- package/templates/app/chatbi.config.ts.hbs +60 -0
- package/{dist/default/apps/main → templates/app}/index.html.hbs +3 -4
- package/templates/app/package.json.hbs +34 -0
- package/templates/app/src/App.tsx.hbs +92 -0
- package/templates/app/src/components/GlobalErrorBoundary.tsx.hbs +69 -0
- package/templates/app/src/components/GlobalSettingsModal.tsx.hbs +35 -0
- package/templates/app/src/components/LayoutSkeletons.tsx.hbs +79 -0
- package/templates/app/src/components/index.ts.hbs +2 -0
- package/templates/app/src/custom-antd.less.hbs +7 -0
- package/templates/app/src/features/settings/ConfigRenderStrategy.tsx.hbs +119 -0
- package/templates/app/src/features/settings/ExtensionSettings.tsx.hbs +52 -0
- package/templates/app/src/features/settings/PluginList.tsx.hbs +115 -0
- package/templates/app/src/features/settings/PluginSettings.tsx.hbs +123 -0
- package/templates/app/src/features/settings/SchemaSettingsRenderer.tsx.hbs +56 -0
- package/templates/app/src/hooks/useAppRoutes.ts.hbs +39 -0
- package/templates/app/src/hooks/usePluginLoader.ts.hbs +22 -0
- package/templates/app/src/hooks/usePluginSettings.ts.hbs +29 -0
- package/templates/app/src/hooks/useThemeSync.ts.hbs +108 -0
- package/templates/app/src/index.css.hbs +45 -0
- package/templates/app/src/layouts/BackgroundEffects.tsx.hbs +10 -0
- package/templates/app/src/layouts/MainContent.tsx.hbs +58 -0
- package/templates/app/src/layouts/SidebarNav.tsx.hbs +182 -0
- package/templates/app/src/main.tsx.hbs +43 -0
- package/templates/app/src/providers/AppProviders.tsx.hbs +36 -0
- package/templates/app/src/services/api/index.ts.hbs +37 -0
- package/templates/app/src/services/api/modules/auth.ts.hbs +18 -0
- package/templates/app/src/services/config-service.ts.hbs +48 -0
- package/templates/app/src/stores/storage-adapter.ts.hbs +29 -0
- package/templates/app/src/stores/useSessionStore.ts.hbs +22 -0
- package/templates/app/src/stores/useUIStore.ts.hbs +64 -0
- package/templates/app/tailwind.config.cjs.hbs +14 -0
- package/templates/app/tsconfig.json.hbs +26 -0
- package/templates/app/vite.config.ts.hbs +89 -0
- package/templates/monorepo/.gitignore.hbs +3 -0
- package/{dist/default → templates/monorepo}/package.json.hbs +5 -0
- package/templates/monorepo/pnpm-workspace.yaml.hbs +10 -0
- package/templates/{default → monorepo}/tsconfig.json.hbs +3 -1
- package/{dist/default/plugins/demo-plugin → templates/plugin}/package.json.hbs +9 -3
- package/templates/plugin/src/index.tsx.hbs +90 -0
- package/templates/plugin/tsconfig.json.hbs +14 -0
- package/dist/default/apps/main/package.json.hbs +0 -20
- package/dist/default/apps/main/src/App.tsx.hbs +0 -162
- package/dist/default/apps/main/src/components/NavIcon.tsx.hbs +0 -41
- package/dist/default/apps/main/src/hooks/usePluginLoader.ts.hbs +0 -25
- package/dist/default/apps/main/src/index.css.hbs +0 -8
- package/dist/default/apps/main/src/main.tsx.hbs +0 -13
- package/dist/default/apps/main/src/pages/Guide.tsx.hbs +0 -133
- package/dist/default/apps/main/tailwind.config.cjs.hbs +0 -17
- package/dist/default/apps/main/tsconfig.json.hbs +0 -10
- package/dist/default/apps/main/vite.config.ts.hbs +0 -16
- package/dist/default/plugins/demo-plugin/src/index.tsx.hbs +0 -44
- package/dist/default/plugins/demo-plugin/tsconfig.json.hbs +0 -10
- package/dist/default/pnpm-workspace.yaml.hbs +0 -3
- package/templates/default/apps/main/package.json.hbs +0 -20
- package/templates/default/apps/main/src/App.tsx.hbs +0 -162
- package/templates/default/apps/main/src/components/NavIcon.tsx.hbs +0 -41
- package/templates/default/apps/main/src/hooks/usePluginLoader.ts.hbs +0 -25
- package/templates/default/apps/main/src/index.css.hbs +0 -8
- package/templates/default/apps/main/src/main.tsx.hbs +0 -13
- package/templates/default/apps/main/src/pages/Guide.tsx.hbs +0 -133
- package/templates/default/apps/main/tailwind.config.cjs.hbs +0 -17
- package/templates/default/apps/main/tsconfig.json.hbs +0 -10
- package/templates/default/apps/main/vite.config.ts.hbs +0 -16
- package/templates/default/plugins/demo-plugin/src/index.tsx.hbs +0 -44
- package/templates/default/plugins/demo-plugin/tsconfig.json.hbs +0 -10
- package/templates/default/pnpm-workspace.yaml.hbs +0 -3
- /package/dist/{default/apps/main → app}/postcss.config.cjs.hbs +0 -0
- /package/{templates/default → dist/monorepo}/.gitignore.hbs +0 -0
- /package/dist/{default → monorepo}/README.md.hbs +0 -0
- /package/templates/{default/apps/main → app}/postcss.config.cjs.hbs +0 -0
- /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
|
+
};
|