@webiny/ai-powerups 6.5.0-beta.0 → 6.5.0-beta.10
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/AiPowerups.js +1 -1
- package/AiPowerups.js.map +1 -1
- package/admin/presentation/AiPowerUpsSettings/AiPowerUpsSettingsPage.d.ts +2 -2
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsDialog.d.ts +2 -2
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsDrawer.d.ts +2 -2
- package/admin/presentation/CmsCompareEntryRevisions/CmsCompareEntryRevisionsMenuItem.d.ts +2 -2
- package/admin/presentation/CmsContentGeneration/CmsGenerateContentButton.d.ts +2 -2
- package/admin/presentation/CmsContentGeneration/CmsGenerateContentDialog.d.ts +2 -2
- package/admin/presentation/CmsEntryWizard/CmsEntryWizard.d.ts +2 -2
- package/admin/presentation/CmsEntryWizard/CmsEntryWizardGate.d.ts +2 -2
- package/admin/presentation/CmsEntryWizard/CmsEntryWizardGate.js +4 -1
- package/admin/presentation/CmsEntryWizard/CmsEntryWizardGate.js.map +1 -1
- package/admin/presentation/WbContentGeneration/GenerateContentDialog.d.ts +2 -2
- package/admin/presentation/modelRoles.js +2 -2
- package/admin/presentation/modelRoles.js.map +1 -1
- package/api/features/AiImageEnrichment/AiImageEnrichmentTask.d.ts +1 -3
- package/api/features/AiImageEnrichment/AiImageEnrichmentTask.js +6 -10
- package/api/features/AiImageEnrichment/AiImageEnrichmentTask.js.map +1 -1
- package/api/features/AiImageEnrichment/capability.d.ts +2 -2
- package/api/features/AiImageEnrichment/capability.js +1 -1
- package/api/features/AiImageEnrichment/capability.js.map +1 -1
- package/api/features/Capabilities/AiCapabilityDisabledError.d.ts +16 -0
- package/api/features/Capabilities/AiCapabilityDisabledError.js +8 -0
- package/api/features/Capabilities/AiCapabilityDisabledError.js.map +1 -0
- package/api/features/Capabilities/ResolveAiCapabilityUseCase.d.ts +2 -1
- package/api/features/Capabilities/ResolveAiCapabilityUseCase.js +3 -1
- package/api/features/Capabilities/ResolveAiCapabilityUseCase.js.map +1 -1
- package/api/features/Capabilities/abstractions.d.ts +7 -1
- package/api/features/Capabilities/abstractions.js.map +1 -1
- package/api/features/Capabilities/index.d.ts +1 -0
- package/api/features/Capabilities/index.js +1 -0
- package/api/features/CmsCompareEntryRevisions/capability.d.ts +2 -2
- package/api/features/CmsCompareEntryRevisions/capability.js +1 -1
- package/api/features/CmsCompareEntryRevisions/capability.js.map +1 -1
- package/api/features/CmsGenerateEntryContent/capability.d.ts +2 -2
- package/api/features/CmsGenerateEntryContent/capability.js +1 -1
- package/api/features/CmsGenerateEntryContent/capability.js.map +1 -1
- package/api/features/ModelRoles/ModelRolesHandler.js +10 -4
- package/api/features/ModelRoles/ModelRolesHandler.js.map +1 -1
- package/api/features/Providers/ProvidersHandler.d.ts +1 -3
- package/api/features/Providers/ProvidersHandler.js +2 -15
- package/api/features/Providers/ProvidersHandler.js.map +1 -1
- package/api/features/WbGeneratePageContent/capability.d.ts +2 -2
- package/api/features/WbGeneratePageContent/capability.js +1 -1
- package/api/features/WbGeneratePageContent/capability.js.map +1 -1
- package/api/features/WbTranslatePage/WbTranslatePageDecorator.js +5 -1
- package/api/features/WbTranslatePage/WbTranslatePageDecorator.js.map +1 -1
- package/api/features/WbTranslatePage/capability.d.ts +2 -2
- package/api/features/WbTranslatePage/capability.js +1 -1
- package/api/features/WbTranslatePage/capability.js.map +1 -1
- package/package.json +32 -32
package/AiPowerups.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import react from "react";
|
|
2
2
|
import { Admin, Api } from "@webiny/project-aws";
|
|
3
3
|
import { Wcp } from "@webiny/project";
|
|
4
|
-
const AiPowerups = ()=>/*#__PURE__*/ react.createElement(Wcp.
|
|
4
|
+
const AiPowerups = ()=>/*#__PURE__*/ react.createElement(Wcp.CanUseAiPowerups, null, /*#__PURE__*/ react.createElement(Api.Extension, {
|
|
5
5
|
src: import.meta.dirname + "/api/Extension.js"
|
|
6
6
|
}), /*#__PURE__*/ react.createElement(Admin.Extension, {
|
|
7
7
|
src: import.meta.dirname + "/admin/Extension.js"
|
package/AiPowerups.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiPowerups.js","sources":["../src/AiPowerups.tsx"],"sourcesContent":["import React from \"react\";\nimport { Api, Admin } from \"@webiny/project-aws\";\nimport { Wcp } from \"@webiny/project\";\n\nexport const AiPowerups = () => {\n return (\n <Wcp.
|
|
1
|
+
{"version":3,"file":"AiPowerups.js","sources":["../src/AiPowerups.tsx"],"sourcesContent":["import React from \"react\";\nimport { Api, Admin } from \"@webiny/project-aws\";\nimport { Wcp } from \"@webiny/project\";\n\nexport const AiPowerups = () => {\n return (\n <Wcp.CanUseAiPowerups>\n {/* Api extensions */}\n <Api.Extension src={import.meta.dirname + \"/api/Extension.js\"} />\n\n {/* Admin extensions */}\n <Admin.Extension src={import.meta.dirname + \"/admin/Extension.js\"} />\n </Wcp.CanUseAiPowerups>\n );\n};\n"],"names":["AiPowerups","Wcp","Api","Admin"],"mappings":";;;AAIO,MAAMA,aAAa,IACf,WAAP,GACI,oBAACC,IAAI,gBAAgB,sBAEjB,oBAACC,IAAI,SAAS;QAAC,KAAK,YAAY,OAAO,GAAG;sBAG1C,oBAACC,MAAM,SAAS;QAAC,KAAK,YAAY,OAAO,GAAG"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
export declare const AiPowerUpsSettingsPage: (() => React.JSX.Element) & {
|
|
3
|
-
|
|
2
|
+
export declare const AiPowerUpsSettingsPage: (() => React.JSX.Element) & React.NamedExoticComponent<unknown> & {
|
|
3
|
+
readonly type: () => React.JSX.Element;
|
|
4
4
|
};
|
|
@@ -3,7 +3,7 @@ import type { CmsCompareEntryRevisionsPresenter } from "./abstractions.js";
|
|
|
3
3
|
interface CmsCompareEntryRevisionsDialogProps {
|
|
4
4
|
presenter: CmsCompareEntryRevisionsPresenter.Interface;
|
|
5
5
|
}
|
|
6
|
-
export declare const CmsCompareEntryRevisionsDialog: (({ presenter }: CmsCompareEntryRevisionsDialogProps) => React.JSX.Element) & {
|
|
7
|
-
|
|
6
|
+
export declare const CmsCompareEntryRevisionsDialog: (({ presenter }: CmsCompareEntryRevisionsDialogProps) => React.JSX.Element) & React.NamedExoticComponent<CmsCompareEntryRevisionsDialogProps> & {
|
|
7
|
+
readonly type: ({ presenter }: CmsCompareEntryRevisionsDialogProps) => React.JSX.Element;
|
|
8
8
|
};
|
|
9
9
|
export {};
|
|
@@ -3,7 +3,7 @@ import type { CmsCompareEntryRevisionsPresenter } from "./abstractions.js";
|
|
|
3
3
|
interface CmsCompareEntryRevisionsDrawerProps {
|
|
4
4
|
presenter: CmsCompareEntryRevisionsPresenter.Interface;
|
|
5
5
|
}
|
|
6
|
-
export declare const CmsCompareEntryRevisionsDrawer: (({ presenter }: CmsCompareEntryRevisionsDrawerProps) => React.JSX.Element) & {
|
|
7
|
-
|
|
6
|
+
export declare const CmsCompareEntryRevisionsDrawer: (({ presenter }: CmsCompareEntryRevisionsDrawerProps) => React.JSX.Element) & React.NamedExoticComponent<CmsCompareEntryRevisionsDrawerProps> & {
|
|
7
|
+
readonly type: ({ presenter }: CmsCompareEntryRevisionsDrawerProps) => React.JSX.Element;
|
|
8
8
|
};
|
|
9
9
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
export declare const CmsCompareEntryRevisionsMenuItem: (() => React.JSX.Element) & {
|
|
3
|
-
|
|
2
|
+
export declare const CmsCompareEntryRevisionsMenuItem: (() => React.JSX.Element) & React.NamedExoticComponent<unknown> & {
|
|
3
|
+
readonly type: () => React.JSX.Element;
|
|
4
4
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
export declare const CmsGenerateContentButton: (() => React.JSX.Element | null) & {
|
|
3
|
-
|
|
2
|
+
export declare const CmsGenerateContentButton: (() => React.JSX.Element | null) & React.NamedExoticComponent<unknown> & {
|
|
3
|
+
readonly type: () => React.JSX.Element | null;
|
|
4
4
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
export declare const CMS_GENERATE_CONTENT_DIALOG = "cms-generate-content";
|
|
3
|
-
export declare const CmsGenerateContentDialog: (() => React.JSX.Element) & {
|
|
4
|
-
|
|
3
|
+
export declare const CmsGenerateContentDialog: (() => React.JSX.Element) & React.NamedExoticComponent<unknown> & {
|
|
4
|
+
readonly type: () => React.JSX.Element;
|
|
5
5
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
export declare const CmsEntryWizard: (() => React.JSX.Element) & {
|
|
3
|
-
|
|
2
|
+
export declare const CmsEntryWizard: (() => React.JSX.Element) & React.NamedExoticComponent<unknown> & {
|
|
3
|
+
readonly type: () => React.JSX.Element;
|
|
4
4
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
export declare const CmsEntryWizardGate: (() => React.JSX.Element) & {
|
|
3
|
-
|
|
2
|
+
export declare const CmsEntryWizardGate: (() => React.JSX.Element | null) & React.NamedExoticComponent<unknown> & {
|
|
3
|
+
readonly type: () => React.JSX.Element | null;
|
|
4
4
|
};
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import react, { useEffect } from "react";
|
|
2
2
|
import { observer } from "mobx-react-lite";
|
|
3
|
+
import { useWcp } from "@webiny/app-admin";
|
|
3
4
|
import { useModel } from "@webiny/app-headless-cms/admin/components/ModelProvider/useModel.js";
|
|
4
5
|
import { useContentEntryFormPresenter } from "@webiny/app-headless-cms/exports/admin/cms/entry/editor.js";
|
|
5
6
|
import { CmsEntryWizard } from "./CmsEntryWizard.js";
|
|
6
7
|
const CmsEntryWizardGate = observer(()=>{
|
|
8
|
+
const wcp = useWcp();
|
|
7
9
|
const { model } = useModel();
|
|
8
10
|
const formPresenter = useContentEntryFormPresenter();
|
|
9
|
-
const showWizard = model.settings?.aiEntryWizard === true;
|
|
11
|
+
const showWizard = model.settings?.aiEntryWizard === true && wcp.canUseAiEntryGeneration();
|
|
10
12
|
useEffect(()=>{
|
|
11
13
|
if (!showWizard) formPresenter.newEntry();
|
|
12
14
|
}, [
|
|
13
15
|
showWizard
|
|
14
16
|
]);
|
|
17
|
+
if (!showWizard) return null;
|
|
15
18
|
return /*#__PURE__*/ react.createElement(CmsEntryWizard, null);
|
|
16
19
|
});
|
|
17
20
|
export { CmsEntryWizardGate };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin/presentation/CmsEntryWizard/CmsEntryWizardGate.js","sources":["../../../../src/admin/presentation/CmsEntryWizard/CmsEntryWizardGate.tsx"],"sourcesContent":["import React, { useEffect } from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { useModel } from \"@webiny/app-headless-cms/admin/components/ModelProvider/useModel.js\";\nimport { useContentEntryFormPresenter } from \"@webiny/app-headless-cms/exports/admin/cms/entry/editor.js\";\nimport { CmsEntryWizard } from \"./CmsEntryWizard.js\";\n\nexport const CmsEntryWizardGate = observer(() => {\n const { model } = useModel();\n const formPresenter = useContentEntryFormPresenter();\n\n const showWizard = model.settings?.aiEntryWizard === true;\n\n useEffect(() => {\n if (!showWizard) {\n formPresenter.newEntry();\n }\n }, [showWizard]);\n\n return <CmsEntryWizard />;\n});\n"],"names":["CmsEntryWizardGate","observer","model","useModel","formPresenter","useContentEntryFormPresenter","showWizard","useEffect","CmsEntryWizard"],"mappings":"
|
|
1
|
+
{"version":3,"file":"admin/presentation/CmsEntryWizard/CmsEntryWizardGate.js","sources":["../../../../src/admin/presentation/CmsEntryWizard/CmsEntryWizardGate.tsx"],"sourcesContent":["import React, { useEffect } from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { useWcp } from \"@webiny/app-admin\";\nimport { useModel } from \"@webiny/app-headless-cms/admin/components/ModelProvider/useModel.js\";\nimport { useContentEntryFormPresenter } from \"@webiny/app-headless-cms/exports/admin/cms/entry/editor.js\";\nimport { CmsEntryWizard } from \"./CmsEntryWizard.js\";\n\nexport const CmsEntryWizardGate = observer(() => {\n const wcp = useWcp();\n const { model } = useModel();\n const formPresenter = useContentEntryFormPresenter();\n\n /**\n * The AI wizard depends on the CMS content generation feature, which is only registered\n * when the license allows AI entry generation.\n */\n const showWizard = model.settings?.aiEntryWizard === true && wcp.canUseAiEntryGeneration();\n\n useEffect(() => {\n if (!showWizard) {\n formPresenter.newEntry();\n }\n }, [showWizard]);\n\n if (!showWizard) {\n return null;\n }\n\n return <CmsEntryWizard />;\n});\n"],"names":["CmsEntryWizardGate","observer","wcp","useWcp","model","useModel","formPresenter","useContentEntryFormPresenter","showWizard","useEffect","CmsEntryWizard"],"mappings":";;;;;;AAOO,MAAMA,qBAAqBC,SAAS;IACvC,MAAMC,MAAMC;IACZ,MAAM,EAAEC,KAAK,EAAE,GAAGC;IAClB,MAAMC,gBAAgBC;IAMtB,MAAMC,aAAaJ,MAAM,QAAQ,EAAE,kBAAkB,QAAQF,IAAI,uBAAuB;IAExFO,UAAU;QACN,IAAI,CAACD,YACDF,cAAc,QAAQ;IAE9B,GAAG;QAACE;KAAW;IAEf,IAAI,CAACA,YACD,OAAO;IAGX,OAAO,WAAP,GAAO,oBAACE,gBAAcA;AAC1B"}
|
|
@@ -16,6 +16,6 @@ export interface GeneratePageContentErrorMessage extends IncomingGenericData {
|
|
|
16
16
|
message: string;
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
-
export declare const GenerateContentDialog: (() => React.JSX.Element) & {
|
|
20
|
-
|
|
19
|
+
export declare const GenerateContentDialog: (() => React.JSX.Element) & React.NamedExoticComponent<unknown> & {
|
|
20
|
+
readonly type: () => React.JSX.Element;
|
|
21
21
|
};
|
|
@@ -6,11 +6,11 @@ const ROLE_TEXT = {
|
|
|
6
6
|
},
|
|
7
7
|
standard: {
|
|
8
8
|
label: "Standard",
|
|
9
|
-
description: "The workhorse. Anything that writes content a person will read, or has to follow a schema and call tools.
|
|
9
|
+
description: "The workhorse. Anything that writes content a person will read, or has to follow a schema and call tools. Fast falls back to this one when left empty."
|
|
10
10
|
},
|
|
11
11
|
vision: {
|
|
12
12
|
label: "Vision",
|
|
13
|
-
description: "Work that reads images. Pick a model that accepts image input.
|
|
13
|
+
description: "Work that reads images, such as image enrichment. Pick a model that accepts image input. There is no fallback: left empty, image features stop and say so."
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
16
|
const AI_MODEL_ROLE_DISPLAY = AI_MODEL_ROLE_IDS.map((id)=>({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin/presentation/modelRoles.js","sources":["../../../src/admin/presentation/modelRoles.ts"],"sourcesContent":["import { AI_MODEL_ROLE_IDS } from \"~/admin/domain/modelRoles.js\";\nimport type { AiModelRoleId } from \"~/admin/domain/modelRoles.js\";\n\n/**\n * The model roles, as the settings screen presents them. The ids live in\n * `~/admin/domain/modelRoles.js`; this owns the words.\n *\n * Typed as a full record, so adding a role id without a label is a compile error.\n */\nconst ROLE_TEXT: Record<AiModelRoleId, { label: string; description: string }> = {\n fast: {\n label: \"Fast\",\n description:\n \"Cheap, low-latency work where a smaller model is good enough: summarising, classifying, comparing.\"\n },\n standard: {\n label: \"Standard\",\n description:\n \"The workhorse. Anything that writes content a person will read, or has to follow a schema and call tools.
|
|
1
|
+
{"version":3,"file":"admin/presentation/modelRoles.js","sources":["../../../src/admin/presentation/modelRoles.ts"],"sourcesContent":["import { AI_MODEL_ROLE_IDS } from \"~/admin/domain/modelRoles.js\";\nimport type { AiModelRoleId } from \"~/admin/domain/modelRoles.js\";\n\n/**\n * The model roles, as the settings screen presents them. The ids live in\n * `~/admin/domain/modelRoles.js`; this owns the words.\n *\n * Typed as a full record, so adding a role id without a label is a compile error.\n */\nconst ROLE_TEXT: Record<AiModelRoleId, { label: string; description: string }> = {\n fast: {\n label: \"Fast\",\n description:\n \"Cheap, low-latency work where a smaller model is good enough: summarising, classifying, comparing.\"\n },\n standard: {\n label: \"Standard\",\n description:\n \"The workhorse. Anything that writes content a person will read, or has to follow a schema and call tools. Fast falls back to this one when left empty.\"\n },\n vision: {\n label: \"Vision\",\n description:\n \"Work that reads images, such as image enrichment. Pick a model that accepts image input. There is no fallback: left empty, image features stop and say so.\"\n }\n};\n\nexport { AI_MODEL_ROLE_IDS };\nexport type { AiModelRoleId };\n\nexport const AI_MODEL_ROLE_DISPLAY = AI_MODEL_ROLE_IDS.map(id => ({ id, ...ROLE_TEXT[id] }));\n\nexport const roleLabel = (id: string): string =>\n id in ROLE_TEXT ? ROLE_TEXT[id as AiModelRoleId].label : id;\n"],"names":["ROLE_TEXT","AI_MODEL_ROLE_DISPLAY","AI_MODEL_ROLE_IDS","id","roleLabel"],"mappings":";AASA,MAAMA,YAA2E;IAC7E,MAAM;QACF,OAAO;QACP,aACI;IACR;IACA,UAAU;QACN,OAAO;QACP,aACI;IACR;IACA,QAAQ;QACJ,OAAO;QACP,aACI;IACR;AACJ;AAKO,MAAMC,wBAAwBC,kBAAkB,GAAG,CAACC,CAAAA,KAAO;QAAEA;QAAI,GAAGH,SAAS,CAACG,GAAG;IAAC;AAElF,MAAMC,YAAY,CAACD,KACtBA,MAAMH,YAAYA,SAAS,CAACG,GAAoB,CAAC,KAAK,GAAGA"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { TaskDefinition } from "@webiny/api-core/features/task/TaskDefinition/index.js";
|
|
2
2
|
import { Ai } from "@webiny/api-core/features/ai/index.js";
|
|
3
|
-
import { Logger } from "@webiny/api-core/features/logger/index.js";
|
|
4
3
|
import { GetFileUseCase } from "@webiny/api-file-manager/features/file/GetFile/index.js";
|
|
5
4
|
import { UpdateFileUseCase } from "@webiny/api-file-manager/features/file/UpdateFile/index.js";
|
|
6
5
|
import { GetSettingsUseCase as FmGetSettingsUseCase } from "@webiny/api-file-manager/features/settings/GetSettings/abstractions.js";
|
|
@@ -17,7 +16,6 @@ declare class AiImageEnrichmentTaskImpl implements TaskDefinition.Interface<IAiI
|
|
|
17
16
|
private updateFile;
|
|
18
17
|
private ai;
|
|
19
18
|
private resolveCapability;
|
|
20
|
-
private logger;
|
|
21
19
|
private identityContext;
|
|
22
20
|
private sendToIdentity;
|
|
23
21
|
id: string;
|
|
@@ -27,7 +25,7 @@ declare class AiImageEnrichmentTaskImpl implements TaskDefinition.Interface<IAiI
|
|
|
27
25
|
isPrivate: boolean;
|
|
28
26
|
databaseLogs: boolean;
|
|
29
27
|
readonly selfCleanup: ("onAbort" | "onSuccess")[];
|
|
30
|
-
constructor(getFile: GetFileUseCase.Interface, fmSettings: FmGetSettingsUseCase.Interface, updateFile: UpdateFileUseCase.Interface, ai: Ai.Interface, resolveCapability: ResolveAiCapabilityUseCase.Interface,
|
|
28
|
+
constructor(getFile: GetFileUseCase.Interface, fmSettings: FmGetSettingsUseCase.Interface, updateFile: UpdateFileUseCase.Interface, ai: Ai.Interface, resolveCapability: ResolveAiCapabilityUseCase.Interface, identityContext: IdentityContext.Interface, sendToIdentity: WebsocketsSendToIdentityUseCase.Interface);
|
|
31
29
|
run({ input, controller }: TaskDefinition.RunParams<IAiImageEnrichmentTaskInput>): Promise<TaskDefinition.Result<IAiImageEnrichmentTaskInput>>;
|
|
32
30
|
}
|
|
33
31
|
export declare const AiImageEnrichmentTask: typeof AiImageEnrichmentTaskImpl & {
|
|
@@ -2,13 +2,12 @@ import { Output } from "ai";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { TaskDefinition } from "@webiny/api-core/features/task/TaskDefinition/index.js";
|
|
4
4
|
import { Ai } from "@webiny/api-core/features/ai/index.js";
|
|
5
|
-
import { Logger } from "@webiny/api-core/features/logger/index.js";
|
|
6
5
|
import { GetFileUseCase } from "@webiny/api-file-manager/features/file/GetFile/index.js";
|
|
7
6
|
import { UpdateFileUseCase } from "@webiny/api-file-manager/features/file/UpdateFile/index.js";
|
|
8
7
|
import { GetSettingsUseCase } from "@webiny/api-file-manager/features/settings/GetSettings/abstractions.js";
|
|
9
8
|
import { WebsocketsSendToIdentityUseCase } from "@webiny/api-websockets/features/SendToIdentity/abstractions.js";
|
|
10
9
|
import { IdentityContext } from "@webiny/api-core/exports/api/security.js";
|
|
11
|
-
import { ResolveAiCapabilityUseCase, withAdditionalInstructions } from "../Capabilities/index.js";
|
|
10
|
+
import { AiCapabilityDisabledError, ResolveAiCapabilityUseCase, withAdditionalInstructions } from "../Capabilities/index.js";
|
|
12
11
|
import { FM_IMAGE_ENRICHMENT_CAPABILITY } from "./capability.js";
|
|
13
12
|
const AI_IMAGE_ENRICHMENT_TASK_ID = "fmAiImageEnrichment";
|
|
14
13
|
const aiOutputSchema = Output.object({
|
|
@@ -18,13 +17,12 @@ const aiOutputSchema = Output.object({
|
|
|
18
17
|
})
|
|
19
18
|
});
|
|
20
19
|
class AiImageEnrichmentTaskImpl {
|
|
21
|
-
constructor(getFile, fmSettings, updateFile, ai, resolveCapability,
|
|
20
|
+
constructor(getFile, fmSettings, updateFile, ai, resolveCapability, identityContext, sendToIdentity){
|
|
22
21
|
this.getFile = getFile;
|
|
23
22
|
this.fmSettings = fmSettings;
|
|
24
23
|
this.updateFile = updateFile;
|
|
25
24
|
this.ai = ai;
|
|
26
25
|
this.resolveCapability = resolveCapability;
|
|
27
|
-
this.logger = logger;
|
|
28
26
|
this.identityContext = identityContext;
|
|
29
27
|
this.sendToIdentity = sendToIdentity;
|
|
30
28
|
this.id = AI_IMAGE_ENRICHMENT_TASK_ID;
|
|
@@ -51,11 +49,10 @@ class AiImageEnrichmentTaskImpl {
|
|
|
51
49
|
const imageUrl = `${srcPrefix}${file.key}`;
|
|
52
50
|
const resolved = await this.resolveCapability.execute(FM_IMAGE_ENRICHMENT_CAPABILITY);
|
|
53
51
|
if (resolved.isFail()) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
return controller.response.done(resolved.error.message);
|
|
52
|
+
if (resolved.error instanceof AiCapabilityDisabledError) return controller.response.done(resolved.error.message);
|
|
53
|
+
return controller.response.error({
|
|
54
|
+
message: resolved.error.message
|
|
55
|
+
});
|
|
59
56
|
}
|
|
60
57
|
const capability = resolved.value;
|
|
61
58
|
let tags = [];
|
|
@@ -125,7 +122,6 @@ const AiImageEnrichmentTask = TaskDefinition.createImplementation({
|
|
|
125
122
|
UpdateFileUseCase,
|
|
126
123
|
Ai,
|
|
127
124
|
ResolveAiCapabilityUseCase,
|
|
128
|
-
Logger,
|
|
129
125
|
IdentityContext,
|
|
130
126
|
WebsocketsSendToIdentityUseCase
|
|
131
127
|
]
|
|
@@ -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 { Logger } from \"@webiny/api-core/features/logger/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 { WebsocketsSendToIdentityUseCase } from \"@webiny/api-websockets/features/SendToIdentity/abstractions.js\";\nimport { IdentityContext } from \"@webiny/api-core/exports/api/security.js\";\nimport {\n ResolveAiCapabilityUseCase,\n withAdditionalInstructions\n} from \"~/api/features/Capabilities/index.js\";\nimport { FM_IMAGE_ENRICHMENT_CAPABILITY } from \"./capability.js\";\n\nexport const AI_IMAGE_ENRICHMENT_TASK_ID = \"fmAiImageEnrichment\";\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 resolveCapability: ResolveAiCapabilityUseCase.Interface,\n private logger: Logger.Interface,\n private identityContext: IdentityContext.Interface,\n private sendToIdentity: WebsocketsSendToIdentityUseCase.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 resolved = await this.resolveCapability.execute(FM_IMAGE_ENRICHMENT_CAPABILITY);\n\n if (resolved.isFail()) {\n /*\n * A skip, not a failure. This feature is not licence-gated on this branch, so every\n * project that uploads an image without AI configured would otherwise get a failed task\n * on every upload. Today's behaviour for an unconfigured provider is the same soft\n * done; the log is new, because a skip and a misconfiguration used to look identical.\n */\n this.logger.warn(\n { fileId: input.fileId, reason: resolved.error.message },\n \"Skipping AI image enrichment.\"\n );\n return controller.response.done(resolved.error.message);\n }\n\n const capability = resolved.value;\n\n let tags: string[] = [];\n let description = \"\";\n try {\n const aiResult = await this.ai.generateText({\n model: capability.model,\n output: aiOutputSchema,\n connection: capability.connection,\n messages: [\n {\n role: \"user\",\n content: [\n {\n type: \"image\",\n image: new URL(imageUrl)\n },\n {\n type: \"text\",\n text: withAdditionalInstructions(capability)\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 const identity = this.identityContext.getIdentity();\n await this.sendToIdentity.execute(\n { id: identity.id },\n {\n action: \"fm.file.enrichment\",\n data: {\n id: file.id,\n tags: mergedTags,\n description\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 ResolveAiCapabilityUseCase,\n Logger,\n IdentityContext,\n WebsocketsSendToIdentityUseCase\n ]\n});\n"],"names":["AI_IMAGE_ENRICHMENT_TASK_ID","aiOutputSchema","Output","z","AiImageEnrichmentTaskImpl","getFile","fmSettings","updateFile","ai","resolveCapability","logger","identityContext","sendToIdentity","input","controller","fileResult","file","settingsResult","srcPrefix","imageUrl","resolved","FM_IMAGE_ENRICHMENT_CAPABILITY","capability","tags","description","aiResult","URL","withAdditionalInstructions","error","console","Error","String","mergedTags","Set","updateResult","identity","AiImageEnrichmentTask","TaskDefinition","GetFileUseCase","FmGetSettingsUseCase","UpdateFileUseCase","Ai","ResolveAiCapabilityUseCase","Logger","IdentityContext","WebsocketsSendToIdentityUseCase"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api/features/AiImageEnrichment/AiImageEnrichmentTask.js","sources":["../../../../src/api/features/AiImageEnrichment/AiImageEnrichmentTask.ts"],"sourcesContent":["import { Output } from \"ai\";\nimport { z } from \"zod\";\nimport { TaskDefinition } from \"@webiny/api-core/features/task/TaskDefinition/index.js\";\nimport { Ai } from \"@webiny/api-core/features/ai/index.js\";\nimport { 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 { WebsocketsSendToIdentityUseCase } from \"@webiny/api-websockets/features/SendToIdentity/abstractions.js\";\nimport { IdentityContext } from \"@webiny/api-core/exports/api/security.js\";\nimport { AiCapabilityDisabledError } from \"~/api/features/Capabilities/index.js\";\nimport {\n ResolveAiCapabilityUseCase,\n withAdditionalInstructions\n} from \"~/api/features/Capabilities/index.js\";\nimport { FM_IMAGE_ENRICHMENT_CAPABILITY } from \"./capability.js\";\n\nexport const AI_IMAGE_ENRICHMENT_TASK_ID = \"fmAiImageEnrichment\";\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 resolveCapability: ResolveAiCapabilityUseCase.Interface,\n private identityContext: IdentityContext.Interface,\n private sendToIdentity: WebsocketsSendToIdentityUseCase.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 resolved = await this.resolveCapability.execute(FM_IMAGE_ENRICHMENT_CAPABILITY);\n\n if (resolved.isFail()) {\n /*\n * Switched off is a setting someone chose, so the task finishes quietly: reporting it as\n * an error would log one on every upload. Every other failure (no Vision model, a\n * deleted connection, a missing key) is a misconfiguration someone needs to hear about,\n * so it fails the task. That is only safe because enrichment is licence-gated in\n * `api/Extension.ts`: a project without it never registers this task at all.\n */\n if (resolved.error instanceof AiCapabilityDisabledError) {\n return controller.response.done(resolved.error.message);\n }\n return controller.response.error({ message: resolved.error.message });\n }\n\n const capability = resolved.value;\n\n let tags: string[] = [];\n let description = \"\";\n try {\n const aiResult = await this.ai.generateText({\n model: capability.model,\n output: aiOutputSchema,\n connection: capability.connection,\n messages: [\n {\n role: \"user\",\n content: [\n {\n type: \"image\",\n image: new URL(imageUrl)\n },\n {\n type: \"text\",\n text: withAdditionalInstructions(capability)\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 const identity = this.identityContext.getIdentity();\n await this.sendToIdentity.execute(\n { id: identity.id },\n {\n action: \"fm.file.enrichment\",\n data: {\n id: file.id,\n tags: mergedTags,\n description\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 ResolveAiCapabilityUseCase,\n IdentityContext,\n WebsocketsSendToIdentityUseCase\n ]\n});\n"],"names":["AI_IMAGE_ENRICHMENT_TASK_ID","aiOutputSchema","Output","z","AiImageEnrichmentTaskImpl","getFile","fmSettings","updateFile","ai","resolveCapability","identityContext","sendToIdentity","input","controller","fileResult","file","settingsResult","srcPrefix","imageUrl","resolved","FM_IMAGE_ENRICHMENT_CAPABILITY","AiCapabilityDisabledError","capability","tags","description","aiResult","URL","withAdditionalInstructions","error","console","Error","String","mergedTags","Set","updateResult","identity","AiImageEnrichmentTask","TaskDefinition","GetFileUseCase","FmGetSettingsUseCase","UpdateFileUseCase","Ai","ResolveAiCapabilityUseCase","IdentityContext","WebsocketsSendToIdentityUseCase"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AiCapability } from "../../../api/features/Capabilities/index.js";
|
|
2
|
-
export declare const FM_IMAGE_ENRICHMENT_CAPABILITY = "
|
|
2
|
+
export declare const FM_IMAGE_ENRICHMENT_CAPABILITY = "fmImageEnrichment";
|
|
3
3
|
declare class FmImageEnrichmentCapabilityImpl implements AiCapability.Interface {
|
|
4
|
-
readonly id = "
|
|
4
|
+
readonly id = "fmImageEnrichment";
|
|
5
5
|
readonly label = "Image enrichment";
|
|
6
6
|
readonly description = "Reads an uploaded image and writes its alt text, title and tags. Needs a model that accepts image input.";
|
|
7
7
|
readonly defaultRole: "vision";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AiCapability } from "../Capabilities/index.js";
|
|
2
|
-
const FM_IMAGE_ENRICHMENT_CAPABILITY = "
|
|
2
|
+
const FM_IMAGE_ENRICHMENT_CAPABILITY = "fmImageEnrichment";
|
|
3
3
|
const guidance = "Analyze this image and return up to 5 lowercase descriptive tags and one short sentence describing the image.";
|
|
4
4
|
class FmImageEnrichmentCapabilityImpl {
|
|
5
5
|
constructor(){
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api/features/AiImageEnrichment/capability.js","sources":["../../../../src/api/features/AiImageEnrichment/capability.ts"],"sourcesContent":["import { AiCapability } from \"~/api/features/Capabilities/index.js\";\n\nexport const FM_IMAGE_ENRICHMENT_CAPABILITY = \"
|
|
1
|
+
{"version":3,"file":"api/features/AiImageEnrichment/capability.js","sources":["../../../../src/api/features/AiImageEnrichment/capability.ts"],"sourcesContent":["import { AiCapability } from \"~/api/features/Capabilities/index.js\";\n\nexport const FM_IMAGE_ENRICHMENT_CAPABILITY = \"fmImageEnrichment\";\n\n/**\n * The prompt lives here rather than in the task now, because it is the capability's, and the\n * resolver hands it back with the project's additional instructions already appended.\n *\n * This is the one AI prompt whose structure does not come from the prose: `Output.object(...)` in\n * the task enforces the tags/description shape. House tag vocabularies go in additional\n * instructions (\"use our taxonomy\", \"no more than three tags\", \"British spelling\").\n */\nconst guidance =\n \"Analyze this image and return up to 5 lowercase descriptive tags and one short sentence describing the image.\";\n\nclass FmImageEnrichmentCapabilityImpl implements AiCapability.Interface {\n readonly id = FM_IMAGE_ENRICHMENT_CAPABILITY;\n readonly label = \"Image enrichment\";\n readonly description =\n \"Reads an uploaded image and writes its alt text, title and tags. Needs a model that accepts image input.\";\n readonly defaultRole = \"vision\" as const;\n readonly guidance = guidance;\n}\n\nexport const FmImageEnrichmentCapability = AiCapability.createImplementation({\n implementation: FmImageEnrichmentCapabilityImpl,\n dependencies: []\n});\n"],"names":["FM_IMAGE_ENRICHMENT_CAPABILITY","guidance","FmImageEnrichmentCapabilityImpl","FmImageEnrichmentCapability","AiCapability"],"mappings":";AAEO,MAAMA,iCAAiC;AAU9C,MAAMC,WACF;AAEJ,MAAMC;;aACO,EAAE,GAAGF;aACL,KAAK,GAAG;aACR,WAAW,GAChB;aACK,WAAW,GAAG;aACd,QAAQ,GAAGC;;AACxB;AAEO,MAAME,8BAA8BC,aAAa,oBAAoB,CAAC;IACzE,gBAAgBF;IAChB,cAAc,EAAE;AACpB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A capability someone switched off in settings.
|
|
3
|
+
*
|
|
4
|
+
* Typed, rather than a plain `Error` like the resolver's other failures, because it is the one
|
|
5
|
+
* failure that is not a problem. Every other reason a capability cannot resolve (no model on the
|
|
6
|
+
* role, a deleted connection, a missing key) is a misconfiguration someone should hear about. This
|
|
7
|
+
* one is a decision someone already made, and a caller that runs automatically, on an upload or a
|
|
8
|
+
* page save, should skip quietly instead of reporting it as a failure every time.
|
|
9
|
+
*
|
|
10
|
+
* A caller a user triggered by hand can still show the message: it names the switch to flip.
|
|
11
|
+
*/
|
|
12
|
+
export declare class AiCapabilityDisabledError extends Error {
|
|
13
|
+
readonly capabilityId: string;
|
|
14
|
+
readonly code: "AI_CAPABILITY_DISABLED";
|
|
15
|
+
constructor(capabilityId: string, label: string, settingsPath: string);
|
|
16
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
class AiCapabilityDisabledError extends Error {
|
|
2
|
+
constructor(capabilityId, label, settingsPath){
|
|
3
|
+
super(`"${label}" is switched off. Turn it back on under ${settingsPath} → Capabilities.`), this.capabilityId = capabilityId, this.code = "AI_CAPABILITY_DISABLED";
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
export { AiCapabilityDisabledError };
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=AiCapabilityDisabledError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api/features/Capabilities/AiCapabilityDisabledError.js","sources":["../../../../src/api/features/Capabilities/AiCapabilityDisabledError.ts"],"sourcesContent":["/**\n * A capability someone switched off in settings.\n *\n * Typed, rather than a plain `Error` like the resolver's other failures, because it is the one\n * failure that is not a problem. Every other reason a capability cannot resolve (no model on the\n * role, a deleted connection, a missing key) is a misconfiguration someone should hear about. This\n * one is a decision someone already made, and a caller that runs automatically, on an upload or a\n * page save, should skip quietly instead of reporting it as a failure every time.\n *\n * A caller a user triggered by hand can still show the message: it names the switch to flip.\n */\nexport class AiCapabilityDisabledError extends Error {\n readonly code = \"AI_CAPABILITY_DISABLED\" as const;\n\n constructor(\n readonly capabilityId: string,\n label: string,\n settingsPath: string\n ) {\n super(`\"${label}\" is switched off. Turn it back on under ${settingsPath} → Capabilities.`);\n }\n}\n"],"names":["AiCapabilityDisabledError","Error","capabilityId","label","settingsPath"],"mappings":"AAWO,MAAMA,kCAAkCC;IAG3C,YACaC,YAAoB,EAC7BC,KAAa,EACbC,YAAoB,CACtB;QACE,KAAK,CAAC,CAAC,CAAC,EAAED,MAAM,yCAAyC,EAAEC,aAAa,gBAAgB,CAAC,QAJhFF,YAAY,GAAZA,cAAAA,IAAAA,CAHJ,IAAI,GAAG;IAQhB;AACJ"}
|
|
@@ -11,7 +11,8 @@ import type { IResolvedAiCapability } from "./abstractions.js";
|
|
|
11
11
|
* themselves, so "which model runs this feature" had six answers that only happened to agree.
|
|
12
12
|
*
|
|
13
13
|
* Precedence: a capability's pinned connection+model, else its chosen role, else its declared
|
|
14
|
-
* default role, else the `standard` role.
|
|
14
|
+
* default role, else the `standard` role. The last step does not apply to `vision`, which fails
|
|
15
|
+
* rather than sending images to a model that may not accept them.
|
|
15
16
|
*
|
|
16
17
|
* There is deliberately no environment-variable fallback. A project whose settings say one thing
|
|
17
18
|
* and whose requests use another is the exact confusion this replaces.
|
|
@@ -4,6 +4,7 @@ import { GetSettingsUseCase } from "../GetSettings/index.js";
|
|
|
4
4
|
import { sdkNameFromModel } from "../shared/modelId.js";
|
|
5
5
|
import { AiCapability, ResolveAiCapabilityUseCase } from "./abstractions.js";
|
|
6
6
|
import { isCapabilityEnabled } from "./types.js";
|
|
7
|
+
import { AiCapabilityDisabledError } from "./AiCapabilityDisabledError.js";
|
|
7
8
|
const SETTINGS_PATH = "Settings → AI Power-Ups";
|
|
8
9
|
class ResolveAiCapabilityUseCaseImpl {
|
|
9
10
|
constructor(capabilities, getSettings, encryption){
|
|
@@ -21,7 +22,7 @@ class ResolveAiCapabilityUseCaseImpl {
|
|
|
21
22
|
if (settingsResult.isFail()) return Result.fail(new Error("Failed to load AI Power-Ups settings."));
|
|
22
23
|
const settings = settingsResult.value;
|
|
23
24
|
const entry = settings.capabilities?.items?.[capabilityId];
|
|
24
|
-
if (!isCapabilityEnabled(entry)) return Result.fail(new
|
|
25
|
+
if (!isCapabilityEnabled(entry)) return Result.fail(new AiCapabilityDisabledError(capabilityId, capability.label, SETTINGS_PATH));
|
|
25
26
|
const override = entry?.overrides ?? {};
|
|
26
27
|
const selection = this.selectModel(settings, capability.defaultRole, override);
|
|
27
28
|
if (selection.isFail()) return Result.fail(selection.error);
|
|
@@ -60,6 +61,7 @@ class ResolveAiCapabilityUseCaseImpl {
|
|
|
60
61
|
roleId: requestedRole,
|
|
61
62
|
fellBackToStandard: false
|
|
62
63
|
});
|
|
64
|
+
if ("vision" === requestedRole) return Result.fail(new Error(`No model is configured for the "vision" role, and image work does not fall back to Standard. Pick one under ${SETTINGS_PATH} → Model roles.`));
|
|
63
65
|
const standard = roles?.standard;
|
|
64
66
|
if (standard?.connectionId && standard.model) return Result.ok({
|
|
65
67
|
connectionId: standard.connectionId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api/features/Capabilities/ResolveAiCapabilityUseCase.js","sources":["../../../../src/api/features/Capabilities/ResolveAiCapabilityUseCase.ts"],"sourcesContent":["import { Result } from \"@webiny/feature/api\";\nimport { Encryption } from \"@webiny/api-core/features/encryption/index.js\";\nimport { GetSettingsUseCase } from \"~/api/features/GetSettings/index.js\";\nimport { sdkNameFromModel } from \"~/api/features/shared/modelId.js\";\nimport { AiCapability, ResolveAiCapabilityUseCase } from \"./abstractions.js\";\nimport type { IResolvedAiCapability } from \"./abstractions.js\";\nimport { isCapabilityEnabled } from \"./types.js\";\nimport type { AiCapabilityOverride } from \"./types.js\";\nimport type { AiModelRoleId } from \"~/api/features/ModelRoles/index.js\";\nimport type { IAiPowerUpsSettings } from \"~/api/types.js\";\n\nconst SETTINGS_PATH = \"Settings → AI Power-Ups\";\n\n/**\n * Turns a capability id into the model, credential and prompt text a use case needs.\n *\n * This is the single place that knows the precedence rules. Before it existed, six use cases each\n * read `providers.presets[0]`, re-derived the SDK name from the model string and decrypted the key\n * themselves, so \"which model runs this feature\" had six answers that only happened to agree.\n *\n * Precedence: a capability's pinned connection+model, else its chosen role, else its declared\n * default role, else the `standard` role.\n *\n * There is deliberately no environment-variable fallback. A project whose settings say one thing\n * and whose requests use another is the exact confusion this replaces.\n */\nclass ResolveAiCapabilityUseCaseImpl implements ResolveAiCapabilityUseCase.Interface {\n private capabilityLookup: Map<string, AiCapability.Interface>;\n\n constructor(\n capabilities: AiCapability.Interface[],\n private getSettings: GetSettingsUseCase.Interface,\n private encryption: Encryption.Interface\n ) {\n this.capabilityLookup = new Map(capabilities.map(c => [c.id, c]));\n }\n\n async execute(capabilityId: string): Promise<Result<IResolvedAiCapability, Error>> {\n const capability = this.capabilityLookup.get(capabilityId);\n\n if (!capability) {\n // Nothing a project can do about this one: the caller passed an unregistered id.\n return Result.fail(\n new Error(\n `Unknown AI capability \"${capabilityId}\". Register it with the AiCapability abstraction.`\n )\n );\n }\n\n const settingsResult = await this.getSettings.execute();\n if (settingsResult.isFail()) {\n return Result.fail(new Error(\"Failed to load AI Power-Ups settings.\"));\n }\n\n const settings = settingsResult.value;\n const entry = settings.capabilities?.items?.[capabilityId];\n\n /*\n * Checked here rather than at every call site, so a capability switched off in settings is\n * off for the GraphQL mutation as much as for the button that usually triggers it. The\n * admin hides the affordance too, but that is a courtesy and this is the actual gate.\n */\n if (!isCapabilityEnabled(entry)) {\n return Result.fail(\n new Error(\n `\"${capability.label}\" is switched off. Turn it back on under ${SETTINGS_PATH} → Capabilities.`\n )\n );\n }\n\n const override: AiCapabilityOverride = entry?.overrides ?? {};\n\n const selection = this.selectModel(settings, capability.defaultRole, override);\n if (selection.isFail()) {\n return Result.fail(selection.error);\n }\n\n const { connectionId, model, roleId, fellBackToStandard } = selection.value;\n\n const connection = settings.connections?.presets?.find(c => c.id === connectionId);\n\n if (!connection) {\n return Result.fail(\n new Error(\n `\"${capability.label}\" points at a connection that no longer exists. Pick one under ${SETTINGS_PATH} → Model roles.`\n )\n );\n }\n\n if (!connection.apiKeyEncrypted) {\n return Result.fail(\n new Error(\n `The connection \"${connection.name}\" has no API key. Add one under ${SETTINGS_PATH} → Connections.`\n )\n );\n }\n\n /*\n * A model is stored fully qualified (\"anthropic/claude-sonnet-4-5\") and a connection stores\n * its vendor separately, so the two can disagree if a connection is re-pointed at another\n * vendor after a role was filled. Catching it here beats a provider-side auth error.\n */\n const modelSdkName = sdkNameFromModel(model);\n if (modelSdkName && connection.sdkName && modelSdkName !== connection.sdkName) {\n return Result.fail(\n new Error(\n `The model \"${model}\" cannot run on the \"${connection.name}\" connection, which is a ${connection.sdkName} credential. Fix the pairing under ${SETTINGS_PATH} → Model roles.`\n )\n );\n }\n\n return Result.ok({\n capabilityId,\n model,\n connection: {\n sdkName: modelSdkName || connection.sdkName,\n apiKey: await this.encryption.decrypt(connection.apiKeyEncrypted)\n },\n roleId,\n fellBackToStandard,\n // Always the capability's own. Projects adjust prompts by appending, never by\n // replacing, because for most capabilities the prompt is the output contract.\n guidance: capability.guidance ?? \"\",\n additionalInstructions: override.additionalInstructions?.trim() ?? \"\"\n });\n }\n\n private selectModel(\n settings: IAiPowerUpsSettings,\n defaultRole: AiModelRoleId,\n override: AiCapabilityOverride\n ): Result<\n {\n connectionId: string;\n model: string;\n roleId: AiModelRoleId | null;\n fellBackToStandard: boolean;\n },\n Error\n > {\n // A pinned connection+model wins outright. Both halves are required; one alone is a\n // half-finished edit, so it is ignored rather than guessed at.\n if (override.connectionId && override.model) {\n return Result.ok({\n connectionId: override.connectionId,\n model: override.model,\n roleId: null,\n fellBackToStandard: false\n });\n }\n\n const roles = settings.modelRoles?.roles;\n const requestedRole = (override.roleId || defaultRole) as AiModelRoleId;\n const requested = roles?.[requestedRole];\n\n if (requested?.connectionId && requested.model) {\n return Result.ok({\n connectionId: requested.connectionId,\n model: requested.model,\n roleId: requestedRole,\n fellBackToStandard: false\n });\n }\n\n /*\n * An unfilled role falls back to `standard` rather than failing. That is what keeps an\n * upgrade quiet: migration fills only `standard`, and every feature that used to read\n * `providers.presets[0]` keeps running on exactly the model it ran on before.\n *\n * It does mean an unfilled `vision` role sends images to whatever `standard` holds. Same\n * as today's behaviour, and the settings screen says so next to the role.\n */\n const standard = roles?.standard;\n\n if (standard?.connectionId && standard.model) {\n return Result.ok({\n connectionId: standard.connectionId,\n model: standard.model,\n roleId: \"standard\",\n fellBackToStandard: requestedRole !== \"standard\"\n });\n }\n\n return Result.fail(\n new Error(\n `No model is configured for the \"${requestedRole}\" role. Pick one under ${SETTINGS_PATH} → Model roles.`\n )\n );\n }\n}\n\nexport const ResolveAiCapabilityUseCaseImplementation =\n ResolveAiCapabilityUseCase.createImplementation({\n implementation: ResolveAiCapabilityUseCaseImpl,\n dependencies: [[AiCapability, { multiple: true }], GetSettingsUseCase, Encryption]\n });\n"],"names":["SETTINGS_PATH","ResolveAiCapabilityUseCaseImpl","capabilities","getSettings","encryption","Map","c","capabilityId","capability","Result","Error","settingsResult","settings","entry","isCapabilityEnabled","override","selection","connectionId","model","roleId","fellBackToStandard","connection","modelSdkName","sdkNameFromModel","defaultRole","roles","requestedRole","requested","standard","ResolveAiCapabilityUseCaseImplementation","ResolveAiCapabilityUseCase","AiCapability","GetSettingsUseCase","Encryption"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api/features/Capabilities/ResolveAiCapabilityUseCase.js","sources":["../../../../src/api/features/Capabilities/ResolveAiCapabilityUseCase.ts"],"sourcesContent":["import { Result } from \"@webiny/feature/api\";\nimport { Encryption } from \"@webiny/api-core/features/encryption/index.js\";\nimport { GetSettingsUseCase } from \"~/api/features/GetSettings/index.js\";\nimport { sdkNameFromModel } from \"~/api/features/shared/modelId.js\";\nimport { AiCapability, ResolveAiCapabilityUseCase } from \"./abstractions.js\";\nimport type { IResolvedAiCapability } from \"./abstractions.js\";\nimport { isCapabilityEnabled } from \"./types.js\";\nimport { AiCapabilityDisabledError } from \"./AiCapabilityDisabledError.js\";\nimport type { AiCapabilityOverride } from \"./types.js\";\nimport type { AiModelRoleId } from \"~/api/features/ModelRoles/index.js\";\nimport type { IAiPowerUpsSettings } from \"~/api/types.js\";\n\nconst SETTINGS_PATH = \"Settings → AI Power-Ups\";\n\n/**\n * Turns a capability id into the model, credential and prompt text a use case needs.\n *\n * This is the single place that knows the precedence rules. Before it existed, six use cases each\n * read `providers.presets[0]`, re-derived the SDK name from the model string and decrypted the key\n * themselves, so \"which model runs this feature\" had six answers that only happened to agree.\n *\n * Precedence: a capability's pinned connection+model, else its chosen role, else its declared\n * default role, else the `standard` role. The last step does not apply to `vision`, which fails\n * rather than sending images to a model that may not accept them.\n *\n * There is deliberately no environment-variable fallback. A project whose settings say one thing\n * and whose requests use another is the exact confusion this replaces.\n */\nclass ResolveAiCapabilityUseCaseImpl implements ResolveAiCapabilityUseCase.Interface {\n private capabilityLookup: Map<string, AiCapability.Interface>;\n\n constructor(\n capabilities: AiCapability.Interface[],\n private getSettings: GetSettingsUseCase.Interface,\n private encryption: Encryption.Interface\n ) {\n this.capabilityLookup = new Map(capabilities.map(c => [c.id, c]));\n }\n\n async execute(capabilityId: string): Promise<Result<IResolvedAiCapability, Error>> {\n const capability = this.capabilityLookup.get(capabilityId);\n\n if (!capability) {\n // Nothing a project can do about this one: the caller passed an unregistered id.\n return Result.fail(\n new Error(\n `Unknown AI capability \"${capabilityId}\". Register it with the AiCapability abstraction.`\n )\n );\n }\n\n const settingsResult = await this.getSettings.execute();\n if (settingsResult.isFail()) {\n return Result.fail(new Error(\"Failed to load AI Power-Ups settings.\"));\n }\n\n const settings = settingsResult.value;\n const entry = settings.capabilities?.items?.[capabilityId];\n\n /*\n * Checked here rather than at every call site, so a capability switched off in settings is\n * off for the GraphQL mutation as much as for the button that usually triggers it. The\n * admin hides the affordance too, but that is a courtesy and this is the actual gate.\n */\n if (!isCapabilityEnabled(entry)) {\n return Result.fail(\n new AiCapabilityDisabledError(capabilityId, capability.label, SETTINGS_PATH)\n );\n }\n\n const override: AiCapabilityOverride = entry?.overrides ?? {};\n\n const selection = this.selectModel(settings, capability.defaultRole, override);\n if (selection.isFail()) {\n return Result.fail(selection.error);\n }\n\n const { connectionId, model, roleId, fellBackToStandard } = selection.value;\n\n const connection = settings.connections?.presets?.find(c => c.id === connectionId);\n\n if (!connection) {\n return Result.fail(\n new Error(\n `\"${capability.label}\" points at a connection that no longer exists. Pick one under ${SETTINGS_PATH} → Model roles.`\n )\n );\n }\n\n if (!connection.apiKeyEncrypted) {\n return Result.fail(\n new Error(\n `The connection \"${connection.name}\" has no API key. Add one under ${SETTINGS_PATH} → Connections.`\n )\n );\n }\n\n /*\n * A model is stored fully qualified (\"anthropic/claude-sonnet-4-5\") and a connection stores\n * its vendor separately, so the two can disagree if a connection is re-pointed at another\n * vendor after a role was filled. Catching it here beats a provider-side auth error.\n */\n const modelSdkName = sdkNameFromModel(model);\n if (modelSdkName && connection.sdkName && modelSdkName !== connection.sdkName) {\n return Result.fail(\n new Error(\n `The model \"${model}\" cannot run on the \"${connection.name}\" connection, which is a ${connection.sdkName} credential. Fix the pairing under ${SETTINGS_PATH} → Model roles.`\n )\n );\n }\n\n return Result.ok({\n capabilityId,\n model,\n connection: {\n sdkName: modelSdkName || connection.sdkName,\n apiKey: await this.encryption.decrypt(connection.apiKeyEncrypted)\n },\n roleId,\n fellBackToStandard,\n // Always the capability's own. Projects adjust prompts by appending, never by\n // replacing, because for most capabilities the prompt is the output contract.\n guidance: capability.guidance ?? \"\",\n additionalInstructions: override.additionalInstructions?.trim() ?? \"\"\n });\n }\n\n private selectModel(\n settings: IAiPowerUpsSettings,\n defaultRole: AiModelRoleId,\n override: AiCapabilityOverride\n ): Result<\n {\n connectionId: string;\n model: string;\n roleId: AiModelRoleId | null;\n fellBackToStandard: boolean;\n },\n Error\n > {\n // A pinned connection+model wins outright. Both halves are required; one alone is a\n // half-finished edit, so it is ignored rather than guessed at.\n if (override.connectionId && override.model) {\n return Result.ok({\n connectionId: override.connectionId,\n model: override.model,\n roleId: null,\n fellBackToStandard: false\n });\n }\n\n const roles = settings.modelRoles?.roles;\n const requestedRole = (override.roleId || defaultRole) as AiModelRoleId;\n const requested = roles?.[requestedRole];\n\n if (requested?.connectionId && requested.model) {\n return Result.ok({\n connectionId: requested.connectionId,\n model: requested.model,\n roleId: requestedRole,\n fellBackToStandard: false\n });\n }\n\n /*\n * `vision` never falls back. Work on this role sends images, and nothing guarantees the\n * Standard model accepts them, so falling through would trade a clear \"set a Vision model\"\n * for a provider error about image input, or a text-only model silently guessing.\n *\n * Upgrades are not broken by this: `ModelRolesHandler` seeds `vision` from the legacy\n * preset alongside `standard`, so a project that ran image work on `providers.presets[0]`\n * keeps running it there, with the choice visible on the settings screen. A capability can\n * still be pointed at another role deliberately through its `roleId` override.\n */\n if (requestedRole === \"vision\") {\n return Result.fail(\n new Error(\n `No model is configured for the \"vision\" role, and image work does not fall back to Standard. Pick one under ${SETTINGS_PATH} → Model roles.`\n )\n );\n }\n\n /*\n * Every other role falls back to `standard`. Running cheap work on a stronger model is\n * always correct, only more expensive.\n */\n const standard = roles?.standard;\n\n if (standard?.connectionId && standard.model) {\n return Result.ok({\n connectionId: standard.connectionId,\n model: standard.model,\n roleId: \"standard\",\n fellBackToStandard: requestedRole !== \"standard\"\n });\n }\n\n return Result.fail(\n new Error(\n `No model is configured for the \"${requestedRole}\" role. Pick one under ${SETTINGS_PATH} → Model roles.`\n )\n );\n }\n}\n\nexport const ResolveAiCapabilityUseCaseImplementation =\n ResolveAiCapabilityUseCase.createImplementation({\n implementation: ResolveAiCapabilityUseCaseImpl,\n dependencies: [[AiCapability, { multiple: true }], GetSettingsUseCase, Encryption]\n });\n"],"names":["SETTINGS_PATH","ResolveAiCapabilityUseCaseImpl","capabilities","getSettings","encryption","Map","c","capabilityId","capability","Result","Error","settingsResult","settings","entry","isCapabilityEnabled","AiCapabilityDisabledError","override","selection","connectionId","model","roleId","fellBackToStandard","connection","modelSdkName","sdkNameFromModel","defaultRole","roles","requestedRole","requested","standard","ResolveAiCapabilityUseCaseImplementation","ResolveAiCapabilityUseCase","AiCapability","GetSettingsUseCase","Encryption"],"mappings":";;;;;;;AAYA,MAAMA,gBAAgB;AAgBtB,MAAMC;IAGF,YACIC,YAAsC,EAC9BC,WAAyC,EACzCC,UAAgC,CAC1C;aAFUD,WAAW,GAAXA;aACAC,UAAU,GAAVA;QAER,IAAI,CAAC,gBAAgB,GAAG,IAAIC,IAAIH,aAAa,GAAG,CAACI,CAAAA,IAAK;gBAACA,EAAE,EAAE;gBAAEA;aAAE;IACnE;IAEA,MAAM,QAAQC,YAAoB,EAAiD;QAC/E,MAAMC,aAAa,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAACD;QAE7C,IAAI,CAACC,YAED,OAAOC,OAAO,IAAI,CACd,IAAIC,MACA,CAAC,uBAAuB,EAAEH,aAAa,iDAAiD,CAAC;QAKrG,MAAMI,iBAAiB,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO;QACrD,IAAIA,eAAe,MAAM,IACrB,OAAOF,OAAO,IAAI,CAAC,IAAIC,MAAM;QAGjC,MAAME,WAAWD,eAAe,KAAK;QACrC,MAAME,QAAQD,SAAS,YAAY,EAAE,OAAO,CAACL,aAAa;QAO1D,IAAI,CAACO,oBAAoBD,QACrB,OAAOJ,OAAO,IAAI,CACd,IAAIM,0BAA0BR,cAAcC,WAAW,KAAK,EAAER;QAItE,MAAMgB,WAAiCH,OAAO,aAAa,CAAC;QAE5D,MAAMI,YAAY,IAAI,CAAC,WAAW,CAACL,UAAUJ,WAAW,WAAW,EAAEQ;QACrE,IAAIC,UAAU,MAAM,IAChB,OAAOR,OAAO,IAAI,CAACQ,UAAU,KAAK;QAGtC,MAAM,EAAEC,YAAY,EAAEC,KAAK,EAAEC,MAAM,EAAEC,kBAAkB,EAAE,GAAGJ,UAAU,KAAK;QAE3E,MAAMK,aAAaV,SAAS,WAAW,EAAE,SAAS,KAAKN,CAAAA,IAAKA,EAAE,EAAE,KAAKY;QAErE,IAAI,CAACI,YACD,OAAOb,OAAO,IAAI,CACd,IAAIC,MACA,CAAC,CAAC,EAAEF,WAAW,KAAK,CAAC,+DAA+D,EAAER,cAAc,eAAe,CAAC;QAKhI,IAAI,CAACsB,WAAW,eAAe,EAC3B,OAAOb,OAAO,IAAI,CACd,IAAIC,MACA,CAAC,gBAAgB,EAAEY,WAAW,IAAI,CAAC,gCAAgC,EAAEtB,cAAc,eAAe,CAAC;QAU/G,MAAMuB,eAAeC,iBAAiBL;QACtC,IAAII,gBAAgBD,WAAW,OAAO,IAAIC,iBAAiBD,WAAW,OAAO,EACzE,OAAOb,OAAO,IAAI,CACd,IAAIC,MACA,CAAC,WAAW,EAAES,MAAM,qBAAqB,EAAEG,WAAW,IAAI,CAAC,yBAAyB,EAAEA,WAAW,OAAO,CAAC,mCAAmC,EAAEtB,cAAc,eAAe,CAAC;QAKxL,OAAOS,OAAO,EAAE,CAAC;YACbF;YACAY;YACA,YAAY;gBACR,SAASI,gBAAgBD,WAAW,OAAO;gBAC3C,QAAQ,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAACA,WAAW,eAAe;YACpE;YACAF;YACAC;YAGA,UAAUb,WAAW,QAAQ,IAAI;YACjC,wBAAwBQ,SAAS,sBAAsB,EAAE,UAAU;QACvE;IACJ;IAEQ,YACJJ,QAA6B,EAC7Ba,WAA0B,EAC1BT,QAA8B,EAShC;QAGE,IAAIA,SAAS,YAAY,IAAIA,SAAS,KAAK,EACvC,OAAOP,OAAO,EAAE,CAAC;YACb,cAAcO,SAAS,YAAY;YACnC,OAAOA,SAAS,KAAK;YACrB,QAAQ;YACR,oBAAoB;QACxB;QAGJ,MAAMU,QAAQd,SAAS,UAAU,EAAE;QACnC,MAAMe,gBAAiBX,SAAS,MAAM,IAAIS;QAC1C,MAAMG,YAAYF,OAAO,CAACC,cAAc;QAExC,IAAIC,WAAW,gBAAgBA,UAAU,KAAK,EAC1C,OAAOnB,OAAO,EAAE,CAAC;YACb,cAAcmB,UAAU,YAAY;YACpC,OAAOA,UAAU,KAAK;YACtB,QAAQD;YACR,oBAAoB;QACxB;QAaJ,IAAIA,AAAkB,aAAlBA,eACA,OAAOlB,OAAO,IAAI,CACd,IAAIC,MACA,CAAC,4GAA4G,EAAEV,cAAc,eAAe,CAAC;QASzJ,MAAM6B,WAAWH,OAAO;QAExB,IAAIG,UAAU,gBAAgBA,SAAS,KAAK,EACxC,OAAOpB,OAAO,EAAE,CAAC;YACb,cAAcoB,SAAS,YAAY;YACnC,OAAOA,SAAS,KAAK;YACrB,QAAQ;YACR,oBAAoBF,AAAkB,eAAlBA;QACxB;QAGJ,OAAOlB,OAAO,IAAI,CACd,IAAIC,MACA,CAAC,gCAAgC,EAAEiB,cAAc,uBAAuB,EAAE3B,cAAc,eAAe,CAAC;IAGpH;AACJ;AAEO,MAAM8B,2CACTC,2BAA2B,oBAAoB,CAAC;IAC5C,gBAAgB9B;IAChB,cAAc;QAAC;YAAC+B;YAAc;gBAAE,UAAU;YAAK;SAAE;QAAEC;QAAoBC;KAAW;AACtF"}
|
|
@@ -8,7 +8,13 @@ import type { AiModelRoleId } from "../../../api/features/ModelRoles/index.js";
|
|
|
8
8
|
* row in Settings for free by registering a capability alongside it.
|
|
9
9
|
*/
|
|
10
10
|
export interface IAiCapability {
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Stable, prefixed with its app (`cmsGenerateEntry`), and persisted as a settings key. Renaming
|
|
13
|
+
* one orphans its overrides.
|
|
14
|
+
*
|
|
15
|
+
* No dots. The id becomes a key under `capabilities.items`, and the settings form names its
|
|
16
|
+
* fields by dotted path, so `cms.generateEntry` reads as two levels of nesting.
|
|
17
|
+
*/
|
|
12
18
|
readonly id: string;
|
|
13
19
|
readonly label: string;
|
|
14
20
|
readonly description: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api/features/Capabilities/abstractions.js","sources":["../../../../src/api/features/Capabilities/abstractions.ts"],"sourcesContent":["import { createAbstraction, Result } from \"@webiny/feature/api\";\nimport type { AiModelRoleId } from \"~/api/features/ModelRoles/index.js\";\n\n/**\n * One AI feature, declared so the settings screen can list it and a project can point it at a\n * different model or adjust its prompt.\n *\n * Features register these; nothing hardcodes the list. An extension that adds an AI feature gets a\n * row in Settings for free by registering a capability alongside it.\n */\nexport interface IAiCapability {\n
|
|
1
|
+
{"version":3,"file":"api/features/Capabilities/abstractions.js","sources":["../../../../src/api/features/Capabilities/abstractions.ts"],"sourcesContent":["import { createAbstraction, Result } from \"@webiny/feature/api\";\nimport type { AiModelRoleId } from \"~/api/features/ModelRoles/index.js\";\n\n/**\n * One AI feature, declared so the settings screen can list it and a project can point it at a\n * different model or adjust its prompt.\n *\n * Features register these; nothing hardcodes the list. An extension that adds an AI feature gets a\n * row in Settings for free by registering a capability alongside it.\n */\nexport interface IAiCapability {\n /**\n * Stable, prefixed with its app (`cmsGenerateEntry`), and persisted as a settings key. Renaming\n * one orphans its overrides.\n *\n * No dots. The id becomes a key under `capabilities.items`, and the settings form names its\n * fields by dotted path, so `cms.generateEntry` reads as two levels of nesting.\n */\n readonly id: string;\n readonly label: string;\n readonly description: string;\n /** Which role supplies the model when the project has not overridden this capability. */\n readonly defaultRole: AiModelRoleId;\n /**\n * The capability's fixed system-prompt block, owned by the code and not configurable.\n *\n * Leave it undefined when the prompt is assembled per request (from a content model schema, a\n * component catalog, a persona) and the use case builds its own system text instead.\n *\n * Either way a project can only append to it, via `additionalInstructions`. These prompts carry\n * the output contract the surrounding code parses, so they are implementation, not settings.\n */\n readonly guidance?: string;\n}\n\nexport const AiCapability = createAbstraction<IAiCapability>(\"AiPowerUpsAiCapability\");\n\nexport namespace AiCapability {\n export type Interface = IAiCapability;\n}\n\n/**\n * What a capability looks like to a caller outside the api.\n *\n * `guidance` is deliberately absent: a prompt is implementation, and there is no reason to ship ours\n * over the wire to anyone who can read settings.\n */\nexport interface IAiCapabilitySummary {\n id: string;\n label: string;\n description: string;\n defaultRole: AiModelRoleId;\n}\n\nexport interface IListAiCapabilitiesUseCase {\n execute(): Promise<IAiCapabilitySummary[]>;\n}\n\n/** Everything registered, for the settings screen to render a row per feature. */\nexport const ListAiCapabilitiesUseCase = createAbstraction<IListAiCapabilitiesUseCase>(\n \"AiPowerUpsListAiCapabilitiesUseCase\"\n);\n\nexport namespace ListAiCapabilitiesUseCase {\n export type Interface = IListAiCapabilitiesUseCase;\n export type Summary = IAiCapabilitySummary;\n}\n\nexport interface IResolvedAiCapability {\n capabilityId: string;\n /** Fully qualified, e.g. `\"anthropic/claude-sonnet-4-5\"`. */\n model: string;\n connection: {\n sdkName: string;\n apiKey: string;\n };\n /** Which role supplied the model. `null` when a capability override pinned it directly. */\n roleId: AiModelRoleId | null;\n /** True when the requested role was empty and `standard` filled in for it. */\n fellBackToStandard: boolean;\n /** The capability's own guidance. Empty when it assembles its prompt per request. */\n guidance: string;\n /** Project text to append to the system prompt. Empty when unset. */\n additionalInstructions: string;\n}\n\nexport interface IResolveAiCapabilityUseCase {\n execute(capabilityId: string): Promise<Result<IResolvedAiCapability, Error>>;\n}\n\nexport const ResolveAiCapabilityUseCase = createAbstraction<IResolveAiCapabilityUseCase>(\n \"AiPowerUpsResolveAiCapabilityUseCase\"\n);\n\nexport namespace ResolveAiCapabilityUseCase {\n export type Interface = IResolveAiCapabilityUseCase;\n export type Resolution = IResolvedAiCapability;\n}\n"],"names":["AiCapability","createAbstraction","ListAiCapabilitiesUseCase","ResolveAiCapabilityUseCase"],"mappings":";AAmCO,MAAMA,eAAeC,kBAAiC;AAwBtD,MAAMC,4BAA4BD,kBACrC;AA8BG,MAAME,6BAA6BF,kBACtC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { AiCapability, ResolveAiCapabilityUseCase, ListAiCapabilitiesUseCase } from "./abstractions.js";
|
|
2
2
|
export type { IAiCapability, IResolvedAiCapability, IAiCapabilitySummary } from "./abstractions.js";
|
|
3
3
|
export { withAdditionalInstructions } from "./composeSystemPrompt.js";
|
|
4
|
+
export { AiCapabilityDisabledError } from "./AiCapabilityDisabledError.js";
|
|
4
5
|
export type { AiCapabilityOverride, CapabilitiesSettings } from "./types.js";
|
|
5
6
|
export { CapabilitiesFeature } from "./feature.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { AiCapability, ListAiCapabilitiesUseCase, ResolveAiCapabilityUseCase } from "./abstractions.js";
|
|
2
2
|
export { withAdditionalInstructions } from "./composeSystemPrompt.js";
|
|
3
|
+
export { AiCapabilityDisabledError } from "./AiCapabilityDisabledError.js";
|
|
3
4
|
export { CapabilitiesFeature } from "./feature.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AiCapability } from "../../../api/features/Capabilities/index.js";
|
|
2
|
-
export declare const CMS_COMPARE_ENTRY_REVISIONS_CAPABILITY = "
|
|
2
|
+
export declare const CMS_COMPARE_ENTRY_REVISIONS_CAPABILITY = "cmsCompareEntryRevisions";
|
|
3
3
|
declare class CmsCompareEntryRevisionsCapabilityImpl implements AiCapability.Interface {
|
|
4
|
-
readonly id = "
|
|
4
|
+
readonly id = "cmsCompareEntryRevisions";
|
|
5
5
|
readonly label = "CMS revision comparison";
|
|
6
6
|
readonly description = "Summarises what changed between two revisions of an entry. Short, structural work that a smaller model handles well.";
|
|
7
7
|
readonly defaultRole: "fast";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AiCapability } from "../Capabilities/index.js";
|
|
2
|
-
const CMS_COMPARE_ENTRY_REVISIONS_CAPABILITY = "
|
|
2
|
+
const CMS_COMPARE_ENTRY_REVISIONS_CAPABILITY = "cmsCompareEntryRevisions";
|
|
3
3
|
const guidance = `You are a content intelligence assistant specialized in version comparison for headless CMS platforms.
|
|
4
4
|
|
|
5
5
|
I will provide:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api/features/CmsCompareEntryRevisions/capability.js","sources":["../../../../src/api/features/CmsCompareEntryRevisions/capability.ts"],"sourcesContent":["import { AiCapability } from \"~/api/features/Capabilities/index.js\";\n\nexport const CMS_COMPARE_ENTRY_REVISIONS_CAPABILITY = \"
|
|
1
|
+
{"version":3,"file":"api/features/CmsCompareEntryRevisions/capability.js","sources":["../../../../src/api/features/CmsCompareEntryRevisions/capability.ts"],"sourcesContent":["import { AiCapability } from \"~/api/features/Capabilities/index.js\";\n\nexport const CMS_COMPARE_ENTRY_REVISIONS_CAPABILITY = \"cmsCompareEntryRevisions\";\n\n/**\n * The output contract lives in this prompt: the use case regex-matches the HTML table it specifies\n * to build the summary, down to the literal \"No differences detected\" sentence and a count of\n * `<tr>` tags, and the dialog styles those classes. That is why a project can only append to it.\n */\nconst guidance = `You are a content intelligence assistant specialized in version comparison for headless CMS platforms.\n\nI will provide:\n\n1. The content model JSON Schema that defines the structure of the entry.\n2. Two versions of the content entry values in JSON format: Version A and Version B.\n\nYour task:\n\n- Parse both versions according to the JSON Schema.\n- Identify all differences between Version A and Version B.\n- For each difference, explain:\n - The field name (use the human-readable label from the schema description if available)\n - The value in Version A\n - The value in Version B\n - A brief summary of the change (e.g., \"Title changed from 'Old' to 'New'\")\n- If nested fields or objects exist, perform a deep comparison.\n- Return the comparison in clean HTML format.\n\nOutput format:\n\n<div class=\"comparison-report\">\n <table class=\"comparison-table\">\n <thead>\n <tr>\n <th>Field</th>\n <th>Version A</th>\n <th>Version B</th>\n <th>Change Summary</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td><strong>title</strong></td>\n <td>Launch Plan</td>\n <td>Updated Launch Plan</td>\n <td>Title changed from 'Launch Plan' to 'Updated Launch Plan'</td>\n </tr>\n </tbody>\n </table>\n</div>\n\nIf no differences are found, return: <div class=\"no-changes\"><h3>No differences detected between Version A and Version B.</h3></div>\n\nUse semantic HTML with appropriate CSS classes for styling. Do not include <style> tags or CSS — only return the HTML structure.\nFor rich text or complex nested values, show a concise summary rather than raw JSON.`;\n\nclass CmsCompareEntryRevisionsCapabilityImpl implements AiCapability.Interface {\n readonly id = CMS_COMPARE_ENTRY_REVISIONS_CAPABILITY;\n readonly label = \"CMS revision comparison\";\n readonly description =\n \"Summarises what changed between two revisions of an entry. Short, structural work that a smaller model handles well.\";\n readonly defaultRole = \"fast\" as const;\n readonly guidance = guidance;\n}\n\nexport const CmsCompareEntryRevisionsCapability = AiCapability.createImplementation({\n implementation: CmsCompareEntryRevisionsCapabilityImpl,\n dependencies: []\n});\n"],"names":["CMS_COMPARE_ENTRY_REVISIONS_CAPABILITY","guidance","CmsCompareEntryRevisionsCapabilityImpl","CmsCompareEntryRevisionsCapability","AiCapability"],"mappings":";AAEO,MAAMA,yCAAyC;AAOtD,MAAMC,WAAW,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oFA6CkE,CAAC;AAErF,MAAMC;;aACO,EAAE,GAAGF;aACL,KAAK,GAAG;aACR,WAAW,GAChB;aACK,WAAW,GAAG;aACd,QAAQ,GAAGC;;AACxB;AAEO,MAAME,qCAAqCC,aAAa,oBAAoB,CAAC;IAChF,gBAAgBF;IAChB,cAAc,EAAE;AACpB"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { AiCapability } from "../../../api/features/Capabilities/index.js";
|
|
2
|
-
export declare const CMS_GENERATE_ENTRY_CAPABILITY = "
|
|
2
|
+
export declare const CMS_GENERATE_ENTRY_CAPABILITY = "cmsGenerateEntry";
|
|
3
3
|
/**
|
|
4
4
|
* No `guidance`: this prompt is rebuilt on every request from the content model's JSON Schema and
|
|
5
5
|
* the File Manager's current image tags, so there is no fixed block a project could replace without
|
|
6
6
|
* dropping the schema the output has to conform to. Additional instructions still apply.
|
|
7
7
|
*/
|
|
8
8
|
declare class CmsGenerateEntryCapabilityImpl implements AiCapability.Interface {
|
|
9
|
-
readonly id = "
|
|
9
|
+
readonly id = "cmsGenerateEntry";
|
|
10
10
|
readonly label = "CMS entry generation";
|
|
11
11
|
readonly description = "Writes a content entry from a prompt, following the content model's schema and picking images from the File Manager.";
|
|
12
12
|
readonly defaultRole: "standard";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AiCapability } from "../Capabilities/index.js";
|
|
2
|
-
const CMS_GENERATE_ENTRY_CAPABILITY = "
|
|
2
|
+
const CMS_GENERATE_ENTRY_CAPABILITY = "cmsGenerateEntry";
|
|
3
3
|
class CmsGenerateEntryCapabilityImpl {
|
|
4
4
|
constructor(){
|
|
5
5
|
this.id = CMS_GENERATE_ENTRY_CAPABILITY;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api/features/CmsGenerateEntryContent/capability.js","sources":["../../../../src/api/features/CmsGenerateEntryContent/capability.ts"],"sourcesContent":["import { AiCapability } from \"~/api/features/Capabilities/index.js\";\n\nexport const CMS_GENERATE_ENTRY_CAPABILITY = \"
|
|
1
|
+
{"version":3,"file":"api/features/CmsGenerateEntryContent/capability.js","sources":["../../../../src/api/features/CmsGenerateEntryContent/capability.ts"],"sourcesContent":["import { AiCapability } from \"~/api/features/Capabilities/index.js\";\n\nexport const CMS_GENERATE_ENTRY_CAPABILITY = \"cmsGenerateEntry\";\n\n/**\n * No `guidance`: this prompt is rebuilt on every request from the content model's JSON Schema and\n * the File Manager's current image tags, so there is no fixed block a project could replace without\n * dropping the schema the output has to conform to. Additional instructions still apply.\n */\nclass CmsGenerateEntryCapabilityImpl implements AiCapability.Interface {\n readonly id = CMS_GENERATE_ENTRY_CAPABILITY;\n readonly label = \"CMS entry generation\";\n readonly description =\n \"Writes a content entry from a prompt, following the content model's schema and picking images from the File Manager.\";\n readonly defaultRole = \"standard\" as const;\n}\n\nexport const CmsGenerateEntryCapability = AiCapability.createImplementation({\n implementation: CmsGenerateEntryCapabilityImpl,\n dependencies: []\n});\n"],"names":["CMS_GENERATE_ENTRY_CAPABILITY","CmsGenerateEntryCapabilityImpl","CmsGenerateEntryCapability","AiCapability"],"mappings":";AAEO,MAAMA,gCAAgC;AAO7C,MAAMC;;aACO,EAAE,GAAGD;aACL,KAAK,GAAG;aACR,WAAW,GAChB;aACK,WAAW,GAAG;;AAC3B;AAEO,MAAME,6BAA6BC,aAAa,oBAAoB,CAAC;IACxE,gBAAgBF;IAChB,cAAc,EAAE;AACpB"}
|
|
@@ -31,10 +31,16 @@ class ModelRolesHandlerImpl {
|
|
|
31
31
|
roles
|
|
32
32
|
};
|
|
33
33
|
const legacy = readLegacyProviderPresets(all)[0];
|
|
34
|
-
if (legacy?.model)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
if (legacy?.model) {
|
|
35
|
+
roles.standard = {
|
|
36
|
+
connectionId: legacy.id,
|
|
37
|
+
model: legacy.model
|
|
38
|
+
};
|
|
39
|
+
if (!roles.vision.model) roles.vision = {
|
|
40
|
+
connectionId: legacy.id,
|
|
41
|
+
model: legacy.model
|
|
42
|
+
};
|
|
43
|
+
}
|
|
38
44
|
return {
|
|
39
45
|
roles
|
|
40
46
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api/features/ModelRoles/ModelRolesHandler.js","sources":["../../../../src/api/features/ModelRoles/ModelRolesHandler.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { AiPowerUpsSettingsGroupHandler } from \"~/api/features/shared/index.js\";\nimport { readLegacyProviderPresets } from \"~/api/features/Connections/legacyProviders.js\";\nimport { AI_MODEL_ROLE_IDS } from \"./roles.js\";\nimport { emptyAssignment } from \"./types.js\";\nimport type { AiModelRoleAssignments, ModelRolesSettings, PersistedModelRoles } from \"./types.js\";\n\n/*\n * The admin form sends `null`, not `\"\"`, for a field nobody has touched, and an unfilled role is\n * the normal state of two of the three. `nullish` keeps an untouched role from failing validation;\n * `mapToStorage` is what normalises it to `\"\"`.\n */\nconst formString = z.string().nullish();\n\nconst assignmentSchema = z.object({\n connectionId: formString,\n model: formString\n});\n\nconst inputSchema = z.object({\n roles: z.object(\n Object.fromEntries(AI_MODEL_ROLE_IDS.map(id => [id, assignmentSchema]))\n ) as unknown as z.ZodType<AiModelRoleAssignments>\n});\n\nclass ModelRolesHandlerImpl implements AiPowerUpsSettingsGroupHandler.Interface {\n readonly name = \"modelRoles\";\n readonly inputSchema = inputSchema;\n\n mapFromStorage(persisted: unknown, all?: Record<string, unknown>): ModelRolesSettings {\n const stored = (persisted ?? {}) as PersistedModelRoles;\n const roles = Object.fromEntries(\n AI_MODEL_ROLE_IDS.map(id => {\n const assignment = stored.roles?.[id];\n return [\n id,\n {\n connectionId: assignment?.connectionId ?? \"\",\n model: assignment?.model ?? \"\"\n }\n ];\n })\n ) as AiModelRoleAssignments;\n\n if (roles.standard.model) {\n return { roles };\n }\n\n /*\n * Nothing has filled `standard` yet, so seed it from the legacy `providers` section. Every\n * AI feature used to read `providers.presets[0]` and nothing else, so lifting exactly that\n * preset
|
|
1
|
+
{"version":3,"file":"api/features/ModelRoles/ModelRolesHandler.js","sources":["../../../../src/api/features/ModelRoles/ModelRolesHandler.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { AiPowerUpsSettingsGroupHandler } from \"~/api/features/shared/index.js\";\nimport { readLegacyProviderPresets } from \"~/api/features/Connections/legacyProviders.js\";\nimport { AI_MODEL_ROLE_IDS } from \"./roles.js\";\nimport { emptyAssignment } from \"./types.js\";\nimport type { AiModelRoleAssignments, ModelRolesSettings, PersistedModelRoles } from \"./types.js\";\n\n/*\n * The admin form sends `null`, not `\"\"`, for a field nobody has touched, and an unfilled role is\n * the normal state of two of the three. `nullish` keeps an untouched role from failing validation;\n * `mapToStorage` is what normalises it to `\"\"`.\n */\nconst formString = z.string().nullish();\n\nconst assignmentSchema = z.object({\n connectionId: formString,\n model: formString\n});\n\nconst inputSchema = z.object({\n roles: z.object(\n Object.fromEntries(AI_MODEL_ROLE_IDS.map(id => [id, assignmentSchema]))\n ) as unknown as z.ZodType<AiModelRoleAssignments>\n});\n\nclass ModelRolesHandlerImpl implements AiPowerUpsSettingsGroupHandler.Interface {\n readonly name = \"modelRoles\";\n readonly inputSchema = inputSchema;\n\n mapFromStorage(persisted: unknown, all?: Record<string, unknown>): ModelRolesSettings {\n const stored = (persisted ?? {}) as PersistedModelRoles;\n const roles = Object.fromEntries(\n AI_MODEL_ROLE_IDS.map(id => {\n const assignment = stored.roles?.[id];\n return [\n id,\n {\n connectionId: assignment?.connectionId ?? \"\",\n model: assignment?.model ?? \"\"\n }\n ];\n })\n ) as AiModelRoleAssignments;\n\n if (roles.standard.model) {\n return { roles };\n }\n\n /*\n * Nothing has filled `standard` yet, so seed it from the legacy `providers` section. Every\n * AI feature used to read `providers.presets[0]` and nothing else, so lifting exactly that\n * preset reproduces the old behaviour for a project that upgrades without touching the\n * settings screen.\n *\n * `vision` gets the same preset, because it does not fall back: image enrichment ran on\n * that model before the upgrade and has to keep running on it. Seeding it, rather than\n * falling back at resolve time, also means the settings screen shows which model reads\n * images instead of hiding the choice.\n *\n * `fast` stays empty on purpose. It falls back to `standard`, so an untouched project keeps\n * working, and the screen shows the fallback rather than pretending someone chose it.\n */\n const legacy = readLegacyProviderPresets(all)[0];\n\n if (legacy?.model) {\n roles.standard = { connectionId: legacy.id, model: legacy.model };\n\n if (!roles.vision.model) {\n roles.vision = { connectionId: legacy.id, model: legacy.model };\n }\n }\n\n return { roles };\n }\n\n async mapToStorage(internal: unknown): Promise<PersistedModelRoles> {\n const input = internal as ModelRolesSettings;\n\n return {\n roles: Object.fromEntries(\n AI_MODEL_ROLE_IDS.map(id => {\n const assignment = input.roles?.[id] ?? emptyAssignment();\n return [\n id,\n {\n connectionId: assignment.connectionId ?? \"\",\n model: assignment.model ?? \"\"\n }\n ];\n })\n )\n };\n }\n}\n\nexport default AiPowerUpsSettingsGroupHandler.createImplementation({\n implementation: ModelRolesHandlerImpl,\n dependencies: []\n});\n"],"names":["formString","z","assignmentSchema","inputSchema","Object","AI_MODEL_ROLE_IDS","id","ModelRolesHandlerImpl","persisted","all","stored","roles","assignment","legacy","readLegacyProviderPresets","internal","input","emptyAssignment","AiPowerUpsSettingsGroupHandler"],"mappings":";;;;;AAYA,MAAMA,aAAaC,EAAE,MAAM,GAAG,OAAO;AAErC,MAAMC,mBAAmBD,EAAE,MAAM,CAAC;IAC9B,cAAcD;IACd,OAAOA;AACX;AAEA,MAAMG,cAAcF,EAAE,MAAM,CAAC;IACzB,OAAOA,EAAE,MAAM,CACXG,OAAO,WAAW,CAACC,kBAAkB,GAAG,CAACC,CAAAA,KAAM;YAACA;YAAIJ;SAAiB;AAE7E;AAEA,MAAMK;IAIF,eAAeC,SAAkB,EAAEC,GAA6B,EAAsB;QAClF,MAAMC,SAAUF,aAAa,CAAC;QAC9B,MAAMG,QAAQP,OAAO,WAAW,CAC5BC,kBAAkB,GAAG,CAACC,CAAAA;YAClB,MAAMM,aAAaF,OAAO,KAAK,EAAE,CAACJ,GAAG;YACrC,OAAO;gBACHA;gBACA;oBACI,cAAcM,YAAY,gBAAgB;oBAC1C,OAAOA,YAAY,SAAS;gBAChC;aACH;QACL;QAGJ,IAAID,MAAM,QAAQ,CAAC,KAAK,EACpB,OAAO;YAAEA;QAAM;QAiBnB,MAAME,SAASC,0BAA0BL,IAAI,CAAC,EAAE;QAEhD,IAAII,QAAQ,OAAO;YACfF,MAAM,QAAQ,GAAG;gBAAE,cAAcE,OAAO,EAAE;gBAAE,OAAOA,OAAO,KAAK;YAAC;YAEhE,IAAI,CAACF,MAAM,MAAM,CAAC,KAAK,EACnBA,MAAM,MAAM,GAAG;gBAAE,cAAcE,OAAO,EAAE;gBAAE,OAAOA,OAAO,KAAK;YAAC;QAEtE;QAEA,OAAO;YAAEF;QAAM;IACnB;IAEA,MAAM,aAAaI,QAAiB,EAAgC;QAChE,MAAMC,QAAQD;QAEd,OAAO;YACH,OAAOX,OAAO,WAAW,CACrBC,kBAAkB,GAAG,CAACC,CAAAA;gBAClB,MAAMM,aAAaI,MAAM,KAAK,EAAE,CAACV,GAAG,IAAIW;gBACxC,OAAO;oBACHX;oBACA;wBACI,cAAcM,WAAW,YAAY,IAAI;wBACzC,OAAOA,WAAW,KAAK,IAAI;oBAC/B;iBACH;YACL;QAER;IACJ;;aAlES,IAAI,GAAG;aACP,WAAW,GAAGT;;AAkE3B;AAEA,0BAAee,+BAA+B,oBAAoB,CAAC;IAC/D,gBAAgBX;IAChB,cAAc,EAAE;AACpB"}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { Encryption } from "@webiny/api-core/features/encryption/index.js";
|
|
3
3
|
import { Masker } from "@webiny/api-core/features/masker/index.js";
|
|
4
|
-
import { AiModelRegistry } from "@webiny/api-core/features/ai/index.js";
|
|
5
4
|
import { AiPowerUpsSettingsGroupHandler } from "../../../api/features/shared/index.js";
|
|
6
5
|
import type { PersistedProviders, ProvidersSettings } from "./types.js";
|
|
7
6
|
declare class ProvidersHandlerImpl implements AiPowerUpsSettingsGroupHandler.Interface {
|
|
8
7
|
private encryption;
|
|
9
8
|
private masker;
|
|
10
|
-
private modelRegistry;
|
|
11
9
|
readonly name = "providers";
|
|
12
10
|
readonly inputSchema: z.ZodObject<{
|
|
13
11
|
presets: z.ZodArray<z.ZodObject<{
|
|
@@ -18,7 +16,7 @@ declare class ProvidersHandlerImpl implements AiPowerUpsSettingsGroupHandler.Int
|
|
|
18
16
|
apiKey: z.ZodOptional<z.ZodString>;
|
|
19
17
|
}, z.core.$strip>>;
|
|
20
18
|
}, z.core.$strip>;
|
|
21
|
-
constructor(encryption: Encryption.Interface, masker: Masker.Interface
|
|
19
|
+
constructor(encryption: Encryption.Interface, masker: Masker.Interface);
|
|
22
20
|
mapFromStorage(persisted: unknown): ProvidersSettings;
|
|
23
21
|
mapToStorage(internal: unknown, existing: unknown | null): Promise<PersistedProviders>;
|
|
24
22
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { Encryption } from "@webiny/api-core/features/encryption/index.js";
|
|
3
3
|
import { Masker } from "@webiny/api-core/features/masker/index.js";
|
|
4
|
-
import { AiModelRegistry } from "@webiny/api-core/features/ai/index.js";
|
|
5
4
|
import { AiPowerUpsSettingsGroupHandler } from "../shared/index.js";
|
|
6
5
|
const inputSchema = z.object({
|
|
7
6
|
presets: z.array(z.object({
|
|
@@ -13,10 +12,9 @@ const inputSchema = z.object({
|
|
|
13
12
|
}))
|
|
14
13
|
});
|
|
15
14
|
class ProvidersHandlerImpl {
|
|
16
|
-
constructor(encryption, masker
|
|
15
|
+
constructor(encryption, masker){
|
|
17
16
|
this.encryption = encryption;
|
|
18
17
|
this.masker = masker;
|
|
19
|
-
this.modelRegistry = modelRegistry;
|
|
20
18
|
this.name = "providers";
|
|
21
19
|
this.inputSchema = inputSchema;
|
|
22
20
|
}
|
|
@@ -41,16 +39,6 @@ class ProvidersHandlerImpl {
|
|
|
41
39
|
const input = internal;
|
|
42
40
|
const existingData = existing;
|
|
43
41
|
const existingPresets = existingData?.presets ?? [];
|
|
44
|
-
const availableModels = await this.modelRegistry.listModels();
|
|
45
|
-
for (const preset of input.presets){
|
|
46
|
-
if (!preset.model) continue;
|
|
47
|
-
const slashIndex = preset.model.indexOf("/");
|
|
48
|
-
if (-1 === slashIndex) throw new Error(`Invalid model ID "${preset.model}" in preset "${preset.name}". Expected format: "<providerId>/<modelId>".`);
|
|
49
|
-
const providerId = preset.model.slice(0, slashIndex);
|
|
50
|
-
const modelId = preset.model.slice(slashIndex + 1);
|
|
51
|
-
const found = availableModels.some((m)=>m.providerId === providerId && m.modelId === modelId);
|
|
52
|
-
if (!found) throw new Error(`Model "${preset.model}" in preset "${preset.name}" is not available. Use listModels() to see available models.`);
|
|
53
|
-
}
|
|
54
42
|
const presets = await Promise.all(input.presets.map(async (preset)=>{
|
|
55
43
|
const existingMatch = existingPresets.find((ep)=>ep.id === preset.id);
|
|
56
44
|
let apiKeyEncrypted;
|
|
@@ -83,8 +71,7 @@ const ProvidersHandler = AiPowerUpsSettingsGroupHandler.createImplementation({
|
|
|
83
71
|
implementation: ProvidersHandlerImpl,
|
|
84
72
|
dependencies: [
|
|
85
73
|
Encryption,
|
|
86
|
-
Masker
|
|
87
|
-
AiModelRegistry
|
|
74
|
+
Masker
|
|
88
75
|
]
|
|
89
76
|
});
|
|
90
77
|
export default ProvidersHandler;
|
|
@@ -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 {
|
|
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 /*\n * No model check here. This section is legacy: the form no longer edits it, and every save\n * carries it forward unchanged from storage. Rejecting a model that has since left the\n * catalog (e.g. a shut-down `openai/gpt-5.3-chat-latest`) blocked every settings save with\n * an error the user had no field to fix. The model a feature uses comes from `modelRoles` and\n * capability overrides, and `Ai` rejects one that isn't available when it's called.\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;QASnD,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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { AiCapability } from "../../../api/features/Capabilities/index.js";
|
|
2
|
-
export declare const WB_GENERATE_PAGE_CAPABILITY = "
|
|
2
|
+
export declare const WB_GENERATE_PAGE_CAPABILITY = "wbGeneratePage";
|
|
3
3
|
/**
|
|
4
4
|
* No `guidance`, for the same reason as CMS entry generation: the prompt carries the component
|
|
5
5
|
* catalog and the tool definitions, both computed per request.
|
|
6
6
|
*/
|
|
7
7
|
declare class WbGeneratePageCapabilityImpl implements AiCapability.Interface {
|
|
8
|
-
readonly id = "
|
|
8
|
+
readonly id = "wbGeneratePage";
|
|
9
9
|
readonly label = "Page generation";
|
|
10
10
|
readonly description = "Writes page content from a prompt using the Website Builder components available in the project.";
|
|
11
11
|
readonly defaultRole: "standard";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api/features/WbGeneratePageContent/capability.js","sources":["../../../../src/api/features/WbGeneratePageContent/capability.ts"],"sourcesContent":["import { AiCapability } from \"~/api/features/Capabilities/index.js\";\n\nexport const WB_GENERATE_PAGE_CAPABILITY = \"
|
|
1
|
+
{"version":3,"file":"api/features/WbGeneratePageContent/capability.js","sources":["../../../../src/api/features/WbGeneratePageContent/capability.ts"],"sourcesContent":["import { AiCapability } from \"~/api/features/Capabilities/index.js\";\n\nexport const WB_GENERATE_PAGE_CAPABILITY = \"wbGeneratePage\";\n\n/**\n * No `guidance`, for the same reason as CMS entry generation: the prompt carries the component\n * catalog and the tool definitions, both computed per request.\n */\nclass WbGeneratePageCapabilityImpl implements AiCapability.Interface {\n readonly id = WB_GENERATE_PAGE_CAPABILITY;\n readonly label = \"Page generation\";\n readonly description =\n \"Writes page content from a prompt using the Website Builder components available in the project.\";\n readonly defaultRole = \"standard\" as const;\n}\n\nexport const WbGeneratePageCapability = AiCapability.createImplementation({\n implementation: WbGeneratePageCapabilityImpl,\n dependencies: []\n});\n"],"names":["WB_GENERATE_PAGE_CAPABILITY","WbGeneratePageCapabilityImpl","WbGeneratePageCapability","AiCapability"],"mappings":";AAEO,MAAMA,8BAA8B;AAM3C,MAAMC;;aACO,EAAE,GAAGD;aACL,KAAK,GAAG;aACR,WAAW,GAChB;aACK,WAAW,GAAG;;AAC3B;AAEO,MAAME,2BAA2BC,aAAa,oBAAoB,CAAC;IACtE,gBAAgBF;IAChB,cAAc,EAAE;AACpB"}
|
|
@@ -5,7 +5,7 @@ import { Logger } from "@webiny/api-core/features/logger/index.js";
|
|
|
5
5
|
import { GetDefaultLanguageUseCase } from "@webiny/languages/exports/api/languages.js";
|
|
6
6
|
import { TranslatePageUseCase } from "@webiny/api-website-builder/features/pages/TranslatePage/index.js";
|
|
7
7
|
import { UpdatePageRepository } from "@webiny/api-website-builder/features/pages/UpdatePage/abstractions.js";
|
|
8
|
-
import { ResolveAiCapabilityUseCase, withAdditionalInstructions } from "../Capabilities/index.js";
|
|
8
|
+
import { AiCapabilityDisabledError, ResolveAiCapabilityUseCase, withAdditionalInstructions } from "../Capabilities/index.js";
|
|
9
9
|
import { WB_TRANSLATE_PAGE_CAPABILITY } from "./capability.js";
|
|
10
10
|
import { LexicalParser } from "./abstractions/LexicalParser.js";
|
|
11
11
|
import { GetPageByIdUseCase } from "@webiny/api-website-builder/exports/api/website-builder/page.js";
|
|
@@ -47,6 +47,10 @@ class WbTranslatePageDecoratorImpl {
|
|
|
47
47
|
async translate(page, sourceLanguage, targetLanguage) {
|
|
48
48
|
const resolved = await this.resolveCapability.execute(WB_TRANSLATE_PAGE_CAPABILITY);
|
|
49
49
|
if (resolved.isFail()) {
|
|
50
|
+
if (resolved.error instanceof AiCapabilityDisabledError) {
|
|
51
|
+
this.logger.info("AI page translation is switched off; skipping.");
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
50
54
|
this.logger.warn({
|
|
51
55
|
reason: resolved.error.message
|
|
52
56
|
}, "Skipping AI page translation.");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api/features/WbTranslatePage/WbTranslatePageDecorator.js","sources":["../../../../src/api/features/WbTranslatePage/WbTranslatePageDecorator.ts"],"sourcesContent":["import set from \"lodash/set\";\nimport { Result } from \"@webiny/feature/api\";\nimport { Ai } from \"@webiny/api-core/features/ai/index.js\";\nimport { Logger } from \"@webiny/api-core/features/logger/index.js\";\nimport { GetDefaultLanguageUseCase } from \"@webiny/languages/exports/api/languages.js\";\nimport { TranslatePageUseCase } from \"@webiny/api-website-builder/features/pages/TranslatePage/index.js\";\nimport { UpdatePageRepository } from \"@webiny/api-website-builder/features/pages/UpdatePage/abstractions.js\";\nimport type { WbPage } from \"@webiny/api-website-builder/domain/page/abstractions.js\";\nimport {\n ResolveAiCapabilityUseCase,\n withAdditionalInstructions\n} from \"~/api/features/Capabilities/index.js\";\nimport { WB_TRANSLATE_PAGE_CAPABILITY } from \"./capability.js\";\nimport { LexicalParser } from \"./abstractions/LexicalParser.js\";\nimport { GetPageByIdUseCase } from \"@webiny/api-website-builder/exports/api/website-builder/page.js\";\nimport { LlmJsonResponse } from \"~/domain/LlmJsonResponse.js\";\n\ntype InputType = \"text\" | \"longText\" | \"lexical\";\n\ninterface TranslatableBinding {\n type: InputType;\n value: string;\n}\n\ntype TranslatableBindings = Record<string, TranslatableBinding>;\n\ninterface TranslatedBindings {\n [key: string]: { type: InputType; value: string };\n}\n\ninterface TranslatedData {\n properties: Record<string, string>;\n bindings: TranslatedBindings;\n}\n\nclass WbTranslatePageDecoratorImpl implements TranslatePageUseCase.Interface {\n constructor(\n private getDefaultLanguage: GetDefaultLanguageUseCase.Interface,\n private getPageById: GetPageByIdUseCase.Interface,\n private resolveCapability: ResolveAiCapabilityUseCase.Interface,\n private logger: Logger.Interface,\n private ai: Ai.Interface,\n private lexicalParser: LexicalParser.Interface,\n private updatePageRepository: UpdatePageRepository.Interface,\n private decoratee: TranslatePageUseCase.Interface\n ) {}\n\n async execute(params: TranslatePageUseCase.Params): TranslatePageUseCase.Return {\n const result = await this.decoratee.execute(params);\n if (result.isFail()) {\n return result;\n }\n\n const page = result.value;\n const sourceLanguage = await this.resolveSourceLanguage(page);\n\n const translations = await this.translate(page, sourceLanguage, params.languageCode);\n if (!translations) {\n return Result.ok(page);\n }\n\n const updateData = await this.buildUpdateData(page, translations);\n const updateResult = await this.updatePageRepository.execute(page.id, updateData);\n if (updateResult.isFail()) {\n return Result.ok(page);\n }\n\n return Result.ok(updateResult.value);\n }\n\n private async resolveSourceLanguage(page: WbPage): Promise<string> {\n if (page.properties[\"sourcePage\"]) {\n const sourcePageId = `${page.properties[\"sourcePage\"]}#0001`;\n const sourcePage = await this.getPageById.execute(sourcePageId);\n if (sourcePage.isOk()) {\n return sourcePage.value.properties[\"language\"];\n }\n return page.properties[\"language\"] ?? \"en\";\n }\n\n const result = await this.getDefaultLanguage.execute();\n if (result.isOk()) {\n return result.value.code;\n }\n\n return \"en\";\n }\n\n private async translate(\n page: WbPage,\n sourceLanguage: string,\n targetLanguage: string\n ): Promise<TranslatedData | null> {\n const resolved = await this.resolveCapability.execute(WB_TRANSLATE_PAGE_CAPABILITY);\n if (resolved.isFail()) {\n /*\n * Translation is a decorator on top of a successful page save, so it stays non-fatal:\n * the page is already created, just untranslated. It does get logged now, because a\n * silently skipped translation was indistinguishable from a misconfigured one.\n */\n this.logger.warn({ reason: resolved.error.message }, \"Skipping AI page translation.\");\n return null;\n }\n\n const capability = resolved.value;\n\n const properties = {\n title: page.properties[\"title\"],\n snippet: page.properties[\"snippet\"],\n pathSlug: page.properties[\"title\"]\n };\n const bindings = this.extractBindings(page.bindings);\n const input = JSON.stringify({ properties, bindings });\n\n const result = await this.ai.generateText({\n model: capability.model,\n connection: capability.connection,\n system: withAdditionalInstructions(capability),\n prompt: `Translate given key-value pairs from \"${sourceLanguage}\" to language code \"${targetLanguage}\". Do not modify the keys. \"properties\" is a simple key-value pair. \"bindings\" values are located in the \"value\" key. ${input}`,\n temperature: 0.3\n });\n\n try {\n return LlmJsonResponse.fromRawText(result.text).toJSON<TranslatedData>();\n } catch {\n return null;\n }\n }\n\n private extractBindings(bindings: Record<string, any>): TranslatableBindings {\n const result: TranslatableBindings = {};\n\n for (const elementId in bindings) {\n const element = bindings[elementId];\n const inputs = element.inputs;\n if (!inputs) {\n continue;\n }\n\n for (const inputName in inputs) {\n const input = inputs[inputName];\n if (input.type === \"text\" || input.type === \"longText\") {\n result[`${elementId}.inputs.${inputName}`] = {\n type: input.type,\n value: input.static\n };\n } else if (input.type === \"lexical\" && input.static?.html) {\n result[`${elementId}.inputs.${inputName}`] = {\n type: input.type,\n value: input.static.html\n };\n }\n }\n }\n\n return result;\n }\n\n private slugify(text: string): string {\n return text\n .toLowerCase()\n .normalize(\"NFKD\")\n .replace(/[̀-ͯ]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n }\n\n private async buildUpdateData(\n page: WbPage,\n translations: TranslatedData\n ): Promise<{ properties: Record<string, any>; bindings: Record<string, any> }> {\n const properties: Record<string, any> = { ...page.properties };\n const bindings: Record<string, any> = structuredClone(page.bindings);\n\n const translatedPathSlug = translations.properties[\"pathSlug\"];\n delete translations.properties[\"pathSlug\"];\n\n for (const [key, value] of Object.entries(translations.properties)) {\n properties[key] = value;\n }\n\n if (translatedPathSlug) {\n const slug = this.slugify(translatedPathSlug);\n if (slug) {\n const languageCode = page.properties[\"language\"];\n properties[\"path\"] = `/${languageCode}/${slug}`;\n }\n }\n\n for (const [key, binding] of Object.entries(translations.bindings)) {\n if (binding.type === \"lexical\") {\n const state = await this.lexicalParser.parse(binding.value);\n if (state) {\n set(bindings, `${key}.static.state`, JSON.stringify(state));\n set(bindings, `${key}.static.html`, binding.value);\n }\n } else {\n set(bindings, `${key}.static`, binding.value);\n }\n }\n\n return { properties, bindings };\n }\n}\n\nexport const WbTranslatePageDecorator = TranslatePageUseCase.createDecorator({\n decorator: WbTranslatePageDecoratorImpl,\n dependencies: [\n GetDefaultLanguageUseCase,\n GetPageByIdUseCase,\n ResolveAiCapabilityUseCase,\n Logger,\n Ai,\n LexicalParser,\n UpdatePageRepository\n ]\n});\n"],"names":["WbTranslatePageDecoratorImpl","getDefaultLanguage","getPageById","resolveCapability","logger","ai","lexicalParser","updatePageRepository","decoratee","params","result","page","sourceLanguage","translations","Result","updateData","updateResult","sourcePageId","sourcePage","targetLanguage","resolved","WB_TRANSLATE_PAGE_CAPABILITY","capability","properties","bindings","input","JSON","withAdditionalInstructions","LlmJsonResponse","elementId","element","inputs","inputName","text","structuredClone","translatedPathSlug","key","value","Object","slug","languageCode","binding","state","set","WbTranslatePageDecorator","TranslatePageUseCase","GetDefaultLanguageUseCase","GetPageByIdUseCase","ResolveAiCapabilityUseCase","Logger","Ai","LexicalParser","UpdatePageRepository"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api/features/WbTranslatePage/WbTranslatePageDecorator.js","sources":["../../../../src/api/features/WbTranslatePage/WbTranslatePageDecorator.ts"],"sourcesContent":["import set from \"lodash/set\";\nimport { Result } from \"@webiny/feature/api\";\nimport { Ai } from \"@webiny/api-core/features/ai/index.js\";\nimport { Logger } from \"@webiny/api-core/features/logger/index.js\";\nimport { GetDefaultLanguageUseCase } from \"@webiny/languages/exports/api/languages.js\";\nimport { TranslatePageUseCase } from \"@webiny/api-website-builder/features/pages/TranslatePage/index.js\";\nimport { UpdatePageRepository } from \"@webiny/api-website-builder/features/pages/UpdatePage/abstractions.js\";\nimport type { WbPage } from \"@webiny/api-website-builder/domain/page/abstractions.js\";\nimport { AiCapabilityDisabledError } from \"~/api/features/Capabilities/index.js\";\nimport {\n ResolveAiCapabilityUseCase,\n withAdditionalInstructions\n} from \"~/api/features/Capabilities/index.js\";\nimport { WB_TRANSLATE_PAGE_CAPABILITY } from \"./capability.js\";\nimport { LexicalParser } from \"./abstractions/LexicalParser.js\";\nimport { GetPageByIdUseCase } from \"@webiny/api-website-builder/exports/api/website-builder/page.js\";\nimport { LlmJsonResponse } from \"~/domain/LlmJsonResponse.js\";\n\ntype InputType = \"text\" | \"longText\" | \"lexical\";\n\ninterface TranslatableBinding {\n type: InputType;\n value: string;\n}\n\ntype TranslatableBindings = Record<string, TranslatableBinding>;\n\ninterface TranslatedBindings {\n [key: string]: { type: InputType; value: string };\n}\n\ninterface TranslatedData {\n properties: Record<string, string>;\n bindings: TranslatedBindings;\n}\n\nclass WbTranslatePageDecoratorImpl implements TranslatePageUseCase.Interface {\n constructor(\n private getDefaultLanguage: GetDefaultLanguageUseCase.Interface,\n private getPageById: GetPageByIdUseCase.Interface,\n private resolveCapability: ResolveAiCapabilityUseCase.Interface,\n private logger: Logger.Interface,\n private ai: Ai.Interface,\n private lexicalParser: LexicalParser.Interface,\n private updatePageRepository: UpdatePageRepository.Interface,\n private decoratee: TranslatePageUseCase.Interface\n ) {}\n\n async execute(params: TranslatePageUseCase.Params): TranslatePageUseCase.Return {\n const result = await this.decoratee.execute(params);\n if (result.isFail()) {\n return result;\n }\n\n const page = result.value;\n const sourceLanguage = await this.resolveSourceLanguage(page);\n\n const translations = await this.translate(page, sourceLanguage, params.languageCode);\n if (!translations) {\n return Result.ok(page);\n }\n\n const updateData = await this.buildUpdateData(page, translations);\n const updateResult = await this.updatePageRepository.execute(page.id, updateData);\n if (updateResult.isFail()) {\n return Result.ok(page);\n }\n\n return Result.ok(updateResult.value);\n }\n\n private async resolveSourceLanguage(page: WbPage): Promise<string> {\n if (page.properties[\"sourcePage\"]) {\n const sourcePageId = `${page.properties[\"sourcePage\"]}#0001`;\n const sourcePage = await this.getPageById.execute(sourcePageId);\n if (sourcePage.isOk()) {\n return sourcePage.value.properties[\"language\"];\n }\n return page.properties[\"language\"] ?? \"en\";\n }\n\n const result = await this.getDefaultLanguage.execute();\n if (result.isOk()) {\n return result.value.code;\n }\n\n return \"en\";\n }\n\n private async translate(\n page: WbPage,\n sourceLanguage: string,\n targetLanguage: string\n ): Promise<TranslatedData | null> {\n const resolved = await this.resolveCapability.execute(WB_TRANSLATE_PAGE_CAPABILITY);\n if (resolved.isFail()) {\n /*\n * Translation is a decorator on top of a successful page save, so it stays non-fatal:\n * the page is already created, just untranslated. A misconfiguration is logged as a\n * warning, because a silently skipped translation was indistinguishable from a broken\n * one. Switched off is not a problem, so it is only noted.\n */\n if (resolved.error instanceof AiCapabilityDisabledError) {\n this.logger.info(\"AI page translation is switched off; skipping.\");\n return null;\n }\n this.logger.warn({ reason: resolved.error.message }, \"Skipping AI page translation.\");\n return null;\n }\n\n const capability = resolved.value;\n\n const properties = {\n title: page.properties[\"title\"],\n snippet: page.properties[\"snippet\"],\n pathSlug: page.properties[\"title\"]\n };\n const bindings = this.extractBindings(page.bindings);\n const input = JSON.stringify({ properties, bindings });\n\n const result = await this.ai.generateText({\n model: capability.model,\n connection: capability.connection,\n system: withAdditionalInstructions(capability),\n prompt: `Translate given key-value pairs from \"${sourceLanguage}\" to language code \"${targetLanguage}\". Do not modify the keys. \"properties\" is a simple key-value pair. \"bindings\" values are located in the \"value\" key. ${input}`,\n temperature: 0.3\n });\n\n try {\n return LlmJsonResponse.fromRawText(result.text).toJSON<TranslatedData>();\n } catch {\n return null;\n }\n }\n\n private extractBindings(bindings: Record<string, any>): TranslatableBindings {\n const result: TranslatableBindings = {};\n\n for (const elementId in bindings) {\n const element = bindings[elementId];\n const inputs = element.inputs;\n if (!inputs) {\n continue;\n }\n\n for (const inputName in inputs) {\n const input = inputs[inputName];\n if (input.type === \"text\" || input.type === \"longText\") {\n result[`${elementId}.inputs.${inputName}`] = {\n type: input.type,\n value: input.static\n };\n } else if (input.type === \"lexical\" && input.static?.html) {\n result[`${elementId}.inputs.${inputName}`] = {\n type: input.type,\n value: input.static.html\n };\n }\n }\n }\n\n return result;\n }\n\n private slugify(text: string): string {\n return text\n .toLowerCase()\n .normalize(\"NFKD\")\n .replace(/[̀-ͯ]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n }\n\n private async buildUpdateData(\n page: WbPage,\n translations: TranslatedData\n ): Promise<{ properties: Record<string, any>; bindings: Record<string, any> }> {\n const properties: Record<string, any> = { ...page.properties };\n const bindings: Record<string, any> = structuredClone(page.bindings);\n\n const translatedPathSlug = translations.properties[\"pathSlug\"];\n delete translations.properties[\"pathSlug\"];\n\n for (const [key, value] of Object.entries(translations.properties)) {\n properties[key] = value;\n }\n\n if (translatedPathSlug) {\n const slug = this.slugify(translatedPathSlug);\n if (slug) {\n const languageCode = page.properties[\"language\"];\n properties[\"path\"] = `/${languageCode}/${slug}`;\n }\n }\n\n for (const [key, binding] of Object.entries(translations.bindings)) {\n if (binding.type === \"lexical\") {\n const state = await this.lexicalParser.parse(binding.value);\n if (state) {\n set(bindings, `${key}.static.state`, JSON.stringify(state));\n set(bindings, `${key}.static.html`, binding.value);\n }\n } else {\n set(bindings, `${key}.static`, binding.value);\n }\n }\n\n return { properties, bindings };\n }\n}\n\nexport const WbTranslatePageDecorator = TranslatePageUseCase.createDecorator({\n decorator: WbTranslatePageDecoratorImpl,\n dependencies: [\n GetDefaultLanguageUseCase,\n GetPageByIdUseCase,\n ResolveAiCapabilityUseCase,\n Logger,\n Ai,\n LexicalParser,\n UpdatePageRepository\n ]\n});\n"],"names":["WbTranslatePageDecoratorImpl","getDefaultLanguage","getPageById","resolveCapability","logger","ai","lexicalParser","updatePageRepository","decoratee","params","result","page","sourceLanguage","translations","Result","updateData","updateResult","sourcePageId","sourcePage","targetLanguage","resolved","WB_TRANSLATE_PAGE_CAPABILITY","AiCapabilityDisabledError","capability","properties","bindings","input","JSON","withAdditionalInstructions","LlmJsonResponse","elementId","element","inputs","inputName","text","structuredClone","translatedPathSlug","key","value","Object","slug","languageCode","binding","state","set","WbTranslatePageDecorator","TranslatePageUseCase","GetDefaultLanguageUseCase","GetPageByIdUseCase","ResolveAiCapabilityUseCase","Logger","Ai","LexicalParser","UpdatePageRepository"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AiCapability } from "../../../api/features/Capabilities/index.js";
|
|
2
|
-
export declare const WB_TRANSLATE_PAGE_CAPABILITY = "
|
|
2
|
+
export declare const WB_TRANSLATE_PAGE_CAPABILITY = "wbTranslatePage";
|
|
3
3
|
declare class WbTranslatePageCapabilityImpl implements AiCapability.Interface {
|
|
4
|
-
readonly id = "
|
|
4
|
+
readonly id = "wbTranslatePage";
|
|
5
5
|
readonly label = "Page translation";
|
|
6
6
|
readonly description = "Translates the text on a page into another language, preserving placeholders.";
|
|
7
7
|
readonly defaultRole: "standard";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AiCapability } from "../Capabilities/index.js";
|
|
2
|
-
const WB_TRANSLATE_PAGE_CAPABILITY = "
|
|
2
|
+
const WB_TRANSLATE_PAGE_CAPABILITY = "wbTranslatePage";
|
|
3
3
|
const guidance = "You are a professional translator. Translate all user-provided text to the requested target language, preserving placeholders and formatting. Return a JSON object with the same keys, only changing the values.";
|
|
4
4
|
class WbTranslatePageCapabilityImpl {
|
|
5
5
|
constructor(){
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api/features/WbTranslatePage/capability.js","sources":["../../../../src/api/features/WbTranslatePage/capability.ts"],"sourcesContent":["import { AiCapability } from \"~/api/features/Capabilities/index.js\";\n\nexport const WB_TRANSLATE_PAGE_CAPABILITY = \"
|
|
1
|
+
{"version":3,"file":"api/features/WbTranslatePage/capability.js","sources":["../../../../src/api/features/WbTranslatePage/capability.ts"],"sourcesContent":["import { AiCapability } from \"~/api/features/Capabilities/index.js\";\n\nexport const WB_TRANSLATE_PAGE_CAPABILITY = \"wbTranslatePage\";\n\n/**\n * The target language used to be interpolated into this sentence. It moved to the user prompt so\n * the instructions could become fixed text a project can replace, which is worth doing here: house\n * translation rules (keep product names in English, use formal address, never translate these\n * twelve terms) are the most commonly requested prompt change in the whole feature set.\n */\nconst guidance =\n \"You are a professional translator. Translate all user-provided text to the requested target language, preserving placeholders and formatting. Return a JSON object with the same keys, only changing the values.\";\n\nclass WbTranslatePageCapabilityImpl implements AiCapability.Interface {\n readonly id = WB_TRANSLATE_PAGE_CAPABILITY;\n readonly label = \"Page translation\";\n readonly description =\n \"Translates the text on a page into another language, preserving placeholders.\";\n readonly defaultRole = \"standard\" as const;\n readonly guidance = guidance;\n}\n\nexport const WbTranslatePageCapability = AiCapability.createImplementation({\n implementation: WbTranslatePageCapabilityImpl,\n dependencies: []\n});\n"],"names":["WB_TRANSLATE_PAGE_CAPABILITY","guidance","WbTranslatePageCapabilityImpl","WbTranslatePageCapability","AiCapability"],"mappings":";AAEO,MAAMA,+BAA+B;AAQ5C,MAAMC,WACF;AAEJ,MAAMC;;aACO,EAAE,GAAGF;aACL,KAAK,GAAG;aACR,WAAW,GAChB;aACK,WAAW,GAAG;aACd,QAAQ,GAAGC;;AACxB;AAEO,MAAME,4BAA4BC,aAAa,oBAAoB,CAAC;IACvE,gBAAgBF;IAChB,cAAc,EAAE;AACpB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webiny/ai-powerups",
|
|
3
|
-
"version": "6.5.0-beta.
|
|
3
|
+
"version": "6.5.0-beta.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -18,45 +18,45 @@
|
|
|
18
18
|
"author": "Webiny Ltd.",
|
|
19
19
|
"license": "Webiny Enterprise",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@webiny/admin-ui": "6.5.0-beta.
|
|
22
|
-
"@webiny/api-core": "6.5.0-beta.
|
|
23
|
-
"@webiny/api-file-manager": "6.5.0-beta.
|
|
24
|
-
"@webiny/api-headless-cms": "6.5.0-beta.
|
|
25
|
-
"@webiny/api-website-builder": "6.5.0-beta.
|
|
26
|
-
"@webiny/api-websockets": "6.5.0-beta.
|
|
27
|
-
"@webiny/app": "6.5.0-beta.
|
|
28
|
-
"@webiny/app-admin": "6.5.0-beta.
|
|
29
|
-
"@webiny/app-headless-cms": "6.5.0-beta.
|
|
30
|
-
"@webiny/app-website-builder": "6.5.0-beta.
|
|
31
|
-
"@webiny/app-websockets": "6.5.0-beta.
|
|
32
|
-
"@webiny/background-tasks": "6.5.0-beta.
|
|
33
|
-
"@webiny/di": "1.0
|
|
34
|
-
"@webiny/feature": "6.5.0-beta.
|
|
35
|
-
"@webiny/icons": "6.5.0-beta.
|
|
36
|
-
"@webiny/languages": "6.5.0-beta.
|
|
37
|
-
"@webiny/lexical-converter": "6.5.0-beta.
|
|
38
|
-
"@webiny/project": "6.5.0-beta.
|
|
39
|
-
"@webiny/project-aws": "6.5.0-beta.
|
|
40
|
-
"@webiny/utils": "6.5.0-beta.
|
|
41
|
-
"ai": "7.0.
|
|
21
|
+
"@webiny/admin-ui": "6.5.0-beta.10",
|
|
22
|
+
"@webiny/api-core": "6.5.0-beta.10",
|
|
23
|
+
"@webiny/api-file-manager": "6.5.0-beta.10",
|
|
24
|
+
"@webiny/api-headless-cms": "6.5.0-beta.10",
|
|
25
|
+
"@webiny/api-website-builder": "6.5.0-beta.10",
|
|
26
|
+
"@webiny/api-websockets": "6.5.0-beta.10",
|
|
27
|
+
"@webiny/app": "6.5.0-beta.10",
|
|
28
|
+
"@webiny/app-admin": "6.5.0-beta.10",
|
|
29
|
+
"@webiny/app-headless-cms": "6.5.0-beta.10",
|
|
30
|
+
"@webiny/app-website-builder": "6.5.0-beta.10",
|
|
31
|
+
"@webiny/app-websockets": "6.5.0-beta.10",
|
|
32
|
+
"@webiny/background-tasks": "6.5.0-beta.10",
|
|
33
|
+
"@webiny/di": "1.1.0",
|
|
34
|
+
"@webiny/feature": "6.5.0-beta.10",
|
|
35
|
+
"@webiny/icons": "6.5.0-beta.10",
|
|
36
|
+
"@webiny/languages": "6.5.0-beta.10",
|
|
37
|
+
"@webiny/lexical-converter": "6.5.0-beta.10",
|
|
38
|
+
"@webiny/project": "6.5.0-beta.10",
|
|
39
|
+
"@webiny/project-aws": "6.5.0-beta.10",
|
|
40
|
+
"@webiny/utils": "6.5.0-beta.10",
|
|
41
|
+
"ai": "7.0.127",
|
|
42
42
|
"front-matter": "4.0.2",
|
|
43
|
-
"happy-dom": "20.
|
|
43
|
+
"happy-dom": "20.14.5",
|
|
44
44
|
"lodash": "4.18.1",
|
|
45
|
-
"mobx": "
|
|
46
|
-
"mobx-react-lite": "
|
|
45
|
+
"mobx": "7.0.6",
|
|
46
|
+
"mobx-react-lite": "5.1.0",
|
|
47
47
|
"react": "18.3.1",
|
|
48
48
|
"react-dom": "18.3.1",
|
|
49
|
-
"zod": "4.
|
|
49
|
+
"zod": "4.6.5"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@webiny/build-tools": "6.5.0-beta.
|
|
53
|
-
"@webiny/handler-aws": "6.5.0-beta.
|
|
54
|
-
"@webiny/handler-graphql": "6.5.0-beta.
|
|
55
|
-
"@webiny/project-utils": "6.5.0-beta.
|
|
56
|
-
"@webiny/wcp": "6.5.0-beta.
|
|
52
|
+
"@webiny/build-tools": "6.5.0-beta.10",
|
|
53
|
+
"@webiny/handler-aws": "6.5.0-beta.10",
|
|
54
|
+
"@webiny/handler-graphql": "6.5.0-beta.10",
|
|
55
|
+
"@webiny/project-utils": "6.5.0-beta.10",
|
|
56
|
+
"@webiny/wcp": "6.5.0-beta.10",
|
|
57
57
|
"rimraf": "6.1.3",
|
|
58
58
|
"typescript": "7.0.2",
|
|
59
|
-
"vitest": "
|
|
59
|
+
"vitest": "5.0.3"
|
|
60
60
|
},
|
|
61
61
|
"publishConfig": {
|
|
62
62
|
"access": "public"
|