@webiny/ai-powerups 6.4.0-beta.3 → 6.4.0-beta.4

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 (30) hide show
  1. package/admin/Extension.js +3 -0
  2. package/admin/Extension.js.map +1 -1
  3. package/admin/presentation/WbContentGeneration/GenerateContentDialog.js +24 -4
  4. package/admin/presentation/WbContentGeneration/GenerateContentDialog.js.map +1 -1
  5. package/admin/presentation/WbContentGeneration/GenerateContentPresenter.d.ts +3 -0
  6. package/admin/presentation/WbContentGeneration/GenerateContentPresenter.js +19 -4
  7. package/admin/presentation/WbContentGeneration/GenerateContentPresenter.js.map +1 -1
  8. package/admin/presentation/WbContentGeneration/abstractions.d.ts +2 -1
  9. package/admin/presentation/WbContentGeneration/abstractions.js.map +1 -1
  10. package/api/features/AiImageEnrichment/AiImageEnrichmentTask.js +1 -1
  11. package/api/features/AiImageEnrichment/AiImageEnrichmentTask.js.map +1 -1
  12. package/api/features/Providers/ProvidersHandler.js +1 -1
  13. package/api/features/Providers/ProvidersHandler.js.map +1 -1
  14. package/api/features/WbGeneratePageContent/ComponentFilter.d.ts +18 -0
  15. package/api/features/WbGeneratePageContent/ComponentFilter.js +69 -0
  16. package/api/features/WbGeneratePageContent/ComponentFilter.js.map +1 -0
  17. package/api/features/WbGeneratePageContent/LlmJsonResponse.d.ts +7 -0
  18. package/api/features/WbGeneratePageContent/LlmJsonResponse.js +35 -0
  19. package/api/features/WbGeneratePageContent/LlmJsonResponse.js.map +1 -0
  20. package/api/features/WbGeneratePageContent/WbGeneratePageContentUseCase.d.ts +3 -1
  21. package/api/features/WbGeneratePageContent/WbGeneratePageContentUseCase.js +23 -9
  22. package/api/features/WbGeneratePageContent/WbGeneratePageContentUseCase.js.map +1 -1
  23. package/api/features/WbGeneratePageContent/abstractions.d.ts +2 -0
  24. package/api/features/WbGeneratePageContent/abstractions.js.map +1 -1
  25. package/api/features/WbGeneratePageContent/buildPrompt.d.ts +3 -1
  26. package/api/features/WbGeneratePageContent/buildPrompt.js +24 -9
  27. package/api/features/WbGeneratePageContent/buildPrompt.js.map +1 -1
  28. package/api/types.d.ts +1 -1
  29. package/api/types.js +1 -1
  30. package/package.json +26 -25
@@ -18,6 +18,9 @@ const AiPowerUpsSettings = ()=>{
18
18
  name: "aiPowerUps",
19
19
  element: /*#__PURE__*/ react.createElement(Menu.Link, {
20
20
  text: "AI Power-Ups",
21
+ badge: /*#__PURE__*/ react.createElement(Menu.Link.Badge, {
22
+ text: "BETA"
23
+ }),
21
24
  to: getLink(Routes.Settings),
22
25
  pinnable: true
23
26
  })
@@ -1 +1 @@
1
- {"version":3,"file":"admin/Extension.js","sources":["../../src/admin/Extension.tsx"],"sourcesContent":["import React from \"react\";\nimport { AdminConfig, AdminLayout, useRouter, RegisterFeature } from \"@webiny/app-admin\";\nimport { AiPowerUpsSettingsFeature } from \"./presentation/AiPowerUpsSettings/index.js\";\nimport { AiPowerUpsSettingsPage } from \"./presentation/AiPowerUpsSettings/AiPowerUpsSettingsPage.js\";\nimport { WbContentGeneration } from \"~/admin/presentation/WbContentGeneration/Extension.js\";\nimport { AiPowerUpsHeadlessFeatures } from \"~/admin/features/feature.js\";\nimport { Routes } from \"./routes.js\";\n\nconst { Menu, Route } = AdminConfig;\n\nconst AiPowerUpsSettings = () => {\n const { getLink } = useRouter();\n\n return (\n <AdminConfig>\n <Route\n route={Routes.Settings}\n element={\n <AdminLayout title={\"AI Power-Ups\"}>\n <AiPowerUpsSettingsPage />\n </AdminLayout>\n }\n />\n <Menu\n parent={\"settings.system\"}\n name={\"aiPowerUps\"}\n element={\n <Menu.Link\n text={\"AI Power-Ups\"}\n to={getLink(Routes.Settings)}\n pinnable={true}\n />\n }\n />\n </AdminConfig>\n );\n};\n\nexport const Extension = () => {\n return (\n <>\n <RegisterFeature feature={AiPowerUpsHeadlessFeatures} />\n <RegisterFeature feature={AiPowerUpsSettingsFeature} />\n <AiPowerUpsSettings />\n {/* Website Builder Extension */}\n <WbContentGeneration />\n </>\n );\n};\n"],"names":["Menu","Route","AdminConfig","AiPowerUpsSettings","getLink","useRouter","Routes","AdminLayout","AiPowerUpsSettingsPage","Extension","RegisterFeature","AiPowerUpsHeadlessFeatures","AiPowerUpsSettingsFeature","WbContentGeneration"],"mappings":";;;;;;;AAQA,MAAM,EAAEA,MAAAA,IAAI,EAAEC,OAAAA,KAAK,EAAE,GAAGC;AAExB,MAAMC,qBAAqB;IACvB,MAAM,EAAEC,OAAO,EAAE,GAAGC;IAEpB,OAAO,WAAP,GACI,oBAACH,aAAWA,MAAAA,WAAAA,GACR,oBAACD,OAAKA;QACF,OAAOK,OAAO,QAAQ;QACtB,uBACI,oBAACC,aAAWA;YAAC,OAAO;yBAChB,oBAACC,wBAAsBA;sBAInC,oBAACR,MAAIA;QACD,QAAQ;QACR,MAAM;QACN,uBACI,oBAACA,KAAK,IAAI;YACN,MAAM;YACN,IAAII,QAAQE,OAAO,QAAQ;YAC3B,UAAU;;;AAMlC;AAEO,MAAMG,YAAY,IACd,WAAP,GACI,wDACI,oBAACC,iBAAeA;QAAC,SAASC;sBAC1B,oBAACD,iBAAeA;QAAC,SAASE;sBAC1B,oBAACT,oBAAkBA,OAAAA,WAAAA,GAEnB,oBAACU,qBAAmBA"}
1
+ {"version":3,"file":"admin/Extension.js","sources":["../../src/admin/Extension.tsx"],"sourcesContent":["import React from \"react\";\nimport { AdminConfig, AdminLayout, useRouter, RegisterFeature } from \"@webiny/app-admin\";\nimport { AiPowerUpsSettingsFeature } from \"./presentation/AiPowerUpsSettings/index.js\";\nimport { AiPowerUpsSettingsPage } from \"./presentation/AiPowerUpsSettings/AiPowerUpsSettingsPage.js\";\nimport { WbContentGeneration } from \"~/admin/presentation/WbContentGeneration/Extension.js\";\nimport { AiPowerUpsHeadlessFeatures } from \"~/admin/features/feature.js\";\nimport { Routes } from \"./routes.js\";\n\nconst { Menu, Route } = AdminConfig;\n\nconst AiPowerUpsSettings = () => {\n const { getLink } = useRouter();\n\n return (\n <AdminConfig>\n <Route\n route={Routes.Settings}\n element={\n <AdminLayout title={\"AI Power-Ups\"}>\n <AiPowerUpsSettingsPage />\n </AdminLayout>\n }\n />\n <Menu\n parent={\"settings.system\"}\n name={\"aiPowerUps\"}\n element={\n <Menu.Link\n text={\"AI Power-Ups\"}\n badge={<Menu.Link.Badge text=\"BETA\" />}\n to={getLink(Routes.Settings)}\n pinnable={true}\n />\n }\n />\n </AdminConfig>\n );\n};\n\nexport const Extension = () => {\n return (\n <>\n <RegisterFeature feature={AiPowerUpsHeadlessFeatures} />\n <RegisterFeature feature={AiPowerUpsSettingsFeature} />\n <AiPowerUpsSettings />\n {/* Website Builder Extension */}\n <WbContentGeneration />\n </>\n );\n};\n"],"names":["Menu","Route","AdminConfig","AiPowerUpsSettings","getLink","useRouter","Routes","AdminLayout","AiPowerUpsSettingsPage","Extension","RegisterFeature","AiPowerUpsHeadlessFeatures","AiPowerUpsSettingsFeature","WbContentGeneration"],"mappings":";;;;;;;AAQA,MAAM,EAAEA,MAAAA,IAAI,EAAEC,OAAAA,KAAK,EAAE,GAAGC;AAExB,MAAMC,qBAAqB;IACvB,MAAM,EAAEC,OAAO,EAAE,GAAGC;IAEpB,OAAO,WAAP,GACI,oBAACH,aAAWA,MAAAA,WAAAA,GACR,oBAACD,OAAKA;QACF,OAAOK,OAAO,QAAQ;QACtB,uBACI,oBAACC,aAAWA;YAAC,OAAO;yBAChB,oBAACC,wBAAsBA;sBAInC,oBAACR,MAAIA;QACD,QAAQ;QACR,MAAM;QACN,uBACI,oBAACA,KAAK,IAAI;YACN,MAAM;YACN,qBAAO,oBAACA,KAAK,IAAI,CAAC,KAAK;gBAAC,MAAK;;YAC7B,IAAII,QAAQE,OAAO,QAAQ;YAC3B,UAAU;;;AAMlC;AAEO,MAAMG,YAAY,IACd,WAAP,GACI,wDACI,oBAACC,iBAAeA;QAAC,SAASC;sBAC1B,oBAACD,iBAAeA;QAAC,SAASE;sBAC1B,oBAACT,oBAAkBA,OAAAA,WAAAA,GAEnB,oBAACU,qBAAmBA"}
@@ -8,6 +8,11 @@ import { useSelectFromEditor } from "@webiny/app-website-builder/BaseEditor/hook
8
8
  import { useCreateElement } from "@webiny/app-website-builder/BaseEditor/hooks/useCreateElement.js";
9
9
  import { GenerateContentFeature } from "./feature.js";
10
10
  import { decompressGzipBase64 } from "./decompressGzipBase64.js";
11
+ function formatElapsed(totalSeconds) {
12
+ const minutes = Math.floor(totalSeconds / 60);
13
+ const seconds = totalSeconds % 60;
14
+ return `${minutes}:${seconds.toString().padStart(2, "0")}`;
15
+ }
11
16
  const GENERATE_CONTENT_DIALOG = "generate-content";
12
17
  const WS_ACTION_CONTENT = "aiPowerUps.generatePageContent.content";
13
18
  const WS_ACTION_ERROR = "aiPowerUps.generatePageContent.error";
@@ -20,8 +25,8 @@ const GenerateContentDialog = observer(()=>{
20
25
  const toast = useToast();
21
26
  const components = useSelectFromEditor((state)=>state.components);
22
27
  const { createElement } = useCreateElement();
23
- const createElements = useCallback((elements)=>{
24
- elements.forEach((el)=>createElement(el));
28
+ const createElements = useCallback(async (elements)=>{
29
+ for (const el of elements)await createElement(el);
25
30
  }, [
26
31
  createElement
27
32
  ]);
@@ -45,7 +50,19 @@ const GenerateContentDialog = observer(()=>{
45
50
  useEffect(()=>{
46
51
  const contentSubscription = websockets.onMessage(WS_ACTION_CONTENT, async (message)=>{
47
52
  const responseText = await decompressGzipBase64(message.data.value);
48
- await presenter.processAiResponse(responseText);
53
+ try {
54
+ await presenter.processAiResponse(responseText);
55
+ } catch (e) {
56
+ console.error("Failed to process AI response", {
57
+ responseText
58
+ });
59
+ console.error(e);
60
+ toast.showWarningToast({
61
+ title: "Failed to process AI response",
62
+ description: "Open the console for more details."
63
+ });
64
+ presenter.cancelPrompt();
65
+ }
49
66
  });
50
67
  const errorSubscription = websockets.onMessage(WS_ACTION_ERROR, async (message)=>{
51
68
  toast.showWarningToast({
@@ -92,7 +109,10 @@ const GenerateContentDialog = observer(()=>{
92
109
  }, vm.loading ? /*#__PURE__*/ react.createElement(OverlayLoader, {
93
110
  text: "Loading..."
94
111
  }) : null, vm.submitting ? /*#__PURE__*/ react.createElement(OverlayLoader, {
95
- text: "Generating content..."
112
+ className: "bg-neutral-base/90",
113
+ text: /*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement("div", null, "Generating content... ", formatElapsed(vm.elapsedSeconds)), /*#__PURE__*/ react.createElement("div", {
114
+ className: "text-sm text-neutral-muted pt-xs"
115
+ }, "Content generation can take a few minutes, depending on the model you're using."))
96
116
  }) : null, vm.processing ? /*#__PURE__*/ react.createElement(OverlayLoader, {
97
117
  text: "Processing content..."
98
118
  }) : null, vm.form ? /*#__PURE__*/ react.createElement(FormView, {
@@ -1 +1 @@
1
- {"version":3,"file":"admin/presentation/WbContentGeneration/GenerateContentDialog.js","sources":["../../../../src/admin/presentation/WbContentGeneration/GenerateContentDialog.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef } from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { useDialog, FormView } from \"@webiny/app-admin\";\nimport { Dialog, OverlayLoader, useToast } from \"@webiny/admin-ui\";\nimport { useFeature } from \"@webiny/app\";\nimport type { IncomingGenericData } from \"@webiny/app-websockets\";\nimport { useWebsockets } from \"@webiny/app-websockets\";\nimport { useSelectFromEditor } from \"@webiny/app-website-builder/BaseEditor/hooks/useSelectFromEditor.js\";\nimport { useCreateElement } from \"@webiny/app-website-builder/BaseEditor/hooks/useCreateElement.js\";\nimport { GenerateContentFeature } from \"./feature.js\";\nimport { decompressGzipBase64 } from \"./decompressGzipBase64.js\";\nimport type { CreateElementParams } from \"./abstractions.js\";\n\nexport const GENERATE_CONTENT_DIALOG = \"generate-content\";\n\nexport const WS_ACTION_CONTENT = \"aiPowerUps.generatePageContent.content\";\nexport const WS_ACTION_ERROR = \"aiPowerUps.generatePageContent.error\";\n\nexport interface GeneratePageContentMessage extends IncomingGenericData {\n action: typeof WS_ACTION_CONTENT;\n data: {\n compression: \"gzip\";\n value: string;\n };\n}\n\nexport interface GeneratePageContentErrorMessage extends IncomingGenericData {\n action: typeof WS_ACTION_ERROR;\n data: {\n message: string;\n };\n}\n\nexport const GenerateContentDialog = observer(() => {\n const { closeDialog } = useDialog();\n const { presenter } = useFeature(GenerateContentFeature);\n const vm = presenter.vm;\n const wasSubmitting = useRef(false);\n const websockets = useWebsockets();\n const toast = useToast();\n\n const components = useSelectFromEditor(state => state.components);\n const { createElement } = useCreateElement();\n\n const createElements = useCallback(\n (elements: CreateElementParams[]) => {\n elements.forEach(el => createElement(el));\n },\n [createElement]\n );\n\n useEffect(() => {\n const aiComponents = Object.values(components)\n .filter(c => c.useInAiContentGeneration !== false)\n .map(c => {\n return {\n name: c.name,\n label: c.label,\n aiContext: c.aiContext,\n inputs: c.inputs.map(input => ({\n type: input.type,\n name: input.name,\n label: input.label,\n description: input.description\n }))\n };\n });\n presenter.init(aiComponents, createElements);\n }, [components, createElements]);\n\n useEffect(() => {\n const contentSubscription = websockets.onMessage<GeneratePageContentMessage>(\n WS_ACTION_CONTENT,\n async message => {\n const responseText = await decompressGzipBase64(message.data.value);\n await presenter.processAiResponse(responseText);\n }\n );\n\n const errorSubscription = websockets.onMessage<GeneratePageContentErrorMessage>(\n WS_ACTION_ERROR,\n async message => {\n toast.showWarningToast({\n title: \"Failed to generate content\",\n description: message.data.message\n });\n presenter.cancelPrompt();\n }\n );\n\n return () => {\n contentSubscription.off();\n errorSubscription.off();\n };\n }, []);\n\n useEffect(() => {\n if (wasSubmitting.current && !vm.submitting && !vm.timedOut) {\n closeDialog();\n }\n wasSubmitting.current = vm.submitting;\n }, [vm.submitting]);\n\n useEffect(() => {\n if (vm.timedOut) {\n toast.showWarningToast({\n title: \"Request timed out\",\n description: \"The AI generation took too long. Please try again.\"\n });\n }\n }, [vm.timedOut]);\n\n const handleSubmit = async () => {\n await presenter.submit();\n };\n\n return (\n <Dialog\n open={true}\n onClose={closeDialog}\n title=\"Generate Content\"\n size={\"lg\"}\n actions={\n <>\n <Dialog.CancelAction onClick={closeDialog} text=\"Cancel\" />\n <Dialog.ConfirmAction\n onClick={handleSubmit}\n text=\"Generate\"\n disabled={vm.loading || vm.submitting}\n />\n </>\n }\n >\n {vm.loading ? <OverlayLoader text={\"Loading...\"} /> : null}\n {vm.submitting ? <OverlayLoader text={\"Generating content...\"} /> : null}\n {vm.processing ? <OverlayLoader text={\"Processing content...\"} /> : null}\n {vm.form ? <FormView name=\"GenerateContent\" form={vm.form} /> : null}\n </Dialog>\n );\n});\n"],"names":["GENERATE_CONTENT_DIALOG","WS_ACTION_CONTENT","WS_ACTION_ERROR","GenerateContentDialog","observer","closeDialog","useDialog","presenter","useFeature","GenerateContentFeature","vm","wasSubmitting","useRef","websockets","useWebsockets","toast","useToast","components","useSelectFromEditor","state","createElement","useCreateElement","createElements","useCallback","elements","el","useEffect","aiComponents","Object","c","input","contentSubscription","message","responseText","decompressGzipBase64","errorSubscription","handleSubmit","Dialog","OverlayLoader","FormView"],"mappings":"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"}
1
+ {"version":3,"file":"admin/presentation/WbContentGeneration/GenerateContentDialog.js","sources":["../../../../src/admin/presentation/WbContentGeneration/GenerateContentDialog.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef } from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { useDialog, FormView } from \"@webiny/app-admin\";\nimport { Dialog, OverlayLoader, useToast } from \"@webiny/admin-ui\";\nimport { useFeature } from \"@webiny/app\";\nimport type { IncomingGenericData } from \"@webiny/app-websockets\";\nimport { useWebsockets } from \"@webiny/app-websockets\";\nimport { useSelectFromEditor } from \"@webiny/app-website-builder/BaseEditor/hooks/useSelectFromEditor.js\";\nimport { useCreateElement } from \"@webiny/app-website-builder/BaseEditor/hooks/useCreateElement.js\";\nimport { GenerateContentFeature } from \"./feature.js\";\nimport { decompressGzipBase64 } from \"./decompressGzipBase64.js\";\nimport type { CreateElementParams } from \"./abstractions.js\";\n\nfunction formatElapsed(totalSeconds: number): string {\n const minutes = Math.floor(totalSeconds / 60);\n const seconds = totalSeconds % 60;\n return `${minutes}:${seconds.toString().padStart(2, \"0\")}`;\n}\n\nexport const GENERATE_CONTENT_DIALOG = \"generate-content\";\n\nexport const WS_ACTION_CONTENT = \"aiPowerUps.generatePageContent.content\";\nexport const WS_ACTION_ERROR = \"aiPowerUps.generatePageContent.error\";\n\nexport interface GeneratePageContentMessage extends IncomingGenericData {\n action: typeof WS_ACTION_CONTENT;\n data: {\n compression: \"gzip\";\n value: string;\n };\n}\n\nexport interface GeneratePageContentErrorMessage extends IncomingGenericData {\n action: typeof WS_ACTION_ERROR;\n data: {\n message: string;\n };\n}\n\nexport const GenerateContentDialog = observer(() => {\n const { closeDialog } = useDialog();\n const { presenter } = useFeature(GenerateContentFeature);\n const vm = presenter.vm;\n const wasSubmitting = useRef(false);\n const websockets = useWebsockets();\n const toast = useToast();\n\n const components = useSelectFromEditor(state => state.components);\n const { createElement } = useCreateElement();\n\n const createElements = useCallback(\n async (elements: CreateElementParams[]) => {\n for (const el of elements) {\n await createElement(el);\n }\n },\n [createElement]\n );\n\n useEffect(() => {\n const aiComponents = Object.values(components)\n .filter(c => c.useInAiContentGeneration !== false)\n .map(c => {\n return {\n name: c.name,\n label: c.label,\n aiContext: c.aiContext,\n inputs: c.inputs.map(input => ({\n type: input.type,\n name: input.name,\n label: input.label,\n description: input.description\n }))\n };\n });\n presenter.init(aiComponents, createElements);\n }, [components, createElements]);\n\n useEffect(() => {\n const contentSubscription = websockets.onMessage<GeneratePageContentMessage>(\n WS_ACTION_CONTENT,\n async message => {\n const responseText = await decompressGzipBase64(message.data.value);\n try {\n await presenter.processAiResponse(responseText);\n } catch (e) {\n console.error(\"Failed to process AI response\", { responseText });\n console.error(e);\n toast.showWarningToast({\n title: \"Failed to process AI response\",\n description: \"Open the console for more details.\"\n });\n presenter.cancelPrompt();\n }\n }\n );\n\n const errorSubscription = websockets.onMessage<GeneratePageContentErrorMessage>(\n WS_ACTION_ERROR,\n async message => {\n toast.showWarningToast({\n title: \"Failed to generate content\",\n description: message.data.message\n });\n presenter.cancelPrompt();\n }\n );\n\n return () => {\n contentSubscription.off();\n errorSubscription.off();\n };\n }, []);\n\n useEffect(() => {\n if (wasSubmitting.current && !vm.submitting && !vm.timedOut) {\n closeDialog();\n }\n wasSubmitting.current = vm.submitting;\n }, [vm.submitting]);\n\n useEffect(() => {\n if (vm.timedOut) {\n toast.showWarningToast({\n title: \"Request timed out\",\n description: \"The AI generation took too long. Please try again.\"\n });\n }\n }, [vm.timedOut]);\n\n const handleSubmit = async () => {\n await presenter.submit();\n };\n\n return (\n <Dialog\n open={true}\n onClose={closeDialog}\n title=\"Generate Content\"\n size={\"lg\"}\n actions={\n <>\n <Dialog.CancelAction onClick={closeDialog} text=\"Cancel\" />\n <Dialog.ConfirmAction\n onClick={handleSubmit}\n text=\"Generate\"\n disabled={vm.loading || vm.submitting}\n />\n </>\n }\n >\n {vm.loading ? <OverlayLoader text={\"Loading...\"} /> : null}\n {vm.submitting ? (\n <OverlayLoader\n className={\"bg-neutral-base/90\"}\n text={\n <>\n <div>Generating content... {formatElapsed(vm.elapsedSeconds)}</div>\n <div className=\"text-sm text-neutral-muted pt-xs\">\n Content generation can take a few minutes, depending on the model\n you&apos;re using.\n </div>\n </>\n }\n />\n ) : null}\n {vm.processing ? <OverlayLoader text={\"Processing content...\"} /> : null}\n {vm.form ? <FormView name=\"GenerateContent\" form={vm.form} /> : null}\n </Dialog>\n );\n});\n"],"names":["formatElapsed","totalSeconds","minutes","Math","seconds","GENERATE_CONTENT_DIALOG","WS_ACTION_CONTENT","WS_ACTION_ERROR","GenerateContentDialog","observer","closeDialog","useDialog","presenter","useFeature","GenerateContentFeature","vm","wasSubmitting","useRef","websockets","useWebsockets","toast","useToast","components","useSelectFromEditor","state","createElement","useCreateElement","createElements","useCallback","elements","el","useEffect","aiComponents","Object","c","input","contentSubscription","message","responseText","decompressGzipBase64","e","console","errorSubscription","handleSubmit","Dialog","OverlayLoader","FormView"],"mappings":"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"}
@@ -17,6 +17,8 @@ declare class GenerateContentPresenterImpl implements GenerateContentPresenter.I
17
17
  private _form;
18
18
  private _settings;
19
19
  private _timeoutId;
20
+ private _intervalId;
21
+ private _elapsedSeconds;
20
22
  constructor(toolRegistry: ToolRegistry.Interface, pipelineRunner: ToolPipelineRunner.Interface, generatePageContent: GeneratePageContentUseCase.Interface, formModelFactory: FormModelFactory.Interface, getSettings: GetSettingsUseCase.Interface);
21
23
  get vm(): GenerateContentPresenter.ViewModel;
22
24
  init(components: Record<string, any>[], createElements: CreateElementsFn): Promise<void>;
@@ -25,6 +27,7 @@ declare class GenerateContentPresenterImpl implements GenerateContentPresenter.I
25
27
  cancelPrompt(): void;
26
28
  private startTimeout;
27
29
  private clearTimeout;
30
+ private clearInterval;
28
31
  private buildForm;
29
32
  private getSelectedProject;
30
33
  private getProjectFileOptions;
@@ -22,6 +22,8 @@ class GenerateContentPresenterImpl {
22
22
  this._form = null;
23
23
  this._settings = null;
24
24
  this._timeoutId = null;
25
+ this._intervalId = null;
26
+ this._elapsedSeconds = 0;
25
27
  makeAutoObservable(this, {
26
28
  vm: computed
27
29
  }, {
@@ -34,7 +36,8 @@ class GenerateContentPresenterImpl {
34
36
  loading: this._loading,
35
37
  submitting: this._submitting,
36
38
  processing: this._processing,
37
- timedOut: this._timedOut
39
+ timedOut: this._timedOut,
40
+ elapsedSeconds: this._elapsedSeconds
38
41
  };
39
42
  }
40
43
  async init(components, createElements) {
@@ -104,7 +107,7 @@ class GenerateContentPresenterImpl {
104
107
  const items = Array.isArray(resolved) ? resolved : [
105
108
  resolved
106
109
  ];
107
- this._createElements(items.map((element)=>({
110
+ await this._createElements(items.map((element)=>({
108
111
  componentName: element.component,
109
112
  parentId: "root",
110
113
  slot: "children",
@@ -112,8 +115,6 @@ class GenerateContentPresenterImpl {
112
115
  inputs: element.inputs
113
116
  }
114
117
  })));
115
- } catch (e) {
116
- console.error(e);
117
118
  } finally{
118
119
  runInAction(()=>{
119
120
  this._processing = false;
@@ -127,11 +128,18 @@ class GenerateContentPresenterImpl {
127
128
  }
128
129
  startTimeout() {
129
130
  this.clearTimeout();
131
+ this._elapsedSeconds = 0;
132
+ this._intervalId = setInterval(()=>{
133
+ runInAction(()=>{
134
+ this._elapsedSeconds++;
135
+ });
136
+ }, 1000);
130
137
  this._timeoutId = setTimeout(()=>{
131
138
  runInAction(()=>{
132
139
  this._submitting = false;
133
140
  this._timedOut = true;
134
141
  });
142
+ this.clearInterval();
135
143
  }, SUBMIT_TIMEOUT_MS);
136
144
  }
137
145
  clearTimeout() {
@@ -139,6 +147,13 @@ class GenerateContentPresenterImpl {
139
147
  clearTimeout(this._timeoutId);
140
148
  this._timeoutId = null;
141
149
  }
150
+ this.clearInterval();
151
+ }
152
+ clearInterval() {
153
+ if (null !== this._intervalId) {
154
+ clearInterval(this._intervalId);
155
+ this._intervalId = null;
156
+ }
142
157
  }
143
158
  buildForm() {
144
159
  return this.formModelFactory.create({
@@ -1 +1 @@
1
- {"version":3,"file":"admin/presentation/WbContentGeneration/GenerateContentPresenter.js","sources":["../../../../src/admin/presentation/WbContentGeneration/GenerateContentPresenter.ts"],"sourcesContent":["import { makeAutoObservable, computed, toJS, runInAction } from \"mobx\";\nimport { z } from \"zod\";\nimport pick from \"lodash/pick.js\";\nimport { FormModelFactory, FormModel, ToolRegistry, ToolPipelineRunner } from \"@webiny/app-admin\";\nimport { GenerateContentPresenter, type CreateElementsFn } from \"./abstractions.js\";\nimport { GeneratePageContentUseCase } from \"~/admin/features/generatePageContent/index.js\";\nimport { GetSettingsUseCase } from \"~/admin/features/settings/getSettings/abstractions.js\";\nimport type { IAiPowerUpsSettings } from \"~/admin/features/settings/shared/abstractions.js\";\n\nconst SUBMIT_TIMEOUT_MS = 300_000;\n\nclass GenerateContentPresenterImpl implements GenerateContentPresenter.Interface {\n private _loading = false;\n private _submitting = false;\n private _processing = false;\n private _timedOut = false;\n private _components: Record<string, any>[] = [];\n private _createElements: CreateElementsFn = () => {};\n private _form: FormModel.Interface | null = null;\n private _settings: IAiPowerUpsSettings | null = null;\n private _timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n constructor(\n private toolRegistry: ToolRegistry.Interface,\n private pipelineRunner: ToolPipelineRunner.Interface,\n private generatePageContent: GeneratePageContentUseCase.Interface,\n private formModelFactory: FormModelFactory.Interface,\n private getSettings: GetSettingsUseCase.Interface\n ) {\n makeAutoObservable(this, { vm: computed }, { autoBind: true });\n }\n\n get vm(): GenerateContentPresenter.ViewModel {\n return {\n form: this._form ? this._form.vm : null,\n loading: this._loading,\n submitting: this._submitting,\n processing: this._processing,\n timedOut: this._timedOut\n };\n }\n\n async init(components: Record<string, any>[], createElements: CreateElementsFn): Promise<void> {\n this._components = components;\n this._createElements = createElements;\n\n this._loading = true;\n\n try {\n const settings = await this.getSettings.execute();\n runInAction(() => {\n this._settings = settings;\n this._form = this.buildForm();\n });\n } finally {\n runInAction(() => {\n this._loading = false;\n });\n }\n }\n\n async submit(): Promise<void> {\n if (!this._form) {\n return;\n }\n\n const data = await this._form.submit<{\n prompt: string;\n project?: string;\n includedFiles?: string[];\n readerPersona?: string;\n writerPersona?: string;\n }>();\n\n if (!data) {\n return;\n }\n\n runInAction(() => {\n this._submitting = true;\n this._timedOut = false;\n });\n\n this.startTimeout();\n\n try {\n const tools = this.toolRegistry.getTools().map(tool => ({\n name: tool.name,\n description: tool.description,\n inputSchema: pick(z.toJSONSchema(tool.inputSchema), [\"properties\", \"type\"]),\n outputSchema: pick(z.toJSONSchema(tool.outputSchema), [\"properties\", \"type\"])\n }));\n\n const excludedFileIds = this.computeExcludedFileIds(data.project, data.includedFiles);\n\n await this.generatePageContent.execute({\n prompt: data.prompt,\n components: toJS(this._components),\n tools,\n projectId: data.project || null,\n excludedFileIds,\n readerPersonaId: data.readerPersona || null,\n writerPersonaId: data.writerPersona || null\n });\n } catch {\n this.clearTimeout();\n runInAction(() => {\n this._submitting = false;\n });\n }\n }\n\n async processAiResponse(responseText: string): Promise<void> {\n this.clearTimeout();\n runInAction(() => {\n this._processing = true;\n this._submitting = false;\n });\n\n try {\n const aiResponseJson = JSON.parse(responseText);\n const resolved = await this.pipelineRunner.resolve(aiResponseJson);\n const items = Array.isArray(resolved) ? resolved : [resolved];\n\n this._createElements(\n items.map((element: { component: string; inputs: Record<string, unknown> }) => ({\n componentName: element.component,\n parentId: \"root\",\n slot: \"children\",\n bindings: { inputs: element.inputs }\n }))\n );\n } catch (e) {\n console.error(e);\n } finally {\n runInAction(() => {\n this._processing = false;\n });\n }\n }\n\n cancelPrompt(): void {\n this.clearTimeout();\n this._processing = false;\n this._submitting = false;\n }\n\n private startTimeout(): void {\n this.clearTimeout();\n this._timeoutId = setTimeout(() => {\n runInAction(() => {\n this._submitting = false;\n this._timedOut = true;\n });\n }, SUBMIT_TIMEOUT_MS);\n }\n\n private clearTimeout(): void {\n if (this._timeoutId !== null) {\n clearTimeout(this._timeoutId);\n this._timeoutId = null;\n }\n }\n\n private buildForm() {\n return this.formModelFactory.create({\n fields: fields => ({\n project: fields\n .text()\n .label(\"Project\")\n .description(\"Select a predefined context to attach.\")\n .options(() => this.getProjectOptions())\n .afterChange((value, form) => {\n const projectId = value as string | undefined;\n const project = projectId\n ? this._settings?.projects?.presets?.find(p => p.id === projectId)\n : undefined;\n\n if (project?.defaultReaderPersonaId) {\n form.field(\"readerPersona\").setValue(project.defaultReaderPersonaId);\n }\n if (project?.defaultWriterPersonaId) {\n form.field(\"writerPersona\").setValue(project.defaultWriterPersonaId);\n }\n\n const fileIds = (project?.files ?? []).map(f => f.id);\n form.field(\"includedFiles\").setValue(fileIds);\n }),\n includedFiles: fields\n .text()\n .label(\"Files included\")\n .description(\"Uncheck files to exclude them from this generation only.\")\n .options(() => this.getProjectFileOptions())\n .renderer(\"checkboxes\")\n .list()\n .hiddenWhen(form => {\n const projectId = form.field(\"project\").as(\"text\").getValue();\n if (!projectId) {\n return true;\n }\n const project = this._settings?.projects?.presets?.find(\n p => p.id === projectId\n );\n return !project?.files || project.files.length === 0;\n }),\n readerPersona: fields\n .text()\n .label(\"Reader Persona\")\n .description(\"Select the target audience for the generated content.\")\n .options(() => this.getPersonaOptions(\"reader\")),\n writerPersona: fields\n .text()\n .label(\"Writer Persona\")\n .description(\"Select the writing style for the generated content.\")\n .options(() => this.getPersonaOptions(\"writer\")),\n prompt: fields\n .text()\n .label(\"Prompt\")\n .description(\"Describe the page content you want to generate.\")\n .required(\"Prompt is required\")\n .renderer(\"textarea\", { rows: 6 })\n }),\n layout: layout => [\n layout.row(\"project\"),\n layout.row(\"includedFiles\"),\n layout.row(\"readerPersona\"),\n layout.row(\"writerPersona\"),\n layout.row(\"prompt\")\n ]\n });\n }\n\n private getSelectedProject() {\n if (!this._form || !this._settings) {\n return undefined;\n }\n const projectId = this._form.field(\"project\").getValue<string>();\n if (!projectId) {\n return undefined;\n }\n return this._settings.projects?.presets?.find(p => p.id === projectId);\n }\n\n private getProjectFileOptions() {\n const project = this.getSelectedProject();\n const files = project?.files;\n if (!files || files.length === 0) {\n return [];\n }\n return files.map(f => ({\n label: f.name,\n value: f.id\n }));\n }\n\n private computeExcludedFileIds(projectId?: string, includedFiles?: string[]): string[] | null {\n if (!projectId) {\n return null;\n }\n const project = this._settings?.projects?.presets?.find(p => p.id === projectId);\n const allFileIds = (project?.files ?? []).map(f => f.id);\n if (allFileIds.length === 0) {\n return null;\n }\n const included = new Set(includedFiles ?? []);\n const excluded = allFileIds.filter(id => !included.has(id));\n return excluded.length > 0 ? excluded : null;\n }\n\n private getProjectOptions() {\n const presets = this._settings?.projects?.presets;\n if (!presets || presets.length === 0) {\n return [];\n }\n return presets.map(preset => ({\n label: preset.name,\n value: preset.id\n }));\n }\n\n private getPersonaOptions(type: \"reader\" | \"writer\") {\n const presets =\n type === \"reader\"\n ? this._settings?.readerPersonas?.presets\n : this._settings?.writerPersonas?.presets;\n\n if (!presets || presets.length === 0) {\n return [];\n }\n\n return presets.map(preset => ({\n label: preset.name,\n value: preset.id\n }));\n }\n}\n\nexport const GenerateContentPresenterRegistration = GenerateContentPresenter.createImplementation({\n implementation: GenerateContentPresenterImpl,\n dependencies: [\n ToolRegistry,\n ToolPipelineRunner,\n GeneratePageContentUseCase,\n FormModelFactory,\n GetSettingsUseCase\n ]\n});\n"],"names":["SUBMIT_TIMEOUT_MS","GenerateContentPresenterImpl","toolRegistry","pipelineRunner","generatePageContent","formModelFactory","getSettings","makeAutoObservable","computed","components","createElements","settings","runInAction","data","tools","tool","pick","z","excludedFileIds","toJS","responseText","aiResponseJson","JSON","resolved","items","Array","element","e","console","setTimeout","clearTimeout","fields","value","form","projectId","project","p","undefined","fileIds","f","layout","files","includedFiles","allFileIds","included","Set","excluded","id","presets","preset","type","GenerateContentPresenterRegistration","GenerateContentPresenter","ToolRegistry","ToolPipelineRunner","GeneratePageContentUseCase","FormModelFactory","GetSettingsUseCase"],"mappings":"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"}
1
+ {"version":3,"file":"admin/presentation/WbContentGeneration/GenerateContentPresenter.js","sources":["../../../../src/admin/presentation/WbContentGeneration/GenerateContentPresenter.ts"],"sourcesContent":["import { makeAutoObservable, computed, toJS, runInAction } from \"mobx\";\nimport { z } from \"zod\";\nimport pick from \"lodash/pick.js\";\nimport { FormModelFactory, FormModel, ToolRegistry, ToolPipelineRunner } from \"@webiny/app-admin\";\nimport { GenerateContentPresenter, type CreateElementsFn } from \"./abstractions.js\";\nimport { GeneratePageContentUseCase } from \"~/admin/features/generatePageContent/index.js\";\nimport { GetSettingsUseCase } from \"~/admin/features/settings/getSettings/abstractions.js\";\nimport type { IAiPowerUpsSettings } from \"~/admin/features/settings/shared/abstractions.js\";\n\nconst SUBMIT_TIMEOUT_MS = 300_000;\n\nclass GenerateContentPresenterImpl implements GenerateContentPresenter.Interface {\n private _loading = false;\n private _submitting = false;\n private _processing = false;\n private _timedOut = false;\n private _components: Record<string, any>[] = [];\n private _createElements: CreateElementsFn = () => {};\n private _form: FormModel.Interface | null = null;\n private _settings: IAiPowerUpsSettings | null = null;\n private _timeoutId: ReturnType<typeof setTimeout> | null = null;\n private _intervalId: ReturnType<typeof setInterval> | null = null;\n private _elapsedSeconds = 0;\n\n constructor(\n private toolRegistry: ToolRegistry.Interface,\n private pipelineRunner: ToolPipelineRunner.Interface,\n private generatePageContent: GeneratePageContentUseCase.Interface,\n private formModelFactory: FormModelFactory.Interface,\n private getSettings: GetSettingsUseCase.Interface\n ) {\n makeAutoObservable(this, { vm: computed }, { autoBind: true });\n }\n\n get vm(): GenerateContentPresenter.ViewModel {\n return {\n form: this._form ? this._form.vm : null,\n loading: this._loading,\n submitting: this._submitting,\n processing: this._processing,\n timedOut: this._timedOut,\n elapsedSeconds: this._elapsedSeconds\n };\n }\n\n async init(components: Record<string, any>[], createElements: CreateElementsFn): Promise<void> {\n this._components = components;\n this._createElements = createElements;\n\n this._loading = true;\n\n try {\n const settings = await this.getSettings.execute();\n runInAction(() => {\n this._settings = settings;\n this._form = this.buildForm();\n });\n } finally {\n runInAction(() => {\n this._loading = false;\n });\n }\n }\n\n async submit(): Promise<void> {\n if (!this._form) {\n return;\n }\n\n const data = await this._form.submit<{\n prompt: string;\n project?: string;\n includedFiles?: string[];\n readerPersona?: string;\n writerPersona?: string;\n }>();\n\n if (!data) {\n return;\n }\n\n runInAction(() => {\n this._submitting = true;\n this._timedOut = false;\n });\n\n this.startTimeout();\n\n try {\n const tools = this.toolRegistry.getTools().map(tool => ({\n name: tool.name,\n description: tool.description,\n inputSchema: pick(z.toJSONSchema(tool.inputSchema), [\"properties\", \"type\"]),\n outputSchema: pick(z.toJSONSchema(tool.outputSchema), [\"properties\", \"type\"])\n }));\n\n const excludedFileIds = this.computeExcludedFileIds(data.project, data.includedFiles);\n\n await this.generatePageContent.execute({\n prompt: data.prompt,\n components: toJS(this._components),\n tools,\n projectId: data.project || null,\n excludedFileIds,\n readerPersonaId: data.readerPersona || null,\n writerPersonaId: data.writerPersona || null\n });\n } catch {\n this.clearTimeout();\n runInAction(() => {\n this._submitting = false;\n });\n }\n }\n\n async processAiResponse(responseText: string): Promise<void> {\n this.clearTimeout();\n runInAction(() => {\n this._processing = true;\n this._submitting = false;\n });\n\n try {\n const aiResponseJson = JSON.parse(responseText);\n const resolved = await this.pipelineRunner.resolve(aiResponseJson);\n const items = Array.isArray(resolved) ? resolved : [resolved];\n\n await this._createElements(\n items.map((element: { component: string; inputs: Record<string, unknown> }) => ({\n componentName: element.component,\n parentId: \"root\",\n slot: \"children\",\n bindings: { inputs: element.inputs }\n }))\n );\n } finally {\n runInAction(() => {\n this._processing = false;\n });\n }\n }\n\n cancelPrompt(): void {\n this.clearTimeout();\n this._processing = false;\n this._submitting = false;\n }\n\n private startTimeout(): void {\n this.clearTimeout();\n this._elapsedSeconds = 0;\n this._intervalId = setInterval(() => {\n runInAction(() => {\n this._elapsedSeconds++;\n });\n }, 1000);\n this._timeoutId = setTimeout(() => {\n runInAction(() => {\n this._submitting = false;\n this._timedOut = true;\n });\n this.clearInterval();\n }, SUBMIT_TIMEOUT_MS);\n }\n\n private clearTimeout(): void {\n if (this._timeoutId !== null) {\n clearTimeout(this._timeoutId);\n this._timeoutId = null;\n }\n this.clearInterval();\n }\n\n private clearInterval(): void {\n if (this._intervalId !== null) {\n clearInterval(this._intervalId);\n this._intervalId = null;\n }\n }\n\n private buildForm() {\n return this.formModelFactory.create({\n fields: fields => ({\n project: fields\n .text()\n .label(\"Project\")\n .description(\"Select a predefined context to attach.\")\n .options(() => this.getProjectOptions())\n .afterChange((value, form) => {\n const projectId = value as string | undefined;\n const project = projectId\n ? this._settings?.projects?.presets?.find(p => p.id === projectId)\n : undefined;\n\n if (project?.defaultReaderPersonaId) {\n form.field(\"readerPersona\").setValue(project.defaultReaderPersonaId);\n }\n if (project?.defaultWriterPersonaId) {\n form.field(\"writerPersona\").setValue(project.defaultWriterPersonaId);\n }\n\n const fileIds = (project?.files ?? []).map(f => f.id);\n form.field(\"includedFiles\").setValue(fileIds);\n }),\n includedFiles: fields\n .text()\n .label(\"Files included\")\n .description(\"Uncheck files to exclude them from this generation only.\")\n .options(() => this.getProjectFileOptions())\n .renderer(\"checkboxes\")\n .list()\n .hiddenWhen(form => {\n const projectId = form.field(\"project\").as(\"text\").getValue();\n if (!projectId) {\n return true;\n }\n const project = this._settings?.projects?.presets?.find(\n p => p.id === projectId\n );\n return !project?.files || project.files.length === 0;\n }),\n readerPersona: fields\n .text()\n .label(\"Reader Persona\")\n .description(\"Select the target audience for the generated content.\")\n .options(() => this.getPersonaOptions(\"reader\")),\n writerPersona: fields\n .text()\n .label(\"Writer Persona\")\n .description(\"Select the writing style for the generated content.\")\n .options(() => this.getPersonaOptions(\"writer\")),\n prompt: fields\n .text()\n .label(\"Prompt\")\n .description(\"Describe the page content you want to generate.\")\n .required(\"Prompt is required\")\n .renderer(\"textarea\", { rows: 6 })\n }),\n layout: layout => [\n layout.row(\"project\"),\n layout.row(\"includedFiles\"),\n layout.row(\"readerPersona\"),\n layout.row(\"writerPersona\"),\n layout.row(\"prompt\")\n ]\n });\n }\n\n private getSelectedProject() {\n if (!this._form || !this._settings) {\n return undefined;\n }\n const projectId = this._form.field(\"project\").getValue<string>();\n if (!projectId) {\n return undefined;\n }\n return this._settings.projects?.presets?.find(p => p.id === projectId);\n }\n\n private getProjectFileOptions() {\n const project = this.getSelectedProject();\n const files = project?.files;\n if (!files || files.length === 0) {\n return [];\n }\n return files.map(f => ({\n label: f.name,\n value: f.id\n }));\n }\n\n private computeExcludedFileIds(projectId?: string, includedFiles?: string[]): string[] | null {\n if (!projectId) {\n return null;\n }\n const project = this._settings?.projects?.presets?.find(p => p.id === projectId);\n const allFileIds = (project?.files ?? []).map(f => f.id);\n if (allFileIds.length === 0) {\n return null;\n }\n const included = new Set(includedFiles ?? []);\n const excluded = allFileIds.filter(id => !included.has(id));\n return excluded.length > 0 ? excluded : null;\n }\n\n private getProjectOptions() {\n const presets = this._settings?.projects?.presets;\n if (!presets || presets.length === 0) {\n return [];\n }\n return presets.map(preset => ({\n label: preset.name,\n value: preset.id\n }));\n }\n\n private getPersonaOptions(type: \"reader\" | \"writer\") {\n const presets =\n type === \"reader\"\n ? this._settings?.readerPersonas?.presets\n : this._settings?.writerPersonas?.presets;\n\n if (!presets || presets.length === 0) {\n return [];\n }\n\n return presets.map(preset => ({\n label: preset.name,\n value: preset.id\n }));\n }\n}\n\nexport const GenerateContentPresenterRegistration = GenerateContentPresenter.createImplementation({\n implementation: GenerateContentPresenterImpl,\n dependencies: [\n ToolRegistry,\n ToolPipelineRunner,\n GeneratePageContentUseCase,\n FormModelFactory,\n GetSettingsUseCase\n ]\n});\n"],"names":["SUBMIT_TIMEOUT_MS","GenerateContentPresenterImpl","toolRegistry","pipelineRunner","generatePageContent","formModelFactory","getSettings","makeAutoObservable","computed","components","createElements","settings","runInAction","data","tools","tool","pick","z","excludedFileIds","toJS","responseText","aiResponseJson","JSON","resolved","items","Array","element","setInterval","setTimeout","clearTimeout","clearInterval","fields","value","form","projectId","project","p","undefined","fileIds","f","layout","files","includedFiles","allFileIds","included","Set","excluded","id","presets","preset","type","GenerateContentPresenterRegistration","GenerateContentPresenter","ToolRegistry","ToolPipelineRunner","GeneratePageContentUseCase","FormModelFactory","GetSettingsUseCase"],"mappings":"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"}
@@ -8,13 +8,14 @@ export type CreateElementParams = {
8
8
  inputs?: Record<string, unknown>;
9
9
  };
10
10
  };
11
- export type CreateElementsFn = (elements: CreateElementParams[]) => void;
11
+ export type CreateElementsFn = (elements: CreateElementParams[]) => void | Promise<void>;
12
12
  export interface IGenerateContentVm {
13
13
  form: IFormVM | null;
14
14
  loading: boolean;
15
15
  submitting: boolean;
16
16
  processing: boolean;
17
17
  timedOut: boolean;
18
+ elapsedSeconds: number;
18
19
  }
19
20
  export interface IGenerateContentPresenter {
20
21
  readonly vm: IGenerateContentVm;
@@ -1 +1 @@
1
- {"version":3,"file":"admin/presentation/WbContentGeneration/abstractions.js","sources":["../../../../src/admin/presentation/WbContentGeneration/abstractions.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/admin\";\nimport type { IFormVM } from \"@webiny/app-admin/features/formModel/abstractions.js\";\n\nexport type CreateElementParams = {\n componentName: string;\n parentId: string;\n slot: string;\n index?: number;\n bindings?: { inputs?: Record<string, unknown> };\n};\n\nexport type CreateElementsFn = (elements: CreateElementParams[]) => void;\n\nexport interface IGenerateContentVm {\n form: IFormVM | null;\n loading: boolean;\n submitting: boolean;\n processing: boolean;\n timedOut: boolean;\n}\n\nexport interface IGenerateContentPresenter {\n readonly vm: IGenerateContentVm;\n init(components: Record<string, any>[], createElements: CreateElementsFn): void;\n submit(): Promise<void>;\n cancelPrompt(): void;\n processAiResponse(responseText: string): Promise<void>;\n}\n\nexport const GenerateContentPresenter = createAbstraction<IGenerateContentPresenter>(\n \"WbContentGeneration/Presenter\"\n);\n\nexport namespace GenerateContentPresenter {\n export type Interface = IGenerateContentPresenter;\n export type ViewModel = IGenerateContentVm;\n}\n"],"names":["GenerateContentPresenter","createAbstraction"],"mappings":";AA6BO,MAAMA,2BAA2BC,kBACpC"}
1
+ {"version":3,"file":"admin/presentation/WbContentGeneration/abstractions.js","sources":["../../../../src/admin/presentation/WbContentGeneration/abstractions.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/admin\";\nimport type { IFormVM } from \"@webiny/app-admin/features/formModel/abstractions.js\";\n\nexport type CreateElementParams = {\n componentName: string;\n parentId: string;\n slot: string;\n index?: number;\n bindings?: { inputs?: Record<string, unknown> };\n};\n\nexport type CreateElementsFn = (elements: CreateElementParams[]) => void | Promise<void>;\n\nexport interface IGenerateContentVm {\n form: IFormVM | null;\n loading: boolean;\n submitting: boolean;\n processing: boolean;\n timedOut: boolean;\n elapsedSeconds: number;\n}\n\nexport interface IGenerateContentPresenter {\n readonly vm: IGenerateContentVm;\n init(components: Record<string, any>[], createElements: CreateElementsFn): void;\n submit(): Promise<void>;\n cancelPrompt(): void;\n processAiResponse(responseText: string): Promise<void>;\n}\n\nexport const GenerateContentPresenter = createAbstraction<IGenerateContentPresenter>(\n \"WbContentGeneration/Presenter\"\n);\n\nexport namespace GenerateContentPresenter {\n export type Interface = IGenerateContentPresenter;\n export type ViewModel = IGenerateContentVm;\n}\n"],"names":["GenerateContentPresenter","createAbstraction"],"mappings":";AA8BO,MAAMA,2BAA2BC,kBACpC"}
@@ -64,7 +64,7 @@ class AiImageEnrichmentTaskImpl {
64
64
  output: aiOutputSchema,
65
65
  connection: {
66
66
  sdkName: firstProvider.model.split("/")[0],
67
- apiKey: this.encryption.decrypt(firstProvider.apiKeyEncrypted)
67
+ apiKey: await this.encryption.decrypt(firstProvider.apiKeyEncrypted)
68
68
  },
69
69
  messages: [
70
70
  {
@@ -1 +1 @@
1
- {"version":3,"file":"api/features/AiImageEnrichment/AiImageEnrichmentTask.js","sources":["../../../../src/api/features/AiImageEnrichment/AiImageEnrichmentTask.ts"],"sourcesContent":["import { Output } from \"ai\";\nimport { z } from \"zod\";\nimport { TaskDefinition } from \"@webiny/api-core/features/task/TaskDefinition/index.js\";\nimport { Ai } from \"@webiny/api-core/features/ai/index.js\";\nimport { Encryption } from \"@webiny/api-core/features/encryption/index.js\";\nimport { GetFileUseCase } from \"@webiny/api-file-manager/features/file/GetFile/index.js\";\nimport { UpdateFileUseCase } from \"@webiny/api-file-manager/features/file/UpdateFile/index.js\";\nimport { GetSettingsUseCase as FmGetSettingsUseCase } from \"@webiny/api-file-manager/features/settings/GetSettings/abstractions.js\";\nimport { WebsocketService } from \"@webiny/api-websockets/features/WebsocketService/index.js\";\nimport { GetSettingsUseCase } from \"~/api/features/GetSettings/index.js\";\n\nexport const AI_IMAGE_ENRICHMENT_TASK_ID = \"fmAiImageEnrichment\";\n\nconst AI_PROMPT =\n \"Analyze this image and return up to 5 lowercase descriptive tags and one short sentence describing the image.\";\n\nconst aiOutputSchema = Output.object({\n schema: z.object({\n tags: z.array(z.string()),\n description: z.string()\n })\n});\n\nexport interface IAiImageEnrichmentTaskInput {\n fileId: string;\n}\n\nclass AiImageEnrichmentTaskImpl implements TaskDefinition.Interface<IAiImageEnrichmentTaskInput> {\n id = AI_IMAGE_ENRICHMENT_TASK_ID;\n title = \"File Manager - AI Image Enrichment\";\n description = \"Automatically enriches uploaded images with AI-generated tags and description.\";\n maxIterations = 1;\n isPrivate = true;\n databaseLogs = false;\n\n public readonly selfCleanup = [\"onSuccess\" as const, \"onAbort\" as const];\n\n constructor(\n private getFile: GetFileUseCase.Interface,\n private fmSettings: FmGetSettingsUseCase.Interface,\n private updateFile: UpdateFileUseCase.Interface,\n private ai: Ai.Interface,\n private getSettings: GetSettingsUseCase.Interface,\n private encryption: Encryption.Interface,\n private websocketService?: WebsocketService.Interface\n ) {}\n\n async run({\n input,\n controller\n }: TaskDefinition.RunParams<IAiImageEnrichmentTaskInput>): Promise<\n TaskDefinition.Result<IAiImageEnrichmentTaskInput>\n > {\n if (controller.runtime.isAborted()) {\n return controller.response.aborted();\n }\n\n const fileResult = await this.getFile.execute(input.fileId);\n if (fileResult.isFail()) {\n return controller.response.error({\n message: `File not found: ${input.fileId}`\n });\n }\n\n const file = fileResult.value;\n\n if (!file.type.startsWith(\"image/\")) {\n return controller.response.done(\"File is not an image; skipping AI enrichment.\");\n }\n\n const settingsResult = await this.fmSettings.execute();\n const srcPrefix = settingsResult.isOk() ? (settingsResult.value.srcPrefix ?? \"\") : \"\";\n const imageUrl = `${srcPrefix}${file.key}`;\n\n const aiSettingsResult = await this.getSettings.execute();\n\n if (aiSettingsResult.isFail()) {\n return controller.response.error({\n message: \"No AI provider configured. Add a provider in AI Power Ups settings.\"\n });\n }\n\n const aiSettings = aiSettingsResult.value;\n\n const firstProvider = aiSettings.providers.presets[0];\n\n if (!firstProvider) {\n return controller.response.done({\n message: \"No AI provider configured. Add a provider in AI Power Ups settings.\"\n });\n }\n\n let tags: string[] = [];\n let description = \"\";\n try {\n const aiResult = await this.ai.generateText({\n model: firstProvider.model,\n output: aiOutputSchema,\n connection: {\n sdkName: firstProvider.model.split(\"/\")[0],\n apiKey: this.encryption.decrypt(firstProvider.apiKeyEncrypted)\n },\n messages: [\n {\n role: \"user\",\n content: [\n {\n type: \"image\",\n image: new URL(imageUrl)\n },\n {\n type: \"text\",\n text: AI_PROMPT\n }\n ]\n }\n ]\n });\n\n tags = aiResult.output.tags;\n description = aiResult.output.description;\n } catch (error) {\n console.log(\"error\", error.message);\n return controller.response.error({\n message: `AI enrichment failed: ${error instanceof Error ? error.message : String(error)}`\n });\n }\n\n const mergedTags = [...new Set([...file.tags, ...tags])];\n\n const updateResult = await this.updateFile.execute({\n id: file.id,\n tags: mergedTags,\n description\n });\n\n if (updateResult.isFail()) {\n return controller.response.error({\n message: `Failed to update file: ${updateResult.error.message}`\n });\n }\n\n if (this.websocketService) {\n const connectionsResult = await this.websocketService.listConnections();\n if (connectionsResult.isOk() && connectionsResult.value.length > 0) {\n await this.websocketService.sendToConnections(connectionsResult.value, {\n action: \"fm.file.enrichment\",\n data: {\n id: file.id,\n tags: mergedTags,\n description\n }\n });\n }\n }\n\n return controller.response.done(\"AI image enrichment completed successfully.\");\n }\n}\n\nexport const AiImageEnrichmentTask = TaskDefinition.createImplementation({\n implementation: AiImageEnrichmentTaskImpl,\n dependencies: [\n GetFileUseCase,\n FmGetSettingsUseCase,\n UpdateFileUseCase,\n Ai,\n GetSettingsUseCase,\n Encryption,\n [WebsocketService, { optional: true }]\n ]\n});\n"],"names":["AI_IMAGE_ENRICHMENT_TASK_ID","AI_PROMPT","aiOutputSchema","Output","z","AiImageEnrichmentTaskImpl","getFile","fmSettings","updateFile","ai","getSettings","encryption","websocketService","input","controller","fileResult","file","settingsResult","srcPrefix","imageUrl","aiSettingsResult","aiSettings","firstProvider","tags","description","aiResult","URL","error","console","Error","String","mergedTags","Set","updateResult","connectionsResult","AiImageEnrichmentTask","TaskDefinition","GetFileUseCase","FmGetSettingsUseCase","UpdateFileUseCase","Ai","GetSettingsUseCase","Encryption","WebsocketService"],"mappings":"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"}
1
+ {"version":3,"file":"api/features/AiImageEnrichment/AiImageEnrichmentTask.js","sources":["../../../../src/api/features/AiImageEnrichment/AiImageEnrichmentTask.ts"],"sourcesContent":["import { Output } from \"ai\";\nimport { z } from \"zod\";\nimport { TaskDefinition } from \"@webiny/api-core/features/task/TaskDefinition/index.js\";\nimport { Ai } from \"@webiny/api-core/features/ai/index.js\";\nimport { Encryption } from \"@webiny/api-core/features/encryption/index.js\";\nimport { GetFileUseCase } from \"@webiny/api-file-manager/features/file/GetFile/index.js\";\nimport { UpdateFileUseCase } from \"@webiny/api-file-manager/features/file/UpdateFile/index.js\";\nimport { GetSettingsUseCase as FmGetSettingsUseCase } from \"@webiny/api-file-manager/features/settings/GetSettings/abstractions.js\";\nimport { WebsocketService } from \"@webiny/api-websockets/features/WebsocketService/index.js\";\nimport { GetSettingsUseCase } from \"~/api/features/GetSettings/index.js\";\n\nexport const AI_IMAGE_ENRICHMENT_TASK_ID = \"fmAiImageEnrichment\";\n\nconst AI_PROMPT =\n \"Analyze this image and return up to 5 lowercase descriptive tags and one short sentence describing the image.\";\n\nconst aiOutputSchema = Output.object({\n schema: z.object({\n tags: z.array(z.string()),\n description: z.string()\n })\n});\n\nexport interface IAiImageEnrichmentTaskInput {\n fileId: string;\n}\n\nclass AiImageEnrichmentTaskImpl implements TaskDefinition.Interface<IAiImageEnrichmentTaskInput> {\n id = AI_IMAGE_ENRICHMENT_TASK_ID;\n title = \"File Manager - AI Image Enrichment\";\n description = \"Automatically enriches uploaded images with AI-generated tags and description.\";\n maxIterations = 1;\n isPrivate = true;\n databaseLogs = false;\n\n public readonly selfCleanup = [\"onSuccess\" as const, \"onAbort\" as const];\n\n constructor(\n private getFile: GetFileUseCase.Interface,\n private fmSettings: FmGetSettingsUseCase.Interface,\n private updateFile: UpdateFileUseCase.Interface,\n private ai: Ai.Interface,\n private getSettings: GetSettingsUseCase.Interface,\n private encryption: Encryption.Interface,\n private websocketService?: WebsocketService.Interface\n ) {}\n\n async run({\n input,\n controller\n }: TaskDefinition.RunParams<IAiImageEnrichmentTaskInput>): Promise<\n TaskDefinition.Result<IAiImageEnrichmentTaskInput>\n > {\n if (controller.runtime.isAborted()) {\n return controller.response.aborted();\n }\n\n const fileResult = await this.getFile.execute(input.fileId);\n if (fileResult.isFail()) {\n return controller.response.error({\n message: `File not found: ${input.fileId}`\n });\n }\n\n const file = fileResult.value;\n\n if (!file.type.startsWith(\"image/\")) {\n return controller.response.done(\"File is not an image; skipping AI enrichment.\");\n }\n\n const settingsResult = await this.fmSettings.execute();\n const srcPrefix = settingsResult.isOk() ? (settingsResult.value.srcPrefix ?? \"\") : \"\";\n const imageUrl = `${srcPrefix}${file.key}`;\n\n const aiSettingsResult = await this.getSettings.execute();\n\n if (aiSettingsResult.isFail()) {\n return controller.response.error({\n message: \"No AI provider configured. Add a provider in AI Power Ups settings.\"\n });\n }\n\n const aiSettings = aiSettingsResult.value;\n\n const firstProvider = aiSettings.providers.presets[0];\n\n if (!firstProvider) {\n return controller.response.done({\n message: \"No AI provider configured. Add a provider in AI Power Ups settings.\"\n });\n }\n\n let tags: string[] = [];\n let description = \"\";\n try {\n const aiResult = await this.ai.generateText({\n model: firstProvider.model,\n output: aiOutputSchema,\n connection: {\n sdkName: firstProvider.model.split(\"/\")[0],\n apiKey: await this.encryption.decrypt(firstProvider.apiKeyEncrypted)\n },\n messages: [\n {\n role: \"user\",\n content: [\n {\n type: \"image\",\n image: new URL(imageUrl)\n },\n {\n type: \"text\",\n text: AI_PROMPT\n }\n ]\n }\n ]\n });\n\n tags = aiResult.output.tags;\n description = aiResult.output.description;\n } catch (error) {\n console.log(\"error\", error.message);\n return controller.response.error({\n message: `AI enrichment failed: ${error instanceof Error ? error.message : String(error)}`\n });\n }\n\n const mergedTags = [...new Set([...file.tags, ...tags])];\n\n const updateResult = await this.updateFile.execute({\n id: file.id,\n tags: mergedTags,\n description\n });\n\n if (updateResult.isFail()) {\n return controller.response.error({\n message: `Failed to update file: ${updateResult.error.message}`\n });\n }\n\n if (this.websocketService) {\n const connectionsResult = await this.websocketService.listConnections();\n if (connectionsResult.isOk() && connectionsResult.value.length > 0) {\n await this.websocketService.sendToConnections(connectionsResult.value, {\n action: \"fm.file.enrichment\",\n data: {\n id: file.id,\n tags: mergedTags,\n description\n }\n });\n }\n }\n\n return controller.response.done(\"AI image enrichment completed successfully.\");\n }\n}\n\nexport const AiImageEnrichmentTask = TaskDefinition.createImplementation({\n implementation: AiImageEnrichmentTaskImpl,\n dependencies: [\n GetFileUseCase,\n FmGetSettingsUseCase,\n UpdateFileUseCase,\n Ai,\n GetSettingsUseCase,\n Encryption,\n [WebsocketService, { optional: true }]\n ]\n});\n"],"names":["AI_IMAGE_ENRICHMENT_TASK_ID","AI_PROMPT","aiOutputSchema","Output","z","AiImageEnrichmentTaskImpl","getFile","fmSettings","updateFile","ai","getSettings","encryption","websocketService","input","controller","fileResult","file","settingsResult","srcPrefix","imageUrl","aiSettingsResult","aiSettings","firstProvider","tags","description","aiResult","URL","error","console","Error","String","mergedTags","Set","updateResult","connectionsResult","AiImageEnrichmentTask","TaskDefinition","GetFileUseCase","FmGetSettingsUseCase","UpdateFileUseCase","Ai","GetSettingsUseCase","Encryption","WebsocketService"],"mappings":"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"}
@@ -44,7 +44,7 @@ class ProvidersHandlerImpl {
44
44
  let apiKeyEncrypted;
45
45
  let apiKeyMasked;
46
46
  if (preset.apiKey && preset.apiKey !== existingMatch?.apiKeyMasked) {
47
- apiKeyEncrypted = this.encryption.encrypt(preset.apiKey);
47
+ apiKeyEncrypted = await this.encryption.encrypt(preset.apiKey);
48
48
  apiKeyMasked = this.masker.mask(preset.apiKey, [
49
49
  8,
50
50
  4
@@ -1 +1 @@
1
- {"version":3,"file":"api/features/Providers/ProvidersHandler.js","sources":["../../../../src/api/features/Providers/ProvidersHandler.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { Encryption } from \"@webiny/api-core/features/encryption/index.js\";\nimport { Masker } from \"@webiny/api-core/features/masker/index.js\";\nimport { AiPowerUpsSettingsGroupHandler } from \"~/api/features/shared/index.js\";\nimport type { PersistedProviderPreset, PersistedProviders, ProvidersSettings } from \"./types.js\";\n\nconst inputSchema = z.object({\n presets: z.array(\n z.object({\n id: z.string().min(1),\n name: z.string().min(1),\n description: z.string().optional(),\n model: z.string().min(1),\n apiKey: z.string().optional()\n })\n )\n});\n\nclass ProvidersHandlerImpl implements AiPowerUpsSettingsGroupHandler.Interface {\n readonly name = \"providers\";\n readonly inputSchema = inputSchema;\n\n constructor(\n private encryption: Encryption.Interface,\n private masker: Masker.Interface\n ) {}\n\n mapFromStorage(persisted: unknown): ProvidersSettings {\n if (!persisted || typeof persisted !== \"object\") {\n return { presets: [] };\n }\n\n const data = persisted as PersistedProviders;\n const presets = (data.presets ?? []).map(p => ({\n id: p.id,\n name: p.name,\n description: p.description,\n model: p.model,\n apiKeyMasked: p.apiKeyMasked ?? \"\",\n apiKeyEncrypted: p.apiKeyEncrypted ?? \"\"\n }));\n\n return { presets };\n }\n\n async mapToStorage(internal: unknown, existing: unknown | null): Promise<PersistedProviders> {\n const input = internal as ProvidersSettings;\n const existingData = existing as ProvidersSettings | null;\n const existingPresets = existingData?.presets ?? [];\n\n const presets: PersistedProviderPreset[] = await Promise.all(\n input.presets.map(async preset => {\n const existingMatch = existingPresets.find(ep => ep.id === preset.id);\n\n let apiKeyEncrypted: string;\n let apiKeyMasked: string;\n\n if (!preset.apiKey || preset.apiKey === existingMatch?.apiKeyMasked) {\n // Carry forward existing encrypted key\n apiKeyEncrypted = existingMatch?.apiKeyEncrypted ?? \"\";\n apiKeyMasked = existingMatch?.apiKeyMasked ?? \"\";\n } else {\n // New plaintext key — encrypt and mask\n apiKeyEncrypted = this.encryption.encrypt(preset.apiKey);\n apiKeyMasked = this.masker.mask(preset.apiKey, [8, 4]);\n }\n\n return {\n id: preset.id,\n name: preset.name,\n description: preset.description,\n model: preset.model,\n apiKeyEncrypted,\n apiKeyMasked\n };\n })\n );\n\n return { presets };\n }\n}\n\nexport default AiPowerUpsSettingsGroupHandler.createImplementation({\n implementation: ProvidersHandlerImpl,\n dependencies: [Encryption, Masker]\n});\n"],"names":["inputSchema","z","ProvidersHandlerImpl","encryption","masker","persisted","data","presets","p","internal","existing","input","existingData","existingPresets","Promise","preset","existingMatch","ep","apiKeyEncrypted","apiKeyMasked","AiPowerUpsSettingsGroupHandler","Encryption","Masker"],"mappings":";;;;AAMA,MAAMA,cAAcC,EAAE,MAAM,CAAC;IACzB,SAASA,EAAE,KAAK,CACZA,EAAE,MAAM,CAAC;QACL,IAAIA,EAAE,MAAM,GAAG,GAAG,CAAC;QACnB,MAAMA,EAAE,MAAM,GAAG,GAAG,CAAC;QACrB,aAAaA,EAAE,MAAM,GAAG,QAAQ;QAChC,OAAOA,EAAE,MAAM,GAAG,GAAG,CAAC;QACtB,QAAQA,EAAE,MAAM,GAAG,QAAQ;IAC/B;AAER;AAEA,MAAMC;IAIF,YACYC,UAAgC,EAChCC,MAAwB,CAClC;aAFUD,UAAU,GAAVA;aACAC,MAAM,GAANA;aALH,IAAI,GAAG;aACP,WAAW,GAAGJ;IAKpB;IAEH,eAAeK,SAAkB,EAAqB;QAClD,IAAI,CAACA,aAAa,AAAqB,YAArB,OAAOA,WACrB,OAAO;YAAE,SAAS,EAAE;QAAC;QAGzB,MAAMC,OAAOD;QACb,MAAME,UAAWD,AAAAA,CAAAA,KAAK,OAAO,IAAI,EAAC,EAAG,GAAG,CAACE,CAAAA,IAAM;gBAC3C,IAAIA,EAAE,EAAE;gBACR,MAAMA,EAAE,IAAI;gBACZ,aAAaA,EAAE,WAAW;gBAC1B,OAAOA,EAAE,KAAK;gBACd,cAAcA,EAAE,YAAY,IAAI;gBAChC,iBAAiBA,EAAE,eAAe,IAAI;YAC1C;QAEA,OAAO;YAAED;QAAQ;IACrB;IAEA,MAAM,aAAaE,QAAiB,EAAEC,QAAwB,EAA+B;QACzF,MAAMC,QAAQF;QACd,MAAMG,eAAeF;QACrB,MAAMG,kBAAkBD,cAAc,WAAW,EAAE;QAEnD,MAAML,UAAqC,MAAMO,QAAQ,GAAG,CACxDH,MAAM,OAAO,CAAC,GAAG,CAAC,OAAMI;YACpB,MAAMC,gBAAgBH,gBAAgB,IAAI,CAACI,CAAAA,KAAMA,GAAG,EAAE,KAAKF,OAAO,EAAE;YAEpE,IAAIG;YACJ,IAAIC;YAEJ,IAAI,AAACJ,OAAO,MAAM,IAAIA,OAAO,MAAM,KAAKC,eAAe,cAIhD;gBAEHE,kBAAkB,IAAI,CAAC,UAAU,CAAC,OAAO,CAACH,OAAO,MAAM;gBACvDI,eAAe,IAAI,CAAC,MAAM,CAAC,IAAI,CAACJ,OAAO,MAAM,EAAE;oBAAC;oBAAG;iBAAE;YACzD,OARqE;gBAEjEG,kBAAkBF,eAAe,mBAAmB;gBACpDG,eAAeH,eAAe,gBAAgB;YAClD;YAMA,OAAO;gBACH,IAAID,OAAO,EAAE;gBACb,MAAMA,OAAO,IAAI;gBACjB,aAAaA,OAAO,WAAW;gBAC/B,OAAOA,OAAO,KAAK;gBACnBG;gBACAC;YACJ;QACJ;QAGJ,OAAO;YAAEZ;QAAQ;IACrB;AACJ;AAEA,yBAAea,+BAA+B,oBAAoB,CAAC;IAC/D,gBAAgBlB;IAChB,cAAc;QAACmB;QAAYC;KAAO;AACtC"}
1
+ {"version":3,"file":"api/features/Providers/ProvidersHandler.js","sources":["../../../../src/api/features/Providers/ProvidersHandler.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { Encryption } from \"@webiny/api-core/features/encryption/index.js\";\nimport { Masker } from \"@webiny/api-core/features/masker/index.js\";\nimport { AiPowerUpsSettingsGroupHandler } from \"~/api/features/shared/index.js\";\nimport type { PersistedProviderPreset, PersistedProviders, ProvidersSettings } from \"./types.js\";\n\nconst inputSchema = z.object({\n presets: z.array(\n z.object({\n id: z.string().min(1),\n name: z.string().min(1),\n description: z.string().optional(),\n model: z.string().min(1),\n apiKey: z.string().optional()\n })\n )\n});\n\nclass ProvidersHandlerImpl implements AiPowerUpsSettingsGroupHandler.Interface {\n readonly name = \"providers\";\n readonly inputSchema = inputSchema;\n\n constructor(\n private encryption: Encryption.Interface,\n private masker: Masker.Interface\n ) {}\n\n mapFromStorage(persisted: unknown): ProvidersSettings {\n if (!persisted || typeof persisted !== \"object\") {\n return { presets: [] };\n }\n\n const data = persisted as PersistedProviders;\n const presets = (data.presets ?? []).map(p => ({\n id: p.id,\n name: p.name,\n description: p.description,\n model: p.model,\n apiKeyMasked: p.apiKeyMasked ?? \"\",\n apiKeyEncrypted: p.apiKeyEncrypted ?? \"\"\n }));\n\n return { presets };\n }\n\n async mapToStorage(internal: unknown, existing: unknown | null): Promise<PersistedProviders> {\n const input = internal as ProvidersSettings;\n const existingData = existing as ProvidersSettings | null;\n const existingPresets = existingData?.presets ?? [];\n\n const presets: PersistedProviderPreset[] = await Promise.all(\n input.presets.map(async preset => {\n const existingMatch = existingPresets.find(ep => ep.id === preset.id);\n\n let apiKeyEncrypted: string;\n let apiKeyMasked: string;\n\n if (!preset.apiKey || preset.apiKey === existingMatch?.apiKeyMasked) {\n // Carry forward existing encrypted key\n apiKeyEncrypted = existingMatch?.apiKeyEncrypted ?? \"\";\n apiKeyMasked = existingMatch?.apiKeyMasked ?? \"\";\n } else {\n // New plaintext key — encrypt and mask\n apiKeyEncrypted = await this.encryption.encrypt(preset.apiKey);\n apiKeyMasked = this.masker.mask(preset.apiKey, [8, 4]);\n }\n\n return {\n id: preset.id,\n name: preset.name,\n description: preset.description,\n model: preset.model,\n apiKeyEncrypted,\n apiKeyMasked\n };\n })\n );\n\n return { presets };\n }\n}\n\nexport default AiPowerUpsSettingsGroupHandler.createImplementation({\n implementation: ProvidersHandlerImpl,\n dependencies: [Encryption, Masker]\n});\n"],"names":["inputSchema","z","ProvidersHandlerImpl","encryption","masker","persisted","data","presets","p","internal","existing","input","existingData","existingPresets","Promise","preset","existingMatch","ep","apiKeyEncrypted","apiKeyMasked","AiPowerUpsSettingsGroupHandler","Encryption","Masker"],"mappings":";;;;AAMA,MAAMA,cAAcC,EAAE,MAAM,CAAC;IACzB,SAASA,EAAE,KAAK,CACZA,EAAE,MAAM,CAAC;QACL,IAAIA,EAAE,MAAM,GAAG,GAAG,CAAC;QACnB,MAAMA,EAAE,MAAM,GAAG,GAAG,CAAC;QACrB,aAAaA,EAAE,MAAM,GAAG,QAAQ;QAChC,OAAOA,EAAE,MAAM,GAAG,GAAG,CAAC;QACtB,QAAQA,EAAE,MAAM,GAAG,QAAQ;IAC/B;AAER;AAEA,MAAMC;IAIF,YACYC,UAAgC,EAChCC,MAAwB,CAClC;aAFUD,UAAU,GAAVA;aACAC,MAAM,GAANA;aALH,IAAI,GAAG;aACP,WAAW,GAAGJ;IAKpB;IAEH,eAAeK,SAAkB,EAAqB;QAClD,IAAI,CAACA,aAAa,AAAqB,YAArB,OAAOA,WACrB,OAAO;YAAE,SAAS,EAAE;QAAC;QAGzB,MAAMC,OAAOD;QACb,MAAME,UAAWD,AAAAA,CAAAA,KAAK,OAAO,IAAI,EAAC,EAAG,GAAG,CAACE,CAAAA,IAAM;gBAC3C,IAAIA,EAAE,EAAE;gBACR,MAAMA,EAAE,IAAI;gBACZ,aAAaA,EAAE,WAAW;gBAC1B,OAAOA,EAAE,KAAK;gBACd,cAAcA,EAAE,YAAY,IAAI;gBAChC,iBAAiBA,EAAE,eAAe,IAAI;YAC1C;QAEA,OAAO;YAAED;QAAQ;IACrB;IAEA,MAAM,aAAaE,QAAiB,EAAEC,QAAwB,EAA+B;QACzF,MAAMC,QAAQF;QACd,MAAMG,eAAeF;QACrB,MAAMG,kBAAkBD,cAAc,WAAW,EAAE;QAEnD,MAAML,UAAqC,MAAMO,QAAQ,GAAG,CACxDH,MAAM,OAAO,CAAC,GAAG,CAAC,OAAMI;YACpB,MAAMC,gBAAgBH,gBAAgB,IAAI,CAACI,CAAAA,KAAMA,GAAG,EAAE,KAAKF,OAAO,EAAE;YAEpE,IAAIG;YACJ,IAAIC;YAEJ,IAAI,AAACJ,OAAO,MAAM,IAAIA,OAAO,MAAM,KAAKC,eAAe,cAIhD;gBAEHE,kBAAkB,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAACH,OAAO,MAAM;gBAC7DI,eAAe,IAAI,CAAC,MAAM,CAAC,IAAI,CAACJ,OAAO,MAAM,EAAE;oBAAC;oBAAG;iBAAE;YACzD,OARqE;gBAEjEG,kBAAkBF,eAAe,mBAAmB;gBACpDG,eAAeH,eAAe,gBAAgB;YAClD;YAMA,OAAO;gBACH,IAAID,OAAO,EAAE;gBACb,MAAMA,OAAO,IAAI;gBACjB,aAAaA,OAAO,WAAW;gBAC/B,OAAOA,OAAO,KAAK;gBACnBG;gBACAC;YACJ;QACJ;QAGJ,OAAO;YAAEZ;QAAQ;IACrB;AACJ;AAEA,yBAAea,+BAA+B,oBAAoB,CAAC;IAC/D,gBAAgBlB;IAChB,cAAc;QAACmB;QAAYC;KAAO;AACtC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Validates LLM-generated page content against the component catalog.
3
+ * Recursively walks the element tree (including nested CreateElement actions
4
+ * inside slot inputs) and removes any element whose component name is not
5
+ * in the catalog. Tool envelopes are left untouched.
6
+ */
7
+ export declare class ComponentFilter {
8
+ private readonly validNames;
9
+ constructor(components: Array<{
10
+ name: string;
11
+ }>);
12
+ filter(elements: unknown[]): unknown[];
13
+ private isValid;
14
+ private processInputs;
15
+ private getInputs;
16
+ private setInputs;
17
+ private processValue;
18
+ }
@@ -0,0 +1,69 @@
1
+ class ComponentFilter {
2
+ constructor(components){
3
+ this.validNames = new Set(components.map((c)=>c.name));
4
+ }
5
+ filter(elements) {
6
+ return elements.filter((el)=>this.isValid(el)).map((el)=>this.processInputs(el));
7
+ }
8
+ isValid(el) {
9
+ if (!el || "object" != typeof el) return true;
10
+ const obj = el;
11
+ if ("component" in obj && "string" == typeof obj.component) return this.validNames.has(obj.component);
12
+ if ("CreateElement" === obj.action && obj.params && "object" == typeof obj.params) {
13
+ const params = obj.params;
14
+ if ("component" in params && "string" == typeof params.component) return this.validNames.has(params.component);
15
+ }
16
+ return true;
17
+ }
18
+ processInputs(el) {
19
+ const inputs = this.getInputs(el);
20
+ if (!inputs) return el;
21
+ const cleaned = {};
22
+ for (const [key, value] of Object.entries(inputs))cleaned[key] = this.processValue(value);
23
+ return this.setInputs(el, cleaned);
24
+ }
25
+ getInputs(el) {
26
+ if ("inputs" in el && el.inputs && "object" == typeof el.inputs) return el.inputs;
27
+ if ("CreateElement" === el.action && el.params && "object" == typeof el.params && "inputs" in el.params) {
28
+ const params = el.params;
29
+ return params.inputs;
30
+ }
31
+ return null;
32
+ }
33
+ setInputs(el, inputs) {
34
+ if ("inputs" in el) return {
35
+ ...el,
36
+ inputs
37
+ };
38
+ if ("CreateElement" === el.action && el.params && "object" == typeof el.params) return {
39
+ ...el,
40
+ params: {
41
+ ...el.params,
42
+ inputs
43
+ }
44
+ };
45
+ return el;
46
+ }
47
+ processValue(value) {
48
+ if (Array.isArray(value)) return value.filter((item)=>this.isValid(item)).map((item)=>this.processValue(item));
49
+ if (!value || "object" != typeof value) return value;
50
+ const obj = value;
51
+ if ("tool" in obj) return value;
52
+ if ("CreateElement" === obj.action) {
53
+ if (!this.isValid(obj)) return null;
54
+ return this.processInputs(obj);
55
+ }
56
+ if ("component" in obj && "inputs" in obj) return this.processInputs(obj);
57
+ const result = {};
58
+ let changed = false;
59
+ for (const [k, v] of Object.entries(obj)){
60
+ const processed = this.processValue(v);
61
+ result[k] = processed;
62
+ if (processed !== v) changed = true;
63
+ }
64
+ return changed ? result : value;
65
+ }
66
+ }
67
+ export { ComponentFilter };
68
+
69
+ //# sourceMappingURL=ComponentFilter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api/features/WbGeneratePageContent/ComponentFilter.js","sources":["../../../../src/api/features/WbGeneratePageContent/ComponentFilter.ts"],"sourcesContent":["/**\n * Validates LLM-generated page content against the component catalog.\n * Recursively walks the element tree (including nested CreateElement actions\n * inside slot inputs) and removes any element whose component name is not\n * in the catalog. Tool envelopes are left untouched.\n */\nexport class ComponentFilter {\n private readonly validNames: Set<string>;\n\n constructor(components: Array<{ name: string }>) {\n this.validNames = new Set(components.map(c => c.name));\n }\n\n filter(elements: unknown[]): unknown[] {\n return elements\n .filter(el => this.isValid(el))\n .map(el => this.processInputs(el as Record<string, unknown>));\n }\n\n private isValid(el: unknown): boolean {\n if (!el || typeof el !== \"object\") {\n return true;\n }\n\n const obj = el as Record<string, unknown>;\n\n if (\"component\" in obj && typeof obj.component === \"string\") {\n return this.validNames.has(obj.component);\n }\n\n if (obj.action === \"CreateElement\" && obj.params && typeof obj.params === \"object\") {\n const params = obj.params as Record<string, unknown>;\n if (\"component\" in params && typeof params.component === \"string\") {\n return this.validNames.has(params.component);\n }\n }\n\n return true;\n }\n\n private processInputs(el: Record<string, unknown>): Record<string, unknown> {\n const inputs = this.getInputs(el);\n if (!inputs) {\n return el;\n }\n\n const cleaned: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(inputs)) {\n cleaned[key] = this.processValue(value);\n }\n\n return this.setInputs(el, cleaned);\n }\n\n private getInputs(el: Record<string, unknown>): Record<string, unknown> | null {\n if (\"inputs\" in el && el.inputs && typeof el.inputs === \"object\") {\n return el.inputs as Record<string, unknown>;\n }\n\n if (\n el.action === \"CreateElement\" &&\n el.params &&\n typeof el.params === \"object\" &&\n \"inputs\" in (el.params as Record<string, unknown>)\n ) {\n const params = el.params as Record<string, unknown>;\n return params.inputs as Record<string, unknown>;\n }\n\n return null;\n }\n\n private setInputs(\n el: Record<string, unknown>,\n inputs: Record<string, unknown>\n ): Record<string, unknown> {\n if (\"inputs\" in el) {\n return { ...el, inputs };\n }\n\n if (el.action === \"CreateElement\" && el.params && typeof el.params === \"object\") {\n return { ...el, params: { ...el.params, inputs } };\n }\n\n return el;\n }\n\n private processValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.filter(item => this.isValid(item)).map(item => this.processValue(item));\n }\n\n if (!value || typeof value !== \"object\") {\n return value;\n }\n\n const obj = value as Record<string, unknown>;\n\n if (\"tool\" in obj) {\n return value;\n }\n\n if (obj.action === \"CreateElement\") {\n if (!this.isValid(obj)) {\n return null;\n }\n return this.processInputs(obj);\n }\n\n if (\"component\" in obj && \"inputs\" in obj) {\n return this.processInputs(obj);\n }\n\n const result: Record<string, unknown> = {};\n let changed = false;\n for (const [k, v] of Object.entries(obj)) {\n const processed = this.processValue(v);\n result[k] = processed;\n if (processed !== v) {\n changed = true;\n }\n }\n return changed ? result : value;\n }\n}\n"],"names":["ComponentFilter","components","Set","c","elements","el","obj","params","inputs","cleaned","key","value","Object","Array","item","result","changed","k","v","processed"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare class LlmJsonResponse {
2
+ private readonly text;
3
+ private constructor();
4
+ static fromRawText(raw: string): LlmJsonResponse;
5
+ toArray(): unknown[];
6
+ toString(): string;
7
+ }
@@ -0,0 +1,35 @@
1
+ class LlmJsonResponse {
2
+ constructor(text){
3
+ this.text = text;
4
+ }
5
+ static fromRawText(raw) {
6
+ let text = raw.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "").trim();
7
+ const startIdx = text.search(/[\[{]/);
8
+ if (startIdx > 0) text = text.slice(startIdx);
9
+ if (text.startsWith("[")) {
10
+ const endIdx = text.lastIndexOf("]");
11
+ if (endIdx >= 0) text = text.slice(0, endIdx + 1);
12
+ } else if (text.startsWith("{")) {
13
+ const endIdx = text.lastIndexOf("}");
14
+ if (endIdx >= 0) text = text.slice(0, endIdx + 1);
15
+ }
16
+ return new LlmJsonResponse(text);
17
+ }
18
+ toArray() {
19
+ const parsed = JSON.parse(this.text);
20
+ if (Array.isArray(parsed)) return parsed;
21
+ if (parsed && "object" == typeof parsed) {
22
+ const firstKey = Object.keys(parsed)[0];
23
+ if (firstKey && Array.isArray(parsed[firstKey])) return parsed[firstKey];
24
+ }
25
+ return [
26
+ parsed
27
+ ];
28
+ }
29
+ toString() {
30
+ return JSON.stringify(this.toArray());
31
+ }
32
+ }
33
+ export { LlmJsonResponse };
34
+
35
+ //# sourceMappingURL=LlmJsonResponse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api/features/WbGeneratePageContent/LlmJsonResponse.js","sources":["../../../../src/api/features/WbGeneratePageContent/LlmJsonResponse.ts"],"sourcesContent":["export class LlmJsonResponse {\n private constructor(private readonly text: string) {}\n\n static fromRawText(raw: string): LlmJsonResponse {\n let text = raw\n .replace(/^```(?:json)?\\s*\\n?/, \"\")\n .replace(/\\n?```\\s*$/, \"\")\n .trim();\n\n // Models that use tool calls sometimes emit a brief preamble before\n // the JSON (\"Now I have the images, here's the page:\") and/or trailing\n // prose after. Slice to the first `[`/`{` and the matching last\n // `]`/`}` so JSON.parse sees only the structured block.\n const startIdx = text.search(/[\\[{]/);\n if (startIdx > 0) {\n text = text.slice(startIdx);\n }\n if (text.startsWith(\"[\")) {\n const endIdx = text.lastIndexOf(\"]\");\n if (endIdx >= 0) {\n text = text.slice(0, endIdx + 1);\n }\n } else if (text.startsWith(\"{\")) {\n const endIdx = text.lastIndexOf(\"}\");\n if (endIdx >= 0) {\n text = text.slice(0, endIdx + 1);\n }\n }\n\n return new LlmJsonResponse(text);\n }\n\n toArray(): unknown[] {\n const parsed = JSON.parse(this.text);\n\n if (Array.isArray(parsed)) {\n return parsed;\n }\n\n if (parsed && typeof parsed === \"object\") {\n const firstKey = Object.keys(parsed)[0];\n if (firstKey && Array.isArray(parsed[firstKey])) {\n return parsed[firstKey];\n }\n }\n\n return [parsed];\n }\n\n toString(): string {\n return JSON.stringify(this.toArray());\n }\n}\n"],"names":["LlmJsonResponse","text","raw","startIdx","endIdx","parsed","JSON","Array","firstKey","Object"],"mappings":"AAAO,MAAMA;IACT,YAAqCC,IAAY,CAAE;aAAdA,IAAI,GAAJA;IAAe;IAEpD,OAAO,YAAYC,GAAW,EAAmB;QAC7C,IAAID,OAAOC,IACN,OAAO,CAAC,uBAAuB,IAC/B,OAAO,CAAC,cAAc,IACtB,IAAI;QAMT,MAAMC,WAAWF,KAAK,MAAM,CAAC;QAC7B,IAAIE,WAAW,GACXF,OAAOA,KAAK,KAAK,CAACE;QAEtB,IAAIF,KAAK,UAAU,CAAC,MAAM;YACtB,MAAMG,SAASH,KAAK,WAAW,CAAC;YAChC,IAAIG,UAAU,GACVH,OAAOA,KAAK,KAAK,CAAC,GAAGG,SAAS;QAEtC,OAAO,IAAIH,KAAK,UAAU,CAAC,MAAM;YAC7B,MAAMG,SAASH,KAAK,WAAW,CAAC;YAChC,IAAIG,UAAU,GACVH,OAAOA,KAAK,KAAK,CAAC,GAAGG,SAAS;QAEtC;QAEA,OAAO,IAAIJ,gBAAgBC;IAC/B;IAEA,UAAqB;QACjB,MAAMI,SAASC,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI;QAEnC,IAAIC,MAAM,OAAO,CAACF,SACd,OAAOA;QAGX,IAAIA,UAAU,AAAkB,YAAlB,OAAOA,QAAqB;YACtC,MAAMG,WAAWC,OAAO,IAAI,CAACJ,OAAO,CAAC,EAAE;YACvC,IAAIG,YAAYD,MAAM,OAAO,CAACF,MAAM,CAACG,SAAS,GAC1C,OAAOH,MAAM,CAACG,SAAS;QAE/B;QAEA,OAAO;YAACH;SAAO;IACnB;IAEA,WAAmB;QACf,OAAOC,KAAK,SAAS,CAAC,IAAI,CAAC,OAAO;IACtC;AACJ"}
@@ -2,6 +2,7 @@ import { Result } from "@webiny/feature/api";
2
2
  import { Ai } from "@webiny/api-core/features/ai/index.js";
3
3
  import { AiSdkTools } from "@webiny/api-core/features/ai/index.js";
4
4
  import { Encryption } from "@webiny/api-core/features/encryption/index.js";
5
+ import { ListTagsUseCase } from "@webiny/api-file-manager/features/file/ListTags/index.js";
5
6
  import { GetSettingsUseCase } from "../../../api/features/GetSettings/index.js";
6
7
  import { AiPromptContextBuilder } from "../../../api/features/AiPromptContext/index.js";
7
8
  import { WbGeneratePageContentUseCase } from "./abstractions.js";
@@ -12,7 +13,8 @@ declare class WbGeneratePageContentUseCaseImpl implements WbGeneratePageContentU
12
13
  private ai;
13
14
  private aiSdkTools;
14
15
  private encryption;
15
- constructor(promptContextBuilder: AiPromptContextBuilder.Interface, getSettings: GetSettingsUseCase.Interface, ai: Ai.Interface, aiSdkTools: AiSdkTools.Interface, encryption: Encryption.Interface);
16
+ private listTags;
17
+ constructor(promptContextBuilder: AiPromptContextBuilder.Interface, getSettings: GetSettingsUseCase.Interface, ai: Ai.Interface, aiSdkTools: AiSdkTools.Interface, encryption: Encryption.Interface, listTags: ListTagsUseCase.Interface);
16
18
  execute(params: WbGeneratePageContentParams): Promise<Result<GeneratePageContentResult, Error>>;
17
19
  }
18
20
  export declare const WbGeneratePageContentUseCaseImplementation: typeof WbGeneratePageContentUseCaseImpl & {
@@ -2,21 +2,22 @@ import { stepCountIs } from "ai";
2
2
  import { Result } from "@webiny/feature/api";
3
3
  import { Ai, AiSdkTools } from "@webiny/api-core/features/ai/index.js";
4
4
  import { Encryption } from "@webiny/api-core/features/encryption/index.js";
5
+ import { ListTagsUseCase } from "@webiny/api-file-manager/features/file/ListTags/index.js";
5
6
  import { GetSettingsUseCase } from "../GetSettings/index.js";
6
7
  import { AiPromptContextBuilder } from "../AiPromptContext/index.js";
7
8
  import { createReadProjectFileTool } from "../AiPromptContext/ReadProjectFileTool.js";
8
9
  import { WbGeneratePageContentUseCase } from "./abstractions.js";
9
10
  import { buildDomainPrompt } from "./buildPrompt.js";
10
- function stripCodeFence(text) {
11
- return text.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "").trim();
12
- }
11
+ import { LlmJsonResponse } from "./LlmJsonResponse.js";
12
+ import { ComponentFilter } from "./ComponentFilter.js";
13
13
  class WbGeneratePageContentUseCaseImpl {
14
- constructor(promptContextBuilder, getSettings, ai, aiSdkTools, encryption){
14
+ constructor(promptContextBuilder, getSettings, ai, aiSdkTools, encryption, listTags){
15
15
  this.promptContextBuilder = promptContextBuilder;
16
16
  this.getSettings = getSettings;
17
17
  this.ai = ai;
18
18
  this.aiSdkTools = aiSdkTools;
19
19
  this.encryption = encryption;
20
+ this.listTags = listTags;
20
21
  }
21
22
  async execute(params) {
22
23
  const settingsResult = await this.getSettings.execute();
@@ -24,7 +25,7 @@ class WbGeneratePageContentUseCaseImpl {
24
25
  const settings = settingsResult.value;
25
26
  const firstProvider = settings.providers.presets[0];
26
27
  if (!firstProvider) return Result.fail(new Error("No AI provider configured. Add a provider in AI Power Ups settings."));
27
- const apiKey = this.encryption.decrypt(firstProvider.apiKeyEncrypted);
28
+ const apiKey = await this.encryption.decrypt(firstProvider.apiKeyEncrypted);
28
29
  const sdkTools = this.aiSdkTools.getToolSet();
29
30
  const context = await this.promptContextBuilder.execute({
30
31
  projectId: params.projectId,
@@ -36,7 +37,15 @@ class WbGeneratePageContentUseCaseImpl {
36
37
  const projectFileTool = createReadProjectFileTool(context.allProjectFiles, context.excludedFileIds);
37
38
  Object.assign(sdkTools, projectFileTool);
38
39
  }
39
- const systemText = buildDomainPrompt(params.components, params.tools) + context.toString();
40
+ const tagsResult = await this.listTags.execute({
41
+ where: {
42
+ type_startsWith: "image/"
43
+ },
44
+ limit: 100
45
+ });
46
+ const imageTags = tagsResult.isOk() ? tagsResult.value.map((t)=>t.tag) : [];
47
+ const components = params.components;
48
+ const systemText = buildDomainPrompt(components, params.tools, imageTags) + context.toString();
40
49
  const system = {
41
50
  role: "system",
42
51
  content: systemText,
@@ -64,7 +73,9 @@ class WbGeneratePageContentUseCaseImpl {
64
73
  } : {}
65
74
  });
66
75
  const text = aiResult.text || (aiResult.steps.filter((step)=>step.text.length > 0).pop()?.text ?? "");
67
- const output = stripCodeFence(text);
76
+ const elements = LlmJsonResponse.fromRawText(text).toArray();
77
+ const componentFilter = new ComponentFilter(components);
78
+ const output = JSON.stringify(componentFilter.filter(elements));
68
79
  const filesRead = new Set();
69
80
  let toolCallsMade = 0;
70
81
  for (const step of aiResult.steps)for (const call of step.toolCalls){
@@ -80,7 +91,9 @@ class WbGeneratePageContentUseCaseImpl {
80
91
  ],
81
92
  cacheHit: context.cacheHit,
82
93
  toolCallsMade,
83
- totalSteps: aiResult.steps.length
94
+ totalSteps: aiResult.steps.length,
95
+ toolsAvailable: Object.keys(sdkTools),
96
+ imageTagsInPrompt: imageTags
84
97
  };
85
98
  return Result.ok({
86
99
  output,
@@ -98,7 +111,8 @@ const WbGeneratePageContentUseCaseImplementation = WbGeneratePageContentUseCase.
98
111
  GetSettingsUseCase,
99
112
  Ai,
100
113
  AiSdkTools,
101
- Encryption
114
+ Encryption,
115
+ ListTagsUseCase
102
116
  ]
103
117
  });
104
118
  export { WbGeneratePageContentUseCaseImplementation };
@@ -1 +1 @@
1
- {"version":3,"file":"api/features/WbGeneratePageContent/WbGeneratePageContentUseCase.js","sources":["../../../../src/api/features/WbGeneratePageContent/WbGeneratePageContentUseCase.ts"],"sourcesContent":["import { stepCountIs } from \"ai\";\nimport { Result } from \"@webiny/feature/api\";\nimport { Ai } from \"@webiny/api-core/features/ai/index.js\";\nimport { AiSdkTools } from \"@webiny/api-core/features/ai/index.js\";\nimport { Encryption } from \"@webiny/api-core/features/encryption/index.js\";\nimport { GetSettingsUseCase } from \"~/api/features/GetSettings/index.js\";\nimport { AiPromptContextBuilder } from \"~/api/features/AiPromptContext/index.js\";\nimport { createReadProjectFileTool } from \"~/api/features/AiPromptContext/ReadProjectFileTool.js\";\nimport { WbGeneratePageContentUseCase } from \"./abstractions.js\";\nimport type {\n WbGeneratePageContentParams,\n GeneratePageContentResult,\n GenerationTelemetry\n} from \"./abstractions.js\";\nimport { buildDomainPrompt } from \"./buildPrompt.js\";\n\nfunction stripCodeFence(text: string): string {\n return text\n .replace(/^```(?:json)?\\s*\\n?/, \"\")\n .replace(/\\n?```\\s*$/, \"\")\n .trim();\n}\n\nclass WbGeneratePageContentUseCaseImpl implements WbGeneratePageContentUseCase.Interface {\n constructor(\n private promptContextBuilder: AiPromptContextBuilder.Interface,\n private getSettings: GetSettingsUseCase.Interface,\n private ai: Ai.Interface,\n private aiSdkTools: AiSdkTools.Interface,\n private encryption: Encryption.Interface\n ) {}\n\n async execute(\n params: WbGeneratePageContentParams\n ): Promise<Result<GeneratePageContentResult, Error>> {\n const settingsResult = await this.getSettings.execute();\n if (settingsResult.isFail()) {\n return Result.fail(new Error(\"Failed to load AI PowerUps settings.\"));\n }\n\n const settings = settingsResult.value;\n const firstProvider = settings.providers.presets[0];\n\n if (!firstProvider) {\n return Result.fail(\n new Error(\"No AI provider configured. Add a provider in AI Power Ups settings.\")\n );\n }\n\n const apiKey = this.encryption.decrypt(firstProvider.apiKeyEncrypted);\n\n const sdkTools = this.aiSdkTools.getToolSet();\n\n const context = await this.promptContextBuilder.execute({\n projectId: params.projectId,\n readerPersonaId: params.readerPersonaId,\n writerPersonaId: params.writerPersonaId,\n excludedFileIds: params.excludedFileIds\n });\n\n if (context.allProjectFiles.length > 0) {\n const projectFileTool = createReadProjectFileTool(\n context.allProjectFiles,\n context.excludedFileIds\n );\n Object.assign(sdkTools, projectFileTool);\n }\n\n const systemText = buildDomainPrompt(params.components, params.tools) + context.toString();\n\n const system = {\n role: \"system\" as const,\n content: systemText,\n providerOptions: {\n anthropic: {\n cacheControl: { type: \"ephemeral\" }\n }\n }\n };\n\n try {\n const aiResult = await this.ai.generateText({\n model: firstProvider.model,\n connection: {\n sdkName: firstProvider.model.split(\"/\")[0],\n apiKey\n },\n system,\n toolChoice: \"auto\",\n prompt: params.prompt,\n ...(Object.keys(sdkTools).length > 0\n ? { tools: sdkTools, stopWhen: stepCountIs(20) }\n : {})\n });\n\n const text =\n aiResult.text ||\n (aiResult.steps.filter(step => step.text.length > 0).pop()?.text ?? \"\");\n\n const output = stripCodeFence(text);\n\n const filesRead = new Set<string>();\n let toolCallsMade = 0;\n for (const step of aiResult.steps) {\n for (const call of step.toolCalls) {\n toolCallsMade++;\n if (call.toolName === \"read_project_file\") {\n const input = call.input as { fileId?: string };\n if (input.fileId) {\n filesRead.add(input.fileId);\n }\n }\n }\n }\n\n const telemetry: GenerationTelemetry = {\n filesRead: [...filesRead],\n cacheHit: context.cacheHit,\n toolCallsMade,\n totalSteps: aiResult.steps.length\n };\n\n return Result.ok({ output, telemetry });\n } catch (error) {\n return Result.fail(\n new Error(\n `AI generation failed: ${error instanceof Error ? error.message : String(error)}`\n )\n );\n }\n }\n}\n\nexport const WbGeneratePageContentUseCaseImplementation =\n WbGeneratePageContentUseCase.createImplementation({\n implementation: WbGeneratePageContentUseCaseImpl,\n dependencies: [AiPromptContextBuilder, GetSettingsUseCase, Ai, AiSdkTools, Encryption]\n });\n"],"names":["stripCodeFence","text","WbGeneratePageContentUseCaseImpl","promptContextBuilder","getSettings","ai","aiSdkTools","encryption","params","settingsResult","Result","Error","settings","firstProvider","apiKey","sdkTools","context","projectFileTool","createReadProjectFileTool","Object","systemText","buildDomainPrompt","system","aiResult","stepCountIs","step","output","filesRead","Set","toolCallsMade","call","input","telemetry","error","String","WbGeneratePageContentUseCaseImplementation","WbGeneratePageContentUseCase","AiPromptContextBuilder","GetSettingsUseCase","Ai","AiSdkTools","Encryption"],"mappings":"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"}
1
+ {"version":3,"file":"api/features/WbGeneratePageContent/WbGeneratePageContentUseCase.js","sources":["../../../../src/api/features/WbGeneratePageContent/WbGeneratePageContentUseCase.ts"],"sourcesContent":["import { stepCountIs } from \"ai\";\nimport { Result } from \"@webiny/feature/api\";\nimport { Ai } from \"@webiny/api-core/features/ai/index.js\";\nimport { AiSdkTools } from \"@webiny/api-core/features/ai/index.js\";\nimport { Encryption } from \"@webiny/api-core/features/encryption/index.js\";\nimport { ListTagsUseCase } from \"@webiny/api-file-manager/features/file/ListTags/index.js\";\nimport { GetSettingsUseCase } from \"~/api/features/GetSettings/index.js\";\nimport { AiPromptContextBuilder } from \"~/api/features/AiPromptContext/index.js\";\nimport { createReadProjectFileTool } from \"~/api/features/AiPromptContext/ReadProjectFileTool.js\";\nimport { WbGeneratePageContentUseCase } from \"./abstractions.js\";\nimport type {\n WbGeneratePageContentParams,\n GeneratePageContentResult,\n GenerationTelemetry\n} from \"./abstractions.js\";\nimport { buildDomainPrompt } from \"./buildPrompt.js\";\nimport { LlmJsonResponse } from \"./LlmJsonResponse.js\";\nimport { ComponentFilter } from \"./ComponentFilter.js\";\n\nclass WbGeneratePageContentUseCaseImpl implements WbGeneratePageContentUseCase.Interface {\n constructor(\n private promptContextBuilder: AiPromptContextBuilder.Interface,\n private getSettings: GetSettingsUseCase.Interface,\n private ai: Ai.Interface,\n private aiSdkTools: AiSdkTools.Interface,\n private encryption: Encryption.Interface,\n private listTags: ListTagsUseCase.Interface\n ) {}\n\n async execute(\n params: WbGeneratePageContentParams\n ): Promise<Result<GeneratePageContentResult, Error>> {\n const settingsResult = await this.getSettings.execute();\n if (settingsResult.isFail()) {\n return Result.fail(new Error(\"Failed to load AI PowerUps settings.\"));\n }\n\n const settings = settingsResult.value;\n const firstProvider = settings.providers.presets[0];\n\n if (!firstProvider) {\n return Result.fail(\n new Error(\"No AI provider configured. Add a provider in AI Power Ups settings.\")\n );\n }\n\n const apiKey = await this.encryption.decrypt(firstProvider.apiKeyEncrypted);\n\n const sdkTools = this.aiSdkTools.getToolSet();\n\n const context = await this.promptContextBuilder.execute({\n projectId: params.projectId,\n readerPersonaId: params.readerPersonaId,\n writerPersonaId: params.writerPersonaId,\n excludedFileIds: params.excludedFileIds\n });\n\n if (context.allProjectFiles.length > 0) {\n const projectFileTool = createReadProjectFileTool(\n context.allProjectFiles,\n context.excludedFileIds\n );\n Object.assign(sdkTools, projectFileTool);\n }\n\n // Fetch image tags so the prompt can enumerate what's queryable; without\n // this the model invents tag/IDs instead of calling listImagesByTag.\n const tagsResult = await this.listTags.execute({\n where: { type_startsWith: \"image/\" },\n limit: 100\n });\n const imageTags = tagsResult.isOk() ? tagsResult.value.map(t => t.tag) : [];\n\n const components = params.components as Array<{ name: string }>;\n const systemText =\n buildDomainPrompt(components, params.tools, imageTags) + context.toString();\n\n const system = {\n role: \"system\" as const,\n content: systemText,\n providerOptions: {\n anthropic: {\n cacheControl: { type: \"ephemeral\" }\n }\n }\n };\n\n try {\n const aiResult = await this.ai.generateText({\n model: firstProvider.model,\n connection: {\n sdkName: firstProvider.model.split(\"/\")[0],\n apiKey\n },\n system,\n toolChoice: \"auto\",\n prompt: params.prompt,\n ...(Object.keys(sdkTools).length > 0\n ? { tools: sdkTools, stopWhen: stepCountIs(20) }\n : {})\n });\n\n const text =\n aiResult.text ||\n (aiResult.steps.filter(step => step.text.length > 0).pop()?.text ?? \"\");\n\n const elements = LlmJsonResponse.fromRawText(text).toArray();\n const componentFilter = new ComponentFilter(components);\n const output = JSON.stringify(componentFilter.filter(elements));\n\n const filesRead = new Set<string>();\n let toolCallsMade = 0;\n for (const step of aiResult.steps) {\n for (const call of step.toolCalls) {\n toolCallsMade++;\n if (call.toolName === \"read_project_file\") {\n const input = call.input as { fileId?: string };\n if (input.fileId) {\n filesRead.add(input.fileId);\n }\n }\n }\n }\n\n const telemetry: GenerationTelemetry = {\n filesRead: [...filesRead],\n cacheHit: context.cacheHit,\n toolCallsMade,\n totalSteps: aiResult.steps.length,\n toolsAvailable: Object.keys(sdkTools),\n imageTagsInPrompt: imageTags\n };\n\n return Result.ok({ output, telemetry });\n } catch (error) {\n return Result.fail(\n new Error(\n `AI generation failed: ${error instanceof Error ? error.message : String(error)}`\n )\n );\n }\n }\n}\n\nexport const WbGeneratePageContentUseCaseImplementation =\n WbGeneratePageContentUseCase.createImplementation({\n implementation: WbGeneratePageContentUseCaseImpl,\n dependencies: [\n AiPromptContextBuilder,\n GetSettingsUseCase,\n Ai,\n AiSdkTools,\n Encryption,\n ListTagsUseCase\n ]\n });\n"],"names":["WbGeneratePageContentUseCaseImpl","promptContextBuilder","getSettings","ai","aiSdkTools","encryption","listTags","params","settingsResult","Result","Error","settings","firstProvider","apiKey","sdkTools","context","projectFileTool","createReadProjectFileTool","Object","tagsResult","imageTags","t","components","systemText","buildDomainPrompt","system","aiResult","stepCountIs","text","step","elements","LlmJsonResponse","componentFilter","ComponentFilter","output","JSON","filesRead","Set","toolCallsMade","call","input","telemetry","error","String","WbGeneratePageContentUseCaseImplementation","WbGeneratePageContentUseCase","AiPromptContextBuilder","GetSettingsUseCase","Ai","AiSdkTools","Encryption","ListTagsUseCase"],"mappings":"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"}
@@ -13,6 +13,8 @@ export interface GenerationTelemetry {
13
13
  cacheHit: boolean;
14
14
  toolCallsMade: number;
15
15
  totalSteps: number;
16
+ toolsAvailable: string[];
17
+ imageTagsInPrompt: string[];
16
18
  }
17
19
  export interface GeneratePageContentResult {
18
20
  output: string;
@@ -1 +1 @@
1
- {"version":3,"file":"api/features/WbGeneratePageContent/abstractions.js","sources":["../../../../src/api/features/WbGeneratePageContent/abstractions.ts"],"sourcesContent":["import { createAbstraction, Result } from \"@webiny/feature/api\";\n\nexport interface WbGeneratePageContentParams {\n prompt: string;\n components: unknown;\n tools: unknown;\n projectId?: string | null;\n excludedFileIds?: string[] | null;\n readerPersonaId?: string | null;\n writerPersonaId?: string | null;\n}\n\nexport interface GenerationTelemetry {\n filesRead: string[];\n cacheHit: boolean;\n toolCallsMade: number;\n totalSteps: number;\n}\n\nexport interface GeneratePageContentResult {\n output: string;\n telemetry: GenerationTelemetry;\n}\n\nexport interface IWbGeneratePageContentUseCase {\n execute(params: WbGeneratePageContentParams): Promise<Result<GeneratePageContentResult, Error>>;\n}\n\nexport const WbGeneratePageContentUseCase = createAbstraction<IWbGeneratePageContentUseCase>(\n \"AiPowerUpsWbGeneratePageContentUseCase\"\n);\n\nexport namespace WbGeneratePageContentUseCase {\n export type Interface = IWbGeneratePageContentUseCase;\n export type Params = WbGeneratePageContentParams;\n}\n"],"names":["WbGeneratePageContentUseCase","createAbstraction"],"mappings":";AA4BO,MAAMA,+BAA+BC,kBACxC"}
1
+ {"version":3,"file":"api/features/WbGeneratePageContent/abstractions.js","sources":["../../../../src/api/features/WbGeneratePageContent/abstractions.ts"],"sourcesContent":["import { createAbstraction, Result } from \"@webiny/feature/api\";\n\nexport interface WbGeneratePageContentParams {\n prompt: string;\n components: unknown;\n tools: unknown;\n projectId?: string | null;\n excludedFileIds?: string[] | null;\n readerPersonaId?: string | null;\n writerPersonaId?: string | null;\n}\n\nexport interface GenerationTelemetry {\n filesRead: string[];\n cacheHit: boolean;\n toolCallsMade: number;\n totalSteps: number;\n toolsAvailable: string[];\n imageTagsInPrompt: string[];\n}\n\nexport interface GeneratePageContentResult {\n output: string;\n telemetry: GenerationTelemetry;\n}\n\nexport interface IWbGeneratePageContentUseCase {\n execute(params: WbGeneratePageContentParams): Promise<Result<GeneratePageContentResult, Error>>;\n}\n\nexport const WbGeneratePageContentUseCase = createAbstraction<IWbGeneratePageContentUseCase>(\n \"AiPowerUpsWbGeneratePageContentUseCase\"\n);\n\nexport namespace WbGeneratePageContentUseCase {\n export type Interface = IWbGeneratePageContentUseCase;\n export type Params = WbGeneratePageContentParams;\n}\n"],"names":["WbGeneratePageContentUseCase","createAbstraction"],"mappings":";AA8BO,MAAMA,+BAA+BC,kBACxC"}
@@ -1 +1,3 @@
1
- export declare function buildDomainPrompt(components: unknown, tools: unknown): string;
1
+ export declare function buildDomainPrompt(components: Array<{
2
+ name: string;
3
+ }>, tools: unknown, availableImageTags: string[]): string;
@@ -1,4 +1,6 @@
1
- function buildDomainPrompt(components, tools) {
1
+ function buildDomainPrompt(components, tools, availableImageTags) {
2
+ const componentNames = components.map((c)=>`"${c.name}"`).join(" | ");
3
+ const tagListing = availableImageTags.length > 0 ? availableImageTags.map((t)=>`\`${t}\``).join(", ") : "(no image tags available in the File Manager — image fields should be left empty or omitted)";
2
4
  return `You are a page content generator. Given a user prompt, generate structured page content using the provided component catalog and available tools.
3
5
 
4
6
  ###
@@ -13,10 +15,17 @@ Example: RichText - Banner - RichText - Image - Banner - RichText
13
15
 
14
16
  ### Image Selection
15
17
 
16
- When the page content requires images, use the listImagesByTag tool
17
- to search for available images. After receiving the results, select
18
- the most appropriate image and reference it in your output using:
19
- { "tool": "resolveImage", "params": { "id": "<image_id_from_search>" } }
18
+ You MUST call the \`listImagesByTag\` tool BEFORE emitting any \`resolveImage\` envelope.
19
+ Do NOT invent image IDs. Any ID that was not returned by \`listImagesByTag\` will fail to resolve and the image field will end up empty in the editor.
20
+
21
+ The File Manager currently has images tagged with: ${tagListing}.
22
+
23
+ When a component needs an image:
24
+ 1. Pick the tag from the list above that best matches the page topic. Only use tags that appear in the list — never invent new tags.
25
+ 2. Call \`listImagesByTag\` with that tag.
26
+ 3. If the results are non-empty, choose the most appropriate image and reference it using:
27
+ { "tool": "resolveImage", "params": { "id": "<image_id_from_search>" } }
28
+ 4. If the results are empty, try one more tag from the list. If still nothing, leave the image field empty (do NOT invent an ID).
20
29
 
21
30
  You MUST generate the full page content as JSON after using any tools.
22
31
  Tool calls are for gathering information — your final response must
@@ -72,8 +81,10 @@ ${JSON.stringify(tools, null, 2)}
72
81
  ### Page Schema
73
82
 
74
83
  \`\`\`typescript
84
+ type ComponentName = ${componentNames};
85
+
75
86
  type ElementSchema = {
76
- component: string;
87
+ component: ComponentName;
77
88
  inputs: Record<string, unknown>;
78
89
  };
79
90
 
@@ -82,10 +93,12 @@ type CreateElementAction = {
82
93
  params: ElementSchema;
83
94
  };
84
95
 
85
- type PageSchema = ElementSchema[];
96
+ type PageSchema = {
97
+ page: ElementSchema[];
98
+ };
86
99
  \`\`\`
87
100
 
88
- For slot inputs, use \`{ "action": "CreateElement", "params": { "component": "...", "inputs": { ... } } }\`. For root array items, use \`ElementSchema\` shape.
101
+ For slot inputs, use \`{ "action": "CreateElement", "params": { "component": "...", "inputs": { ... } } }\`. For root items inside the "page" array, use \`ElementSchema\` shape.
89
102
  Note: \`CreateElement\` uses "action" — it is a structural instruction for the page builder, not a tool invocation.
90
103
 
91
104
  ### Grid Structure Example
@@ -136,7 +149,9 @@ Key rules:
136
149
  - Webiny/GridColumn's "children" is an array of CreateElement actions for
137
150
  the actual content
138
151
 
139
- You MUST return parsable JSON string without any extra text or envelopes.`;
152
+ IMPORTANT: Only use components listed in the Component Catalog above. Do NOT invent component names. Any element with an unrecognized component name will be silently removed from the output.
153
+
154
+ You MUST return a parsable JSON object with a "page" key containing the array of elements. No extra text outside the JSON.`;
140
155
  }
141
156
  export { buildDomainPrompt };
142
157
 
@@ -1 +1 @@
1
- {"version":3,"file":"api/features/WbGeneratePageContent/buildPrompt.js","sources":["../../../../src/api/features/WbGeneratePageContent/buildPrompt.ts"],"sourcesContent":["export function buildDomainPrompt(components: unknown, tools: unknown): string {\n return `You are a page content generator. Given a user prompt, generate structured page content using the provided component catalog and available tools.\n\n###\n\n### Rich Text Content\n\nA single Webiny/Lexical element CAN contain an entire article, blog post, or content section,\nwith all headings, paragraphs, lists, and blockquotes in a single HTML string.\n\nCreate separate Webiny/Lexical elements when it needs to be mixed with other content elements.\nExample: RichText - Banner - RichText - Image - Banner - RichText\n\n### Image Selection\n\nWhen the page content requires images, use the listImagesByTag tool\nto search for available images. After receiving the results, select\nthe most appropriate image and reference it in your output using:\n{ \"tool\": \"resolveImage\", \"params\": { \"id\": \"<image_id_from_search>\" } }\n\nYou MUST generate the full page content as JSON after using any tools.\nTool calls are for gathering information — your final response must\nalways be the complete page JSON array.\n\n### SEO & Content Structure Best Practices\n\nWhen generating page content, follow these SEO guidelines:\n\nHeading hierarchy:\n- Use exactly ONE <h1> per page, as the main title/headline.\n- Use <h2> for major sections, <h3> for subsections. Never skip levels (e.g., no <h3> directly after <h1>).\n- Headings should be descriptive and include relevant keywords naturally — avoid generic headings like \"Introduction\" or \"Section 1\".\n\nParagraphs and readability:\n- Keep paragraphs short — 2 to 4 sentences each.\n- Use <strong> and <em> to emphasize key terms and phrases that a reader scanning the page should notice.\n- Use bullet lists (<ul>) or numbered lists (<ol>) to break down complex information.\n- Write in an active voice. Be direct and concise.\n\nContent structure:\n- Lead with the most important information first (inverted pyramid).\n- Include a compelling opening paragraph immediately after the <h1> that summarizes the page's value proposition.\n- Use transition sentences between sections to maintain reading flow.\n- End with a clear call-to-action or summary.\n\nKeyword usage:\n- Incorporate the main topic keywords naturally in the <h1>, at least one <h2>, the opening paragraph, and throughout the body.\n- Avoid keyword stuffing — content must read naturally to a human.\n- Use semantic variations and related terms rather than repeating the exact same phrase.\n\nLinks:\n- Where appropriate, use descriptive anchor text for links (not \"click here\" or \"read more\").\n\n### Component Catalog\n\n\\`\\`\\`json\n${JSON.stringify(components, null, 2)}\n\\`\\`\\`\n\nGrid layout options: \\`12\\`, \\`6-6\\`, \\`4-4-4\\`, \\`8-4\\`, \\`4-8\\`, \\`3-3-3-3\\`\n\n### Available Tools\n\nFor input fields that require post-processing, wrap the value in a tool envelope: \\`{ \"tool\": \"<toolName>\", \"params\": { ... } }\\`.\n\nPlain values (text, number, boolean, select) should be set directly without an envelope.\n\n\\`\\`\\`json\n${JSON.stringify(tools, null, 2)}\n\\`\\`\\`\n\n### Page Schema\n\n\\`\\`\\`typescript\ntype ElementSchema = {\n component: string;\n inputs: Record<string, unknown>;\n};\n\ntype CreateElementAction = {\n action: \"CreateElement\";\n params: ElementSchema;\n};\n\ntype PageSchema = ElementSchema[];\n\\`\\`\\`\n\nFor slot inputs, use \\`{ \"action\": \"CreateElement\", \"params\": { \"component\": \"...\", \"inputs\": { ... } } }\\`. For root array items, use \\`ElementSchema\\` shape.\nNote: \\`CreateElement\\` uses \"action\" — it is a structural instruction for the page builder, not a tool invocation.\n\n### Grid Structure Example\n\nWhen using Webiny/Grid, each column entry must use a CreateElement action\nto create a Webiny/GridColumn, and the GridColumn's children contain the\nactual content elements:\n\n\\`\\`\\`json\n{\n \"component\": \"Webiny/Grid\",\n \"inputs\": {\n \"gridLayout\": \"6-6\",\n \"columns\": [\n {\n \"children\": {\n \"action\": \"CreateElement\",\n \"params\": {\n \"component\": \"Webiny/GridColumn\",\n \"inputs\": {\n \"children\": [\n {\n \"action\": \"CreateElement\",\n \"params\": {\n \"component\": \"Webiny/Lexical\",\n \"inputs\": {\n \"content\": {\n \"tool\": \"textToLexical\",\n \"params\": { \"text\": \"...\" }\n }\n }\n }\n }\n ]\n }\n }\n }\n }\n ]\n }\n}\n\\`\\`\\`\n\nKey rules:\n- \"columns\" is an array, not an object with numeric keys\n- Each column has a \"children\" property containing a single CreateElement\n for Webiny/GridColumn\n- Webiny/GridColumn's \"children\" is an array of CreateElement actions for\n the actual content\n\nYou MUST return parsable JSON string without any extra text or envelopes.`;\n}\n"],"names":["buildDomainPrompt","components","tools","JSON"],"mappings":"AAAO,SAASA,kBAAkBC,UAAmB,EAAEC,KAAc;IACjE,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuDZ,EAAEC,KAAK,SAAS,CAACF,YAAY,MAAM,GAAG;;;;;;;;;;;;AAYtC,EAAEE,KAAK,SAAS,CAACD,OAAO,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yEAsEwC,CAAC;AAC1E"}
1
+ {"version":3,"file":"api/features/WbGeneratePageContent/buildPrompt.js","sources":["../../../../src/api/features/WbGeneratePageContent/buildPrompt.ts"],"sourcesContent":["export function buildDomainPrompt(\n components: Array<{ name: string }>,\n tools: unknown,\n availableImageTags: string[]\n): string {\n const componentNames = components.map(c => `\"${c.name}\"`).join(\" | \");\n const tagListing =\n availableImageTags.length > 0\n ? availableImageTags.map(t => `\\`${t}\\``).join(\", \")\n : \"(no image tags available in the File Manager — image fields should be left empty or omitted)\";\n return `You are a page content generator. Given a user prompt, generate structured page content using the provided component catalog and available tools.\n\n###\n\n### Rich Text Content\n\nA single Webiny/Lexical element CAN contain an entire article, blog post, or content section,\nwith all headings, paragraphs, lists, and blockquotes in a single HTML string.\n\nCreate separate Webiny/Lexical elements when it needs to be mixed with other content elements.\nExample: RichText - Banner - RichText - Image - Banner - RichText\n\n### Image Selection\n\nYou MUST call the \\`listImagesByTag\\` tool BEFORE emitting any \\`resolveImage\\` envelope.\nDo NOT invent image IDs. Any ID that was not returned by \\`listImagesByTag\\` will fail to resolve and the image field will end up empty in the editor.\n\nThe File Manager currently has images tagged with: ${tagListing}.\n\nWhen a component needs an image:\n1. Pick the tag from the list above that best matches the page topic. Only use tags that appear in the list — never invent new tags.\n2. Call \\`listImagesByTag\\` with that tag.\n3. If the results are non-empty, choose the most appropriate image and reference it using:\n { \"tool\": \"resolveImage\", \"params\": { \"id\": \"<image_id_from_search>\" } }\n4. If the results are empty, try one more tag from the list. If still nothing, leave the image field empty (do NOT invent an ID).\n\nYou MUST generate the full page content as JSON after using any tools.\nTool calls are for gathering information — your final response must\nalways be the complete page JSON array.\n\n### SEO & Content Structure Best Practices\n\nWhen generating page content, follow these SEO guidelines:\n\nHeading hierarchy:\n- Use exactly ONE <h1> per page, as the main title/headline.\n- Use <h2> for major sections, <h3> for subsections. Never skip levels (e.g., no <h3> directly after <h1>).\n- Headings should be descriptive and include relevant keywords naturally — avoid generic headings like \"Introduction\" or \"Section 1\".\n\nParagraphs and readability:\n- Keep paragraphs short — 2 to 4 sentences each.\n- Use <strong> and <em> to emphasize key terms and phrases that a reader scanning the page should notice.\n- Use bullet lists (<ul>) or numbered lists (<ol>) to break down complex information.\n- Write in an active voice. Be direct and concise.\n\nContent structure:\n- Lead with the most important information first (inverted pyramid).\n- Include a compelling opening paragraph immediately after the <h1> that summarizes the page's value proposition.\n- Use transition sentences between sections to maintain reading flow.\n- End with a clear call-to-action or summary.\n\nKeyword usage:\n- Incorporate the main topic keywords naturally in the <h1>, at least one <h2>, the opening paragraph, and throughout the body.\n- Avoid keyword stuffing — content must read naturally to a human.\n- Use semantic variations and related terms rather than repeating the exact same phrase.\n\nLinks:\n- Where appropriate, use descriptive anchor text for links (not \"click here\" or \"read more\").\n\n### Component Catalog\n\n\\`\\`\\`json\n${JSON.stringify(components, null, 2)}\n\\`\\`\\`\n\nGrid layout options: \\`12\\`, \\`6-6\\`, \\`4-4-4\\`, \\`8-4\\`, \\`4-8\\`, \\`3-3-3-3\\`\n\n### Available Tools\n\nFor input fields that require post-processing, wrap the value in a tool envelope: \\`{ \"tool\": \"<toolName>\", \"params\": { ... } }\\`.\n\nPlain values (text, number, boolean, select) should be set directly without an envelope.\n\n\\`\\`\\`json\n${JSON.stringify(tools, null, 2)}\n\\`\\`\\`\n\n### Page Schema\n\n\\`\\`\\`typescript\ntype ComponentName = ${componentNames};\n\ntype ElementSchema = {\n component: ComponentName;\n inputs: Record<string, unknown>;\n};\n\ntype CreateElementAction = {\n action: \"CreateElement\";\n params: ElementSchema;\n};\n\ntype PageSchema = {\n page: ElementSchema[];\n};\n\\`\\`\\`\n\nFor slot inputs, use \\`{ \"action\": \"CreateElement\", \"params\": { \"component\": \"...\", \"inputs\": { ... } } }\\`. For root items inside the \"page\" array, use \\`ElementSchema\\` shape.\nNote: \\`CreateElement\\` uses \"action\" — it is a structural instruction for the page builder, not a tool invocation.\n\n### Grid Structure Example\n\nWhen using Webiny/Grid, each column entry must use a CreateElement action\nto create a Webiny/GridColumn, and the GridColumn's children contain the\nactual content elements:\n\n\\`\\`\\`json\n{\n \"component\": \"Webiny/Grid\",\n \"inputs\": {\n \"gridLayout\": \"6-6\",\n \"columns\": [\n {\n \"children\": {\n \"action\": \"CreateElement\",\n \"params\": {\n \"component\": \"Webiny/GridColumn\",\n \"inputs\": {\n \"children\": [\n {\n \"action\": \"CreateElement\",\n \"params\": {\n \"component\": \"Webiny/Lexical\",\n \"inputs\": {\n \"content\": {\n \"tool\": \"textToLexical\",\n \"params\": { \"text\": \"...\" }\n }\n }\n }\n }\n ]\n }\n }\n }\n }\n ]\n }\n}\n\\`\\`\\`\n\nKey rules:\n- \"columns\" is an array, not an object with numeric keys\n- Each column has a \"children\" property containing a single CreateElement\n for Webiny/GridColumn\n- Webiny/GridColumn's \"children\" is an array of CreateElement actions for\n the actual content\n\nIMPORTANT: Only use components listed in the Component Catalog above. Do NOT invent component names. Any element with an unrecognized component name will be silently removed from the output.\n\nYou MUST return a parsable JSON object with a \"page\" key containing the array of elements. No extra text outside the JSON.`;\n}\n"],"names":["buildDomainPrompt","components","tools","availableImageTags","componentNames","c","tagListing","t","JSON"],"mappings":"AAAO,SAASA,kBACZC,UAAmC,EACnCC,KAAc,EACdC,kBAA4B;IAE5B,MAAMC,iBAAiBH,WAAW,GAAG,CAACI,CAAAA,IAAK,CAAC,CAAC,EAAEA,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;IAC/D,MAAMC,aACFH,mBAAmB,MAAM,GAAG,IACtBA,mBAAmB,GAAG,CAACI,CAAAA,IAAK,CAAC,EAAE,EAAEA,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,QAC7C;IACV,OAAO,CAAC;;;;;;;;;;;;;;;;;mDAiBuC,EAAED,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6ChE,EAAEE,KAAK,SAAS,CAACP,YAAY,MAAM,GAAG;;;;;;;;;;;;AAYtC,EAAEO,KAAK,SAAS,CAACN,OAAO,MAAM,GAAG;;;;;;qBAMZ,EAAEE,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0HAsEoF,CAAC;AAC3H"}
package/api/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import "@webiny/tasks/features/TaskController/augmentation.js";
1
+ import "@webiny/background-tasks/api/features/TaskController/augmentation.js";
2
2
  import "~/api/features/Providers/types.js";
3
3
  import "~/api/features/Projects/types.js";
4
4
  export interface IAiPowerUpsSettings {
package/api/types.js CHANGED
@@ -1,3 +1,3 @@
1
- import "@webiny/tasks/features/TaskController/augmentation.js";
1
+ import "@webiny/background-tasks/api/features/TaskController/augmentation.js";
2
2
  import "./features/Providers/types.js";
3
3
  import "./features/Projects/types.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webiny/ai-powerups",
3
- "version": "6.4.0-beta.3",
3
+ "version": "6.4.0-beta.4",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -18,21 +18,21 @@
18
18
  "author": "Webiny Ltd.",
19
19
  "license": "Webiny Enterprise",
20
20
  "dependencies": {
21
- "@webiny/admin-ui": "6.4.0-beta.3",
22
- "@webiny/api-core": "6.4.0-beta.3",
23
- "@webiny/api-file-manager": "6.4.0-beta.3",
24
- "@webiny/api-websockets": "6.4.0-beta.3",
25
- "@webiny/app": "6.4.0-beta.3",
26
- "@webiny/app-admin": "6.4.0-beta.3",
27
- "@webiny/app-website-builder": "6.4.0-beta.3",
28
- "@webiny/app-websockets": "6.4.0-beta.3",
29
- "@webiny/feature": "6.4.0-beta.3",
30
- "@webiny/icons": "6.4.0-beta.3",
31
- "@webiny/project": "6.4.0-beta.3",
32
- "@webiny/project-aws": "6.4.0-beta.3",
33
- "@webiny/tasks": "6.4.0-beta.3",
34
- "@webiny/utils": "6.4.0-beta.3",
35
- "ai": "6.0.185",
21
+ "@webiny/admin-ui": "6.4.0-beta.4",
22
+ "@webiny/api-core": "6.4.0-beta.4",
23
+ "@webiny/api-file-manager": "6.4.0-beta.4",
24
+ "@webiny/api-websockets": "6.4.0-beta.4",
25
+ "@webiny/app": "6.4.0-beta.4",
26
+ "@webiny/app-admin": "6.4.0-beta.4",
27
+ "@webiny/app-website-builder": "6.4.0-beta.4",
28
+ "@webiny/app-websockets": "6.4.0-beta.4",
29
+ "@webiny/background-tasks": "6.4.0-beta.4",
30
+ "@webiny/feature": "6.4.0-beta.4",
31
+ "@webiny/icons": "6.4.0-beta.4",
32
+ "@webiny/project": "6.4.0-beta.4",
33
+ "@webiny/project-aws": "6.4.0-beta.4",
34
+ "@webiny/utils": "6.4.0-beta.4",
35
+ "ai": "6.0.191",
36
36
  "front-matter": "4.0.2",
37
37
  "lodash": "4.18.1",
38
38
  "mobx": "6.15.3",
@@ -42,18 +42,19 @@
42
42
  "zod": "4.4.3"
43
43
  },
44
44
  "devDependencies": {
45
- "@webiny/build-tools": "6.4.0-beta.3",
46
- "@webiny/handler-aws": "6.4.0-beta.3",
47
- "@webiny/handler-graphql": "6.4.0-beta.3",
48
- "@webiny/project-utils": "6.4.0-beta.3",
49
- "@webiny/wcp": "6.4.0-beta.3",
45
+ "@webiny/build-tools": "6.4.0-beta.4",
46
+ "@webiny/handler-aws": "6.4.0-beta.4",
47
+ "@webiny/handler-graphql": "6.4.0-beta.4",
48
+ "@webiny/project-utils": "6.4.0-beta.4",
49
+ "@webiny/wcp": "6.4.0-beta.4",
50
50
  "rimraf": "6.1.3",
51
51
  "typescript": "6.0.3",
52
- "vitest": "4.1.6"
52
+ "vitest": "4.1.7"
53
53
  },
54
54
  "publishConfig": {
55
- "access": "public",
56
- "directory": "dist"
55
+ "access": "public"
57
56
  },
58
- "gitHead": "2e58681d4344024bfb60e6180338e2f154ec87f0"
57
+ "webiny": {
58
+ "publishFrom": "dist"
59
+ }
59
60
  }