plugin-build-guide-block 1.1.5 → 1.1.6

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 (41) hide show
  1. package/dist/client/index.js +2 -2
  2. package/dist/externalVersion.js +7 -7
  3. package/dist/node_modules/sanitize-html/index.js +1 -1
  4. package/dist/node_modules/sanitize-html/package.json +1 -1
  5. package/dist/server/actions/build.js +682 -83
  6. package/dist/server/collections/ai-build-guide-spaces.js +20 -0
  7. package/dist/server/plugin.js +21 -19
  8. package/dist/server/tools/search-guides.js +41 -30
  9. package/package.json +2 -2
  10. package/src/client/components/BuildButton.tsx +20 -9
  11. package/src/server/actions/build.ts +768 -86
  12. package/src/server/collections/ai-build-guide-spaces.ts +77 -57
  13. package/src/server/plugin.ts +170 -163
  14. package/src/server/tools/search-guides.ts +113 -95
  15. package/dist/client/UserGuideBlock.d.ts +0 -2
  16. package/dist/client/UserGuideBlockInitializer.d.ts +0 -2
  17. package/dist/client/UserGuideBlockProvider.d.ts +0 -2
  18. package/dist/client/UserGuideManager.d.ts +0 -2
  19. package/dist/client/components/BuildButton.d.ts +0 -2
  20. package/dist/client/components/LLMServiceSelect.d.ts +0 -2
  21. package/dist/client/components/ModelSelect.d.ts +0 -2
  22. package/dist/client/components/SpaceSelect.d.ts +0 -2
  23. package/dist/client/components/StatusTag.d.ts +0 -2
  24. package/dist/client/index.d.ts +0 -1
  25. package/dist/client/locale.d.ts +0 -3
  26. package/dist/client/models/UserGuideBlockModel.d.ts +0 -9
  27. package/dist/client/models/index.d.ts +0 -9
  28. package/dist/client/plugin.d.ts +0 -5
  29. package/dist/client/schemaSettings.d.ts +0 -2
  30. package/dist/client/schemas/spacesSchema.d.ts +0 -437
  31. package/dist/index.d.ts +0 -2
  32. package/dist/locale/namespace.d.ts +0 -6
  33. package/dist/server/actions/build.d.ts +0 -2
  34. package/dist/server/actions/getHtml.d.ts +0 -2
  35. package/dist/server/actions/getMarkdown.d.ts +0 -2
  36. package/dist/server/collections/ai-build-guide-pages.d.ts +0 -2
  37. package/dist/server/collections/ai-build-guide-spaces.d.ts +0 -2
  38. package/dist/server/index.d.ts +0 -2
  39. package/dist/server/plugin.d.ts +0 -16
  40. package/dist/server/tools/index.d.ts +0 -1
  41. package/dist/server/tools/search-guides.d.ts +0 -28
@@ -89,6 +89,26 @@ var ai_build_guide_spaces_default = (0, import_database.defineCollection)({
89
89
  name: "buildPhase",
90
90
  defaultValue: "idle"
91
91
  },
92
+ {
93
+ type: "string",
94
+ name: "buildRunId"
95
+ },
96
+ {
97
+ type: "date",
98
+ name: "buildQueuedAt"
99
+ },
100
+ {
101
+ type: "date",
102
+ name: "buildStartedAt"
103
+ },
104
+ {
105
+ type: "date",
106
+ name: "buildHeartbeatAt"
107
+ },
108
+ {
109
+ type: "string",
110
+ name: "buildWorkerId"
111
+ },
92
112
  {
93
113
  type: "integer",
94
114
  name: "pageCount",
@@ -46,6 +46,7 @@ class PluginBuildGuideBlockServer extends import_server.Plugin {
46
46
  await this.db.import({
47
47
  directory: (0, import_path.resolve)(__dirname, "collections")
48
48
  });
49
+ await this.ensureSchema();
49
50
  this.app.resourceManager.registerActionHandlers({
50
51
  "aiBuildGuideSpaces:build": import_build.build,
51
52
  "aiBuildGuideSpaces:getHtml": import_getHtml.getHtml,
@@ -66,36 +67,29 @@ class PluginBuildGuideBlockServer extends import_server.Plugin {
66
67
  "aiBuildGuidePages:get"
67
68
  ]
68
69
  });
70
+ (0, import_build.registerBuildGuideQueue)(this.app);
69
71
  this.app.on("afterStart", async () => {
70
72
  try {
71
- const repo = this.db.getRepository("aiBuildGuideSpaces");
72
- await repo.update({
73
- filter: { status: "building" },
74
- values: {
75
- status: "error",
76
- buildPhase: "error",
77
- buildLog: "Build interrupted by server restart"
78
- }
79
- });
80
- const pageRepo = this.db.getRepository("aiBuildGuidePages");
81
- await pageRepo.update({
82
- filter: { status: "building" },
83
- values: {
84
- status: "error",
85
- buildLog: "Build interrupted by server restart"
86
- }
87
- });
73
+ await (0, import_build.recoverInterruptedBuilds)(this.app);
88
74
  } catch (err) {
89
- this.app.logger.warn("[plugin-build-guide-block] Failed to recover stale builds", err);
75
+ this.app.logger.warn("[plugin-build-guide-block] Failed to recover interrupted builds", err);
90
76
  }
91
77
  });
78
+ this.app.on("beforeStop", () => {
79
+ (0, import_build.unregisterBuildGuideQueue)(this.app);
80
+ });
81
+ this.app.on("beforeDestroy", () => {
82
+ (0, import_build.unregisterBuildGuideQueue)(this.app);
83
+ });
92
84
  this.registerAITools();
93
85
  }
94
86
  registerAITools() {
95
87
  var _a;
96
88
  const toolsManager = (_a = this.app.aiManager) == null ? void 0 : _a.toolsManager;
97
89
  if (!toolsManager) {
98
- this.app.logger.warn("[plugin-build-guide-block] aiManager.toolsManager is not available; skipping tool registration");
90
+ this.app.logger.warn(
91
+ "[plugin-build-guide-block] aiManager.toolsManager is not available; skipping tool registration"
92
+ );
99
93
  return;
100
94
  }
101
95
  const tools = [import_tools.searchBuildGuidesTool];
@@ -168,9 +162,17 @@ class PluginBuildGuideBlockServer extends import_server.Plugin {
168
162
  }
169
163
  async afterEnable() {
170
164
  }
165
+ async beforeDisable() {
166
+ (0, import_build.unregisterBuildGuideQueue)(this.app);
167
+ }
171
168
  async afterDisable() {
169
+ (0, import_build.unregisterBuildGuideQueue)(this.app);
170
+ }
171
+ async beforeUnload() {
172
+ (0, import_build.unregisterBuildGuideQueue)(this.app);
172
173
  }
173
174
  async remove() {
175
+ (0, import_build.unregisterBuildGuideQueue)(this.app);
174
176
  }
175
177
  }
176
178
  var plugin_default = PluginBuildGuideBlockServer;
@@ -30,6 +30,10 @@ __export(search_guides_exports, {
30
30
  });
31
31
  module.exports = __toCommonJS(search_guides_exports);
32
32
  var import_zod = require("zod");
33
+ function getValue(record, key) {
34
+ var _a;
35
+ return ((_a = record == null ? void 0 : record.get) == null ? void 0 : _a.call(record, key)) ?? (record == null ? void 0 : record[key]);
36
+ }
33
37
  var search_guides_default = {
34
38
  groupName: "plugin-build-guide",
35
39
  tool: {
@@ -38,57 +42,61 @@ var search_guides_default = {
38
42
  description: "Search for available user guides, documentation, or read a specific guide page. Use this to help users find information in the build guide block.",
39
43
  execution: "backend",
40
44
  schema: import_zod.z.object({
41
- action: import_zod.z.enum(["list_spaces", "search_pages", "read_page"]).describe("The action to perform: list_spaces (find available guide books), search_pages (search for pages across all or specific spaces), read_page (read the full content of a specific page)"),
45
+ action: import_zod.z.enum(["list_spaces", "search_pages", "read_page"]).describe(
46
+ "The action to perform: list_spaces (find available guide books), search_pages (search for pages across all or specific spaces), read_page (read the full content of a specific page)"
47
+ ),
42
48
  query: import_zod.z.string().optional().describe("Search keyword for finding spaces or pages. Required for search_pages."),
43
49
  pageId: import_zod.z.string().optional().describe("The ID of the page to read. Required for read_page.")
44
50
  }),
45
51
  invoke: async (args, ctx) => {
46
- var _a;
47
52
  const { action, query, pageId } = args;
48
53
  const { db } = ctx.app;
49
54
  const spaceRepo = db.getRepository("aiBuildGuideSpaces");
50
55
  const pageRepo = db.getRepository("aiBuildGuidePages");
51
56
  try {
52
57
  if (action === "list_spaces") {
58
+ const filter = { status: "completed" };
59
+ if (query) {
60
+ filter.title = { $iLike: `%${query}%` };
61
+ }
53
62
  const spaces = await spaceRepo.find({
54
- filter: query ? { title: { $iLike: `%${query}%` } } : {},
63
+ filter,
55
64
  fields: ["id", "title", "chapterGuidance", "pageCount"],
56
65
  limit: 20
57
66
  });
58
67
  return {
59
68
  status: "success",
60
69
  content: spaces.map((s) => ({
61
- id: s.id,
62
- title: s.title,
63
- description: s.chapterGuidance,
64
- pageCount: s.pageCount
70
+ id: getValue(s, "id"),
71
+ title: getValue(s, "title"),
72
+ description: getValue(s, "chapterGuidance"),
73
+ pageCount: getValue(s, "pageCount")
65
74
  }))
66
75
  };
67
76
  }
68
77
  if (action === "search_pages") {
78
+ const filter = { status: "completed" };
79
+ if (query) {
80
+ filter.$or = [
81
+ { title: { $iLike: `%${query}%` } },
82
+ { goal: { $iLike: `%${query}%` } },
83
+ { generatedMarkdown: { $iLike: `%${query}%` } }
84
+ ];
85
+ }
69
86
  const pages = await pageRepo.find({
70
- filter: query ? {
71
- $or: [
72
- { title: { $iLike: `%${query}%` } },
73
- { goal: { $iLike: `%${query}%` } },
74
- { generatedMarkdown: { $iLike: `%${query}%` } }
75
- ]
76
- } : {},
87
+ filter,
77
88
  fields: ["id", "title", "slug", "goal", "spaceId"],
78
89
  limit: 10,
79
- appends: ["space(id,title)"]
90
+ appends: ["space(id,title,status)"]
80
91
  });
81
92
  return {
82
93
  status: "success",
83
- content: pages.map((p) => {
84
- var _a2;
85
- return {
86
- id: p.id,
87
- title: p.title,
88
- goal: p.goal,
89
- spaceName: (_a2 = p.space) == null ? void 0 : _a2.title
90
- };
91
- })
94
+ content: pages.filter((p) => getValue(getValue(p, "space"), "status") === "completed").map((p) => ({
95
+ id: getValue(p, "id"),
96
+ title: getValue(p, "title"),
97
+ goal: getValue(p, "goal"),
98
+ spaceName: getValue(getValue(p, "space"), "title")
99
+ }))
92
100
  };
93
101
  }
94
102
  if (action === "read_page") {
@@ -96,19 +104,22 @@ var search_guides_default = {
96
104
  return { status: "error", content: "pageId is required for read_page action" };
97
105
  }
98
106
  const page = await pageRepo.findById(pageId, {
99
- appends: ["space(id,title)"]
107
+ appends: ["space(id,title,status)"]
100
108
  });
101
109
  if (!page) {
102
110
  return { status: "error", content: `Page with ID ${pageId} not found.` };
103
111
  }
112
+ if (getValue(page, "status") !== "completed" || getValue(getValue(page, "space"), "status") !== "completed") {
113
+ return { status: "error", content: `Page with ID ${pageId} is not completed.` };
114
+ }
104
115
  return {
105
116
  status: "success",
106
117
  content: {
107
- id: page.id,
108
- title: page.title,
109
- spaceName: (_a = page.space) == null ? void 0 : _a.title,
110
- goal: page.goal,
111
- markdown: page.generatedMarkdown
118
+ id: getValue(page, "id"),
119
+ title: getValue(page, "title"),
120
+ spaceName: getValue(getValue(page, "space"), "title"),
121
+ goal: getValue(page, "goal"),
122
+ markdown: getValue(page, "generatedMarkdown")
112
123
  }
113
124
  };
114
125
  }
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.1.5",
8
+ "version": "1.1.6",
9
9
  "license": "Apache-2.0",
10
10
  "keywords": [
11
11
  "ai",
@@ -48,4 +48,4 @@
48
48
  ],
49
49
  "editionLevel": 0
50
50
  }
51
- }
51
+ }
@@ -5,7 +5,8 @@ import { PlayCircleOutlined } from '@ant-design/icons';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
7
  const POLL_INTERVAL_MS = 3000;
8
- const MAX_POLL_COUNT = 100; // ~5 minutes max
8
+ const STILL_RUNNING_AFTER_MS = 5 * 60 * 1000;
9
+ const SLOW_POLL_INTERVAL_MS = 10000;
9
10
 
10
11
  export const BuildButton = () => {
11
12
  const [loading, setLoading] = useState(false);
@@ -14,11 +15,11 @@ export const BuildButton = () => {
14
15
  const { t } = useTranslation();
15
16
  const record = useCollectionRecordData();
16
17
  const { refresh } = useDataBlockRequest();
17
- const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
18
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
18
19
 
19
20
  const stopPolling = useCallback(() => {
20
21
  if (timerRef.current) {
21
- clearInterval(timerRef.current);
22
+ clearTimeout(timerRef.current);
22
23
  timerRef.current = null;
23
24
  }
24
25
  setLoading(false);
@@ -36,14 +37,13 @@ export const BuildButton = () => {
36
37
  });
37
38
  message.success(t('Build started'));
38
39
 
39
- // Poll until status leaves "building"
40
- let pollCount = 0;
41
- timerRef.current = setInterval(async () => {
42
- pollCount++;
40
+ const startedAt = Date.now();
41
+ let stillRunningNotified = false;
42
+ const poll = async () => {
43
43
  try {
44
44
  const res = await api.resource('aiBuildGuideSpaces').get({ filterByTk: record.id });
45
45
  const status = res?.data?.data?.status;
46
- if (status !== 'building' || pollCount >= MAX_POLL_COUNT) {
46
+ if (status !== 'building') {
47
47
  stopPolling();
48
48
  refresh?.();
49
49
  if (status === 'completed') {
@@ -51,12 +51,23 @@ export const BuildButton = () => {
51
51
  } else if (status === 'error') {
52
52
  message.error(t('Build failed'));
53
53
  }
54
+ return;
54
55
  }
56
+
57
+ const elapsed = Date.now() - startedAt;
58
+ if (elapsed >= STILL_RUNNING_AFTER_MS && !stillRunningNotified) {
59
+ stillRunningNotified = true;
60
+ message.info(t('Build is still running'));
61
+ }
62
+ const nextPollDelay = elapsed >= STILL_RUNNING_AFTER_MS ? SLOW_POLL_INTERVAL_MS : POLL_INTERVAL_MS;
63
+ timerRef.current = setTimeout(poll, nextPollDelay);
55
64
  } catch {
56
65
  stopPolling();
57
66
  refresh?.();
58
67
  }
59
- }, POLL_INTERVAL_MS);
68
+ };
69
+
70
+ timerRef.current = setTimeout(poll, POLL_INTERVAL_MS);
60
71
  } catch (err: any) {
61
72
  console.error(err);
62
73
  message.error(err?.response?.data?.error?.message || t('Build failed'));