@webiny/ai-powerups 6.5.0-beta.3 → 6.5.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/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/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/index.d.ts +1 -0
- package/api/features/Capabilities/index.js +1 -0
- package/api/features/ModelRoles/ModelRolesHandler.js +10 -4
- package/api/features/ModelRoles/ModelRolesHandler.js.map +1 -1
- package/api/features/WbTranslatePage/WbTranslatePageDecorator.js +5 -1
- package/api/features/WbTranslatePage/WbTranslatePageDecorator.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 | null) & {
|
|
3
|
-
|
|
2
|
+
export declare const CmsEntryWizardGate: (() => React.JSX.Element | null) & React.NamedExoticComponent<unknown> & {
|
|
3
|
+
readonly type: () => React.JSX.Element | null;
|
|
4
4
|
};
|
|
@@ -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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"}
|
|
@@ -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":"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"}
|
|
@@ -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";
|
|
@@ -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"}
|
|
@@ -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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"}
|
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.5",
|
|
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.5",
|
|
22
|
+
"@webiny/api-core": "6.5.0-beta.5",
|
|
23
|
+
"@webiny/api-file-manager": "6.5.0-beta.5",
|
|
24
|
+
"@webiny/api-headless-cms": "6.5.0-beta.5",
|
|
25
|
+
"@webiny/api-website-builder": "6.5.0-beta.5",
|
|
26
|
+
"@webiny/api-websockets": "6.5.0-beta.5",
|
|
27
|
+
"@webiny/app": "6.5.0-beta.5",
|
|
28
|
+
"@webiny/app-admin": "6.5.0-beta.5",
|
|
29
|
+
"@webiny/app-headless-cms": "6.5.0-beta.5",
|
|
30
|
+
"@webiny/app-website-builder": "6.5.0-beta.5",
|
|
31
|
+
"@webiny/app-websockets": "6.5.0-beta.5",
|
|
32
|
+
"@webiny/background-tasks": "6.5.0-beta.5",
|
|
33
|
+
"@webiny/di": "1.1.0",
|
|
34
|
+
"@webiny/feature": "6.5.0-beta.5",
|
|
35
|
+
"@webiny/icons": "6.5.0-beta.5",
|
|
36
|
+
"@webiny/languages": "6.5.0-beta.5",
|
|
37
|
+
"@webiny/lexical-converter": "6.5.0-beta.5",
|
|
38
|
+
"@webiny/project": "6.5.0-beta.5",
|
|
39
|
+
"@webiny/project-aws": "6.5.0-beta.5",
|
|
40
|
+
"@webiny/utils": "6.5.0-beta.5",
|
|
41
|
+
"ai": "7.0.118",
|
|
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.5",
|
|
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.5",
|
|
53
|
+
"@webiny/handler-aws": "6.5.0-beta.5",
|
|
54
|
+
"@webiny/handler-graphql": "6.5.0-beta.5",
|
|
55
|
+
"@webiny/project-utils": "6.5.0-beta.5",
|
|
56
|
+
"@webiny/wcp": "6.5.0-beta.5",
|
|
57
57
|
"rimraf": "6.1.3",
|
|
58
58
|
"typescript": "7.0.2",
|
|
59
|
-
"vitest": "
|
|
59
|
+
"vitest": "5.0.2"
|
|
60
60
|
},
|
|
61
61
|
"publishConfig": {
|
|
62
62
|
"access": "public"
|