@webiny/ai-powerups 6.4.0-beta.3 → 6.4.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/Extension.js +3 -0
- package/admin/Extension.js.map +1 -1
- package/admin/presentation/WbContentGeneration/GenerateContentDialog.js +24 -4
- package/admin/presentation/WbContentGeneration/GenerateContentDialog.js.map +1 -1
- package/admin/presentation/WbContentGeneration/GenerateContentPresenter.d.ts +3 -0
- package/admin/presentation/WbContentGeneration/GenerateContentPresenter.js +19 -4
- package/admin/presentation/WbContentGeneration/GenerateContentPresenter.js.map +1 -1
- package/admin/presentation/WbContentGeneration/abstractions.d.ts +2 -1
- package/admin/presentation/WbContentGeneration/abstractions.js.map +1 -1
- package/api/features/AiImageEnrichment/AiImageEnrichmentTask.js +1 -1
- package/api/features/AiImageEnrichment/AiImageEnrichmentTask.js.map +1 -1
- package/api/features/Providers/ProvidersHandler.js +1 -1
- package/api/features/Providers/ProvidersHandler.js.map +1 -1
- package/api/features/WbGeneratePageContent/ComponentFilter.d.ts +18 -0
- package/api/features/WbGeneratePageContent/ComponentFilter.js +69 -0
- package/api/features/WbGeneratePageContent/ComponentFilter.js.map +1 -0
- package/api/features/WbGeneratePageContent/LlmJsonResponse.d.ts +7 -0
- package/api/features/WbGeneratePageContent/LlmJsonResponse.js +35 -0
- package/api/features/WbGeneratePageContent/LlmJsonResponse.js.map +1 -0
- package/api/features/WbGeneratePageContent/WbGeneratePageContentUseCase.d.ts +3 -1
- package/api/features/WbGeneratePageContent/WbGeneratePageContentUseCase.js +23 -9
- package/api/features/WbGeneratePageContent/WbGeneratePageContentUseCase.js.map +1 -1
- package/api/features/WbGeneratePageContent/abstractions.d.ts +2 -0
- package/api/features/WbGeneratePageContent/abstractions.js.map +1 -1
- package/api/features/WbGeneratePageContent/buildPrompt.d.ts +3 -1
- package/api/features/WbGeneratePageContent/buildPrompt.js +24 -9
- package/api/features/WbGeneratePageContent/buildPrompt.js.map +1 -1
- package/api/types.d.ts +1 -1
- package/api/types.js +1 -1
- package/package.json +26 -25
package/admin/Extension.js
CHANGED
|
@@ -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
|
})
|
package/admin/Extension.js.map
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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'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
|
|
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":";;;;;;;;;;AAWO,MAAMA,8BAA8B;AAE3C,MAAMC,YACF;AAEJ,MAAMC,iBAAiBC,OAAO,MAAM,CAAC;IACjC,QAAQC,EAAE,MAAM,CAAC;QACb,MAAMA,EAAE,KAAK,CAACA,EAAE,MAAM;QACtB,aAAaA,EAAE,MAAM;IACzB;AACJ;AAMA,MAAMC;IAUF,YACYC,OAAiC,EACjCC,UAA0C,EAC1CC,UAAuC,EACvCC,EAAgB,EAChBC,WAAyC,EACzCC,UAAgC,EAChCC,gBAA6C,CACvD;aAPUN,OAAO,GAAPA;aACAC,UAAU,GAAVA;aACAC,UAAU,GAAVA;aACAC,EAAE,GAAFA;aACAC,WAAW,GAAXA;aACAC,UAAU,GAAVA;aACAC,gBAAgB,GAAhBA;aAhBZ,EAAE,GAAGZ;aACL,KAAK,GAAG;aACR,WAAW,GAAG;aACd,aAAa,GAAG;aAChB,SAAS,GAAG;aACZ,YAAY,GAAG;aAEC,WAAW,GAAG;YAAC;YAAsB;SAAmB;IAUrE;IAEH,MAAM,IAAI,EACNa,KAAK,EACLC,UAAU,EAC0C,EAEtD;QACE,IAAIA,WAAW,OAAO,CAAC,SAAS,IAC5B,OAAOA,WAAW,QAAQ,CAAC,OAAO;QAGtC,MAAMC,aAAa,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAACF,MAAM,MAAM;QAC1D,IAAIE,WAAW,MAAM,IACjB,OAAOD,WAAW,QAAQ,CAAC,KAAK,CAAC;YAC7B,SAAS,CAAC,gBAAgB,EAAED,MAAM,MAAM,EAAE;QAC9C;QAGJ,MAAMG,OAAOD,WAAW,KAAK;QAE7B,IAAI,CAACC,KAAK,IAAI,CAAC,UAAU,CAAC,WACtB,OAAOF,WAAW,QAAQ,CAAC,IAAI,CAAC;QAGpC,MAAMG,iBAAiB,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO;QACpD,MAAMC,YAAYD,eAAe,IAAI,KAAMA,eAAe,KAAK,CAAC,SAAS,IAAI,KAAM;QACnF,MAAME,WAAW,GAAGD,YAAYF,KAAK,GAAG,EAAE;QAE1C,MAAMI,mBAAmB,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO;QAEvD,IAAIA,iBAAiB,MAAM,IACvB,OAAON,WAAW,QAAQ,CAAC,KAAK,CAAC;YAC7B,SAAS;QACb;QAGJ,MAAMO,aAAaD,iBAAiB,KAAK;QAEzC,MAAME,gBAAgBD,WAAW,SAAS,CAAC,OAAO,CAAC,EAAE;QAErD,IAAI,CAACC,eACD,OAAOR,WAAW,QAAQ,CAAC,IAAI,CAAC;YAC5B,SAAS;QACb;QAGJ,IAAIS,OAAiB,EAAE;QACvB,IAAIC,cAAc;QAClB,IAAI;YACA,MAAMC,WAAW,MAAM,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC;gBACxC,OAAOH,cAAc,KAAK;gBAC1B,QAAQpB;gBACR,YAAY;oBACR,SAASoB,cAAc,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE;oBAC1C,QAAQ,IAAI,CAAC,UAAU,CAAC,OAAO,CAACA,cAAc,eAAe;gBACjE;gBACA,UAAU;oBACN;wBACI,MAAM;wBACN,SAAS;4BACL;gCACI,MAAM;gCACN,OAAO,IAAII,IAAIP;4BACnB;4BACA;gCACI,MAAM;gCACN,MAAMlB;4BACV;yBACH;oBACL;iBACH;YACL;YAEAsB,OAAOE,SAAS,MAAM,CAAC,IAAI;YAC3BD,cAAcC,SAAS,MAAM,CAAC,WAAW;QAC7C,EAAE,OAAOE,OAAO;YACZC,QAAQ,GAAG,CAAC,SAASD,MAAM,OAAO;YAClC,OAAOb,WAAW,QAAQ,CAAC,KAAK,CAAC;gBAC7B,SAAS,CAAC,sBAAsB,EAAEa,iBAAiBE,QAAQF,MAAM,OAAO,GAAGG,OAAOH,QAAQ;YAC9F;QACJ;QAEA,MAAMI,aAAa;eAAI,IAAIC,IAAI;mBAAIhB,KAAK,IAAI;mBAAKO;aAAK;SAAE;QAExD,MAAMU,eAAe,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAC/C,IAAIjB,KAAK,EAAE;YACX,MAAMe;YACNP;QACJ;QAEA,IAAIS,aAAa,MAAM,IACnB,OAAOnB,WAAW,QAAQ,CAAC,KAAK,CAAC;YAC7B,SAAS,CAAC,uBAAuB,EAAEmB,aAAa,KAAK,CAAC,OAAO,EAAE;QACnE;QAGJ,IAAI,IAAI,CAAC,gBAAgB,EAAE;YACvB,MAAMC,oBAAoB,MAAM,IAAI,CAAC,gBAAgB,CAAC,eAAe;YACrE,IAAIA,kBAAkB,IAAI,MAAMA,kBAAkB,KAAK,CAAC,MAAM,GAAG,GAC7D,MAAM,IAAI,CAAC,gBAAgB,CAAC,iBAAiB,CAACA,kBAAkB,KAAK,EAAE;gBACnE,QAAQ;gBACR,MAAM;oBACF,IAAIlB,KAAK,EAAE;oBACX,MAAMe;oBACNP;gBACJ;YACJ;QAER;QAEA,OAAOV,WAAW,QAAQ,CAAC,IAAI,CAAC;IACpC;AACJ;AAEO,MAAMqB,wBAAwBC,eAAe,oBAAoB,CAAC;IACrE,gBAAgB/B;IAChB,cAAc;QACVgC;QACAC;QACAC;QACAC;QACAC;QACAC;QACA;YAACC;YAAkB;gBAAE,UAAU;YAAK;SAAE;KACzC;AACL"}
|
|
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;
|
|
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,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
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
|
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
|
|
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\";\
|
|
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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"}
|
|
@@ -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":";
|
|
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,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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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:
|
|
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 =
|
|
96
|
+
type PageSchema = {
|
|
97
|
+
page: ElementSchema[];
|
|
98
|
+
};
|
|
86
99
|
\`\`\`
|
|
87
100
|
|
|
88
|
-
For slot inputs, use \`{ "action": "CreateElement", "params": { "component": "...", "inputs": { ... } } }\`. For root array
|
|
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
|
-
|
|
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:
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webiny/ai-powerups",
|
|
3
|
-
"version": "6.4.0-beta.
|
|
3
|
+
"version": "6.4.0-beta.5",
|
|
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.
|
|
22
|
-
"@webiny/api-core": "6.4.0-beta.
|
|
23
|
-
"@webiny/api-file-manager": "6.4.0-beta.
|
|
24
|
-
"@webiny/api-websockets": "6.4.0-beta.
|
|
25
|
-
"@webiny/app": "6.4.0-beta.
|
|
26
|
-
"@webiny/app-admin": "6.4.0-beta.
|
|
27
|
-
"@webiny/app-website-builder": "6.4.0-beta.
|
|
28
|
-
"@webiny/app-websockets": "6.4.0-beta.
|
|
29
|
-
"@webiny/
|
|
30
|
-
"@webiny/
|
|
31
|
-
"@webiny/
|
|
32
|
-
"@webiny/project
|
|
33
|
-
"@webiny/
|
|
34
|
-
"@webiny/utils": "6.4.0-beta.
|
|
35
|
-
"ai": "6.0.
|
|
21
|
+
"@webiny/admin-ui": "6.4.0-beta.5",
|
|
22
|
+
"@webiny/api-core": "6.4.0-beta.5",
|
|
23
|
+
"@webiny/api-file-manager": "6.4.0-beta.5",
|
|
24
|
+
"@webiny/api-websockets": "6.4.0-beta.5",
|
|
25
|
+
"@webiny/app": "6.4.0-beta.5",
|
|
26
|
+
"@webiny/app-admin": "6.4.0-beta.5",
|
|
27
|
+
"@webiny/app-website-builder": "6.4.0-beta.5",
|
|
28
|
+
"@webiny/app-websockets": "6.4.0-beta.5",
|
|
29
|
+
"@webiny/background-tasks": "6.4.0-beta.5",
|
|
30
|
+
"@webiny/feature": "6.4.0-beta.5",
|
|
31
|
+
"@webiny/icons": "6.4.0-beta.5",
|
|
32
|
+
"@webiny/project": "6.4.0-beta.5",
|
|
33
|
+
"@webiny/project-aws": "6.4.0-beta.5",
|
|
34
|
+
"@webiny/utils": "6.4.0-beta.5",
|
|
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.
|
|
46
|
-
"@webiny/handler-aws": "6.4.0-beta.
|
|
47
|
-
"@webiny/handler-graphql": "6.4.0-beta.
|
|
48
|
-
"@webiny/project-utils": "6.4.0-beta.
|
|
49
|
-
"@webiny/wcp": "6.4.0-beta.
|
|
45
|
+
"@webiny/build-tools": "6.4.0-beta.5",
|
|
46
|
+
"@webiny/handler-aws": "6.4.0-beta.5",
|
|
47
|
+
"@webiny/handler-graphql": "6.4.0-beta.5",
|
|
48
|
+
"@webiny/project-utils": "6.4.0-beta.5",
|
|
49
|
+
"@webiny/wcp": "6.4.0-beta.5",
|
|
50
50
|
"rimraf": "6.1.3",
|
|
51
51
|
"typescript": "6.0.3",
|
|
52
|
-
"vitest": "4.1.
|
|
52
|
+
"vitest": "4.1.7"
|
|
53
53
|
},
|
|
54
54
|
"publishConfig": {
|
|
55
|
-
"access": "public"
|
|
56
|
-
"directory": "dist"
|
|
55
|
+
"access": "public"
|
|
57
56
|
},
|
|
58
|
-
"
|
|
57
|
+
"webiny": {
|
|
58
|
+
"publishFrom": "dist"
|
|
59
|
+
}
|
|
59
60
|
}
|