plugin-build-ui-template 1.0.1
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/client-v2.d.ts +2 -0
- package/client-v2.js +1 -0
- package/client.d.ts +2 -0
- package/client.js +1 -0
- package/dist/client/index.js +10 -0
- package/dist/client-v2/380.b4d1d20b1e27ac78.js +10 -0
- package/dist/client-v2/index.js +10 -0
- package/dist/externalVersion.js +22 -0
- package/dist/index.js +48 -0
- package/dist/server/actions/build.js +422 -0
- package/dist/server/collections/ai-build-ui-template-spaces.js +114 -0
- package/dist/server/index.js +48 -0
- package/dist/server/plugin.js +128 -0
- package/package.json +48 -0
- package/server.d.ts +2 -0
- package/server.js +1 -0
- package/src/client/BuildUITemplateManager.tsx +450 -0
- package/src/client/index.tsx +2 -0
- package/src/client/plugin.tsx +15 -0
- package/src/client-v2/index.tsx +1 -0
- package/src/client-v2/plugin.tsx +24 -0
- package/src/index.ts +2 -0
- package/src/server/actions/build.ts +454 -0
- package/src/server/collections/ai-build-ui-template-spaces.ts +83 -0
- package/src/server/index.ts +2 -0
- package/src/server/plugin.ts +125 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { useAPIClient } from '@nocobase/client';
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
Space,
|
|
6
|
+
Button,
|
|
7
|
+
Modal,
|
|
8
|
+
Form,
|
|
9
|
+
Input,
|
|
10
|
+
Select,
|
|
11
|
+
Radio,
|
|
12
|
+
Tag,
|
|
13
|
+
Typography,
|
|
14
|
+
List,
|
|
15
|
+
Progress,
|
|
16
|
+
Spin,
|
|
17
|
+
Alert,
|
|
18
|
+
message,
|
|
19
|
+
} from 'antd';
|
|
20
|
+
import {
|
|
21
|
+
PlayCircleOutlined,
|
|
22
|
+
DeleteOutlined,
|
|
23
|
+
PlusOutlined,
|
|
24
|
+
LayoutOutlined,
|
|
25
|
+
CheckCircleOutlined,
|
|
26
|
+
SyncOutlined,
|
|
27
|
+
ExclamationCircleOutlined,
|
|
28
|
+
ArrowRightOutlined,
|
|
29
|
+
} from '@ant-design/icons';
|
|
30
|
+
|
|
31
|
+
const { Title, Paragraph, Text } = Typography;
|
|
32
|
+
|
|
33
|
+
export const BuildUITemplateManager: React.FC = () => {
|
|
34
|
+
const api = useAPIClient();
|
|
35
|
+
const [spaces, setSpaces] = useState<any[]>([]);
|
|
36
|
+
const [collections, setCollections] = useState<any[]>([]);
|
|
37
|
+
const [services, setServices] = useState<any[]>([]);
|
|
38
|
+
const [models, setModels] = useState<any[]>([]);
|
|
39
|
+
const [loading, setLoading] = useState(false);
|
|
40
|
+
const [modalVisible, setModalVisible] = useState(false);
|
|
41
|
+
const [editingSpace, setEditingSpace] = useState<any | null>(null);
|
|
42
|
+
|
|
43
|
+
const [form] = Form.useForm();
|
|
44
|
+
const selectedService = Form.useWatch('llmService', form);
|
|
45
|
+
|
|
46
|
+
// 1. Fetch Spaces
|
|
47
|
+
const fetchSpaces = async () => {
|
|
48
|
+
setLoading(true);
|
|
49
|
+
try {
|
|
50
|
+
const res = await api.resource('aiBuildUiTemplateSpaces').list({
|
|
51
|
+
sort: ['-createdAt'],
|
|
52
|
+
});
|
|
53
|
+
setSpaces(res?.data?.data || []);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error('Failed to load spaces:', err);
|
|
56
|
+
message.error('Failed to load UI generation spaces');
|
|
57
|
+
} finally {
|
|
58
|
+
setLoading(false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// 2. Fetch Collections
|
|
63
|
+
const fetchCollections = async () => {
|
|
64
|
+
try {
|
|
65
|
+
const res = await api.resource('collections').list();
|
|
66
|
+
setCollections(res?.data?.data || []);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error('Failed to load collections:', err);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// 3. Fetch LLM Services
|
|
73
|
+
const fetchServices = async () => {
|
|
74
|
+
try {
|
|
75
|
+
const res = await api.resource('ai').listLLMServices();
|
|
76
|
+
setServices(res?.data?.data || []);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.error('Failed to load LLM services:', err);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// 4. Fetch Models
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!selectedService) {
|
|
85
|
+
setModels([]);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
api
|
|
89
|
+
.resource('ai')
|
|
90
|
+
.listModels({ llmService: selectedService })
|
|
91
|
+
.then((res) => {
|
|
92
|
+
setModels(res?.data?.data || []);
|
|
93
|
+
})
|
|
94
|
+
.catch((err) => {
|
|
95
|
+
console.error('Failed to load models:', err);
|
|
96
|
+
});
|
|
97
|
+
}, [selectedService, api]);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
fetchSpaces();
|
|
101
|
+
fetchCollections();
|
|
102
|
+
fetchServices();
|
|
103
|
+
|
|
104
|
+
// Auto refresh active builds every 3 seconds
|
|
105
|
+
const interval = setInterval(() => {
|
|
106
|
+
const hasActiveBuild = spaces.some((s) => s.status === 'building');
|
|
107
|
+
if (hasActiveBuild) {
|
|
108
|
+
api
|
|
109
|
+
.resource('aiBuildUiTemplateSpaces')
|
|
110
|
+
.list({ sort: ['-createdAt'] })
|
|
111
|
+
.then((res) => setSpaces(res?.data?.data || []))
|
|
112
|
+
.catch(() => undefined);
|
|
113
|
+
}
|
|
114
|
+
}, 3000);
|
|
115
|
+
|
|
116
|
+
return () => clearInterval(interval);
|
|
117
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
118
|
+
}, [spaces]);
|
|
119
|
+
|
|
120
|
+
// 5. Open modal for create/edit
|
|
121
|
+
const openModal = (space?: any) => {
|
|
122
|
+
if (space) {
|
|
123
|
+
setEditingSpace(space);
|
|
124
|
+
form.setFieldsValue({
|
|
125
|
+
title: space.title,
|
|
126
|
+
llmService: space.llmService,
|
|
127
|
+
model: space.model,
|
|
128
|
+
systemPrompt: space.systemPrompt,
|
|
129
|
+
promptRequirements: space.promptRequirements,
|
|
130
|
+
type: space.type,
|
|
131
|
+
targetCollection: space.targetCollection,
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
setEditingSpace(null);
|
|
135
|
+
form.resetFields();
|
|
136
|
+
form.setFieldsValue({
|
|
137
|
+
type: 'block',
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
setModalVisible(true);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// 6. Save Space
|
|
144
|
+
const handleSave = async () => {
|
|
145
|
+
try {
|
|
146
|
+
const values = await form.validateFields();
|
|
147
|
+
if (editingSpace) {
|
|
148
|
+
await api.resource('aiBuildUiTemplateSpaces').update({
|
|
149
|
+
filterByTk: editingSpace.id,
|
|
150
|
+
values,
|
|
151
|
+
});
|
|
152
|
+
message.success('Space updated successfully');
|
|
153
|
+
} else {
|
|
154
|
+
await api.resource('aiBuildUiTemplateSpaces').create({
|
|
155
|
+
values,
|
|
156
|
+
});
|
|
157
|
+
message.success('Space created successfully');
|
|
158
|
+
}
|
|
159
|
+
setModalVisible(false);
|
|
160
|
+
fetchSpaces();
|
|
161
|
+
} catch (err: any) {
|
|
162
|
+
if (err?.name !== 'ValidateError') {
|
|
163
|
+
message.error(err?.message || 'Failed to save space settings');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// 7. Delete Space
|
|
169
|
+
const handleDelete = async (id: string) => {
|
|
170
|
+
Modal.confirm({
|
|
171
|
+
title: 'Are you sure to delete this generation space?',
|
|
172
|
+
icon: <ExclamationCircleOutlined />,
|
|
173
|
+
okType: 'danger',
|
|
174
|
+
onOk: async () => {
|
|
175
|
+
try {
|
|
176
|
+
await api.resource('aiBuildUiTemplateSpaces').destroy({
|
|
177
|
+
filterByTk: id,
|
|
178
|
+
});
|
|
179
|
+
message.success('Space deleted');
|
|
180
|
+
fetchSpaces();
|
|
181
|
+
} catch (err) {
|
|
182
|
+
message.error('Failed to delete space');
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// 8. Trigger AI build
|
|
189
|
+
const handleBuild = async (id: string) => {
|
|
190
|
+
try {
|
|
191
|
+
message.loading('Triggering AI generation...', 1);
|
|
192
|
+
await api.resource('aiBuildUiTemplateSpaces').build({
|
|
193
|
+
filterByTk: id,
|
|
194
|
+
});
|
|
195
|
+
message.success('UI Template generation task started successfully!');
|
|
196
|
+
fetchSpaces();
|
|
197
|
+
} catch (err: any) {
|
|
198
|
+
message.error(err?.message || 'Failed to trigger build');
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Render Helpers
|
|
203
|
+
const renderStatusTag = (status: string, phase: string) => {
|
|
204
|
+
if (status === 'completed') {
|
|
205
|
+
return (
|
|
206
|
+
<Tag color="success" icon={<CheckCircleOutlined />}>
|
|
207
|
+
Completed
|
|
208
|
+
</Tag>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
if (status === 'error') {
|
|
212
|
+
return (
|
|
213
|
+
<Tag color="error" icon={<ExclamationCircleOutlined />}>
|
|
214
|
+
Failed
|
|
215
|
+
</Tag>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
if (status === 'building') {
|
|
219
|
+
return (
|
|
220
|
+
<Tag color="processing" icon={<SyncOutlined spin />}>
|
|
221
|
+
Generating ({phase || 'queued'})
|
|
222
|
+
</Tag>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return <Tag color="default">Draft</Tag>;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const getPhaseProgress = (phase: string) => {
|
|
229
|
+
switch (phase) {
|
|
230
|
+
case 'queued':
|
|
231
|
+
return 10;
|
|
232
|
+
case 'preparing':
|
|
233
|
+
return 25;
|
|
234
|
+
case 'generating':
|
|
235
|
+
return 60;
|
|
236
|
+
case 'saving':
|
|
237
|
+
return 90;
|
|
238
|
+
case 'completed':
|
|
239
|
+
return 100;
|
|
240
|
+
default:
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<div style={{ padding: '24px', maxWidth: '1200px', margin: '0 auto' }}>
|
|
247
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
|
|
248
|
+
<div>
|
|
249
|
+
<Title level={2}>AI UI Template Builder</Title>
|
|
250
|
+
<Paragraph type="secondary">
|
|
251
|
+
Generate stunning custom UI Blocks and Popups in seconds using state-of-the-art LLMs, then reuse them in
|
|
252
|
+
NocoBase v2 dynamic forms, dashboards and listings.
|
|
253
|
+
</Paragraph>
|
|
254
|
+
</div>
|
|
255
|
+
<Button type="primary" icon={<PlusOutlined />} onClick={() => openModal()}>
|
|
256
|
+
New Generation Space
|
|
257
|
+
</Button>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<List
|
|
261
|
+
loading={loading && spaces.length === 0}
|
|
262
|
+
dataSource={spaces}
|
|
263
|
+
renderItem={(space: any) => (
|
|
264
|
+
<Card
|
|
265
|
+
key={space.id}
|
|
266
|
+
style={{ marginBottom: '20px', borderRadius: '12px', boxShadow: '0 4px 12px rgba(0,0,0,0.05)' }}
|
|
267
|
+
actions={[
|
|
268
|
+
<Button
|
|
269
|
+
key="generate"
|
|
270
|
+
type="link"
|
|
271
|
+
icon={<PlayCircleOutlined />}
|
|
272
|
+
onClick={() => handleBuild(space.id)}
|
|
273
|
+
disabled={space.status === 'building'}
|
|
274
|
+
>
|
|
275
|
+
Generate
|
|
276
|
+
</Button>,
|
|
277
|
+
<Button key="edit" type="link" onClick={() => openModal(space)}>
|
|
278
|
+
Edit Settings
|
|
279
|
+
</Button>,
|
|
280
|
+
<Button key="delete" type="link" danger icon={<DeleteOutlined />} onClick={() => handleDelete(space.id)}>
|
|
281
|
+
Delete
|
|
282
|
+
</Button>,
|
|
283
|
+
]}
|
|
284
|
+
>
|
|
285
|
+
<div
|
|
286
|
+
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start', marginBottom: '16px' }}
|
|
287
|
+
>
|
|
288
|
+
<div>
|
|
289
|
+
<Space align="baseline">
|
|
290
|
+
<Title level={4} style={{ margin: 0 }}>
|
|
291
|
+
{space.title}
|
|
292
|
+
</Title>
|
|
293
|
+
{renderStatusTag(space.status, space.buildPhase)}
|
|
294
|
+
</Space>
|
|
295
|
+
<div style={{ marginTop: '8px' }}>
|
|
296
|
+
<Tag color="blue">{space.type === 'popup' ? 'Popup Template' : 'Block Template'}</Tag>
|
|
297
|
+
{space.targetCollection && <Tag color="purple">Collection: {space.targetCollection}</Tag>}
|
|
298
|
+
<Tag color="cyan">
|
|
299
|
+
LLM: {space.llmService} ({space.model})
|
|
300
|
+
</Tag>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
{space.templateUid && (
|
|
305
|
+
<Button type="primary" ghost icon={<ArrowRightOutlined />} href="/admin/settings/ui-templates.block">
|
|
306
|
+
View Template Library
|
|
307
|
+
</Button>
|
|
308
|
+
)}
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<Paragraph
|
|
312
|
+
style={{ background: '#f5f5f5', padding: '12px', borderRadius: '8px', borderLeft: '4px solid #1890ff' }}
|
|
313
|
+
>
|
|
314
|
+
<Text strong>User Requirements: </Text>
|
|
315
|
+
{space.promptRequirements || 'No specific requirements typed.'}
|
|
316
|
+
</Paragraph>
|
|
317
|
+
|
|
318
|
+
{space.status === 'building' && (
|
|
319
|
+
<div style={{ marginTop: '16px', background: '#fafafa', padding: '16px', borderRadius: '8px' }}>
|
|
320
|
+
<Text type="secondary">Build progress: </Text>
|
|
321
|
+
<Progress percent={getPhaseProgress(space.buildPhase)} status="active" strokeColor="#1890ff" />
|
|
322
|
+
<div style={{ marginTop: '8px', fontFamily: 'monospace', color: '#666' }}>
|
|
323
|
+
<Spin size="small" style={{ marginRight: '8px' }} />
|
|
324
|
+
{space.buildLog || 'AI is initiating task...'}
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
)}
|
|
328
|
+
|
|
329
|
+
{space.status === 'completed' && space.buildLog && (
|
|
330
|
+
<Alert
|
|
331
|
+
message="Build Complete"
|
|
332
|
+
description={space.buildLog}
|
|
333
|
+
type="success"
|
|
334
|
+
showIcon
|
|
335
|
+
style={{ marginTop: '12px' }}
|
|
336
|
+
/>
|
|
337
|
+
)}
|
|
338
|
+
|
|
339
|
+
{space.status === 'error' && space.buildLog && (
|
|
340
|
+
<Alert
|
|
341
|
+
message="Generation Failed"
|
|
342
|
+
description={space.buildLog}
|
|
343
|
+
type="error"
|
|
344
|
+
showIcon
|
|
345
|
+
style={{ marginTop: '12px' }}
|
|
346
|
+
/>
|
|
347
|
+
)}
|
|
348
|
+
</Card>
|
|
349
|
+
)}
|
|
350
|
+
/>
|
|
351
|
+
|
|
352
|
+
<Modal
|
|
353
|
+
title={editingSpace ? 'Edit Generation Settings' : 'New UI Generation Space'}
|
|
354
|
+
open={modalVisible}
|
|
355
|
+
onOk={handleSave}
|
|
356
|
+
onCancel={() => setModalVisible(false)}
|
|
357
|
+
width={720}
|
|
358
|
+
destroyOnClose
|
|
359
|
+
>
|
|
360
|
+
<Form form={form} layout="vertical" style={{ marginTop: '16px' }}>
|
|
361
|
+
<Form.Item
|
|
362
|
+
name="title"
|
|
363
|
+
label={<Text strong>Space Name</Text>}
|
|
364
|
+
rules={[{ required: true, message: 'Please enter a space name' }]}
|
|
365
|
+
>
|
|
366
|
+
<Input placeholder="e.g. Sales KPI Dashboard, Customer Contact Form" />
|
|
367
|
+
</Form.Item>
|
|
368
|
+
|
|
369
|
+
<Space size="large" style={{ display: 'flex', width: '100%' }}>
|
|
370
|
+
<Form.Item
|
|
371
|
+
name="llmService"
|
|
372
|
+
label={<Text strong>AI Service</Text>}
|
|
373
|
+
rules={[{ required: true, message: 'Please select an LLM Service' }]}
|
|
374
|
+
style={{ flex: 1, minWidth: '300px' }}
|
|
375
|
+
>
|
|
376
|
+
<Select placeholder="Select Service" onChange={() => form.setFieldValue('model', undefined)}>
|
|
377
|
+
{services.map((s) => (
|
|
378
|
+
<Select.Option key={s.name} value={s.name}>
|
|
379
|
+
{s.title || s.name}
|
|
380
|
+
</Select.Option>
|
|
381
|
+
))}
|
|
382
|
+
</Select>
|
|
383
|
+
</Form.Item>
|
|
384
|
+
|
|
385
|
+
<Form.Item
|
|
386
|
+
name="model"
|
|
387
|
+
label={<Text strong>Model</Text>}
|
|
388
|
+
rules={[{ required: true, message: 'Please select an LLM Model' }]}
|
|
389
|
+
style={{ flex: 1, minWidth: '300px' }}
|
|
390
|
+
>
|
|
391
|
+
<Select placeholder="Select Model" disabled={!selectedService}>
|
|
392
|
+
{models.map((m) => (
|
|
393
|
+
<Select.Option key={m.id || m.name} value={m.id || m.name}>
|
|
394
|
+
{m.id || m.name}
|
|
395
|
+
</Select.Option>
|
|
396
|
+
))}
|
|
397
|
+
</Select>
|
|
398
|
+
</Form.Item>
|
|
399
|
+
</Space>
|
|
400
|
+
|
|
401
|
+
<Space size="large" style={{ display: 'flex', width: '100%' }}>
|
|
402
|
+
<Form.Item
|
|
403
|
+
name="type"
|
|
404
|
+
label={<Text strong>Template Type</Text>}
|
|
405
|
+
rules={[{ required: true }]}
|
|
406
|
+
style={{ flex: 1 }}
|
|
407
|
+
>
|
|
408
|
+
<Radio.Group>
|
|
409
|
+
<Radio.Button value="block">Block (V2)</Radio.Button>
|
|
410
|
+
<Radio.Button value="popup">Popup (V2)</Radio.Button>
|
|
411
|
+
</Radio.Group>
|
|
412
|
+
</Form.Item>
|
|
413
|
+
|
|
414
|
+
<Form.Item
|
|
415
|
+
name="targetCollection"
|
|
416
|
+
label={<Text strong>Bind Database Collection</Text>}
|
|
417
|
+
style={{ flex: 1, minWidth: '300px' }}
|
|
418
|
+
>
|
|
419
|
+
<Select placeholder="Select target collection (optional)" allowClear showSearch>
|
|
420
|
+
{collections.map((c) => (
|
|
421
|
+
<Select.Option key={c.name} value={c.name}>
|
|
422
|
+
{c.title || c.name}
|
|
423
|
+
</Select.Option>
|
|
424
|
+
))}
|
|
425
|
+
</Select>
|
|
426
|
+
</Form.Item>
|
|
427
|
+
</Space>
|
|
428
|
+
|
|
429
|
+
<Form.Item
|
|
430
|
+
name="promptRequirements"
|
|
431
|
+
label={<Text strong>UI Requirements & Features Description</Text>}
|
|
432
|
+
rules={[{ required: true, message: 'Please describe the layout you need AI to generate' }]}
|
|
433
|
+
>
|
|
434
|
+
<Input.TextArea
|
|
435
|
+
rows={4}
|
|
436
|
+
placeholder="e.g. Build a comprehensive customer feedback form featuring inputs for name, email, rating slider, multi-line comment text area, and an agreement checkbox. Place them in a nice 2-column grid."
|
|
437
|
+
/>
|
|
438
|
+
</Form.Item>
|
|
439
|
+
|
|
440
|
+
<Form.Item name="systemPrompt" label={<Text strong>Advanced System Prompt Override (Optional)</Text>}>
|
|
441
|
+
<Input.TextArea
|
|
442
|
+
rows={3}
|
|
443
|
+
placeholder="Override the default system prompt to customize how the LLM structures the component trees."
|
|
444
|
+
/>
|
|
445
|
+
</Form.Item>
|
|
446
|
+
</Form>
|
|
447
|
+
</Modal>
|
|
448
|
+
</div>
|
|
449
|
+
);
|
|
450
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Plugin } from '@nocobase/client';
|
|
2
|
+
import { BuildUITemplateManager } from './BuildUITemplateManager';
|
|
3
|
+
|
|
4
|
+
export class PluginBuildUITemplateClient extends Plugin {
|
|
5
|
+
async load() {
|
|
6
|
+
this.app.pluginSettingsManager.add('ai-build-ui-template', {
|
|
7
|
+
icon: 'LayoutOutlined',
|
|
8
|
+
title: 'Build UI Template',
|
|
9
|
+
Component: BuildUITemplateManager,
|
|
10
|
+
aclSnippet: 'pm.ai-build-ui-template',
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default PluginBuildUITemplateClient;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './plugin';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Plugin, Application } from '@nocobase/client-v2';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
export class PluginBuildUiTemplateClient extends Plugin<Record<string, never>, Application> {
|
|
5
|
+
async load() {
|
|
6
|
+
this.pluginSettingsManager.addMenuItem({
|
|
7
|
+
key: 'ai-build-ui-template',
|
|
8
|
+
title: this.t('Build UI Template'),
|
|
9
|
+
icon: 'LayoutOutlined',
|
|
10
|
+
aclSnippet: 'pm.ai-build-ui-template',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
14
|
+
menuKey: 'ai-build-ui-template',
|
|
15
|
+
key: 'index',
|
|
16
|
+
title: this.t('Build UI Template'),
|
|
17
|
+
|
|
18
|
+
componentLoader: () => import('../client/BuildUITemplateManager').then(m => ({ default: m.BuildUITemplateManager })),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default PluginBuildUiTemplateClient;
|
package/src/index.ts
ADDED