@btst/stack 2.3.0 → 2.4.0

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 (136) hide show
  1. package/dist/packages/stack/src/client/components/compose.cjs +1 -2
  2. package/dist/packages/stack/src/client/components/compose.mjs +1 -2
  3. package/dist/packages/stack/src/plugins/ai-chat/api/page-tools.cjs +71 -0
  4. package/dist/packages/stack/src/plugins/ai-chat/api/page-tools.mjs +68 -0
  5. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +54 -7
  6. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +54 -7
  7. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.cjs +2 -2
  8. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.mjs +2 -2
  9. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.cjs +89 -22
  10. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +90 -23
  11. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.cjs +110 -33
  12. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.mjs +112 -35
  13. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-sidebar.cjs +1 -1
  14. package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-sidebar.mjs +1 -1
  15. package/dist/packages/stack/src/plugins/ai-chat/schemas.cjs +17 -1
  16. package/dist/packages/stack/src/plugins/ai-chat/schemas.mjs +17 -1
  17. package/dist/packages/stack/src/plugins/blog/client/components/forms/post-forms.cjs +15 -2
  18. package/dist/packages/stack/src/plugins/blog/client/components/forms/post-forms.mjs +16 -3
  19. package/dist/packages/stack/src/plugins/blog/client/components/pages/edit-post-page.internal.cjs +24 -1
  20. package/dist/packages/stack/src/plugins/blog/client/components/pages/edit-post-page.internal.mjs +24 -1
  21. package/dist/packages/stack/src/plugins/blog/client/components/pages/fill-blog-form-handler.cjs +26 -0
  22. package/dist/packages/stack/src/plugins/blog/client/components/pages/fill-blog-form-handler.mjs +24 -0
  23. package/dist/packages/stack/src/plugins/blog/client/components/pages/new-post-page.internal.cjs +30 -1
  24. package/dist/packages/stack/src/plugins/blog/client/components/pages/new-post-page.internal.mjs +30 -1
  25. package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.cjs +18 -0
  26. package/dist/packages/stack/src/plugins/blog/client/components/pages/post-page.internal.mjs +18 -0
  27. package/dist/packages/stack/src/plugins/cms/api/mutations.cjs +48 -0
  28. package/dist/packages/stack/src/plugins/cms/api/mutations.mjs +46 -0
  29. package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +7 -1
  30. package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +7 -1
  31. package/dist/packages/stack/src/plugins/kanban/api/mutations.cjs +91 -0
  32. package/dist/packages/stack/src/plugins/kanban/api/mutations.mjs +87 -0
  33. package/dist/packages/stack/src/plugins/kanban/api/plugin.cjs +6 -1
  34. package/dist/packages/stack/src/plugins/kanban/api/plugin.mjs +6 -1
  35. package/dist/packages/stack/src/plugins/kanban/client/hooks/kanban-hooks.cjs +7 -3
  36. package/dist/packages/stack/src/plugins/kanban/client/hooks/kanban-hooks.mjs +7 -3
  37. package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.cjs +89 -0
  38. package/dist/packages/stack/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.mjs +89 -0
  39. package/dist/plugins/ai-chat/api/index.d.cts +1 -1
  40. package/dist/plugins/ai-chat/api/index.d.mts +1 -1
  41. package/dist/plugins/ai-chat/api/index.d.ts +1 -1
  42. package/dist/plugins/ai-chat/client/components/index.d.cts +1 -1
  43. package/dist/plugins/ai-chat/client/components/index.d.mts +1 -1
  44. package/dist/plugins/ai-chat/client/components/index.d.ts +1 -1
  45. package/dist/plugins/ai-chat/client/context/page-ai-context.cjs +92 -0
  46. package/dist/plugins/ai-chat/client/context/page-ai-context.d.cts +84 -0
  47. package/dist/plugins/ai-chat/client/context/page-ai-context.d.mts +84 -0
  48. package/dist/plugins/ai-chat/client/context/page-ai-context.d.ts +84 -0
  49. package/dist/plugins/ai-chat/client/context/page-ai-context.mjs +88 -0
  50. package/dist/plugins/ai-chat/client/hooks/index.d.cts +1 -1
  51. package/dist/plugins/ai-chat/client/hooks/index.d.mts +1 -1
  52. package/dist/plugins/ai-chat/client/hooks/index.d.ts +1 -1
  53. package/dist/plugins/ai-chat/client/index.d.cts +2 -2
  54. package/dist/plugins/ai-chat/client/index.d.mts +2 -2
  55. package/dist/plugins/ai-chat/client/index.d.ts +2 -2
  56. package/dist/plugins/ai-chat/query-keys.d.cts +1 -1
  57. package/dist/plugins/ai-chat/query-keys.d.mts +1 -1
  58. package/dist/plugins/ai-chat/query-keys.d.ts +1 -1
  59. package/dist/plugins/blog/api/index.d.cts +2 -2
  60. package/dist/plugins/blog/api/index.d.mts +2 -2
  61. package/dist/plugins/blog/api/index.d.ts +2 -2
  62. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  63. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  64. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  65. package/dist/plugins/blog/client/index.d.cts +1 -1
  66. package/dist/plugins/blog/client/index.d.mts +1 -1
  67. package/dist/plugins/blog/client/index.d.ts +1 -1
  68. package/dist/plugins/blog/query-keys.d.cts +2 -2
  69. package/dist/plugins/blog/query-keys.d.mts +2 -2
  70. package/dist/plugins/blog/query-keys.d.ts +2 -2
  71. package/dist/plugins/cms/api/index.cjs +2 -0
  72. package/dist/plugins/cms/api/index.d.cts +1 -1
  73. package/dist/plugins/cms/api/index.d.mts +1 -1
  74. package/dist/plugins/cms/api/index.d.ts +1 -1
  75. package/dist/plugins/cms/api/index.mjs +1 -0
  76. package/dist/plugins/cms/query-keys.d.cts +1 -1
  77. package/dist/plugins/cms/query-keys.d.mts +1 -1
  78. package/dist/plugins/cms/query-keys.d.ts +1 -1
  79. package/dist/plugins/form-builder/api/index.d.cts +1 -1
  80. package/dist/plugins/form-builder/api/index.d.mts +1 -1
  81. package/dist/plugins/form-builder/api/index.d.ts +1 -1
  82. package/dist/plugins/form-builder/query-keys.d.cts +1 -1
  83. package/dist/plugins/form-builder/query-keys.d.mts +1 -1
  84. package/dist/plugins/form-builder/query-keys.d.ts +1 -1
  85. package/dist/plugins/kanban/api/index.cjs +4 -0
  86. package/dist/plugins/kanban/api/index.d.cts +1 -1
  87. package/dist/plugins/kanban/api/index.d.mts +1 -1
  88. package/dist/plugins/kanban/api/index.d.ts +1 -1
  89. package/dist/plugins/kanban/api/index.mjs +1 -0
  90. package/dist/plugins/kanban/query-keys.d.cts +1 -1
  91. package/dist/plugins/kanban/query-keys.d.mts +1 -1
  92. package/dist/plugins/kanban/query-keys.d.ts +1 -1
  93. package/dist/shared/{stack.BeSm90va.d.ts → stack.BEn34wW6.d.ts} +60 -2
  94. package/dist/shared/{stack.IdtKDRka.d.cts → stack.BUkC2EsZ.d.cts} +32 -2
  95. package/dist/shared/{stack.DaOcgmrM.d.ts → stack.BV9hnvu4.d.cts} +31 -7
  96. package/dist/shared/{stack.DaOcgmrM.d.cts → stack.BV9hnvu4.d.mts} +31 -7
  97. package/dist/shared/{stack.DaOcgmrM.d.mts → stack.BV9hnvu4.d.ts} +31 -7
  98. package/dist/shared/{stack.rTy7-wQU.d.mts → stack.BepFXT3w.d.mts} +70 -15
  99. package/dist/shared/{stack.BKfolAyK.d.ts → stack.CL8ts1Mu.d.ts} +3 -3
  100. package/dist/shared/{stack.CP68pFEH.d.mts → stack.CczspVn2.d.mts} +32 -2
  101. package/dist/shared/{stack.TIBF2AOx.d.ts → stack.CgWzG5jH.d.ts} +70 -15
  102. package/dist/shared/{stack.BpolpQpf.d.cts → stack.D3GB6wKv.d.cts} +70 -15
  103. package/dist/shared/{stack.B1EeBt1b.d.ts → stack.DASmUVjX.d.ts} +32 -2
  104. package/dist/shared/{stack.Dg09R0oB.d.mts → stack.DTDxgFj8.d.mts} +60 -2
  105. package/dist/shared/{stack.CMh_EdxW.d.cts → stack.DWoCZff7.d.cts} +60 -2
  106. package/dist/shared/{stack.snB1EDP7.d.cts → stack.Dk5r4W1F.d.mts} +3 -3
  107. package/dist/shared/{stack.BIXEI6v_.d.mts → stack.heOA9gzA.d.cts} +3 -3
  108. package/package.json +14 -1
  109. package/src/client/components/compose.tsx +7 -4
  110. package/src/plugins/ai-chat/api/page-tools.ts +111 -0
  111. package/src/plugins/ai-chat/api/plugin.ts +180 -9
  112. package/src/plugins/ai-chat/client/components/chat-input.tsx +2 -2
  113. package/src/plugins/ai-chat/client/components/chat-interface.tsx +154 -58
  114. package/src/plugins/ai-chat/client/components/chat-layout.tsx +166 -32
  115. package/src/plugins/ai-chat/client/components/chat-sidebar.tsx +1 -1
  116. package/src/plugins/ai-chat/client/context/page-ai-context.tsx +240 -0
  117. package/src/plugins/ai-chat/schemas.ts +16 -0
  118. package/src/plugins/blog/client/components/forms/post-forms.tsx +29 -2
  119. package/src/plugins/blog/client/components/pages/edit-post-page.internal.tsx +28 -0
  120. package/src/plugins/blog/client/components/pages/fill-blog-form-handler.ts +38 -0
  121. package/src/plugins/blog/client/components/pages/new-post-page.internal.tsx +33 -1
  122. package/src/plugins/blog/client/components/pages/post-page.internal.tsx +20 -0
  123. package/src/plugins/cms/api/index.ts +4 -0
  124. package/src/plugins/cms/api/mutations.ts +84 -0
  125. package/src/plugins/cms/api/plugin.ts +9 -0
  126. package/src/plugins/kanban/api/index.ts +6 -0
  127. package/src/plugins/kanban/api/mutations.ts +169 -0
  128. package/src/plugins/kanban/api/plugin.ts +12 -0
  129. package/src/plugins/kanban/client/hooks/kanban-hooks.tsx +4 -0
  130. package/src/plugins/ui-builder/client/components/pages/page-builder-page.internal.tsx +132 -0
  131. package/dist/shared/{stack.C5dtIncc.d.mts → stack.B7ONvlD_.d.mts} +1 -1
  132. package/dist/shared/{stack.CBON0dWL.d.cts → stack.BQmuNl5p.d.cts} +2 -2
  133. package/dist/shared/{stack.CBON0dWL.d.mts → stack.BQmuNl5p.d.mts} +2 -2
  134. package/dist/shared/{stack.CBON0dWL.d.ts → stack.BQmuNl5p.d.ts} +2 -2
  135. package/dist/shared/{stack.CIP6QS9l.d.ts → stack.Kq2-QzOC.d.ts} +1 -1
  136. package/dist/shared/{stack.Dw0Ly2TM.d.cts → stack.kcdnD4gA.d.cts} +1 -1
@@ -250,9 +250,14 @@ const CustomPostUpdateSchema = schemas.updatePostSchema.omit({
250
250
  const addPostFormPropsAreEqual = (prevProps, nextProps) => {
251
251
  if (prevProps.onClose !== nextProps.onClose) return false;
252
252
  if (prevProps.onSuccess !== nextProps.onSuccess) return false;
253
+ if (prevProps.onFormReady !== nextProps.onFormReady) return false;
253
254
  return true;
254
255
  };
255
- const AddPostFormComponent = ({ onClose, onSuccess }) => {
256
+ const AddPostFormComponent = ({
257
+ onClose,
258
+ onSuccess,
259
+ onFormReady
260
+ }) => {
256
261
  const [featuredImageUploading, setFeaturedImageUploading] = React.useState(false);
257
262
  const { localization } = context.usePluginOverrides("blog", {
258
263
  localization: index.BLOG_LOCALIZATION
@@ -290,6 +295,9 @@ const AddPostFormComponent = ({ onClose, onSuccess }) => {
290
295
  tags: []
291
296
  }
292
297
  });
298
+ React.useEffect(() => {
299
+ onFormReady?.(form);
300
+ }, []);
293
301
  return /* @__PURE__ */ jsxRuntime.jsx(
294
302
  PostFormBody,
295
303
  {
@@ -309,13 +317,15 @@ const editPostFormPropsAreEqual = (prevProps, nextProps) => {
309
317
  if (prevProps.onClose !== nextProps.onClose) return false;
310
318
  if (prevProps.onSuccess !== nextProps.onSuccess) return false;
311
319
  if (prevProps.onDelete !== nextProps.onDelete) return false;
320
+ if (prevProps.onFormReady !== nextProps.onFormReady) return false;
312
321
  return true;
313
322
  };
314
323
  const EditPostFormComponent = ({
315
324
  postSlug,
316
325
  onClose,
317
326
  onSuccess,
318
- onDelete
327
+ onDelete,
328
+ onFormReady
319
329
  }) => {
320
330
  const [featuredImageUploading, setFeaturedImageUploading] = React.useState(false);
321
331
  const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
@@ -391,6 +401,9 @@ const EditPostFormComponent = ({
391
401
  },
392
402
  values: initialData
393
403
  });
404
+ React.useEffect(() => {
405
+ onFormReady?.(form);
406
+ }, []);
394
407
  if (!post) {
395
408
  return /* @__PURE__ */ jsxRuntime.jsx(emptyList.EmptyList, { message: localization.BLOG_PAGE_NOT_FOUND_DESCRIPTION });
396
409
  }
@@ -11,7 +11,7 @@ import { slugify } from '../../../utils.mjs';
11
11
  import { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from '../../../../../../../ui/src/components/alert-dialog.mjs';
12
12
  import { zodResolver } from '@hookform/resolvers/zod';
13
13
  import { Loader2 } from 'lucide-react';
14
- import { memo, useState, useMemo, Suspense, lazy } from 'react';
14
+ import { memo, useState, useEffect, useMemo, Suspense, lazy } from 'react';
15
15
  import { useForm } from 'react-hook-form';
16
16
  import { toast } from 'sonner';
17
17
  import { FeaturedImageField } from './image-field.mjs';
@@ -248,9 +248,14 @@ const CustomPostUpdateSchema = updatePostSchema.omit({
248
248
  const addPostFormPropsAreEqual = (prevProps, nextProps) => {
249
249
  if (prevProps.onClose !== nextProps.onClose) return false;
250
250
  if (prevProps.onSuccess !== nextProps.onSuccess) return false;
251
+ if (prevProps.onFormReady !== nextProps.onFormReady) return false;
251
252
  return true;
252
253
  };
253
- const AddPostFormComponent = ({ onClose, onSuccess }) => {
254
+ const AddPostFormComponent = ({
255
+ onClose,
256
+ onSuccess,
257
+ onFormReady
258
+ }) => {
254
259
  const [featuredImageUploading, setFeaturedImageUploading] = useState(false);
255
260
  const { localization } = usePluginOverrides("blog", {
256
261
  localization: BLOG_LOCALIZATION
@@ -288,6 +293,9 @@ const AddPostFormComponent = ({ onClose, onSuccess }) => {
288
293
  tags: []
289
294
  }
290
295
  });
296
+ useEffect(() => {
297
+ onFormReady?.(form);
298
+ }, []);
291
299
  return /* @__PURE__ */ jsx(
292
300
  PostFormBody,
293
301
  {
@@ -307,13 +315,15 @@ const editPostFormPropsAreEqual = (prevProps, nextProps) => {
307
315
  if (prevProps.onClose !== nextProps.onClose) return false;
308
316
  if (prevProps.onSuccess !== nextProps.onSuccess) return false;
309
317
  if (prevProps.onDelete !== nextProps.onDelete) return false;
318
+ if (prevProps.onFormReady !== nextProps.onFormReady) return false;
310
319
  return true;
311
320
  };
312
321
  const EditPostFormComponent = ({
313
322
  postSlug,
314
323
  onClose,
315
324
  onSuccess,
316
- onDelete
325
+ onDelete,
326
+ onFormReady
317
327
  }) => {
318
328
  const [featuredImageUploading, setFeaturedImageUploading] = useState(false);
319
329
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
@@ -389,6 +399,9 @@ const EditPostFormComponent = ({
389
399
  },
390
400
  values: initialData
391
401
  });
402
+ useEffect(() => {
403
+ onFormReady?.(form);
404
+ }, []);
392
405
  if (!post) {
393
406
  return /* @__PURE__ */ jsx(EmptyList, { message: localization.BLOG_PAGE_NOT_FOUND_DESCRIPTION });
394
407
  }
@@ -8,6 +8,9 @@ const pageHeader = require('../shared/page-header.cjs');
8
8
  const pageWrapper = require('../shared/page-wrapper.cjs');
9
9
  const index = require('../../localization/index.cjs');
10
10
  const useRouteLifecycle = require('../../../../../../../ui/src/hooks/use-route-lifecycle.cjs');
11
+ const context$1 = require('@btst/stack/plugins/ai-chat/client/context');
12
+ const React = require('react');
13
+ const fillBlogFormHandler = require('./fill-blog-form-handler.cjs');
11
14
 
12
15
  function EditPostPage({ slug }) {
13
16
  const overrides = context.usePluginOverrides("blog", {
@@ -30,6 +33,25 @@ function EditPostPage({ slug }) {
30
33
  return true;
31
34
  }
32
35
  });
36
+ const formRef = React.useRef(null);
37
+ const handleFormReady = React.useCallback((form) => {
38
+ formRef.current = form;
39
+ }, []);
40
+ context$1.useRegisterPageAIContext({
41
+ routeName: "blog-edit-post",
42
+ pageDescription: `User is editing a blog post (slug: "${slug}") in the admin editor.`,
43
+ suggestions: [
44
+ "Improve this post's title",
45
+ "Rewrite the intro paragraph",
46
+ "Suggest better tags"
47
+ ],
48
+ clientTools: {
49
+ fillBlogForm: fillBlogFormHandler.createFillBlogFormHandler(
50
+ formRef,
51
+ "Form updated successfully"
52
+ )
53
+ }
54
+ });
33
55
  const handleClose = () => {
34
56
  navigate(`${basePath}/blog`);
35
57
  };
@@ -53,7 +75,8 @@ function EditPostPage({ slug }) {
53
75
  postSlug: slug,
54
76
  onClose: handleClose,
55
77
  onSuccess: handleSuccess,
56
- onDelete: handleDelete
78
+ onDelete: handleDelete,
79
+ onFormReady: handleFormReady
57
80
  }
58
81
  )
59
82
  ] });
@@ -6,6 +6,9 @@ import { PageHeader } from '../shared/page-header.mjs';
6
6
  import { PageWrapper } from '../shared/page-wrapper.mjs';
7
7
  import { BLOG_LOCALIZATION } from '../../localization/index.mjs';
8
8
  import { useRouteLifecycle } from '../../../../../../../ui/src/hooks/use-route-lifecycle.mjs';
9
+ import { useRegisterPageAIContext } from '@btst/stack/plugins/ai-chat/client/context';
10
+ import { useRef, useCallback } from 'react';
11
+ import { createFillBlogFormHandler } from './fill-blog-form-handler.mjs';
9
12
 
10
13
  function EditPostPage({ slug }) {
11
14
  const overrides = usePluginOverrides("blog", {
@@ -28,6 +31,25 @@ function EditPostPage({ slug }) {
28
31
  return true;
29
32
  }
30
33
  });
34
+ const formRef = useRef(null);
35
+ const handleFormReady = useCallback((form) => {
36
+ formRef.current = form;
37
+ }, []);
38
+ useRegisterPageAIContext({
39
+ routeName: "blog-edit-post",
40
+ pageDescription: `User is editing a blog post (slug: "${slug}") in the admin editor.`,
41
+ suggestions: [
42
+ "Improve this post's title",
43
+ "Rewrite the intro paragraph",
44
+ "Suggest better tags"
45
+ ],
46
+ clientTools: {
47
+ fillBlogForm: createFillBlogFormHandler(
48
+ formRef,
49
+ "Form updated successfully"
50
+ )
51
+ }
52
+ });
31
53
  const handleClose = () => {
32
54
  navigate(`${basePath}/blog`);
33
55
  };
@@ -51,7 +73,8 @@ function EditPostPage({ slug }) {
51
73
  postSlug: slug,
52
74
  onClose: handleClose,
53
75
  onSuccess: handleSuccess,
54
- onDelete: handleDelete
76
+ onDelete: handleDelete,
77
+ onFormReady: handleFormReady
55
78
  }
56
79
  )
57
80
  ] });
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ function createFillBlogFormHandler(formRef, successMessage) {
4
+ return async ({
5
+ title,
6
+ content,
7
+ excerpt,
8
+ tags
9
+ }) => {
10
+ const form = formRef.current;
11
+ if (!form) return { success: false, message: "Form not ready" };
12
+ if (title !== void 0)
13
+ form.setValue("title", title, { shouldValidate: true });
14
+ if (content !== void 0)
15
+ form.setValue("content", content, { shouldValidate: true });
16
+ if (excerpt !== void 0) form.setValue("excerpt", excerpt);
17
+ if (tags !== void 0)
18
+ form.setValue(
19
+ "tags",
20
+ tags.map((name) => ({ name }))
21
+ );
22
+ return { success: true, message: successMessage };
23
+ };
24
+ }
25
+
26
+ exports.createFillBlogFormHandler = createFillBlogFormHandler;
@@ -0,0 +1,24 @@
1
+ function createFillBlogFormHandler(formRef, successMessage) {
2
+ return async ({
3
+ title,
4
+ content,
5
+ excerpt,
6
+ tags
7
+ }) => {
8
+ const form = formRef.current;
9
+ if (!form) return { success: false, message: "Form not ready" };
10
+ if (title !== void 0)
11
+ form.setValue("title", title, { shouldValidate: true });
12
+ if (content !== void 0)
13
+ form.setValue("content", content, { shouldValidate: true });
14
+ if (excerpt !== void 0) form.setValue("excerpt", excerpt);
15
+ if (tags !== void 0)
16
+ form.setValue(
17
+ "tags",
18
+ tags.map((name) => ({ name }))
19
+ );
20
+ return { success: true, message: successMessage };
21
+ };
22
+ }
23
+
24
+ export { createFillBlogFormHandler };
@@ -8,6 +8,9 @@ const pageHeader = require('../shared/page-header.cjs');
8
8
  const pageWrapper = require('../shared/page-wrapper.cjs');
9
9
  const index = require('../../localization/index.cjs');
10
10
  const useRouteLifecycle = require('../../../../../../../ui/src/hooks/use-route-lifecycle.cjs');
11
+ const context$1 = require('@btst/stack/plugins/ai-chat/client/context');
12
+ const React = require('react');
13
+ const fillBlogFormHandler = require('./fill-blog-form-handler.cjs');
11
14
 
12
15
  function NewPostPage() {
13
16
  const overrides = context.usePluginOverrides("blog", {
@@ -29,6 +32,25 @@ function NewPostPage() {
29
32
  return true;
30
33
  }
31
34
  });
35
+ const formRef = React.useRef(null);
36
+ const handleFormReady = React.useCallback((form) => {
37
+ formRef.current = form;
38
+ }, []);
39
+ context$1.useRegisterPageAIContext({
40
+ routeName: "blog-new-post",
41
+ pageDescription: "User is creating a new blog post in the admin editor. IMPORTANT: When asked to write, draft, or create a blog post, you MUST call the fillBlogForm tool to populate the form fields directly \u2014 do NOT just output the text in your response.",
42
+ suggestions: [
43
+ "Write a post about AI trends",
44
+ "Draft an intro paragraph",
45
+ "Suggest 5 tags for this post"
46
+ ],
47
+ clientTools: {
48
+ fillBlogForm: fillBlogFormHandler.createFillBlogFormHandler(
49
+ formRef,
50
+ "Form filled successfully"
51
+ )
52
+ }
53
+ });
32
54
  const handleClose = () => {
33
55
  navigate(`${basePath}/blog`);
34
56
  };
@@ -47,7 +69,14 @@ function NewPostPage() {
47
69
  description: localization.BLOG_POST_ADD_DESCRIPTION
48
70
  }
49
71
  ),
50
- /* @__PURE__ */ jsxRuntime.jsx(postForms.AddPostForm, { onClose: handleClose, onSuccess: handleSuccess })
72
+ /* @__PURE__ */ jsxRuntime.jsx(
73
+ postForms.AddPostForm,
74
+ {
75
+ onClose: handleClose,
76
+ onSuccess: handleSuccess,
77
+ onFormReady: handleFormReady
78
+ }
79
+ )
51
80
  ] });
52
81
  }
53
82
 
@@ -6,6 +6,9 @@ import { PageHeader } from '../shared/page-header.mjs';
6
6
  import { PageWrapper } from '../shared/page-wrapper.mjs';
7
7
  import { BLOG_LOCALIZATION } from '../../localization/index.mjs';
8
8
  import { useRouteLifecycle } from '../../../../../../../ui/src/hooks/use-route-lifecycle.mjs';
9
+ import { useRegisterPageAIContext } from '@btst/stack/plugins/ai-chat/client/context';
10
+ import { useRef, useCallback } from 'react';
11
+ import { createFillBlogFormHandler } from './fill-blog-form-handler.mjs';
9
12
 
10
13
  function NewPostPage() {
11
14
  const overrides = usePluginOverrides("blog", {
@@ -27,6 +30,25 @@ function NewPostPage() {
27
30
  return true;
28
31
  }
29
32
  });
33
+ const formRef = useRef(null);
34
+ const handleFormReady = useCallback((form) => {
35
+ formRef.current = form;
36
+ }, []);
37
+ useRegisterPageAIContext({
38
+ routeName: "blog-new-post",
39
+ pageDescription: "User is creating a new blog post in the admin editor. IMPORTANT: When asked to write, draft, or create a blog post, you MUST call the fillBlogForm tool to populate the form fields directly \u2014 do NOT just output the text in your response.",
40
+ suggestions: [
41
+ "Write a post about AI trends",
42
+ "Draft an intro paragraph",
43
+ "Suggest 5 tags for this post"
44
+ ],
45
+ clientTools: {
46
+ fillBlogForm: createFillBlogFormHandler(
47
+ formRef,
48
+ "Form filled successfully"
49
+ )
50
+ }
51
+ });
30
52
  const handleClose = () => {
31
53
  navigate(`${basePath}/blog`);
32
54
  };
@@ -45,7 +67,14 @@ function NewPostPage() {
45
67
  description: localization.BLOG_POST_ADD_DESCRIPTION
46
68
  }
47
69
  ),
48
- /* @__PURE__ */ jsx(AddPostForm, { onClose: handleClose, onSuccess: handleSuccess })
70
+ /* @__PURE__ */ jsx(
71
+ AddPostForm,
72
+ {
73
+ onClose: handleClose,
74
+ onSuccess: handleSuccess,
75
+ onFormReady: handleFormReady
76
+ }
77
+ )
49
78
  ] });
50
79
  }
51
80
 
@@ -16,6 +16,7 @@ const recentPostsCarousel = require('../shared/recent-posts-carousel.cjs');
16
16
  const badge = require('../../../../../../../ui/src/components/badge.cjs');
17
17
  const useRouteLifecycle = require('../../../../../../../ui/src/hooks/use-route-lifecycle.cjs');
18
18
  const onThisPage = require('../shared/on-this-page.cjs');
19
+ const context$1 = require('@btst/stack/plugins/ai-chat/client/context');
19
20
 
20
21
  function PostPage({ slug }) {
21
22
  const overrides = context.usePluginOverrides("blog", {
@@ -50,6 +51,23 @@ function PostPage({ slug }) {
50
51
  excludeSlug: slug,
51
52
  enabled: !!post
52
53
  });
54
+ context$1.useRegisterPageAIContext(
55
+ post ? {
56
+ routeName: "blog-post",
57
+ pageDescription: `Blog post: "${post.title}"
58
+ Author: ${post.authorId ?? "Unknown"}
59
+
60
+ ${post.content ?? ""}`.slice(
61
+ 0,
62
+ 16e3
63
+ ),
64
+ suggestions: [
65
+ "Summarize this post",
66
+ "What are the key takeaways?",
67
+ "Explain this in simpler terms"
68
+ ]
69
+ } : null
70
+ );
53
71
  if (!slug || !post) {
54
72
  return /* @__PURE__ */ jsxRuntime.jsx(pageWrapper.PageWrapper, { children: /* @__PURE__ */ jsxRuntime.jsx(emptyList.EmptyList, { message: localization.BLOG_PAGE_NOT_FOUND_DESCRIPTION }) });
55
73
  }
@@ -14,6 +14,7 @@ import { RecentPostsCarousel } from '../shared/recent-posts-carousel.mjs';
14
14
  import { Badge } from '../../../../../../../ui/src/components/badge.mjs';
15
15
  import { useRouteLifecycle } from '../../../../../../../ui/src/hooks/use-route-lifecycle.mjs';
16
16
  import { OnThisPageSelect, OnThisPage } from '../shared/on-this-page.mjs';
17
+ import { useRegisterPageAIContext } from '@btst/stack/plugins/ai-chat/client/context';
17
18
 
18
19
  function PostPage({ slug }) {
19
20
  const overrides = usePluginOverrides("blog", {
@@ -48,6 +49,23 @@ function PostPage({ slug }) {
48
49
  excludeSlug: slug,
49
50
  enabled: !!post
50
51
  });
52
+ useRegisterPageAIContext(
53
+ post ? {
54
+ routeName: "blog-post",
55
+ pageDescription: `Blog post: "${post.title}"
56
+ Author: ${post.authorId ?? "Unknown"}
57
+
58
+ ${post.content ?? ""}`.slice(
59
+ 0,
60
+ 16e3
61
+ ),
62
+ suggestions: [
63
+ "Summarize this post",
64
+ "What are the key takeaways?",
65
+ "Explain this in simpler terms"
66
+ ]
67
+ } : null
68
+ );
51
69
  if (!slug || !post) {
52
70
  return /* @__PURE__ */ jsx(PageWrapper, { children: /* @__PURE__ */ jsx(EmptyList, { message: localization.BLOG_PAGE_NOT_FOUND_DESCRIPTION }) });
53
71
  }
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ const getters = require('./getters.cjs');
4
+
5
+ async function createCMSContentItem(adapter, contentTypeSlug, input) {
6
+ const contentType = await adapter.findOne({
7
+ model: "contentType",
8
+ where: [
9
+ {
10
+ field: "slug",
11
+ value: contentTypeSlug,
12
+ operator: "eq"
13
+ }
14
+ ]
15
+ });
16
+ if (!contentType) {
17
+ throw new Error(`Content type "${contentTypeSlug}" not found`);
18
+ }
19
+ const existing = await adapter.findOne({
20
+ model: "contentItem",
21
+ where: [
22
+ {
23
+ field: "contentTypeId",
24
+ value: contentType.id,
25
+ operator: "eq"
26
+ },
27
+ { field: "slug", value: input.slug, operator: "eq" }
28
+ ]
29
+ });
30
+ if (existing) {
31
+ throw new Error(
32
+ `Content item with slug "${input.slug}" already exists in type "${contentTypeSlug}"`
33
+ );
34
+ }
35
+ const item = await adapter.create({
36
+ model: "contentItem",
37
+ data: {
38
+ contentTypeId: contentType.id,
39
+ slug: input.slug,
40
+ data: JSON.stringify(input.data),
41
+ createdAt: /* @__PURE__ */ new Date(),
42
+ updatedAt: /* @__PURE__ */ new Date()
43
+ }
44
+ });
45
+ return getters.serializeContentItem(item);
46
+ }
47
+
48
+ exports.createCMSContentItem = createCMSContentItem;
@@ -0,0 +1,46 @@
1
+ import { serializeContentItem } from './getters.mjs';
2
+
3
+ async function createCMSContentItem(adapter, contentTypeSlug, input) {
4
+ const contentType = await adapter.findOne({
5
+ model: "contentType",
6
+ where: [
7
+ {
8
+ field: "slug",
9
+ value: contentTypeSlug,
10
+ operator: "eq"
11
+ }
12
+ ]
13
+ });
14
+ if (!contentType) {
15
+ throw new Error(`Content type "${contentTypeSlug}" not found`);
16
+ }
17
+ const existing = await adapter.findOne({
18
+ model: "contentItem",
19
+ where: [
20
+ {
21
+ field: "contentTypeId",
22
+ value: contentType.id,
23
+ operator: "eq"
24
+ },
25
+ { field: "slug", value: input.slug, operator: "eq" }
26
+ ]
27
+ });
28
+ if (existing) {
29
+ throw new Error(
30
+ `Content item with slug "${input.slug}" already exists in type "${contentTypeSlug}"`
31
+ );
32
+ }
33
+ const item = await adapter.create({
34
+ model: "contentItem",
35
+ data: {
36
+ contentTypeId: contentType.id,
37
+ slug: input.slug,
38
+ data: JSON.stringify(input.data),
39
+ createdAt: /* @__PURE__ */ new Date(),
40
+ updatedAt: /* @__PURE__ */ new Date()
41
+ }
42
+ });
43
+ return serializeContentItem(item);
44
+ }
45
+
46
+ export { createCMSContentItem };
@@ -7,6 +7,7 @@ const db = require('../db.cjs');
7
7
  const schemas = require('../schemas.cjs');
8
8
  const utils = require('../utils.cjs');
9
9
  const getters = require('./getters.cjs');
10
+ const mutations = require('./mutations.cjs');
10
11
  const queryKeyDefs = require('./query-key-defs.cjs');
11
12
 
12
13
  async function syncContentTypes(adapter, config) {
@@ -327,7 +328,12 @@ const cmsBackendPlugin = (config) => {
327
328
  await ensureSynced(adapter);
328
329
  return getters.getContentItemById(adapter, id);
329
330
  },
330
- prefetchForRoute: createCMSPrefetchForRoute(adapter)
331
+ prefetchForRoute: createCMSPrefetchForRoute(adapter),
332
+ // Mutations
333
+ createContentItem: async (typeSlug, input) => {
334
+ await ensureSynced(adapter);
335
+ return mutations.createCMSContentItem(adapter, typeSlug, input);
336
+ }
331
337
  }),
332
338
  routes: (adapter) => {
333
339
  const getContentType = async (slug) => {
@@ -5,6 +5,7 @@ import { cmsSchema } from '../db.mjs';
5
5
  import { listContentQuerySchema } from '../schemas.mjs';
6
6
  import { slugify } from '../utils.mjs';
7
7
  import { serializeContentType, getAllContentItems, serializeContentItemWithType, serializeContentItem, getContentItemById, getContentItemBySlug, getAllContentTypes } from './getters.mjs';
8
+ import { createCMSContentItem } from './mutations.mjs';
8
9
  import { CMS_QUERY_KEYS } from './query-key-defs.mjs';
9
10
 
10
11
  async function syncContentTypes(adapter, config) {
@@ -325,7 +326,12 @@ const cmsBackendPlugin = (config) => {
325
326
  await ensureSynced(adapter);
326
327
  return getContentItemById(adapter, id);
327
328
  },
328
- prefetchForRoute: createCMSPrefetchForRoute(adapter)
329
+ prefetchForRoute: createCMSPrefetchForRoute(adapter),
330
+ // Mutations
331
+ createContentItem: async (typeSlug, input) => {
332
+ await ensureSynced(adapter);
333
+ return createCMSContentItem(adapter, typeSlug, input);
334
+ }
329
335
  }),
330
336
  routes: (adapter) => {
331
337
  const getContentType = async (slug) => {
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ async function createKanbanTask(adapter, input) {
4
+ const existingTasks = await adapter.findMany({
5
+ model: "kanbanTask",
6
+ where: [
7
+ {
8
+ field: "columnId",
9
+ value: input.columnId,
10
+ operator: "eq"
11
+ }
12
+ ]
13
+ });
14
+ const nextOrder = existingTasks.length > 0 ? Math.max(...existingTasks.map((t) => t.order)) + 1 : 0;
15
+ return adapter.create({
16
+ model: "kanbanTask",
17
+ data: {
18
+ title: input.title,
19
+ columnId: input.columnId,
20
+ description: input.description,
21
+ priority: input.priority ?? "MEDIUM",
22
+ order: nextOrder,
23
+ assigneeId: input.assigneeId,
24
+ isArchived: false,
25
+ createdAt: /* @__PURE__ */ new Date(),
26
+ updatedAt: /* @__PURE__ */ new Date()
27
+ }
28
+ });
29
+ }
30
+ const _pendingBoardCreations = /* @__PURE__ */ new Map();
31
+ async function findOrCreateKanbanBoard(adapter, slug, name, columnTitles) {
32
+ const existing = await adapter.findOne({
33
+ model: "kanbanBoard",
34
+ where: [{ field: "slug", value: slug, operator: "eq" }]
35
+ });
36
+ if (existing) return existing;
37
+ const inflight = _pendingBoardCreations.get(slug);
38
+ if (inflight) return inflight;
39
+ const creation = (async () => {
40
+ try {
41
+ const board = await adapter.create({
42
+ model: "kanbanBoard",
43
+ data: {
44
+ name,
45
+ slug,
46
+ createdAt: /* @__PURE__ */ new Date(),
47
+ updatedAt: /* @__PURE__ */ new Date()
48
+ }
49
+ });
50
+ await Promise.all(
51
+ columnTitles.map(
52
+ (title, index) => adapter.create({
53
+ model: "kanbanColumn",
54
+ data: {
55
+ title,
56
+ boardId: board.id,
57
+ order: index,
58
+ createdAt: /* @__PURE__ */ new Date(),
59
+ updatedAt: /* @__PURE__ */ new Date()
60
+ }
61
+ })
62
+ )
63
+ );
64
+ return board;
65
+ } catch (err) {
66
+ const winner = await adapter.findOne({
67
+ model: "kanbanBoard",
68
+ where: [{ field: "slug", value: slug, operator: "eq" }]
69
+ });
70
+ if (winner) return winner;
71
+ throw err;
72
+ }
73
+ })();
74
+ _pendingBoardCreations.set(slug, creation);
75
+ try {
76
+ return await creation;
77
+ } finally {
78
+ _pendingBoardCreations.delete(slug);
79
+ }
80
+ }
81
+ async function getKanbanColumnsByBoardId(adapter, boardId) {
82
+ return adapter.findMany({
83
+ model: "kanbanColumn",
84
+ where: [{ field: "boardId", value: boardId, operator: "eq" }],
85
+ sortBy: { field: "order", direction: "asc" }
86
+ });
87
+ }
88
+
89
+ exports.createKanbanTask = createKanbanTask;
90
+ exports.findOrCreateKanbanBoard = findOrCreateKanbanBoard;
91
+ exports.getKanbanColumnsByBoardId = getKanbanColumnsByBoardId;