plugin-build-guide-block 1.0.12 → 1.1.3
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/README.md +16 -74
- package/dist/client/components/SpaceSelect.d.ts +2 -0
- package/dist/client/index.js +9 -10
- package/dist/client/schemas/spacesSchema.d.ts +81 -0
- package/dist/externalVersion.js +7 -16
- package/dist/index.js +0 -9
- package/dist/locale/en-US.json +12 -4
- package/dist/locale/namespace.js +0 -9
- package/dist/locale/vi-VN.json +12 -4
- package/dist/locale/zh-CN.json +12 -4
- package/dist/server/actions/build.js +346 -80
- package/dist/server/actions/getHtml.js +0 -9
- package/dist/server/actions/getMarkdown.js +0 -9
- package/dist/server/collections/ai-build-guide-pages.d.ts +2 -0
- package/dist/server/collections/ai-build-guide-pages.js +81 -0
- package/dist/server/collections/ai-build-guide-spaces.js +33 -9
- package/dist/server/index.js +0 -9
- package/dist/server/plugin.d.ts +3 -0
- package/dist/server/plugin.js +50 -21
- package/package.json +1 -1
- package/src/client/UserGuideBlock.tsx +368 -53
- package/src/client/UserGuideBlockProvider.tsx +9 -8
- package/src/client/UserGuideManager.tsx +52 -23
- package/src/client/components/SpaceSelect.tsx +37 -0
- package/src/client/models/UserGuideBlockModel.ts +19 -29
- package/src/client/plugin.tsx +3 -2
- package/src/client/schemaSettings.ts +2 -12
- package/src/client/schemas/spacesSchema.ts +439 -357
- package/src/locale/en-US.json +12 -4
- package/src/locale/vi-VN.json +12 -4
- package/src/locale/zh-CN.json +12 -4
- package/src/server/actions/build.ts +501 -189
- package/src/server/collections/ai-build-guide-pages.ts +60 -0
- package/src/server/collections/ai-build-guide-spaces.ts +57 -24
- package/src/server/plugin.ts +58 -11
package/dist/server/plugin.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
var __defProp = Object.defineProperty;
|
|
11
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -36,6 +27,7 @@ var import_build = require("./actions/build");
|
|
|
36
27
|
var import_getHtml = require("./actions/getHtml");
|
|
37
28
|
var import_getMarkdown = require("./actions/getMarkdown");
|
|
38
29
|
class PluginBuildGuideBlockServer extends import_server.Plugin {
|
|
30
|
+
schemaCollections = ["aiBuildGuideSpaces", "aiBuildGuidePages"];
|
|
39
31
|
afterAdd() {
|
|
40
32
|
}
|
|
41
33
|
beforeLoad() {
|
|
@@ -59,13 +51,24 @@ class PluginBuildGuideBlockServer extends import_server.Plugin {
|
|
|
59
51
|
"aiBuildGuideSpaces:destroy",
|
|
60
52
|
"aiBuildGuideSpaces:list",
|
|
61
53
|
"aiBuildGuideSpaces:get",
|
|
62
|
-
"aiBuildGuideSpaces:build"
|
|
54
|
+
"aiBuildGuideSpaces:build",
|
|
55
|
+
"aiBuildGuidePages:list",
|
|
56
|
+
"aiBuildGuidePages:get"
|
|
63
57
|
]
|
|
64
58
|
});
|
|
65
59
|
this.app.on("afterStart", async () => {
|
|
66
60
|
try {
|
|
67
61
|
const repo = this.db.getRepository("aiBuildGuideSpaces");
|
|
68
62
|
await repo.update({
|
|
63
|
+
filter: { status: "building" },
|
|
64
|
+
values: {
|
|
65
|
+
status: "error",
|
|
66
|
+
buildPhase: "error",
|
|
67
|
+
buildLog: "Build interrupted by server restart"
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
const pageRepo = this.db.getRepository("aiBuildGuidePages");
|
|
71
|
+
await pageRepo.update({
|
|
69
72
|
filter: { status: "building" },
|
|
70
73
|
values: {
|
|
71
74
|
status: "error",
|
|
@@ -77,26 +80,52 @@ class PluginBuildGuideBlockServer extends import_server.Plugin {
|
|
|
77
80
|
}
|
|
78
81
|
});
|
|
79
82
|
}
|
|
80
|
-
async
|
|
81
|
-
const collection = this.db.getCollection(
|
|
82
|
-
if (collection) {
|
|
83
|
+
async ensureCollectionSchema(collectionName) {
|
|
84
|
+
const collection = this.db.getCollection(collectionName);
|
|
85
|
+
if (!collection) {
|
|
86
|
+
this.app.logger.warn(`[plugin-build-guide-block] Collection "${collectionName}" is not registered`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const queryInterface = this.db.sequelize.getQueryInterface();
|
|
90
|
+
const tableName = collection.getTableNameWithSchema();
|
|
91
|
+
let columns = null;
|
|
92
|
+
try {
|
|
93
|
+
columns = await queryInterface.describeTable(tableName);
|
|
94
|
+
} catch (error) {
|
|
83
95
|
await collection.model.sync();
|
|
96
|
+
columns = await queryInterface.describeTable(tableName);
|
|
84
97
|
}
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
98
|
+
const attributes = collection.model.rawAttributes;
|
|
99
|
+
for (const [attributeName, attribute] of Object.entries(attributes)) {
|
|
100
|
+
const columnName = attribute.field || attributeName;
|
|
101
|
+
if (columns[columnName]) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const columnDefinition = { ...attribute };
|
|
105
|
+
delete columnDefinition.Model;
|
|
106
|
+
delete columnDefinition.fieldName;
|
|
107
|
+
await queryInterface.addColumn(tableName, columnName, columnDefinition);
|
|
108
|
+
columns[columnName] = columnDefinition;
|
|
109
|
+
this.app.logger.info(`[plugin-build-guide-block] Added missing column "${columnName}" to "${collectionName}"`);
|
|
88
110
|
}
|
|
89
111
|
}
|
|
90
|
-
async
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
await collection.model.sync();
|
|
112
|
+
async ensureSchema() {
|
|
113
|
+
for (const collectionName of this.schemaCollections) {
|
|
114
|
+
await this.ensureCollectionSchema(collectionName);
|
|
94
115
|
}
|
|
95
116
|
const repo = this.db.getRepository("collections");
|
|
96
117
|
if (repo) {
|
|
97
|
-
|
|
118
|
+
for (const collectionName of this.schemaCollections) {
|
|
119
|
+
await repo.db2cm(collectionName);
|
|
120
|
+
}
|
|
98
121
|
}
|
|
99
122
|
}
|
|
123
|
+
async install(options) {
|
|
124
|
+
await this.ensureSchema();
|
|
125
|
+
}
|
|
126
|
+
async upgrade() {
|
|
127
|
+
await this.ensureSchema();
|
|
128
|
+
}
|
|
100
129
|
async afterEnable() {
|
|
101
130
|
}
|
|
102
131
|
async afterDisable() {
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"displayName.zh-CN": "构建指南区块",
|
|
6
6
|
"description": "Generate user guides, tutorials and help books from documents using AI, then render the result as a block (HTML or Markdown).",
|
|
7
7
|
"description.vi-VN": "Tạo hướng dẫn người dùng, tutorial và help book từ tài liệu bằng AI, hiển thị kết quả dưới dạng block (HTML hoặc Markdown).",
|
|
8
|
-
"version": "1.
|
|
8
|
+
"version": "1.1.3",
|
|
9
9
|
"license": "Apache-2.0",
|
|
10
10
|
"keywords": [
|
|
11
11
|
"ai",
|
|
@@ -1,53 +1,368 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Card, Spin } from 'antd';
|
|
3
|
-
import { useRequest } from '@nocobase/client';
|
|
4
|
-
import { observer } from '@formily/react';
|
|
5
|
-
import { useTranslation } from 'react-i18next';
|
|
6
|
-
import DOMPurify from 'dompurify';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Card, Empty, Spin } from 'antd';
|
|
3
|
+
import { useRequest } from '@nocobase/client';
|
|
4
|
+
import { observer, useFieldSchema } from '@formily/react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import DOMPurify from 'dompurify';
|
|
7
|
+
import { marked } from 'marked';
|
|
8
|
+
|
|
9
|
+
type TocItem = {
|
|
10
|
+
id: string;
|
|
11
|
+
text: string;
|
|
12
|
+
level: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function getHtmlContent(response: unknown) {
|
|
16
|
+
if (typeof response === 'string') {
|
|
17
|
+
return response;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (response && typeof response === 'object' && 'data' in response) {
|
|
21
|
+
const data = (response as { data?: unknown }).data;
|
|
22
|
+
return typeof data === 'string' ? data : '';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function decodeHtml(text: string) {
|
|
29
|
+
if (typeof document === 'undefined') return text;
|
|
30
|
+
const textarea = document.createElement('textarea');
|
|
31
|
+
textarea.innerHTML = text;
|
|
32
|
+
return textarea.value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function extractTextFromContentArray(value: unknown) {
|
|
36
|
+
if (!Array.isArray(value)) return '';
|
|
37
|
+
return value
|
|
38
|
+
.map((item: any) => {
|
|
39
|
+
if (typeof item === 'string') return item;
|
|
40
|
+
if (typeof item?.text === 'string') return item.text;
|
|
41
|
+
if (typeof item?.content === 'string') return item.content;
|
|
42
|
+
return '';
|
|
43
|
+
})
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.join('\n');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function extractLegacyMarkdown(html: string) {
|
|
49
|
+
const trimmed = html.trim();
|
|
50
|
+
const paragraphMatch = trimmed.match(/^<p>([\s\S]*)<\/p>$/i);
|
|
51
|
+
const maybeJson = decodeHtml(paragraphMatch?.[1] || trimmed);
|
|
52
|
+
|
|
53
|
+
if (!maybeJson.startsWith('[')) {
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
return extractTextFromContentArray(JSON.parse(maybeJson));
|
|
59
|
+
} catch {
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeGuideHtml(html: string) {
|
|
65
|
+
const legacyMarkdown = extractLegacyMarkdown(html);
|
|
66
|
+
if (legacyMarkdown) {
|
|
67
|
+
return marked.parse(legacyMarkdown) as string;
|
|
68
|
+
}
|
|
69
|
+
return html;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function slugify(text: string, fallback: string) {
|
|
73
|
+
const slug = text
|
|
74
|
+
.toLowerCase()
|
|
75
|
+
.normalize('NFKD')
|
|
76
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
77
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
78
|
+
.replace(/^-+|-+$/g, '');
|
|
79
|
+
return slug || fallback;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseGuideContent(html: string): { html: string; toc: TocItem[] } {
|
|
83
|
+
if (typeof DOMParser === 'undefined') {
|
|
84
|
+
return { html, toc: [] };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const document = new DOMParser().parseFromString(`<article>${html}</article>`, 'text/html');
|
|
88
|
+
const article = document.querySelector('article');
|
|
89
|
+
if (!article) {
|
|
90
|
+
return { html, toc: [] };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const usedIds = new Set<string>();
|
|
94
|
+
const toc = Array.from(article.querySelectorAll('h2, h3'))
|
|
95
|
+
.map((heading, index) => {
|
|
96
|
+
const text = heading.textContent?.trim() || '';
|
|
97
|
+
if (!text) return null;
|
|
98
|
+
|
|
99
|
+
const baseId = slugify(text, `section-${index + 1}`);
|
|
100
|
+
let id = baseId;
|
|
101
|
+
let suffix = 2;
|
|
102
|
+
while (usedIds.has(id)) {
|
|
103
|
+
id = `${baseId}-${suffix}`;
|
|
104
|
+
suffix++;
|
|
105
|
+
}
|
|
106
|
+
usedIds.add(id);
|
|
107
|
+
heading.setAttribute('id', id);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
id,
|
|
111
|
+
text,
|
|
112
|
+
level: heading.tagName === 'H3' ? 3 : 2,
|
|
113
|
+
};
|
|
114
|
+
})
|
|
115
|
+
.filter(Boolean) as TocItem[];
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
html: article.innerHTML,
|
|
119
|
+
toc,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const guideStyles = `
|
|
124
|
+
.user-guide-block .ant-card-body {
|
|
125
|
+
padding: 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.user-guide-shell {
|
|
129
|
+
display: grid;
|
|
130
|
+
grid-template-columns: minmax(0, 1fr);
|
|
131
|
+
max-width: 95%;
|
|
132
|
+
margin: 0 auto;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.user-guide-shell.has-toc {
|
|
136
|
+
grid-template-columns: 220px minmax(0, 1fr);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.user-guide-toc {
|
|
140
|
+
position: sticky;
|
|
141
|
+
top: 16px;
|
|
142
|
+
align-self: start;
|
|
143
|
+
max-height: calc(100vh - 120px);
|
|
144
|
+
overflow: auto;
|
|
145
|
+
padding: 32px 0 32px 24px;
|
|
146
|
+
border-right: 1px solid #eef0f2;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.user-guide-toc-title {
|
|
150
|
+
margin-bottom: 10px;
|
|
151
|
+
color: rgba(0, 0, 0, 0.45);
|
|
152
|
+
font-size: 12px;
|
|
153
|
+
font-weight: 600;
|
|
154
|
+
text-transform: uppercase;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.user-guide-toc a {
|
|
158
|
+
display: block;
|
|
159
|
+
padding: 5px 10px;
|
|
160
|
+
border-radius: 4px;
|
|
161
|
+
color: rgba(0, 0, 0, 0.66);
|
|
162
|
+
font-size: 13px;
|
|
163
|
+
line-height: 1.42;
|
|
164
|
+
text-decoration: none;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.user-guide-toc a:hover {
|
|
168
|
+
background: #f5f7fa;
|
|
169
|
+
color: #1677ff;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.user-guide-toc .level-3 {
|
|
173
|
+
padding-left: 22px;
|
|
174
|
+
font-size: 12px;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.user-guide-content {
|
|
178
|
+
max-width: 95%;
|
|
179
|
+
margin: 0 auto;
|
|
180
|
+
padding: 32px;
|
|
181
|
+
color: rgba(0, 0, 0, 0.82);
|
|
182
|
+
font-size: 15px;
|
|
183
|
+
line-height: 1.78;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.user-guide-content h1,
|
|
187
|
+
.user-guide-content h2,
|
|
188
|
+
.user-guide-content h3,
|
|
189
|
+
.user-guide-content h4 {
|
|
190
|
+
color: rgba(0, 0, 0, 0.92);
|
|
191
|
+
font-weight: 650;
|
|
192
|
+
line-height: 1.28;
|
|
193
|
+
margin: 28px 0 12px;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.user-guide-content h1:first-child,
|
|
197
|
+
.user-guide-content h2:first-child,
|
|
198
|
+
.user-guide-content h3:first-child {
|
|
199
|
+
margin-top: 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.user-guide-content h1 {
|
|
203
|
+
font-size: 30px;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.user-guide-content h2 {
|
|
207
|
+
font-size: 24px;
|
|
208
|
+
padding-bottom: 8px;
|
|
209
|
+
border-bottom: 1px solid #e6e8eb;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.user-guide-content h3 {
|
|
213
|
+
font-size: 19px;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.user-guide-content p,
|
|
217
|
+
.user-guide-content ul,
|
|
218
|
+
.user-guide-content ol,
|
|
219
|
+
.user-guide-content table,
|
|
220
|
+
.user-guide-content blockquote,
|
|
221
|
+
.user-guide-content pre {
|
|
222
|
+
margin: 0 0 16px;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.user-guide-content ul,
|
|
226
|
+
.user-guide-content ol {
|
|
227
|
+
padding-left: 24px;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.user-guide-content li + li {
|
|
231
|
+
margin-top: 6px;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.user-guide-content table {
|
|
235
|
+
width: 100%;
|
|
236
|
+
border-collapse: collapse;
|
|
237
|
+
overflow: hidden;
|
|
238
|
+
border: 1px solid #e6e8eb;
|
|
239
|
+
border-radius: 6px;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.user-guide-content th,
|
|
243
|
+
.user-guide-content td {
|
|
244
|
+
padding: 10px 12px;
|
|
245
|
+
border: 1px solid #e6e8eb;
|
|
246
|
+
vertical-align: top;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.user-guide-content th {
|
|
250
|
+
background: #f7f8fa;
|
|
251
|
+
font-weight: 600;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.user-guide-content blockquote {
|
|
255
|
+
padding: 12px 16px;
|
|
256
|
+
border-left: 4px solid #1677ff;
|
|
257
|
+
background: #f3f8ff;
|
|
258
|
+
color: rgba(0, 0, 0, 0.76);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.user-guide-content code {
|
|
262
|
+
padding: 2px 5px;
|
|
263
|
+
border-radius: 4px;
|
|
264
|
+
background: #f2f4f7;
|
|
265
|
+
font-size: 0.92em;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.user-guide-content pre {
|
|
269
|
+
padding: 14px 16px;
|
|
270
|
+
overflow: auto;
|
|
271
|
+
border-radius: 6px;
|
|
272
|
+
background: #111827;
|
|
273
|
+
color: #f9fafb;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.user-guide-content pre code {
|
|
277
|
+
padding: 0;
|
|
278
|
+
background: transparent;
|
|
279
|
+
color: inherit;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.user-guide-content hr {
|
|
283
|
+
border: 0;
|
|
284
|
+
border-top: 1px solid #e6e8eb;
|
|
285
|
+
margin: 28px 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@media (max-width: 900px) {
|
|
289
|
+
.user-guide-shell.has-toc {
|
|
290
|
+
grid-template-columns: minmax(0, 1fr);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.user-guide-toc {
|
|
294
|
+
position: static;
|
|
295
|
+
max-height: none;
|
|
296
|
+
padding: 20px 24px 0;
|
|
297
|
+
border-right: 0;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.user-guide-content {
|
|
301
|
+
padding: 24px;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
`;
|
|
305
|
+
|
|
306
|
+
export const UserGuideBlock = observer(
|
|
307
|
+
(props: any) => {
|
|
308
|
+
const fieldSchema = useFieldSchema();
|
|
309
|
+
const spaceId = props.spaceId || fieldSchema?.['x-component-props']?.spaceId;
|
|
310
|
+
const { t } = useTranslation();
|
|
311
|
+
|
|
312
|
+
const { loading, data: htmlContent } = useRequest<string>(
|
|
313
|
+
{
|
|
314
|
+
url: `aiBuildGuideSpaces:getHtml/${spaceId}`,
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
refreshDeps: [spaceId],
|
|
318
|
+
ready: !!spaceId,
|
|
319
|
+
},
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
if (!spaceId) {
|
|
323
|
+
return (
|
|
324
|
+
<Card style={{ padding: 24, textAlign: 'center', color: '#888' }}>
|
|
325
|
+
{t('Please select a User Guide Space in block settings')}
|
|
326
|
+
</Card>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (loading) {
|
|
331
|
+
return (
|
|
332
|
+
<Card style={{ padding: 24, textAlign: 'center' }}>
|
|
333
|
+
<Spin size="large" />
|
|
334
|
+
</Card>
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const guide = parseGuideContent(normalizeGuideHtml(getHtmlContent(htmlContent)));
|
|
339
|
+
const showToc = guide.toc.length > 2;
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<Card bordered={false} className="user-guide-block" style={{ width: '100%', minHeight: 300 }}>
|
|
343
|
+
<style>{guideStyles}</style>
|
|
344
|
+
{guide.html ? (
|
|
345
|
+
<div className={`user-guide-shell${showToc ? ' has-toc' : ''}`}>
|
|
346
|
+
{showToc && (
|
|
347
|
+
<nav className="user-guide-toc">
|
|
348
|
+
<div className="user-guide-toc-title">{t('Contents')}</div>
|
|
349
|
+
{guide.toc.map((item) => (
|
|
350
|
+
<a key={item.id} href={`#${item.id}`} className={`level-${item.level}`}>
|
|
351
|
+
{item.text}
|
|
352
|
+
</a>
|
|
353
|
+
))}
|
|
354
|
+
</nav>
|
|
355
|
+
)}
|
|
356
|
+
<article
|
|
357
|
+
className="user-guide-content"
|
|
358
|
+
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(guide.html) }}
|
|
359
|
+
/>
|
|
360
|
+
</div>
|
|
361
|
+
) : (
|
|
362
|
+
<Empty description={t('No guide content available')} style={{ padding: 32 }} />
|
|
363
|
+
)}
|
|
364
|
+
</Card>
|
|
365
|
+
);
|
|
366
|
+
},
|
|
367
|
+
{ displayName: 'UserGuideBlock' },
|
|
368
|
+
);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { SchemaComponentOptions } from '@nocobase/client';
|
|
3
|
-
import { UserGuideBlock } from './UserGuideBlock';
|
|
4
|
-
import { UserGuideBlockInitializer } from './UserGuideBlockInitializer';
|
|
2
|
+
import { SchemaComponentOptions } from '@nocobase/client';
|
|
3
|
+
import { UserGuideBlock } from './UserGuideBlock';
|
|
4
|
+
import { UserGuideBlockInitializer } from './UserGuideBlockInitializer';
|
|
5
|
+
import { SpaceSelect } from './components/SpaceSelect';
|
|
5
6
|
|
|
6
7
|
export const UserGuideBlockProvider = (props: any) => {
|
|
7
8
|
return (
|
|
8
|
-
<SchemaComponentOptions components={{ UserGuideBlock, UserGuideBlockInitializer }}>
|
|
9
|
-
{props.children}
|
|
10
|
-
</SchemaComponentOptions>
|
|
11
|
-
);
|
|
12
|
-
};
|
|
9
|
+
<SchemaComponentOptions components={{ UserGuideBlock, UserGuideBlockInitializer, SpaceSelect }}>
|
|
10
|
+
{props.children}
|
|
11
|
+
</SchemaComponentOptions>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
@@ -12,25 +12,53 @@ import { createForm } from '@formily/core';
|
|
|
12
12
|
import { useForm } from '@formily/react';
|
|
13
13
|
import { App } from 'antd';
|
|
14
14
|
import { useTranslation } from 'react-i18next';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
DEFAULT_TARGET_CHAPTER_COUNT,
|
|
17
|
+
MAX_TARGET_CHAPTER_COUNT,
|
|
18
|
+
MIN_TARGET_CHAPTER_COUNT,
|
|
19
|
+
spacesSchema,
|
|
20
|
+
} from './schemas/spacesSchema';
|
|
16
21
|
import { LLMServiceSelect } from './components/LLMServiceSelect';
|
|
17
|
-
import { ModelSelect } from './components/ModelSelect';
|
|
18
|
-
import { StatusTag } from './components/StatusTag';
|
|
19
|
-
import { BuildButton } from './components/BuildButton';
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const form = useMemo(
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
import { ModelSelect } from './components/ModelSelect';
|
|
23
|
+
import { StatusTag } from './components/StatusTag';
|
|
24
|
+
import { BuildButton } from './components/BuildButton';
|
|
25
|
+
|
|
26
|
+
const normalizeTargetChapterCount = (value: unknown) => {
|
|
27
|
+
const count = Number(value);
|
|
28
|
+
if (!Number.isFinite(count)) return DEFAULT_TARGET_CHAPTER_COUNT;
|
|
29
|
+
return Math.max(MIN_TARGET_CHAPTER_COUNT, Math.min(MAX_TARGET_CHAPTER_COUNT, Math.round(count)));
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const UserGuideManager = () => {
|
|
33
|
+
const { t } = useTranslation();
|
|
34
|
+
|
|
35
|
+
const useCreateFormProps = () => {
|
|
36
|
+
const form = useMemo(
|
|
37
|
+
() =>
|
|
38
|
+
createForm({
|
|
39
|
+
initialValues: {
|
|
40
|
+
targetChapterCount: DEFAULT_TARGET_CHAPTER_COUNT,
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
[],
|
|
44
|
+
);
|
|
45
|
+
return { form };
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const useEditFormProps = () => {
|
|
49
|
+
const record = useCollectionRecordData();
|
|
50
|
+
const form = useMemo(
|
|
51
|
+
() =>
|
|
52
|
+
createForm({
|
|
53
|
+
initialValues: {
|
|
54
|
+
...record,
|
|
55
|
+
targetChapterCount: normalizeTargetChapterCount(record?.targetChapterCount),
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
[record],
|
|
59
|
+
);
|
|
60
|
+
return { form };
|
|
61
|
+
};
|
|
34
62
|
|
|
35
63
|
const useCancelActionProps = () => {
|
|
36
64
|
const { setVisible } = useActionContext();
|
|
@@ -42,11 +70,12 @@ export const UserGuideManager = () => {
|
|
|
42
70
|
};
|
|
43
71
|
};
|
|
44
72
|
|
|
45
|
-
const normalizeValues = (values: any) => {
|
|
46
|
-
const { documents, ...rest } = values;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
73
|
+
const normalizeValues = (values: any) => {
|
|
74
|
+
const { documents, ...rest } = values;
|
|
75
|
+
rest.targetChapterCount = normalizeTargetChapterCount(rest.targetChapterCount);
|
|
76
|
+
if (Array.isArray(documents)) {
|
|
77
|
+
rest.documents = documents.map((doc: any) => (typeof doc === 'object' && doc?.id ? { id: doc.id } : doc));
|
|
78
|
+
}
|
|
50
79
|
return rest;
|
|
51
80
|
};
|
|
52
81
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Select } from 'antd';
|
|
3
|
+
import { useRequest } from '@nocobase/client';
|
|
4
|
+
|
|
5
|
+
function normalizeRecords(response: any) {
|
|
6
|
+
const records = response?.data?.data || response?.data || response || [];
|
|
7
|
+
return Array.isArray(records) ? records : [];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const SpaceSelect = (props: any) => {
|
|
11
|
+
const { data, loading } = useRequest<any>({
|
|
12
|
+
resource: 'aiBuildGuideSpaces',
|
|
13
|
+
action: 'list',
|
|
14
|
+
params: {
|
|
15
|
+
filter: { status: 'completed' },
|
|
16
|
+
pageSize: 100,
|
|
17
|
+
sort: ['-createdAt'],
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const options = normalizeRecords(data).map((item: any) => ({
|
|
22
|
+
label: item.title,
|
|
23
|
+
value: item.id,
|
|
24
|
+
})) || [];
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Select
|
|
28
|
+
{...props}
|
|
29
|
+
loading={loading}
|
|
30
|
+
options={options}
|
|
31
|
+
showSearch
|
|
32
|
+
filterOption={(input, option) =>
|
|
33
|
+
(option?.label as string)?.toLowerCase().includes(input.toLowerCase())
|
|
34
|
+
}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
};
|